From 5fb9c09ab15c6504e148ad6187f078d9037e4213 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 12:57:08 -0400 Subject: [PATCH 001/907] Draw 3-D pie and column charts as projections in the chart renderer A 3-D pie is a tilted ellipse from view3D's rotX with its front rim, the cut faces an explosion shows and its tops in painter's order; a 3-D column or bar chart is Excel's right-angle oblique projection with lighter tops, darker sides and its walls. 3-D line and area charts stay framed and their frames name the type. --- i18n/de.js | 6 +- i18n/es.js | 6 +- i18n/fr.js | 6 +- i18n/nl.js | 6 +- i18n/pt-BR.js | 6 +- lib/sheet-chart-xml.js | 50 ++++++--- lib/sheet-chart.js | 183 +++++++++++++++++++++++++++++-- sheet.css | 2 + test/fixtures/sheets/charts.js | 26 ++++- test/sheet-chart-3d.test.js | 189 +++++++++++++++++++++++++++++++++ test/sheet-chart.test.js | 6 +- 11 files changed, 447 insertions(+), 39 deletions(-) create mode 100644 test/sheet-chart-3d.test.js diff --git a/i18n/de.js b/i18n/de.js index 691f5601b..6615ea943 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3562,7 +3562,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Auf diesem Tabellenblatt können keine Zeilen und Spalten eingefügt oder gelöscht werden.", "Series {n}": "Reihe {n}", "Combo chart": "Kombinationsdiagramm", - "3-D chart": "3D-Diagramm", + "3-D pie chart": "3D-Kreisdiagramm", + "3-D column chart": "3D-Säulendiagramm", + "3-D bar chart": "3D-Balkendiagramm", + "3-D line chart": "3D-Liniendiagramm", + "3-D area chart": "3D-Flächendiagramm", "Surface chart": "Oberflächendiagramm", "Radar chart": "Netzdiagramm", "Bubble chart": "Blasendiagramm", diff --git a/i18n/es.js b/i18n/es.js index 6a4de5868..c3cfe535b 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3562,7 +3562,11 @@ "Rows and columns can’t be added or removed on this sheet.": "En esta hoja no se pueden insertar ni eliminar filas o columnas.", "Series {n}": "Serie {n}", "Combo chart": "Gráfico combinado", - "3-D chart": "Gráfico 3D", + "3-D pie chart": "Gráfico circular 3D", + "3-D column chart": "Gráfico de columnas 3D", + "3-D bar chart": "Gráfico de barras 3D", + "3-D line chart": "Gráfico de líneas 3D", + "3-D area chart": "Gráfico de áreas 3D", "Surface chart": "Gráfico de superficie", "Radar chart": "Gráfico radial", "Bubble chart": "Gráfico de burbujas", diff --git a/i18n/fr.js b/i18n/fr.js index b4c680545..e9c152147 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3562,7 +3562,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Impossible d’ajouter ou de supprimer des lignes et des colonnes sur cette feuille.", "Series {n}": "Série {n}", "Combo chart": "Graphique combiné", - "3-D chart": "Graphique 3D", + "3-D pie chart": "Graphique en secteurs 3D", + "3-D column chart": "Histogramme 3D", + "3-D bar chart": "Graphique à barres 3D", + "3-D line chart": "Graphique en courbes 3D", + "3-D area chart": "Graphique en aires 3D", "Surface chart": "Graphique en surface", "Radar chart": "Graphique en radar", "Bubble chart": "Graphique en bulles", diff --git a/i18n/nl.js b/i18n/nl.js index 82d2b918d..5c0d4e3b8 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3562,7 +3562,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Op dit blad kunnen geen rijen en kolommen worden ingevoegd of verwijderd.", "Series {n}": "Reeks {n}", "Combo chart": "Combinatiegrafiek", - "3-D chart": "3D-grafiek", + "3-D pie chart": "3D-cirkelgrafiek", + "3-D column chart": "3D-kolomgrafiek", + "3-D bar chart": "3D-staafgrafiek", + "3-D line chart": "3D-lijngrafiek", + "3-D area chart": "3D-vlakgrafiek", "Surface chart": "Oppervlaktegrafiek", "Radar chart": "Radargrafiek", "Bubble chart": "Bellengrafiek", diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 36abac48d..4324f36d0 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3562,7 +3562,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Não é possível inserir ou excluir linhas e colunas nesta planilha.", "Series {n}": "Série {n}", "Combo chart": "Gráfico de combinação", - "3-D chart": "Gráfico 3D", + "3-D pie chart": "Gráfico de pizza 3D", + "3-D column chart": "Gráfico de colunas 3D", + "3-D bar chart": "Gráfico de barras 3D", + "3-D line chart": "Gráfico de linhas 3D", + "3-D area chart": "Gráfico de área 3D", "Surface chart": "Gráfico de superfície", "Radar chart": "Gráfico de radar", "Bubble chart": "Gráfico de bolhas", diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index acdbfb024..55cdfb4b0 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -2,9 +2,9 @@ // drawing's anchors (two-cell, one-cell, absolute) and each chart part read into a plain description, never written back. // charts(wb, sheet) → [{ id, path, anchor, chart }] with each anchor and every c:f reference moved through wb.keep.log // (the rows and columns inserted or deleted, the sheets renamed since the file was read), as the writer moves the parts. -// A chart: { kind: 'plot' | 'frame', type (the frame's word), title, legend, groups: [{ type: bar | line | pie | doughnut | -// scatter | area, dir, grouping, varyColors, gap, overlap, hole, first, series: [{ name, cat, val, x, y, color, line, -// marker, points, labels }] }], axes: { cat, val, x, y } }. A series' name, cat and val are { f, cache: { pts, fmt } }: +// A chart: { kind: 'plot' | 'frame', type (the frame's word), title, legend, view3D, walls, groups: [{ type: bar | line | pie | +// doughnut | scatter | area | pie3D | bar3D, dir, grouping, varyColors, gap, gapDepth, shape, overlap, hole, first, series: +// [{ name, cat, val, x, y, color, line, marker, points, explosion, explode, labels }] }], axes: { cat, val, x, y } }. A series' name, cat and val are { f, cache: { pts, fmt } }: // resolve(chart, lookup) reads each f live (lookup: lib/sheet-model's cells through the engine's values) and falls back // to the cache. Colours are '#RRGGBB' from the theme (wb.keep.theme), the series' own, or Excel's automatic order. // Classic script; doctext, the model and the formula module are looked up when a function runs. module.exports for node. @@ -195,10 +195,16 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { return null; }; const labelsOf = (d) => d && { val: flag(d, 'showVal'), pct: flag(d, 'showPercent'), cat: flag(d, 'showCatName'), fmt: kid(d, 'numFmt')?.a.formatCode ?? null, pos: val(d, 'dLblPos') ?? null, del: flag(d, 'delete') }; - const TYPES = { barChart: 'bar', lineChart: 'line', pieChart: 'pie', doughnutChart: 'doughnut', scatterChart: 'scatter', areaChart: 'area' }; + const TYPES = { barChart: 'bar', lineChart: 'line', pieChart: 'pie', doughnutChart: 'doughnut', scatterChart: 'scatter', areaChart: 'area', pie3DChart: 'pie3D', bar3DChart: 'bar3D' }; // Words for the frame a chart Glaze doesn't draw shows (lib/i18n catalogues). - const OTHER = { bar3DChart: '3-D chart', line3DChart: '3-D chart', pie3DChart: '3-D chart', area3DChart: '3-D chart', surfaceChart: 'Surface chart', surface3DChart: 'Surface chart', + const OTHER = { line3DChart: '3-D line chart', area3DChart: '3-D area chart', surfaceChart: 'Surface chart', surface3DChart: 'Surface chart', radarChart: 'Radar chart', bubbleChart: 'Bubble chart', stockChart: 'Stock chart', ofPieChart: 'Pie chart' }; + // A 3-D pie or column chart Glaze draws, framed when it can't be (beside another chart, on a second axis). + const word3D = (g) => (g.type === 'pie3D' ? '3-D pie chart' : g.dir === 'bar' ? '3-D bar chart' : '3-D column chart'); + // c:view3D as written (null where it says nothing: the drawer has each type's default) and the walls' fills. + const view3DOf = (n) => n && { rotX: num(val(n, 'rotX')), rotY: num(val(n, 'rotY')), perspective: num(val(n, 'perspective')), depthPercent: num(val(n, 'depthPercent')), + hPercent: num(val(n, 'hPercent')), rAngAx: kid(n, 'rAngAx') ? flag(n, 'rAngAx') : null }; + const wallOf = (n, theme) => n && { fill: shapeOf(kid(n, 'spPr'), theme, null).fill }; const EX = { waterfall: 'Waterfall chart', funnel: 'Funnel chart', treemap: 'Treemap chart', sunburst: 'Sunburst chart', boxWhisker: 'Box and whisker chart', clusteredColumn: 'Histogram', paretoLine: 'Pareto chart', regionMap: 'Map chart' }; const axisOf = (n, theme, log) => { @@ -233,6 +239,8 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const autoDeleted = flag(ch, 'autoTitleDeleted'); const legendEl = kid(ch, 'legend'); const legend = legendEl ? { pos: val(legendEl, 'legendPos') ?? 'r', overlay: flag(legendEl, 'overlay'), hidden: kids(legendEl, 'legendEntry').filter((e) => flag(e, 'delete')).map((e) => num(val(e, 'idx'), -1)) } : null; + const view3D = view3DOf(kid(ch, 'view3D')); + const walls = { floor: wallOf(kid(ch, 'floor'), theme), side: wallOf(kid(ch, 'sideWall'), theme), back: wallOf(kid(ch, 'backWall'), theme) }; const groups = [], other = []; let auto = 0; for (const g of plot.kids) { @@ -249,30 +257,38 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const mk = kid(s, 'marker'); const mkSp = shapeOf(kid(mk, 'spPr'), theme, color); const points = new Map(); - for (const p of kids(s, 'dPt')) { const i = num(val(p, 'idx'), -1); const c = shapeOf(kid(p, 'spPr'), theme, null); if (i >= 0 && (c.fill || c.line)) points.set(i, c.fill && c.fill !== 'none' ? c.fill : c.line); } + const explode = new Map(); + for (const p of kids(s, 'dPt')) { + const i = num(val(p, 'idx'), -1); const c = shapeOf(kid(p, 'spPr'), theme, null); + if (i >= 0 && (c.fill || c.line)) points.set(i, c.fill && c.fill !== 'none' ? c.fill : c.line); + if (i >= 0 && kid(p, 'explosion')) explode.set(i, num(val(p, 'explosion'), 0)); + } const dl = labelsOf(kid(s, 'dLbls')); return { idx, order: num(val(s, 'order'), idx), name: dataOf(kid(s, 'tx'), log), color, fill: sp.fill, line: sp.line, width: sp.width, cat: dataOf(kid(s, 'cat') ?? kid(s, 'xVal'), log), val: dataOf(kid(s, 'val') ?? kid(s, 'yVal'), log), marker: mk ? { symbol: val(mk, 'symbol') ?? null, size: num(val(mk, 'size'), 5), fill: mkSp.fill, line: mkSp.line } : null, - smooth: flag(s, 'smooth'), points, labels: dl, + smooth: flag(s, 'smooth'), points, explosion: num(val(s, 'explosion'), 0), explode, labels: dl, }; }).sort((a, b) => a.order - b.order); groups.push({ - type, dir: val(g, 'barDir') === 'bar' ? 'bar' : 'col', grouping: val(g, 'grouping') ?? (type === 'bar' ? 'clustered' : 'standard'), vary, - gap: num(val(g, 'gapWidth'), 150), overlap: num(val(g, 'overlap'), null), hole: num(val(g, 'holeSize'), 50), first: num(val(g, 'firstSliceAng'), 0), + type, dir: val(g, 'barDir') === 'bar' ? 'bar' : 'col', grouping: val(g, 'grouping') ?? (type === 'bar' || type === 'bar3D' ? 'clustered' : 'standard'), vary, + gap: num(val(g, 'gapWidth'), 150), overlap: num(val(g, 'overlap'), null), hole: num(val(g, 'holeSize'), 50), + // A 3-D pie has no c:firstSliceAng: Excel turns it with c:view3D's rotY. + first: num(val(g, 'firstSliceAng'), type === 'pie3D' ? (view3D?.rotY ?? 0) : 0), gapDepth: num(val(g, 'gapDepth'), 150), shape: val(g, 'shape') ?? 'box', style: val(g, 'scatterStyle') ?? null, markers: kid(g, 'marker') ? flag(g, 'marker') : null, axes: kids(g, 'axId').map((a) => a.a.val), series, labels: labelsOf(kid(g, 'dLbls')), }); } const axes = {}; for (const n of plot.kids) if (/^(valAx|catAx|dateAx|serAx)$/.test(local(n.name))) { const a = axisOf(n, theme, log); axes[a.id] = a; } const blanks = val(ch, 'dispBlanksAs') ?? 'gap'; - const out = { kind: 'plot', title, autoDeleted, legend, groups, axes, blanks, visible: flag(ch, 'plotVisOnly', true) }; - // Drawn: one or more of the six on one pair of axes (a pie or doughnut alone); else the titled frame. + const out = { kind: 'plot', title, autoDeleted, legend, view3D, walls, groups, axes, blanks, visible: flag(ch, 'plotVisOnly', true) }; + // Drawn: one or more of the six on one pair of axes (a pie, doughnut or 3-D chart alone); else the titled frame. const pairs = new Set(groups.map((g) => g.axes.slice(0, 2).join(','))); - const round = groups.some((g) => g.type === 'pie' || g.type === 'doughnut'); - if (other.length || !groups.length || pairs.size > 1 || Object.values(axes).some((a) => a.log) || (round && groups.length > 1) || (groups.some((g) => g.type === 'scatter') && groups.length > 1)) { - return { ...out, kind: 'frame', type: other[0] ?? (groups.length > 1 ? 'Combo chart' : 'Chart') }; + const round = groups.some((g) => g.type === 'pie' || g.type === 'doughnut' || g.type === 'pie3D'); + const d3 = groups.find((g) => g.type === 'pie3D' || g.type === 'bar3D'); + if (other.length || !groups.length || pairs.size > 1 || Object.values(axes).some((a) => a.log) || (round && groups.length > 1) || (d3 && groups.length > 1) || (groups.some((g) => g.type === 'scatter') && groups.length > 1)) { + return { ...out, kind: 'frame', type: other[0] ?? (d3 ? word3D(d3) : groups.length > 1 ? 'Combo chart' : 'Chart') }; } return out; }; @@ -428,8 +444,8 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { let title = null; if (c.title?.text !== undefined) title = c.title.text; else if (c.title?.f) title = read({ f: c.title.f, cache: c.title.cache, kind: 'str' }, look, show)?.texts.join(' ') ?? ''; - else if (!c.autoDeleted && groups.length === 1 && groups[0].series.length === 1 && (c.title?.auto || ['pie', 'doughnut'].includes(groups[0].type) || c.title)) title = first?.name || null; - return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks }; + else if (!c.autoDeleted && groups.length === 1 && groups[0].series.length === 1 && (c.title?.auto || ['pie', 'doughnut', 'pie3D'].includes(groups[0].type) || c.title)) title = first?.name || null; + return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks, view3D: c.view3D ?? null, walls: c.walls ?? null }; }; // ── Writing a chart (Insert): the parts in Excel's element order ─────────────────────────────────────────────────── @@ -594,7 +610,7 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { }; // Every word a frame can show, for the catalogues. - const WORDS = [...new Set(['Chart', 'Combo chart', ...Object.values(OTHER), ...Object.values(EX)])]; + const WORDS = [...new Set(['Chart', 'Combo chart', ...Object.values(OTHER), '3-D pie chart', '3-D column chart', '3-D bar chart', ...Object.values(EX)])]; return { EMU, MAX_POINTS, DEFAULT_THEME, WORDS, logFor, owner, TYPE_CHART, TYPE_RELS, build, plan, spans, addAnchor, setAnchor, relsXml, relList, tree, anchors, chart, chartEx, charts, lookup, resolve, autoColor, transform, moveFormula, moveAnchor, relsOf, partText }; })(); diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 1638c2d96..223e6d20d 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -1,6 +1,6 @@ // A sheet's charts drawn on the grid (sheet-grid.js): scene() lays a resolved chart (lib/sheet-chart-xml.js resolve) out -// in a w×h box as plain shapes (pure, for node: bars, lines, areas, pies and doughnuts, scatter, axes with nice ticks, -// titles, the legend), svg() builds them with DOM calls, and layer() keeps a chart layer in each of the grid's panes, each +// in a w×h box as plain shapes (pure, for node: bars, lines, areas, pies and doughnuts, scatter, 3-D pies and columns, +// axes with nice ticks, titles, the legend), svg() builds them with DOM calls, and layer() keeps a chart layer in each of the grid's panes, each // chart at its anchor's cells, moving and sizing with them and with the frozen panes, drawn again a moment after the // cells it reads change. The chart's own colours draw its data; its text, axes and ground take the Look (sheet.css // .gc-*). A chart Glaze doesn't draw is a frame with its title and kind. Nothing here writes the file's chart parts. @@ -25,6 +25,12 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const round = (x) => Math.round(x * 100) / 100; const clean = (x) => Number(x.toPrecision(12)); const formatNumber = (v, code, opts = {}) => { if (v === null || v === undefined) return ''; try { return NF().format(v, code || 'General', opts) ?? String(v); } catch { return String(v); } }; + const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x)); + const RAD = Math.PI / 180; + // A 3-D face's shade of its data colour: tops lighter, sides darker (no palette of their own). + const lighter = (hex) => CX().transform(hex, [{ name: 'tint', a: { val: '75000' } }]); + const darker = (hex, k = 72000) => CX().transform(hex, [{ name: 'shade', a: { val: String(k) } }]); + const poly = (pts) => `M${pts.map(([x, y]) => `${round(x)} ${round(y)}`).join('L')}Z`; // An axis's scale: Excel's rule (from 0 unless the values sit within a sixth of their top, 5% of room past them), steps // of 1, 2 or 5 a power of ten as many as `len` px holds at `gap` px apart, the file's min, max and major unit where set. @@ -61,7 +67,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return scene({ kind: 'frame', type: 'Chart', title: data.title }, w, h, opts); } const t = opts.t || ((s) => s); - const prims = [], marks = { bars: [], points: [], slices: [], ticks: { val: [], cat: [], x: [] }, legend: [], title: null, areas: [], lines: [] }; + const prims = [], marks = { bars: [], points: [], slices: [], sides: [], faces: [], labels: [], ticks: { val: [], cat: [], x: [] }, legend: [], title: null, areas: [], lines: [] }; const add = (el, a, cls, text, tip) => { prims.push({ el, a, cls: cls || null, text: text ?? null, tip: tip ?? null }); return prims[prims.length - 1]; }; add('rect', { x: 0.5, y: 0.5, width: Math.max(0, w - 1), height: Math.max(0, h - 1), rx: 3 }, data.kind === 'frame' ? 'gc-bg gc-frame' : 'gc-bg'); let top = P, left = P, right = w - P, bottom = h - P; @@ -86,7 +92,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { } const groups = data.groups.filter((g) => g.series.length); if (!groups.length) return { prims, marks }; - const round1 = groups[0].type === 'pie' || groups[0].type === 'doughnut'; + const round1 = groups[0].type === 'pie' || groups[0].type === 'doughnut' || groups[0].type === 'pie3D'; const sourceIndex = (s, i) => s.indices?.[i] ?? i; const pointColor = (g, s, i) => s.points?.get(sourceIndex(s, i)) ?? (g.vary || round1 ? CX().autoColor(sourceIndex(s, i), opts.theme || CX().DEFAULT_THEME) : null) ?? fillOf(s); const fillOf = (s) => (s.fill && s.fill !== 'none' ? s.fill : s.color); @@ -143,6 +149,83 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { } if (right - left < 16 || bottom - top < 16) return { prims, marks }; + // ── A 3-D pie, as Excel projects it: the pie tilted back by view3D's rotX (default 30°) so its top is an ellipse + // sin(rotX) as tall as wide, extruded down by a thickness 18% of its radius (projected by cos(rotX)). Painter's order: + // each slice's sides (the cut faces an explosion shows, the rim of the front half) back to front by its mid-angle, + // then the tops. An exploded slice (c:explosion, % of the radius) moves out along its mid-angle; the pie shrinks so + // it still fits. Perspective is not drawn: the projection is parallel. ── + if (groups[0].type === 'pie3D') { + const g = groups[0], s = g.series[0]; + const v3 = data.view3D ?? {}; + const tilt = clamp(v3.rotX ?? 30, 10, 90) * RAD; + const k = Math.sin(tilt); + const vals = s.vals.map((v) => (v === null ? 0 : Math.abs(v))); + const total = vals.reduce((n, v) => n + v, 0); + if (!total) return { prims, marks }; + const exOf = (i) => clamp(s.explode?.get(sourceIndex(s, i)) ?? s.explosion ?? 0, 0, 400) / 100; + const maxE = Math.max(0, ...vals.map((v, i) => (v ? exOf(i) : 0))); + const thick = 0.18 * Math.cos(tilt); + const W = right - left, H = bottom - top; + const rx = Math.max(4, Math.min(W / 2 / (1 + maxE), H / (2 * k * (1 + maxE) + thick)) - 2), ry = rx * k, depth = rx * thick; + const cx = (left + right) / 2, cy = top + (H - (2 * ry * (1 + maxE) + depth)) / 2 + ry * (1 + maxE); + marks.pie3d = { cx: round(cx), cy: round(cy), rx: round(rx), ry: round(ry), depth: round(depth) }; + const at = (deg, f, [ox, oy], dz = 0) => [cx + ox + rx * f * Math.sin(deg * RAD), cy + oy - ry * f * Math.cos(deg * RAD) + dz]; + const slices = []; + let a = g.first || 0; + vals.forEach((v, i) => { + if (!v) return; + const a1 = a + (v / total) * 360, mid = (a + a1) / 2, e = exOf(i); + slices.push({ i, a0: a, a1, mid, v, e, off: [rx * e * Math.sin(mid * RAD), -ry * e * Math.cos(mid * RAD)], color: pointColor(g, s, i) }); + a = a1; + }); + const exploded = slices.some((x) => x.e > 0); + const tipOf = (x) => `${s.cats?.[x.i] ?? x.i + 1}: ${format(s.vals[x.i], s.fmt)}`; + // Back (mid-angle at 12 o'clock) to front (6 o'clock). + const order = [...slices].sort((p, q) => Math.cos(q.mid * RAD) - Math.cos(p.mid * RAD)); + for (const x of order) { + // A cut face shows only when the pie is apart somewhere and the face turns toward the viewer: the slice's first + // edge on the left half, its last edge on the right half. + if (exploded && slices.length > 1) { + for (const [edge, facing] of [[x.a0, Math.sin(x.a0 * RAD) < -1e-9], [x.a1, Math.sin(x.a1 * RAD) > 1e-9]]) { + if (!facing) continue; + const c = [cx + x.off[0], cy + x.off[1]], p = at(edge, 1, x.off); + add('path', { d: poly([c, p, [p[0], p[1] + depth], [c[0], c[1] + depth]]), fill: darker(x.color, 60000) }, 'gc-side', null, tipOf(x)); + marks.sides.push({ i: x.i, kind: 'face', a: round(edge) }); + } + } + // The rim: the part of the slice's arc on the front half (90° to 270°, every turn), as a band `depth` tall. + for (let turn = Math.floor((x.a0 - 270) / 360); turn * 360 + 90 < x.a1; turn++) { + const b0 = Math.max(x.a0, turn * 360 + 90), b1 = Math.min(x.a1, turn * 360 + 270); + if (b1 - b0 < 1e-6) continue; + const p0 = at(b0, 1, x.off), p1 = at(b1, 1, x.off); + const d = `M${round(p0[0])} ${round(p0[1])}A${round(rx)} ${round(ry)} 0 0 1 ${round(p1[0])} ${round(p1[1])}L${round(p1[0])} ${round(p1[1] + depth)}` + + `A${round(rx)} ${round(ry)} 0 0 0 ${round(p0[0])} ${round(p0[1] + depth)}Z`; + add('path', { d, fill: darker(x.color) }, 'gc-side', null, tipOf(x)); + marks.sides.push({ i: x.i, kind: 'rim', a0: round(b0), a1: round(b1) }); + } + } + for (const x of order) { + const c = [cx + x.off[0], cy + x.off[1]]; + if (slices.length === 1) add('ellipse', { cx: round(c[0]), cy: round(c[1]), rx: round(rx), ry: round(ry), fill: x.color }, 'gc-slice', null, tipOf(x)); + else { + const p0 = at(x.a0, 1, x.off), p1 = at(x.a1, 1, x.off); + add('path', { d: `M${round(c[0])} ${round(c[1])}L${round(p0[0])} ${round(p0[1])}A${round(rx)} ${round(ry)} 0 ${x.a1 - x.a0 > 180 ? 1 : 0} 1 ${round(p1[0])} ${round(p1[1])}Z`, fill: x.color }, 'gc-slice', null, tipOf(x)); + } + marks.slices.push({ i: x.i, a0: round(x.a0), a1: round(x.a1), color: x.color, explosion: x.e, dx: round(x.off[0]), dy: round(x.off[1]) }); + } + // Labels on the slice's top (outside past its edge for outEnd), in slice order; leader lines are not drawn. + for (const x of slices) { + const lab = s.labels ?? g.labels; + if (!lab || lab.del || !(lab.val || lab.pct || lab.cat) || x.a1 - x.a0 < 12) continue; + const text = [lab.cat ? s.cats?.[x.i] : null, lab.val ? format(s.vals[x.i], lab.fmt || s.fmt) : null, lab.pct ? format(x.v / total, lab.fmt && /%/.test(lab.fmt) ? lab.fmt : '0%') : null].filter(Boolean).join(' '); + const out = lab.pos === 'outEnd'; + const [lx, ly] = at(x.mid, out ? 1.16 : 0.62, x.off, out && Math.cos(x.mid * RAD) < 0 ? depth : 0); + add('text', { x: round(lx), y: round(ly + 4), 'text-anchor': out ? (Math.sin(x.mid * RAD) > 0.2 ? 'start' : Math.sin(x.mid * RAD) < -0.2 ? 'end' : 'middle') : 'middle' }, out ? 'gc-label' : 'gc-plabel', text); + marks.labels.push(text); + } + return { prims, marks }; + } + // ── Pies and doughnuts: the first series a pie, every series a ring (the first innermost) ── if (round1) { const g = groups[0]; @@ -188,7 +271,9 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { // ── Cartesian: one pair of axes ── const axList = Object.values(data.axes || {}); const scatter = groups[0].type === 'scatter'; - const horiz = groups[0].type === 'bar' && groups[0].dir === 'bar'; + const isBar = (g) => g.type === 'bar' || g.type === 'bar3D'; + const d3 = groups[0].type === 'bar3D'; + const horiz = isBar(groups[0]) && groups[0].dir === 'bar'; const valAx = axList.find((a) => a.kind === 'val' && (!scatter || a.pos === 'l' || a.pos === 'r')) ?? axList.find((a) => a.kind === 'val') ?? { kind: 'val', grid: true }; const catAx = scatter ? (axList.find((a) => a.kind === 'val' && a !== valAx) ?? { kind: 'val' }) : (axList.find((a) => a.kind === 'cat' || a.kind === 'date') ?? { kind: 'cat' }); const n = Math.max(1, ...groups.flatMap((g) => g.series.map((s) => s.vals.length))); @@ -219,7 +304,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const stacked = groups[gi].grouping !== 'standard' && groups[gi].grouping !== 'clustered'; for (const v of stacked ? [p.lo, p.hi] : [p.hi]) { lo = Math.min(lo, v); hi = Math.max(hi, v); } }))); - if (groups.some((g) => g.type === 'bar' || g.type === 'area') && Number.isFinite(lo)) { lo = Math.min(lo, 0); hi = Math.max(hi, 0); } + if (groups.some((g) => isBar(g) || g.type === 'area') && Number.isFinite(lo)) { lo = Math.min(lo, 0); hi = Math.max(hi, 0); } if (anyPct && Number.isFinite(hi)) { hi = Math.max(hi, 1e-9) > 0 ? 1 : hi; lo = lo < 0 ? -1 : 0; } const valFmt = valAx.fmt && !valAx.fmt.linked ? valAx.fmt.code : anyPct ? '0%' : (groups[0].series[0].fmt || valAx.fmt?.code || 'General'); const catFmt = catAx.fmt && !catAx.fmt.linked ? catAx.fmt.code : (groups[0].series[0].catFmt || catAx.fmt?.code || 'General'); @@ -246,6 +331,28 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { if (showC) bottom -= TICK + 6; } if (right - left < 12 || bottom - top < 12) return { prims, marks }; + // ── 3-D columns and bars: Excel's right-angle axes (c:rAngAx, its default for columns) as an oblique projection. The + // plot's front stays a flat rectangle and depth z recedes by (sin rotY, −sin rotX·|cos rotY|) per px (rotY 20°, rotX 15° + // by default). A file with rAngAx 0 and a perspective is drawn the same way: parallel, no vanishing point. A bar is as + // deep as wide (× depthPercent), a row of them gapDepth % deeper; series stand side by side (clustered, stacked) or one + // row each, first in front (standard). Cylinders, cones and pyramids (c:shape) are drawn as boxes. The plot's room + // gives up the depth's projection, at most 35% each way. ── + let sx = 0, sy = 0, depth = 0, rowDepth = 0, barDepth = 0; + if (d3) { + const g = groups[0], v3 = data.view3D ?? {}; + const turn = ((((v3.rotY ?? 20) % 360) + 360) % 360) * RAD; + sx = Math.sin(turn); sy = Math.sin(clamp(v3.rotX ?? 15, 0, 90) * RAD) * Math.abs(Math.cos(turn)); + const rows = g.grouping === 'standard' ? g.series.length : 1; + const kk = g.grouping === 'clustered' ? g.series.length : 1; + const gap = clamp(g.gap, 0, 500) / 100, gd = clamp(g.gapDepth ?? 150, 0, 500) / 100, dp = clamp(v3.depthPercent ?? 100, 20, 2000) / 100; + const per = rows * dp * (1 + gd); + depth = ((horiz ? bottom - top : right - left) / (n * (kk + gap) + (horiz ? sy : Math.abs(sx)) * per)) * per; + depth = Math.min(depth, Math.abs(sx) > 1e-6 ? ((right - left) * 0.35) / Math.abs(sx) : Infinity, sy > 1e-6 ? ((bottom - top) * 0.35) / sy : Infinity); + rowDepth = depth / rows; barDepth = rowDepth / (1 + gd); + if (sx > 0) right -= sx * depth; else left -= sx * depth; + top += sy * depth; + } + const pj = (x, y, z) => [x + sx * z, y - sy * z]; vs = scale(lo, hi, horiz ? right - left : bottom - top, horiz ? 64 : 28, anyPct ? { ...valAx, min: valAx.min ?? lo, max: valAx.max ?? hi } : valAx); if (scatter) xScale = scale(xLo, xHi, right - left, 56, catAx); const vRev = !!valAx.reverse, cRev = !!catAx.reverse; @@ -255,7 +362,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return horiz ? left + (vRev ? 1 - f : f) * (right - left) : bottom - (vRev ? 1 - f : f) * (bottom - top); }; const cLen = horiz ? bottom - top : right - left; - const mid = !scatter && !groups.some((g) => g.type === 'bar') && (valAx.between === 'midCat' || (!valAx.between && groups.every((g) => g.type === 'area'))); + const mid = !scatter && !groups.some(isBar) && (valAx.between === 'midCat' || (!valAx.between && groups.every((g) => g.type === 'area'))); const band = mid && n > 1 ? cLen / (n - 1) : cLen / n; const cpx = (i, f = 0.5) => { const k = cRev ? n - 1 - i : i; @@ -263,10 +370,21 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return horiz ? bottom - along : left + along; }; const xpx = (x) => { const f = (x - xScale.min) / (xScale.max - xScale.min); return left + (cRev ? 1 - f : f) * (right - left); }; - // Gridlines and the value axis's labels. + // A 3-D plot's walls behind it: the back, the side seen from inside, the floor (their file's fill; none said, the Look's). + const xs3 = sx >= 0 ? left : right, xo3 = sx >= 0 ? right : left; + if (d3) { + const wl = data.walls ?? {}; + const wall = (w, pts, name) => { if (w?.fill === 'none') return; add('path', { d: poly(pts), ...(w?.fill && { fill: w.fill }) }, 'gc-wall'); marks.faces.push({ kind: name }); }; + wall(wl.back, [pj(left, top, depth), pj(right, top, depth), pj(right, bottom, depth), pj(left, bottom, depth)], 'back'); + wall(wl.side, [pj(xs3, top, 0), pj(xs3, top, depth), pj(xs3, bottom, depth), pj(xs3, bottom, 0)], 'side'); + wall(wl.floor, [pj(left, bottom, 0), pj(right, bottom, 0), pj(right, bottom, depth), pj(left, bottom, depth)], 'floor'); + } + // Gridlines (a 3-D plot's across its back wall and along the side wall or floor) and the value axis's labels. vs.ticks.forEach((v) => { const p = round(vpx(v)); - if (valAx.grid !== false && (valAx.grid || !data.axes || !Object.keys(data.axes).length)) add('line', horiz ? { x1: p, y1: round(top), x2: p, y2: round(bottom) } : { x1: round(left), y1: p, x2: round(right), y2: p }, 'gc-grid'); + const gridOn = valAx.grid !== false && (valAx.grid || !data.axes || !Object.keys(data.axes).length); + if (gridOn && d3) add('path', { d: (horiz ? [pj(p, top, depth), pj(p, bottom, depth), pj(p, bottom, 0)] : [pj(xs3, p, 0), pj(xs3, p, depth), pj(xo3, p, depth)]).map(([x, y], i) => `${i ? 'L' : 'M'}${round(x)} ${round(y)}`).join(''), fill: 'none' }, 'gc-grid'); + else if (gridOn) add('line', horiz ? { x1: p, y1: round(top), x2: p, y2: round(bottom) } : { x1: round(left), y1: p, x2: round(right), y2: p }, 'gc-grid'); if (showV) add('text', horiz ? { x: p, y: round(bottom + TICK + 3), 'text-anchor': 'middle' } : { x: round(left - 5), y: round(p + TICK / 2 - 1), 'text-anchor': 'end' }, 'gc-tick', format(v, valFmt)); marks.ticks.val.push(v); }); @@ -281,7 +399,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { // The category axis's line where the values are 0 (or the scale's edge), and its labels, thinned to fit. const zero = Math.min(Math.max(0, vs.min), vs.max); const base = round(vpx(valAx.crossesAt ?? (vs.min > 0 ? vs.min : vs.max < 0 ? vs.max : zero))); - if (!catAx.deleted || scatter) add('line', horiz ? { x1: base, y1: round(top), x2: base, y2: round(bottom) } : { x1: round(left), y1: base, x2: round(right), y2: base }, 'gc-axis'); + if (!d3 && (!catAx.deleted || scatter)) add('line', horiz ? { x1: base, y1: round(top), x2: base, y2: round(bottom) } : { x1: round(left), y1: base, x2: round(right), y2: base }, 'gc-axis'); if (!scatter && showC) { const each = horiz ? TICK + 4 : Math.max(...cats.map((c) => width(format(c, catFmt), TICK))) + 8; const skip = Math.max(1, Math.ceil(each / Math.max(1, band))); @@ -342,6 +460,51 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { if (lab && !lab.del && lab.val) add('text', horiz ? { x: round(Math.max(a, b) + 3), y: round(box.y + box.height / 2 + 3.5) } : { x: round(box.x + box.width / 2), y: round(Math.min(a, b) - 3), 'text-anchor': 'middle' }, 'gc-label', format(p.v, lab.fmt || s.fmt)); }); }); + } else if (g.type === 'bar3D') { + // Each bar a front rectangle, a lighter top and a darker side, painted far to near: back rows first, then across + // from the side away from the viewer, lower stacked parts before the ones on them. + const stacked = g.grouping === 'stacked' || g.grouping === 'percentStacked'; + const rows = g.grouping === 'standard' ? S : 1, kk = g.grouping === 'clustered' ? S : 1; + const barW = band / (kk + clamp(g.gap, 0, 500) / 100), start = (band - barW * kk) / 2; + const items = []; + g.series.forEach((s, j) => stacks[gi][j].forEach((p, i) => { + if (!p) return; + const off = start + (kk > 1 ? j * barW : 0); + const a = vpx(clamp(stacked ? p.lo : clamp(0, vs.min, vs.max), vs.min, vs.max)), b = vpx(clamp(p.hi, vs.min, vs.max)); + const along = cpx(i, 0) + (horiz ? -off - barW : off); + const box = horiz ? { x: Math.min(a, b), y: along, width: Math.abs(b - a), height: barW } : { x: along, y: Math.min(a, b), width: barW, height: Math.abs(b - a) }; + const z0 = (rows > 1 ? j : 0) * rowDepth + (rowDepth - barDepth) / 2; + items.push({ s, j, i, p, a, b, box, z0, z1: z0 + barDepth }); + })); + const dir = sx >= 0 ? 1 : -1; + items.sort((P, Q) => Q.z0 - P.z0 || (horiz ? Q.box.y - P.box.y || dir * (P.box.x - Q.box.x) : dir * (P.box.x - Q.box.x) || Q.box.y + Q.box.height - (P.box.y + P.box.height))); + const pts = (list) => list.map(([x, y]) => [round(x), round(y)]); + for (const it of items) { + const { s, j, i, p, box, z0, z1 } = it; + const x0 = box.x, x1 = box.x + box.width, y0 = box.y, y1 = box.y + box.height; + const color = pointColor(g, s, i); + const tip = `${s.name ? `${s.name}, ` : ''}${cats[i]}: ${format(p.v, s.fmt)}`; + const f = pj(x0, y0, z0); + add('rect', { x: round(f[0]), y: round(f[1]), width: round(box.width), height: round(box.height), fill: color }, 'gc-bar', null, tip); + marks.faces.push({ s: j, i, kind: 'front', pts: pts([pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y1, z0), pj(x0, y1, z0)]) }); + if (sy > 1e-6) { + const q = [pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y0, z1), pj(x0, y0, z1)]; + add('path', { d: poly(q), fill: lighter(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'top', pts: pts(q) }); + } + if (Math.abs(sx) > 1e-6) { + const xe = sx > 0 ? x1 : x0, q = [pj(xe, y0, z0), pj(xe, y0, z1), pj(xe, y1, z1), pj(xe, y1, z0)]; + add('path', { d: poly(q), fill: darker(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'side', pts: pts(q) }); + } + marks.bars.push({ s: j, i, x: round(f[0]), y: round(f[1]), w: round(box.width), h: round(box.height), v: p.v, top: p.hi, color, z0: round(z0), z1: round(z1) }); + const lab = s.labels ?? g.labels; + if (lab && !lab.del && lab.val) { + const text = format(p.v, lab.fmt || s.fmt), zm = (z0 + z1) / 2; + add('text', horiz ? { x: round(Math.max(it.a, it.b) + sx * z1 + 3), y: round(y0 + box.height / 2 - sy * zm + 3.5) } : { x: round((x0 + x1) / 2 + sx * zm), y: round(Math.min(it.a, it.b) - sy * zm - 3), 'text-anchor': 'middle' }, 'gc-label', text); + marks.labels.push(text); + } + } } else if (g.type === 'line' || g.type === 'area') { const stacked = g.grouping !== 'standard'; for (const j of g.series.keys()) { diff --git a/sheet.css b/sheet.css index eeb86aa85..b03cf6166 100644 --- a/sheet.css +++ b/sheet.css @@ -81,6 +81,8 @@ .gc-grid { stroke: var(--gx-line); } .gc-axis { stroke: var(--gz-line-strong); } .gc-slice { stroke: var(--gx-cell); } +.gc-side { stroke: var(--gx-cell); stroke-width: .5px; } +.gc-wall:not([fill]) { fill: var(--gx-head); } .gx-chart[data-on] { outline: 2px solid var(--gz-accent); outline-offset: -1px; } .gx-chart-h { position: absolute; right: -4px; bottom: -4px; width: 8px; height: 8px; background: var(--gz-accent); border: 1px solid var(--gx-cell); } diff --git a/test/fixtures/sheets/charts.js b/test/fixtures/sheets/charts.js index 10c5eb891..e63da304c 100644 --- a/test/fixtures/sheets/charts.js +++ b/test/fixtures/sheets/charts.js @@ -1,6 +1,7 @@ // A workbook with charts as Excel writes them (its element order, namespaces, caches and two-cell anchors), built with // lib/zip.js for test/sheet-chart*.test.js and scripts/sheets-check.js: sheet Data's months, sales, costs and an x/y -// pair; a clustered column, a line, a pie and a scatter chart, and a radar chart Glaze draws as a frame. +// pair; a clustered column, a line, a pie and a scatter chart, and a radar chart Glaze draws as a frame; a 3-D pie (its +// second slice exploded, percent labels) and a 3-D clustered column chart (walls, value labels) for workbooks that ask. const Z = require('../../../lib/zip.js'); const enc = new TextEncoder(); const RT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'; @@ -19,9 +20,20 @@ const ser = (i, col, cat, vals, extra = '') => `Data!$A$2:$A$5${strCache(cat)}Data!$${col}$2:$${col}$5${numCache(vals)}`; const axes = (cat = 'catAx') => `${cat === 'catAx' ? '' : ''}` + ''; -const space = (title, plot, legend = 'b') => `${HEAD}${title ? rich(title) : ''}` +// pre: what Excel writes between autoTitleDeleted and the plot area (a 3-D chart's view3D, floor and walls). +const space = (title, plot, legend = 'b', pre = '') => `${HEAD}${title ? rich(title) : ''}${pre}` + `${plot}${legend ? `` : ''}`; +// Excel's 3-D parts: c:view3D, then the floor and walls (thickness 0; no spPr is the default fill, noFill none). +const view3D = (x, y, rAng) => ``; +const noFill = ''; +const walls = (floor, side, back) => `${floor}${side}${back}`; +const flags = (o) => ['showLegendKey', 'showVal', 'showCatName', 'showSerName', 'showPercent', 'showBubbleSize'].map((k) => ``).join(''); +const slice3D = (i, k, ex = '') => `${ex}`; +const PIE3D_LABELS = `${flags({ showPercent: 1 })}`; +const COL3D_LABELS = `${flags({ showVal: 1 })}`; +const fill3D = (k) => ``; + const CHARTS = { column: space('Sales by month', `${ser(0, 'B', MONTHS, SALES, fill(1))}${ser(1, 'C', MONTHS, COSTS, fill(2))}${axes()}`), line: space('Trend', `${ser(0, 'B', MONTHS, SALES, '')}${axes()}`), @@ -29,9 +41,15 @@ const CHARTS = { scatter: space('Spread', `Data!$E$1${strCache(['y'])}` + `Data!$D$2:$D$5${numCache(XS)}Data!$E$2:$E$5${numCache(YS)}${axes('valAx').replace('', '')}`, null), radar: space('Radar view', `${ser(0, 'B', MONTHS, SALES)}${axes()}`), + pie3d: space('Share in 3-D', `${ser(0, 'B', MONTHS, SALES, [1, 2, 3, 4].map((k, i) => slice3D(i, k, i === 1 ? '' : '')).join('') + PIE3D_LABELS)}` + + `${flags({})}`, 'r', view3D(30, 0, 0) + walls('', noFill, noFill)), + col3d: space('Sales in 3-D', `${ser(0, 'B', MONTHS, SALES, fill3D(1) + COL3D_LABELS)}${ser(1, 'C', MONTHS, COSTS, fill3D(2))}` + + `${flags({})}${axes()}`, 'b', view3D(15, 20, 1) + walls(noFill, '', '')), }; // Where each sits: [from col, from row, to col, to row] (0-based cells). const PLACES = { column: [6, 1, 12, 15], line: [13, 1, 19, 15], pie: [6, 16, 12, 30], scatter: [13, 16, 19, 30], radar: [6, 31, 12, 45] }; +// The 3-D charts, in a workbook of their own (kinds: ['pie3d', 'col3d']). +const PLACES_3D = { pie3d: [6, 1, 13, 18], col3d: [6, 19, 13, 36] }; const anchor = ([c0, r0, c1, r1], i, name) => `${c0}0${r0}0${c1}0${r1}0` + ``; @@ -53,7 +71,7 @@ const workbook = ({ kinds = ['column', 'line', 'pie', 'scatter', 'radar'], over 'xl/sharedStrings.xml': `${HEAD}${strings.map((x) => `${x}`).join('')}`, 'xl/worksheets/sheet1.xml': `${HEAD}${rows.join('')}`, 'xl/worksheets/_rels/sheet1.xml.rels': rels([['rId1', 'drawing', '../drawings/drawing1.xml']]), - 'xl/drawings/drawing1.xml': `${HEAD}${kinds.map((k, i) => anchor(PLACES[k], i, `Chart ${i + 1}`)).join('')}`, + 'xl/drawings/drawing1.xml': `${HEAD}${kinds.map((k, i) => anchor(PLACES[k] ?? PLACES_3D[k], i, `Chart ${i + 1}`)).join('')}`, 'xl/drawings/_rels/drawing1.xml.rels': rels(kinds.map((k, i) => [`rId${i + 1}`, 'chart', `../charts/chart${i + 1}.xml`])), ...Object.fromEntries(kinds.map((k, i) => [`xl/charts/chart${i + 1}.xml`, CHARTS[k]])), ...over, @@ -85,4 +103,4 @@ const layerOn = (wb, sh = wb.sheets[0]) => { return CH.layer({ panes: { br: { el: { hidden: false }, inner: el(), fr: false, fc: false } }, wb: () => wb, sheet: () => sh, geo: () => geo, t: (s) => s }); }; -module.exports = { fakeDom, layerOn, workbook, CHARTS, PLACES, MONTHS, SALES, COSTS, XS, YS, pack, rels, space, ser, axes, fill, rich, strCache, numCache, C_NS }; +module.exports = { fakeDom, layerOn, workbook, CHARTS, PLACES, PLACES_3D, MONTHS, SALES, COSTS, XS, YS, pack, rels, space, ser, axes, fill, rich, strCache, numCache, C_NS }; diff --git a/test/sheet-chart-3d.test.js b/test/sheet-chart-3d.test.js new file mode 100644 index 000000000..36cb8bb47 --- /dev/null +++ b/test/sheet-chart-3d.test.js @@ -0,0 +1,189 @@ +// 3-D pie and column charts (lib/sheet-chart-xml.js reads c:view3D, explosions, c:shape and the walls; lib/sheet-chart.js +// projects them into the SVG scene): a 3-D pie's tilted ellipse, its front rim and cut faces in painter's order, an +// exploded slice's offset and percent labels; a 3-D column's front, top and side faces for clustered, stacked and +// standard groupings; the kinds still framed name their type; the work stays bounded; and the parts round-trip untouched. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const X = require('../lib/sheet-xlsx.js'); +const C = require('../lib/sheet-calc.js'); +const D = require('../lib/doctext.js'); +const CX = require('../lib/sheet-chart-xml.js'); +const CH = require('../lib/sheet-chart.js'); +const FX = require('./fixtures/sheets/charts.js'); + +const RAD = Math.PI / 180; +const near = (a, b, eps = 0.02) => Math.abs(a - b) <= eps; +const data = (xml) => CX.resolve(CX.chart(xml), () => null); +const pie3d = (edit = (x) => x) => data(edit(FX.CHARTS.pie3d)); +const col3d = (edit = (x) => x) => data(edit(FX.CHARTS.col3d)); +const view = (x, y) => (xml) => xml.replace(//, ``); +const grouping = (g) => (xml) => xml.replace('', ``); + +test('parse: view3D, the walls, an exploded slice, a bar\'s shape and gap depth', () => { + const pie = CX.chart(FX.CHARTS.pie3d), col = CX.chart(FX.CHARTS.col3d); + assert.deepEqual([pie.kind, pie.groups[0].type, col.kind, col.groups[0].type], ['plot', 'pie3D', 'plot', 'bar3D']); + assert.deepEqual(pie.view3D, { rotX: 30, rotY: 0, perspective: null, depthPercent: 100, hPercent: null, rAngAx: false }); + assert.deepEqual(col.view3D, { rotX: 15, rotY: 20, perspective: null, depthPercent: 100, hPercent: null, rAngAx: true }); + const s = pie.groups[0].series[0]; + assert.deepEqual([...s.explode], [[1, 18]], 'c:explosion on the second dPt'); + assert.equal(s.explosion, 0); + assert.equal(CX.chart(FX.CHARTS.pie3d.replace('', '')).groups[0].series[0].explosion, 25, 'the series\' own'); + assert.deepEqual(s.labels && [s.labels.pct, s.labels.val], [true, false]); + assert.equal(pie.groups[0].first, 0, 'a 3-D pie turns by rotY'); + assert.equal(CX.chart(view(30, 75)(FX.CHARTS.pie3d)).groups[0].first, 75); + const g = col.groups[0]; + assert.deepEqual([g.dir, g.grouping, g.gap, g.gapDepth, g.shape, g.axes], ['col', 'clustered', 150, 150, 'box', ['10', '20', '0']]); + assert.equal(CX.chart(FX.CHARTS.col3d.replace('', '')).groups[0].shape, 'cylinder'); + assert.deepEqual(col.walls, { floor: { fill: 'none' }, side: { fill: null }, back: { fill: null } }); + assert.deepEqual(pie.walls, { floor: { fill: null }, side: { fill: 'none' }, back: { fill: 'none' } }); + const r = pie3d(); + assert.deepEqual([r.view3D.rotX, r.walls.side.fill, r.title], [30, 'none', 'Share in 3-D'], 'resolve keeps view3D and the walls'); +}); + +test('3-D pie: the slices\' angles, the ellipse from rotX, the rim only on front slices', () => { + const { marks } = CH.scene(pie3d(), 480, 300); + assert.deepEqual(marks.slices.map((x) => [x.i, x.a0, x.a1]).sort((a, b) => a[0] - b[0]), [[0, 0, 36], [1, 36, 108], [2, 108, 216], [3, 216, 360]]); + const { rx, ry, depth } = marks.pie3d; + assert.ok(near(ry / rx, Math.sin(30 * RAD), 0.005), `ry/rx = sin 30° (${ry}/${rx})`); + assert.ok(near(depth / rx, 0.18 * Math.cos(30 * RAD), 0.005), 'thickness 18% of the radius, projected'); + for (const [x, want] of [[60, Math.sin(60 * RAD)], [90, 1], [5, Math.sin(10 * RAD)]]) { + const m = CH.scene(pie3d(view(x, 0)), 480, 300).marks.pie3d; + assert.ok(near(m.ry / m.rx, want, 0.005), `rotX ${x}: ${m.ry / m.rx}`); + } + const rims = marks.sides.filter((x) => x.kind === 'rim'); + assert.deepEqual(rims.map((x) => [x.i, x.a0, x.a1]).sort((a, b) => a[0] - b[0]), [[1, 90, 108], [2, 108, 216], [3, 216, 270]], 'the back slice (0° to 36°) shows no rim; the others only from 90° to 270°'); + assert.deepEqual(marks.legend, FX.MONTHS); + assert.deepEqual(marks.labels, ['10%', '20%', '30%', '40%'], 'percent labels'); +}); + +test('3-D pie: an exploded slice moves out along its mid-angle; cut faces turn toward the viewer, painted back to front', () => { + const sc = CH.scene(pie3d(), 480, 300); + const { rx, ry, cx, cy } = sc.marks.pie3d; + const by = Object.fromEntries(sc.marks.slices.map((x) => [x.i, x])); + assert.ok(near(by[1].dx, rx * 0.18 * Math.sin(72 * RAD), 0.02) && near(by[1].dy, -ry * 0.18 * Math.cos(72 * RAD), 0.02), JSON.stringify(by[1])); + assert.deepEqual([by[0].dx, by[0].dy, by[2].dx, by[3].dy].map((v) => v + 0), [0, 0, 0, 0]); + // Shrunk to fit: the exploded slice's far edge stays inside the plot. + assert.ok(cx + by[1].dx + rx <= 480 && cy - ry - 0.18 * ry >= 0); + // Faces: the first slice's last edge (36°, right half) and the second's last edge (108°), and the fourth's first (216°). + const faces = sc.marks.sides.filter((x) => x.kind === 'face'); + assert.deepEqual(faces.map((x) => [x.i, x.a]).sort((a, b) => a[0] - b[0]), [[0, 36], [1, 108], [3, 216]]); + // Unexploded: no cut faces at all. + const flat = CH.scene(pie3d((x) => x.replace('', '')), 480, 300); + assert.equal(flat.marks.sides.filter((x) => x.kind === 'face').length, 0); + assert.ok(flat.marks.slices.every((x) => x.dx === 0 && x.dy === 0)); + // Painter's order: every side before every top; sides back (mid-angle near 0°) to front (near 180°). + const kinds = sc.prims.filter((p) => p.cls === 'gc-side' || p.cls === 'gc-slice').map((p) => p.cls); + assert.equal(kinds.lastIndexOf('gc-side') < kinds.indexOf('gc-slice'), true); + const order = sc.marks.slices.map((x) => Math.cos(((x.a0 + x.a1) / 2) * RAD)); + assert.deepEqual(order, [...order].sort((a, b) => b - a)); + // Sides shade the slice's own colour darker; tops keep it. + const top1 = sc.prims.find((p) => p.cls === 'gc-slice' && p.a.fill === by[1].color); + assert.ok(top1 && sc.prims.some((p) => p.cls === 'gc-side' && p.a.fill === CX.transform(by[1].color, [{ name: 'shade', a: { val: '72000' } }]))); +}); + +test('3-D pie: rotY turns the first slice; a lone slice is a whole ellipse', () => { + const turned = CH.scene(pie3d(view(30, 90)), 480, 300).marks.slices; + assert.deepEqual(turned.map((x) => [x.i, x.a0]).sort((a, b) => a[0] - b[0]).map((x) => x[1]), [90, 126, 198, 306]); + const one = pie3d(); + one.groups[0].series[0].vals = [5, 0, null, 0]; + const sc = CH.scene(one, 480, 300); + assert.equal(sc.prims.filter((p) => p.el === 'ellipse' && p.cls === 'gc-slice').length, 1); + assert.deepEqual(sc.marks.sides.map((x) => [x.kind, x.a0, x.a1]), [['rim', 90, 270]]); +}); + +test('3-D column, clustered: front, top and side faces in the oblique projection, Excel\'s gap width', () => { + const sc = CH.scene(col3d(), 480, 300); + const { bars, faces } = sc.marks; + assert.equal(bars.length, 8); + const sx = Math.sin(20 * RAD), sy = Math.sin(15 * RAD) * Math.cos(20 * RAD); + for (const b of bars) { + const top = faces.find((f) => f.kind === 'top' && f.s === b.s && f.i === b.i).pts, side = faces.find((f) => f.kind === 'side' && f.s === b.s && f.i === b.i).pts; + const dz = b.z1 - b.z0; + assert.ok(near(top[0][0], b.x) && near(top[0][1], b.y) && near(top[1][0], b.x + b.w), 'the top starts on the front\'s top edge'); + assert.ok(near(top[2][0] - top[1][0], sx * dz, 0.03) && near(top[2][1] - top[1][1], -sy * dz, 0.03), `depth recedes by (sin rotY, −sin rotX cos rotY) (${JSON.stringify({ top, dz })})`); + assert.ok(near(side[0][0], b.x + b.w) && near(side[3][1], b.y + b.h), 'the side on the right of the front'); + assert.ok(near(dz, b.w, 0.03), 'as deep as wide (depthPercent 100)'); + } + const a = bars.find((b) => b.s === 0 && b.i === 0), b = bars.find((x) => x.s === 1 && x.i === 0), a2 = bars.find((x) => x.s === 0 && x.i === 1); + assert.ok(near(b.x - a.x, a.w) && a.z0 === b.z0, 'the second series beside the first, at the same depth'); + assert.ok(near(a.w, (a2.x - a.x) / (2 + 1.5), 0.03), 'gapWidth 150: two bars in a band of 3.5'); + assert.ok(near(a2.h / a.h, 2, 0.01)); + assert.deepEqual(sc.marks.ticks.val, [0, 10, 20, 30, 40, 50]); + assert.deepEqual(sc.marks.labels, ['10', '20', '30', '40'], 'the first series\' value labels'); + // Walls: the side and back in the Look's fill (none said), no floor (noFill). + assert.deepEqual(faces.filter((f) => !('s' in f)).map((f) => f.kind), ['back', 'side']); + assert.ok(sc.prims.filter((p) => p.cls === 'gc-wall').every((p) => !('fill' in p.a))); + // Painted far to near: left to right across the categories. + const fronts = sc.prims.filter((p) => p.cls === 'gc-bar').map((p) => p.a.x); + assert.deepEqual(fronts, [...fronts].sort((p, q) => p - q)); + // Faces shade the series colour: top lighter, side darker. + const blue = a.color; + assert.ok(sc.prims.some((p) => p.cls === 'gc-face' && p.a.fill === CX.transform(blue, [{ name: 'tint', a: { val: '75000' } }]))); + assert.ok(sc.prims.some((p) => p.cls === 'gc-face' && p.a.fill === CX.transform(blue, [{ name: 'shade', a: { val: '72000' } }]))); +}); + +test('3-D column, stacked, 100% and standard; a cylinder drawn as a box; horizontal bars', () => { + let { bars } = CH.scene(col3d(grouping('stacked')), 480, 300).marks; + for (let i = 0; i < 4; i++) { + const a = bars.find((b) => b.s === 0 && b.i === i), b = bars.find((x) => x.s === 1 && x.i === i); + assert.ok(near(b.y + b.h, a.y) && a.x === b.x && a.z0 === b.z0, `category ${i}: costs on sales`); + assert.equal(b.top, FX.SALES[i] + FX.COSTS[i]); + } + ({ bars } = CH.scene(col3d(grouping('percentStacked')), 480, 300).marks); + const full = (i) => bars.filter((b) => b.i === i).reduce((n, b) => n + b.h, 0); + assert.ok(near(full(0), full(3), 0.05)); + const std = CH.scene(col3d(grouping('standard')), 480, 300); + ({ bars } = std.marks); + const s0 = bars.find((b) => b.s === 0 && b.i === 0), s1 = bars.find((b) => b.s === 1 && b.i === 0); + assert.ok(s1.z0 > s0.z1, 'the second series a row behind the first'); + const firstFront = std.prims.findIndex((p) => p.cls === 'gc-bar' && p.a.fill === s0.color); + assert.ok(std.prims.findIndex((p) => p.cls === 'gc-bar' && p.a.fill === s1.color) < firstFront, 'the back row painted first'); + const box = CH.scene(col3d(), 480, 300), cyl = CH.scene(col3d((x) => x.replace('', '')), 480, 300); + assert.deepEqual(cyl.marks.faces, box.marks.faces); + const across = CH.scene(col3d((x) => x.replace('', '')), 480, 300).marks.bars; + const j0 = across.find((b) => b.s === 0 && b.i === 0), j3 = across.find((b) => b.s === 0 && b.i === 3); + assert.ok(near(j3.w / j0.w, 4, 0.01) && j3.y < j0.y); +}); + +test('frames: 3-D line and area charts, and a 3-D chart beside another, name their type', () => { + const line3d = FX.CHARTS.line.replace(/lineChart>/g, 'line3DChart>'); + assert.deepEqual([CX.chart(line3d).kind, CX.chart(line3d).type], ['frame', '3-D line chart']); + assert.equal(CX.chart(FX.CHARTS.line.replace(/lineChart>/g, 'area3DChart>')).type, '3-D area chart'); + const both = (a, b) => FX.CHARTS.col3d.replace('', `${a}`).replace('', `${b}`); + assert.equal(CX.chart(both('', '')).type, '3-D column chart'); + assert.equal(CX.chart(both('', '')).type, '3-D pie chart'); + for (const w of ['3-D pie chart', '3-D column chart', '3-D bar chart', '3-D line chart', '3-D area chart']) assert.ok(CX.WORDS.includes(w), w); + assert.ok(!CX.WORDS.includes('3-D chart'), 'no frame says only "3-D"'); +}); + +test('bounded: an oversized 3-D chart is a titled frame; a 3-D pie\'s shapes grow by a few per slice', () => { + const big = pie3d(); + big.groups[0].series[0].vals = new Array(10001).fill(1); + const sc = CH.scene(big, 480, 288); + assert.equal(sc.marks.kind, 'Chart'); + assert.ok(sc.prims.length < 10); + const many = pie3d(); + const s = many.groups[0].series[0]; + s.vals = Array.from({ length: 400 }, (_, i) => i + 1); s.cats = s.vals.map(String); s.explode = new Map(s.vals.map((_, i) => [i, 10])); + const wide = CH.scene(many, 480, 288); + assert.ok(wide.prims.length <= 30 + 400 * 6, `${wide.prims.length} shapes for 400 slices`); + const cols = col3d(); + cols.groups[0].series.forEach((x) => { x.vals = new Array(4000).fill(3); x.cats = x.vals.map(String); }); + assert.ok(CH.scene(cols, 480, 288).prims.length <= 60 + 8000 * 4 + 50); +}); + +test('round trip: a workbook with 3-D charts read, drawn and written untouched keeps their parts byte for byte', async () => { + const bytes = FX.workbook({ kinds: ['pie3d', 'col3d'] }); + const z = D.unzip(bytes), before = {}; + for (const n of z.names) before[n] = await z.text(n); + const wb = await X.read(bytes); + C.engine(wb).recalc(); + const list = CX.charts(wb, wb.sheets[0]).map((c) => CX.resolve(c.chart, CX.lookup(wb))); + assert.deepEqual(list.map((d) => [d.kind, d.groups[0].type]), [['plot', 'pie3D'], ['plot', 'bar3D']]); + assert.deepEqual(list[0].groups[0].series[0].vals, FX.SALES, 'read live from the sheet'); + for (const d of list) CH.scene(d, 480, 300); + const out = D.unzip(await X.write(wb, { date: new Date(2026, 0, 1) })); + const names = Object.keys(before).filter((n) => /^xl\/(charts|drawings)\//.test(n)); + assert.equal(names.length, 4); + for (const n of names) assert.equal(await out.text(n), before[n], n); +}); diff --git a/test/sheet-chart.test.js b/test/sheet-chart.test.js index 3fb9233f2..8011c0a47 100644 --- a/test/sheet-chart.test.js +++ b/test/sheet-chart.test.js @@ -175,15 +175,15 @@ test('frames: a chartex chart, 3-D, a second value axis, a missing part', async + '7010' + '' + '0020020240'; - const bar3d = FX.CHARTS.column.replace(/barChart>/g, 'bar3DChart>'); + const line3d = FX.CHARTS.line.replace(/lineChart>/g, 'line3DChart>'); const { wb } = await open({ kinds: ['column', 'line'], over: { 'xl/drawings/drawing1.xml': drawing, 'xl/drawings/_rels/drawing1.xml.rels': FX.rels([['rId1', 'http://schemas.microsoft.com/office/2014/relationships/chartEx', '../charts/chartEx1.xml'], ['rId2', 'chart', '../charts/chart1.xml']]), - 'xl/charts/chartEx1.xml': ex, 'xl/charts/chart1.xml': bar3d, + 'xl/charts/chartEx1.xml': ex, 'xl/charts/chart1.xml': line3d, } }); const list = drawn(wb); assert.equal(list.length, 3, 'the picture is not a chart'); - assert.deepEqual(list.map((c) => [c.data.kind, c.data.type, c.data.title]), [['frame', 'Waterfall chart', 'Cash flow'], ['frame', '3-D chart', 'Sales by month'], ['frame', 'Chart', null]]); + assert.deepEqual(list.map((c) => [c.data.kind, c.data.type, c.data.title]), [['frame', 'Waterfall chart', 'Cash flow'], ['frame', '3-D line chart', 'Trend'], ['frame', 'Chart', null]]); assert.deepEqual(list[0].anchor.from, { col: 1, colOff: 1, row: 1, rowOff: 0 }, '9525 EMU is a pixel'); assert.deepEqual([list[1].anchor.kind, list[1].anchor.ext], ['oneCellAnchor', { w: 480, h: 288 }]); assert.deepEqual([list[2].anchor.pos, list[2].anchor.ext], [{ x: 100, y: 10 }, { w: 100, h: 50 }]); From 7935bf8f9118d5d4c54a65c5637e56cb3af54808 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 12:57:08 -0400 Subject: [PATCH 002/907] Check 3-D pie and column charts in the Documents grid --- scripts/sheets-check.js | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/scripts/sheets-check.js b/scripts/sheets-check.js index 10fca63fa..6e7f72801 100644 --- a/scripts/sheets-check.js +++ b/scripts/sheets-check.js @@ -610,6 +610,50 @@ const seeded = (key, wb = M.workbook()) => { check(ibook.keep.problems.length === 0 && ilist.length === 6 && sixth?.type === 'bar' && sixth.series.map((s) => s.val.f).join() === 'Data!$B$2:$B$5,Data!$C$2:$C$5' && ilist[5].anchor.from.col === 6, `the export holds the inserted chart, its references and its dragged anchor (${JSON.stringify({ problems: ibook.keep.problems, n: ilist.length, refs: sixth?.series.map((s) => s.val.f), col: ilist[5]?.anchor.from.col })})`); + // ---- 3-D charts: a workbook with a 3-D pie (its second slice exploded, percent labels) and a 3-D clustered column + // chart (value labels, the side and back walls) draws both projected: the pie's four tops, the rim of its three front + // slices and the three cut faces its explosion shows; the columns' eight fronts with a top and a side each; the + // dark Look's labels read; 640 px; screenshots of each. + const file3d = path.join(tmp, 'Charts 3D.xlsx'); + fs.writeFileSync(file3d, FX.workbook({ kinds: ['pie3d', 'col3d'] })); + await openHome(); + const cards3d = await page.evaluate(() => [...document.querySelectorAll('.gd-card')].map((c) => c.dataset.key)); + await page.setInputFiles('#gd-file', file3d); + const key3d = await until(() => page.evaluate((was) => [...document.querySelectorAll('.gd-card')].map((c) => c.dataset.key).find((k) => !was.includes(k)), cards3d), 'the 3-D chart workbook card'); + await until(() => local(`writerPaper:${key3d}`), 'the 3-D chart workbook record'); + await page.click(`.gd-card[data-key="${key3d}"] .gd-title`); + await grid(); + const count3d = () => page.evaluate(() => [...document.querySelectorAll('#gx-grid .gx-chart')].map((el) => { + const n = (s) => el.querySelectorAll(s).length; + return { id: el.dataset.chart, title: [...el.querySelectorAll('.gc-title')].map((t) => t.textContent).join(' '), kind: el.querySelector('.gc-kind')?.textContent ?? null, tops: n('.gc-slice'), sides: n('.gc-side'), + bars: n('.gc-bar'), faces: n('.gc-face'), walls: n('.gc-wall'), plabels: [...el.querySelectorAll('.gc-plabel')].map((t) => t.textContent), labels: [...el.querySelectorAll('.gc-label')].map((t) => t.textContent) }; + })); + const three = await until(async () => { const l = await count3d(); return l.length === 2 ? l : null; }, 'two 3-D charts drawn'); + const pie3 = three.find((c) => c.title === 'Share in 3-D'), col3 = three.find((c) => c.title === 'Sales in 3-D'); + check(pie3 && !pie3.kind && pie3.tops === 4 && pie3.sides === 6 && pie3.plabels.join() === '10%,20%,30%,40%', + `a 3-D pie draws its four tops, three front rims and three cut faces, with percent labels (${JSON.stringify(pie3)})`); + check(col3 && !col3.kind && col3.bars === 8 && col3.faces === 16 && col3.walls === 2 && col3.labels.join() === '10,20,30,40', + `a 3-D clustered column chart draws eight bars, each with a top and a side, on its two walls, with value labels (${JSON.stringify(col3)})`); + const ink3 = await page.evaluate(() => { + const el = document.querySelector('#gx-grid .gx-chart .gc-label'), bg = document.querySelector('#gx-grid .gx-chart .gc-bg'), wall = document.querySelector('#gx-grid .gx-chart .gc-wall'); + return { dark: document.documentElement.hasAttribute('data-dark'), label: getComputedStyle(el).fill, ground: getComputedStyle(bg).fill, wall: getComputedStyle(wall).fill }; + }); + check(ink3.dark && ratio(ink3.label, ink3.ground) >= 4.5 && ink3.wall !== ink3.ground && !/^rgb\(0, 0, 0\)$/.test(ink3.wall), + `dark Look: a 3-D chart's value labels read on its ground and its walls take the Look (${JSON.stringify(ink3)}; ${ratio(ink3.label, ink3.ground).toFixed(1)}:1)`); + await shot('charts-3d-1280.png'); + for (const [title, name] of [['Share in 3-D', 'charts-3d-pie.png'], ['Sales in 3-D', 'charts-3d-column.png']]) { + const id = three.find((c) => c.title === title).id; + const f = path.join(out, name); + await page.locator(`#gx-grid .gx-chart[data-chart="${id}"]`).first().screenshot({ path: f }); + shots.push(f); + } + await page.setViewportSize({ width: 640, height: 900 }); + const narrow3 = await until(async () => { const l = await count3d(); const g = await cellBox(1, 6); const b = await page.evaluate(() => { const el = document.querySelector('#gx-grid .gx-chart[data-chart$="#0"]'); if (!el) return null; const r = el.getBoundingClientRect(); return { x: Math.round(r.left), y: Math.round(r.top) }; }); return l.length === 2 && near(b, g) ? l : null; }, '3-D charts at 640 px'); + const over3 = await page.evaluate(() => document.documentElement.scrollWidth - innerWidth); + check(over3 <= 0 && narrow3.find((c) => c.title === 'Share in 3-D')?.tops === 4, `640 px: the 3-D pie stays at G2 and draws, no page overflow (overflow ${over3})`); + await shot('charts-3d-640.png'); + await page.setViewportSize({ width: 1280, height: 900 }); + const intervals = await page.evaluate(() => globalThis.gzIntervals || []); check(intervals.every((ms) => ms >= 60000), `no interval under a minute (${JSON.stringify(intervals)})`); check(!errors.some((e) => /sender/.test(e)), 'no worker sender refusals'); From 7709423c50ecb36f49bf8d73b28dff372443be9a Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:04:14 -0400 Subject: [PATCH 003/907] =?UTF-8?q?Show=20imports=20as=20they=20happen:=20?= =?UTF-8?q?an=20importing=20card=20on=20Documents,=20the=20stage=20in=20th?= =?UTF-8?q?e=20assignment=20frame,=20Saving=E2=80=A6=20progress=20on=20Sav?= =?UTF-8?q?e=20to=20Documents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/de.js | 6 +++ i18n/es.js | 6 +++ i18n/fr.js | 6 +++ i18n/nl.js | 6 +++ i18n/pt-BR.js | 6 +++ lib/doc-files.js | 11 ++-- lib/doc-kinds.js | 36 ++++++++++++- papers.css | 20 +++++-- papers.js | 109 ++++++++++++++++++++++++++++++++++---- pdf-kind.js | 50 ++++++++++------- sheet-kind.js | 19 ++++--- slides-kind.js | 47 +++++++++------- test/writer-kinds.test.js | 2 +- writer-kinds.js | 16 +++++- writer-paper.css | 9 ++++ writer-paper.js | 18 ++++++- 16 files changed, 301 insertions(+), 66 deletions(-) diff --git a/i18n/de.js b/i18n/de.js index 691f5601b..d26c154e1 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3505,6 +3505,12 @@ "Open original": "Original öffnen", "Saved to Documents": "In Dokumenten gespeichert", "This PDF is too big to save.": "Diese PDF-Datei ist zu groß zum Speichern.", + "Preparing pages…": "Seiten werden vorbereitet…", + "Preparing slides…": "Folien werden vorbereitet…", + "Preparing sheets…": "Blätter werden vorbereitet…", + "Copying…": "Wird kopiert…", + "Copying… {done} / {total} MB": "Wird kopiert… {done} / {total} MB", + "Saving… {n}%": "Wird gespeichert… {n} %", "Spreadsheets": "Tabellen", "Spreadsheet": "Tabelle", "Number format": "Zahlenformat", diff --git a/i18n/es.js b/i18n/es.js index 6a4de5868..0c1387877 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3505,6 +3505,12 @@ "Open original": "Abrir original", "Saved to Documents": "Guardado en Documentos", "This PDF is too big to save.": "Este PDF es demasiado grande para guardarlo.", + "Preparing pages…": "Preparando páginas…", + "Preparing slides…": "Preparando diapositivas…", + "Preparing sheets…": "Preparando hojas…", + "Copying…": "Copiando…", + "Copying… {done} / {total} MB": "Copiando… {done} / {total} MB", + "Saving… {n}%": "Guardando… {n} %", "Spreadsheets": "Hojas de cálculo", "Spreadsheet": "Hoja de cálculo", "Number format": "Formato de número", diff --git a/i18n/fr.js b/i18n/fr.js index b4c680545..a7a9e8e75 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3505,6 +3505,12 @@ "Open original": "Ouvrir l’original", "Saved to Documents": "Enregistré dans Documents", "This PDF is too big to save.": "Ce PDF est trop volumineux pour être enregistré.", + "Preparing pages…": "Préparation des pages…", + "Preparing slides…": "Préparation des diapositives…", + "Preparing sheets…": "Préparation des feuilles…", + "Copying…": "Copie…", + "Copying… {done} / {total} MB": "Copie… {done} / {total} Mo", + "Saving… {n}%": "Enregistrement… {n} %", "Spreadsheets": "Feuilles de calcul", "Spreadsheet": "Feuille de calcul", "Number format": "Format de nombre", diff --git a/i18n/nl.js b/i18n/nl.js index 82d2b918d..82c16512e 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3505,6 +3505,12 @@ "Open original": "Origineel openen", "Saved to Documents": "Opgeslagen in Documenten", "This PDF is too big to save.": "Deze pdf is te groot om op te slaan.", + "Preparing pages…": "Pagina's voorbereiden…", + "Preparing slides…": "Dia's voorbereiden…", + "Preparing sheets…": "Bladen voorbereiden…", + "Copying…": "Kopiëren…", + "Copying… {done} / {total} MB": "Kopiëren… {done} / {total} MB", + "Saving… {n}%": "Opslaan… {n}%", "Spreadsheets": "Spreadsheets", "Spreadsheet": "Spreadsheet", "Number format": "Getalnotatie", diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 36abac48d..7f9469db9 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3505,6 +3505,12 @@ "Open original": "Abrir original", "Saved to Documents": "Salvo em Documentos", "This PDF is too big to save.": "Este PDF é grande demais para salvar.", + "Preparing pages…": "Preparando páginas…", + "Preparing slides…": "Preparando slides…", + "Preparing sheets…": "Preparando planilhas…", + "Copying…": "Copiando…", + "Copying… {done} / {total} MB": "Copiando… {done} / {total} MB", + "Saving… {n}%": "Salvando… {n}%", "Spreadsheets": "Planilhas", "Spreadsheet": "Planilha", "Number format": "Formato de número", diff --git a/lib/doc-files.js b/lib/doc-files.js index fabdbbc9a..218e50377 100644 --- a/lib/doc-files.js +++ b/lib/doc-files.js @@ -303,14 +303,19 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { const send = (m) => { if (gone) throw fail('interrupted'); try { port.postMessage(m); } catch { throw fail('interrupted'); } }; return { send, next, close: () => { if (gone) return; gone = true; try { port.disconnect(); } catch { /* gone */ } } }; }; - // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent. - const put = async (bytes, type = '') => { + // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent; sent(done, total) once the worker has + // each one. + const put = async (bytes, type = '', sent = null) => { if (!(bytes instanceof Uint8Array) || !bytes.length || bytes.length > MAX) throw fail('size'); const s = session(); try { s.send({ op: 'put', size: bytes.length, type: String(type).slice(0, 200) }); await s.next(); - for (let at = 0; at < bytes.length; at += CHUNK) { s.send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); await s.next(); } + for (let at = 0; at < bytes.length; at += CHUNK) { + s.send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); + await s.next(); + try { sent?.(Math.min(at + CHUNK, bytes.length), bytes.length); } catch (e) { console.error(e); } + } s.send({ op: 'end' }); const { file } = await s.next(); if (!ref(file) || file.size !== bytes.length) throw fail('unavailable'); diff --git a/lib/doc-kinds.js b/lib/doc-kinds.js index ae2643dec..d71f6ba90 100644 --- a/lib/doc-kinds.js +++ b/lib/doc-kinds.js @@ -47,7 +47,41 @@ var GZ_DOC_KINDS = globalThis.GZ_DOC_KINDS || (() => { const pending = new Set(); const wait = (p) => { const q = Promise.resolve(p).catch((e) => console.error(e)); pending.add(q); q.then(() => pending.delete(q)); return q; }; const settled = async () => { while (pending.size) await Promise.all([...pending]); }; - return { KINDS, FILE, SAYS, kindOf, forFile, register, get, taking, opens, accept, wait, settled }; + // ---- an import's progress: the kind doing it reports { stage, done, total } as each stage starts and as its bytes go; + // the page showing it hears { stage, done, total, fraction } and, at the end, end or failed. stages: the ones the kind + // goes through, in order; each its share of the bar by what it took when measured (copying a 50 MB PDF into the file + // store is most of an import). A stage only goes forward, its bytes only up, the bar never back. + const STAGES = freeze({ read: 3, prepare: 15, copy: 77, save: 5 }); + const progress = (listen, stages = Object.keys(STAGES)) => { + const order = stages.filter((s) => Object.hasOwn(STAGES, s)), sum = order.reduce((a, s) => a + STAGES[s], 0) || 1; + let at = -1, done = 0, total = 0, fraction = 0, over = false; + const tell = (more) => { try { listen?.({ stage: order[at] ?? null, done, total, fraction, ...more }); } catch (e) { console.error(e); } }; + const report = ({ stage, done: d = 0, total: n = 0 } = {}) => { + const i = order.indexOf(stage); + if (over || i < 0 || i < at) return; + if (i > at) { at = i; done = 0; total = 0; } + if (Number.isFinite(n) && n > 0) total = n; + if (Number.isFinite(d) && d > done) done = total ? Math.min(d, total) : d; + const before = order.slice(0, at).reduce((a, s) => a + STAGES[s], 0); + fraction = Math.max(fraction, Math.min(1, (before + STAGES[stage] * (total ? done / total : 0)) / sum)); + tell(); + }; + report.end = () => { if (over) return; over = true; fraction = 1; tell({ end: true }); }; + report.fail = (error) => { if (over) return; over = true; tell({ failed: error ?? true }); }; + return report; + }; + // What a page says of a progress state: [English words, their values], or null before the first stage. The bytes show + // from 1 MB (a smaller file is copied at once); spoken: the words without them (a live region hears each stage once). + const PREPARING = freeze({ paper: 'Preparing pages…', pdf: 'Preparing pages…', slides: 'Preparing slides…', sheet: 'Preparing sheets…' }); + const mb = (n) => Math.round(n / 1e5) / 10; + const said = (p, id, { spoken = false } = {}) => { + if (p?.stage === 'read') return ['Reading…']; + if (p?.stage === 'prepare') return [PREPARING[id] || PREPARING.pdf]; + if (p?.stage === 'copy') return spoken || !(p.total >= 1e6) ? ['Copying…'] : ['Copying… {done} / {total} MB', { done: mb(p.done), total: mb(p.total) }]; + if (p?.stage === 'save') return ['Saving…']; + return null; + }; + return { KINDS, FILE, SAYS, STAGES, kindOf, forFile, register, get, taking, opens, accept, wait, settled, progress, said }; })(); if (typeof module !== 'undefined') module.exports = GZ_DOC_KINDS; else GZ_DOC_KINDS; diff --git a/papers.css b/papers.css index d96c1e4c8..bebae3633 100644 --- a/papers.css +++ b/papers.css @@ -19,7 +19,7 @@ .gd-home { max-width: 1160px; margin: 0 auto; padding: 32px 24px 72px; display: grid; gap: 18px; position: relative; } .gd-home .ico { width: 16px; height: 16px; flex: none; } .gd-top, .gd-bar { border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); box-shadow: var(--gz-rest-shadow, none); } -:root[data-gz-bg]:not([data-gz-bg="solid"]) :is(.gd-top, .gd-bar, .gd-card, .gd-menu, .gd-toast) { backdrop-filter: blur(var(--gd-glass)) saturate(1.4); } +:root[data-gz-bg]:not([data-gz-bg="solid"]) :is(.gd-top, .gd-bar, .gd-card, .gd-pending, .gd-menu, .gd-toast) { backdrop-filter: blur(var(--gd-glass)) saturate(1.4); } /* The heading and New: a tile per format, each its first page */ .gd-top { display: grid; gap: 14px; padding: 22px 24px 20px; } @@ -62,10 +62,10 @@ /* The documents: cards in a grid, or rows */ .gd-docs { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(var(--gd-column), 100%), 1fr)); gap: 16px; } -.gd-card { position: relative; display: grid; grid-template-rows: auto 1fr; min-width: 0; border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); overflow: hidden; +:is(.gd-card, .gd-pending) { position: relative; display: grid; grid-template-rows: auto 1fr; min-width: 0; border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); overflow: hidden; box-shadow: var(--gz-rest-shadow, none); transition: var(--gz-card-transition); } .gd-card:hover { border-color: color-mix(in srgb, var(--gz-accent) 55%, var(--gz-border-strong, var(--gz-line-strong))); box-shadow: var(--gz-shadow, 0 8px 24px rgb(0 0 0 / .3)); transform: var(--gd-lift); } -.gd-card:focus-within { border-color: var(--gz-accent); } +:is(.gd-card, .gd-pending):focus-within { border-color: var(--gz-accent); } .gd-pic { height: calc(214px * var(--gd-thumb)); display: flex; justify-content: center; padding-top: 16px; overflow: hidden; border-bottom: 1px solid var(--gz-border-line, var(--gz-line)); background: color-mix(in srgb, var(--gz-surface-paint-2, var(--gz-surface-2)) 55%, transparent); } .gd-pic .gt-thumb { zoom: var(--gd-thumb); background: var(--sheet); border-radius: 2px; box-shadow: 0 1px 3px rgba(0,0,0,.35); } @@ -98,6 +98,20 @@ .gd-home[data-view="list"] .gd-badge { position: static; } .gd-home[data-view="list"] .gd-more { margin: 0; } +/* An import under way (papers.js bring): a blank page where the document's will be, the file's name, its stage, and a bar + filling as the import goes; failed, the reason with Retry and Remove. */ +.gd-blank { width: calc(204px * var(--gd-thumb)); background: var(--sheet); border-radius: 2px 2px 0 0; box-shadow: 0 1px 3px rgba(0,0,0,.35); } +:root:not([data-dark]) .gd-blank { box-shadow: 0 1px 3px rgba(15,23,42,.16), 0 0 0 1px rgba(15,23,42,.06); } +.gd-progress { height: 4px; margin-top: 8px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--gz-ink-muted) 24%, transparent); } +.gd-fill { display: block; height: 100%; border-radius: inherit; background: var(--gz-accent); transform: scaleX(0); transform-origin: left; transition: transform var(--gz-t-move, .2s) ease-out; } +:root[dir="rtl"] .gd-fill { transform-origin: right; } +.gd-pending .gd-title::after { content: none; } +.gd-pending[data-failed] .gd-meta { color: var(--gz-err); -webkit-line-clamp: 3; } +.gd-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; } +.gd-acts[hidden] { display: none; } +.gd-acts .gd-btn { height: 28px; padding: 0 12px; font-size: 12px; } +.gd-pending .gd-more:disabled { opacity: .4; cursor: default; } + /* Files over the page: the page is the drop target */ .gd-home::after { content: ""; position: fixed; inset: 8px; z-index: 4; border: 2px dashed var(--gz-accent); border-radius: var(--gd-radius); background: color-mix(in srgb, var(--gz-accent) 8%, transparent); pointer-events: none; } diff --git a/papers.js b/papers.js index e6bb421b7..d31140201 100644 --- a/papers.js +++ b/papers.js @@ -124,18 +124,23 @@ // A Word file as a new standalone document, as the editor's import reads one (writer-paper.js importFile): in the format // Settings → Writer starts papers in, its own formatting kept where the format would flag it, the save taking the // document's first version. Its title is the file's name. → its key. - const importFile = async (file) => { + const importFile = async (file, { progress = null } = {}) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > GZ_DOC_KINDS.KINDS.paper.max) throw new RangeError('Choose a Word file smaller than 15 MB.'); + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: file.size }); await GZ_WRITER_PAPER.need(['lib/writer-styles.js', 'lib/writer-format.js', 'lib/doc-home.js', ...DOCX]); const ST = GZ_WRITER_STYLES, F = GZ_WRITER_FORMAT; const id = await chrome.storage.sync.get('writer').then((r) => r?.writer?.format, () => null); const format = ST.FORMATS[ST.IDS.includes(id) ? id : 'mla9']; const bytes = await file.arrayBuffer(); if (!(bytes instanceof ArrayBuffer) || bytes.byteLength !== file.size) throw new Error('invalid file read'); + p({ stage: 'read', done: bytes.byteLength, total: bytes.byteLength }); + p({ stage: 'prepare' }); const r = await GZ_WRITER_DOCX.importDocx(new Uint8Array(bytes), { format }); const doc = GZ_WRITER_SCHEMA.paper.nodeFromJSON(r.doc); const keep = F.check(doc, format, {}, {}).issues.some((i) => F.KEPT.has(i.rule) && i.fix?.op !== 'format'); const key = GZ_WRITER_PAPERS.newKey(), title = GZ_DOC_HOME.stem(file.name); + p({ stage: 'save' }); await papers().save(key, { kind: 'paper', doc: doc.toJSON(), format: format.id, keep, named: !!title, title }, { reason: 'import' }); return key; }; @@ -235,6 +240,8 @@ let rows = [], view = 'grid', sortBy = 'updated', course = 'all', kind = '', words = [], shown = [], current = null, focusNext = null, seq = 0; try { if (localStorage.getItem(VIEW) === 'list') view = 'list'; } catch { /* storage can be unavailable */ } let active = true, life = 0, textSize = 0; + // incoming: the imports under way, newest first (bring, below). + const incoming = []; const gone = new Set(), texts = new Map(), derived = new Map(), thumbs = new Map(), near = new Set(), cards = new Map(), epochs = new Map(), hitsByKey = new Map(); // bin: the Trash is the view. moving: key → into Trash (true) or out (false), as shown until storage says so. let bin = false; @@ -319,7 +326,7 @@ }; const inCourse = (r) => DH.inCourse(r, course === 'all' ? '' : course === 'none' ? DH.NONE : course.slice(2)); // The cards as the filters, search and order have them; the rest leave the page. One Tab stop: the current card's link. - let renaming = null, held = false; + let renaming = null, held = false, counted = ''; const render = () => { if (!active) return; if (renaming) { held = true; return; } @@ -331,18 +338,25 @@ const titles = new Map(); const hits = list.filter((r) => { const title = titleOf(r); titles.set(r.key, title); return (bin || (inCourse(r) && (!kind || DK.kindOf(r) === kind))) && matches(r, title) === true; }); shown = DH.sort(hits, bin ? 'trashed' : sortBy, titles, locale); - const els = shown.map(cardFor); + const listed = new Set(rows.map((r) => r.key)); + // An import's card gives way to the document's once it is listed (its focus with it). + for (let i = incoming.length; i--;) { + const j = incoming[i]; + if (!j.key || !listed.has(j.key)) continue; + if (j.refocus || j.ui.el.contains(document.activeElement)) focusNext = j.key; + incoming.splice(i, 1); + } + const els = [...(bin ? [] : incoming.map((j) => j.ui.el)), ...shown.map(cardFor)]; if (els.length !== docs.children.length || els.some((e, i) => docs.children[i] !== e)) { const had = docs.contains(document.activeElement) ? document.activeElement : null; motion().cards(() => docs.replaceChildren(...els)); if (had?.isConnected) had.focus({ preventScroll: true }); } - const listed = new Set(rows.map((r) => r.key)); for (const [k, c] of cards) if (!listed.has(k)) { io.unobserve(c.pic); near.delete(k); cards.delete(k); thumbs.delete(k); forgetText(k); derived.delete(k); epochs.delete(k); } for (const k of epochs.keys()) if (!listed.has(k)) { epochs.delete(k); forgetText(k); derived.delete(k); } roving(); const n = t('{n} documents', { n: shown.length }); - if (count.textContent !== n) count.textContent = n; + if (counted !== n) { counted = n; count.textContent = n; } // A copy or an undone delete takes focus once it is listed; one the filters hide doesn't take it later. if (focusNext && listed.has(focusNext)) { const k = focusNext; focusNext = null; if (cards.get(k)?.el.isConnected) go(k); } later(); @@ -644,6 +658,82 @@ const KIND_SAYS = { pdf: { size: DK.KINDS.pdf.big, read: 'That file could not be read.' } }; const saysOf = (id) => (id && id !== 'paper' && (DK.get(id)?.says || KIND_SAYS[id])) || PAPER_SAYS; const unread = many ? 'That file could not be read.' : PAPER_SAYS.read; + // ---- imports under way: a card for each file from the moment it is taken, in the list's first place, its stage and + // bytes as the kind reports them (lib/doc-kinds.js progress) on a bar, its menu off, until the document it made is + // listed (render); one that failed says why on its card, with Retry and Remove. The stages are spoken in the count's + // live region, the failure in the status line too. A batch's files go one after another. + const STEPS = { pdf: ['read', 'prepare', 'copy', 'save'], slides: ['read', 'prepare', 'copy', 'save'] }; + let jobs = 0; + const showJob = (j) => { + const { el, bar, fill, meta, acts } = j.ui, f = j.failed ? 0 : j.p?.fraction || 0, w = j.failed ? [j.failed] : DK.said(j.p, j.kind); + el.toggleAttribute('data-failed', !!j.failed); + meta.textContent = w ? t(...w) : ''; + bar.hidden = !!j.failed; acts.hidden = !j.failed; + fill.style.transform = `scaleX(${f})`; + bar.setAttribute('aria-valuenow', String(Math.round(f * 100))); + }; + const importJob = async (j) => { + if (j.running) return {}; + j.running = true; j.failed = ''; j.p = null; + let stage = null; + const report = DK.progress((p) => { + j.p = p; + const w = !p.end && !p.failed && p.stage !== stage && DK.said(p, j.kind, { spoken: true }); + if (w) { stage = p.stage; count.textContent = t(...w); } + showJob(j); + }, STEPS[j.kind] || ['read', 'prepare', 'save']); + showJob(j); + try { + // An import that kept the file with parts left out says so (sheet-kind.js: { key, notice }). + const got = await DK.get(j.kind).importFile(j.file, { progress: report }); + report.end(); + j.key = typeof got === 'string' ? got : got?.key || null; + return { notice: got?.notice || '' }; + } catch (e) { + console.error(e); + const says = saysOf(j.kind); + j.failed = e instanceof RangeError && (REFUSALS.has(e.message) || Object.values(says).includes(e.message)) ? e.message : says.read; + report.fail(e); + return { failed: j.failed }; + } finally { + j.running = false; + // The count's region says the count again once the stages are over. + counted = ''; + if (j.key) await refresh(); else render(); + } + }; + const removeJob = (j) => { + const i = incoming.indexOf(j); + if (i < 0 || j.running) return; + const had = j.ui.el.contains(document.activeElement); + incoming.splice(i, 1); + say(''); + render(); + if (had) importB.focus(); + }; + // Retry keeps the focus with the import: on the document once it is listed, on Retry again if it fails again. + const retry = async (j) => { + say(''); + j.refocus = j.ui.el.contains(document.activeElement); + const r = await importJob(j); + if (r.failed && j.refocus && j.ui.el.isConnected) j.ui.again.focus(); + if (r.failed || r.notice) say(t(r.failed || r.notice)); + }; + const job = (f, id) => { + const j = { id: ++jobs, file: f, kind: id, p: null, failed: '', key: null, running: false }; + const fill = h('span', { class: 'gd-fill' }); + const bar = h('div', { class: 'gd-progress', role: 'progressbar', 'aria-label': f.name, 'aria-valuemin': '0', 'aria-valuemax': '100', 'aria-valuenow': '0' }, fill); + const meta = h('p', { class: 'gd-meta' }); + const again = btn({ class: 'gd-btn gd-quiet', 'data-act': 'retry', onclick: () => { retry(j).catch((e) => console.error(e)); } }, t('Retry')); + const acts = h('div', { class: 'gd-acts', hidden: true }, again, btn({ class: 'gd-btn gd-quiet', 'data-act': 'remove', onclick: () => removeJob(j) }, t('Remove'))); + const more = btn({ class: 'gd-more', disabled: true, 'aria-label': t('More'), title: t('More'), tabindex: '-1' }, icon('dots')); + const el = h('li', { class: 'gd-pending', 'data-kind': id, 'data-import': String(j.id) }, + h('div', { class: 'gd-pic' }, h('div', { class: 'gd-blank' })), + h('div', { class: 'gd-info' }, icon(DK.KINDS[id]?.icon || 'paper', 'gd-kind'), h('div', { class: 'gd-text' }, h('span', { class: 'gd-title', text: f.name }), meta, bar, acts), more)); + j.ui = { el, bar, fill, meta, acts, again }; + showJob(j); + return j; + }; const bring = async (list) => { dismiss(); say(''); @@ -651,12 +741,9 @@ const first = refused.find((x) => x.why !== 'count'); const limitText = refused.some((x) => x.why === 'count') ? t('Choose up to 20 files at a time.') : ''; let why = first ? (first.why === 'size' ? saysOf(DK.forFile(first.file?.name)).size : unread) : ''; - for (const { file: f, kind: id } of take) { - const says = saysOf(id); - // An import that kept the file with parts left out says so (sheet-kind.js: { key, notice }). - try { const got = await DK.get(id).importFile(f); if (got?.notice) why ||= got.notice; } - catch (e) { console.error(e); why ||= e instanceof RangeError && (REFUSALS.has(e.message) || Object.values(says).includes(e.message)) ? e.message : says.read; } - } + const mine = take.map(({ file: f, kind: id }) => job(f, id)); + if (mine.length) { incoming.unshift(...mine); render(); } + for (const j of mine) { const r = await importJob(j); why ||= r.failed || r.notice || ''; } say([why && t(why), limitText].filter(Boolean).join(' ')); }; importB.addEventListener('click', () => file.click()); diff --git a/pdf-kind.js b/pdf-kind.js index b2e6f9763..f7174b0e5 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -35,11 +35,12 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const LEGACY = 3e6; const STORE = { max: GZ_DOC_KINDS.KINDS.pdf.max, - // bytes → the record's fields for them, the bytes kept in the store; a RangeError says why not. - put: async (bytes) => { + // bytes → the record's fields for them, the bytes kept in the store (sent(done, total) as they go); a RangeError says + // why not. + put: async (bytes, sent = null) => { if (!(bytes?.length > 0) || bytes.length > STORE.max) throw new RangeError(TOO_BIG); const F = await files(); - try { return { file: await F.client().put(bytes, 'application/pdf'), size: bytes.length }; } catch (e) { + try { return { file: await F.client().put(bytes, 'application/pdf', sent), size: bytes.length }; } catch (e) { if (e.why === 'unavailable' && bytes.length <= LEGACY) return { file: b64(bytes), size: bytes.length }; throw new FileError(e.why); } @@ -135,17 +136,30 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const nameOf = (rec) => `${String(rec?.title || '').replace(/[\\/:*?"<>|\u0000-\u001f]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Document'}.pdf`; // ---- the page code GZ_DOC_KINDS takes - // A PDF file as a new standalone document, its title the file's name, its first page drawn for the card. → its key. - const importFile = async (file) => { - if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(TOO_BIG); - await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/doc-home.js']); - const bytes = new Uint8Array(await file.arrayBuffer()); - if (bytes.length !== file.size) throw new Error('invalid file read'); + // A file's bytes and what pdf.js makes of them, each stage told to progress (lib/doc-kinds.js progress): reading, page 1 + // read and drawn, the bytes copied into the store. → the record's fields for the file. + const bring = async (f, progress) => { + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: f.size }); + const bytes = new Uint8Array(await f.arrayBuffer()); + if (bytes.length !== f.size) throw new Error('invalid file read'); + p({ stage: 'read', done: bytes.length, total: bytes.length }); + p({ stage: 'prepare' }); const { task, doc } = await read(bytes); let thumb = null, pages = 0; try { pages = doc.numPages; thumb = await picture(doc).catch(() => null); } finally { await task.destroy().catch(() => {}); } + p({ stage: 'copy', done: 0, total: bytes.length }); + const kept = await STORE.put(bytes, (done, total) => p({ stage: 'copy', done, total })); + p({ stage: 'save' }); + return { ...kept, pages, ...(thumb && { thumb }) }; + }; + // A PDF file as a new standalone document, its title the file's name, its first page drawn for the card. → its key. + const importFile = async (file, { progress = null } = {}) => { + if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(TOO_BIG); + await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/doc-home.js']); + const fields = await bring(file, progress); const key = GZ_WRITER_PAPERS.newKey(), title = GZ_DOC_HOME.stem(file.name); - await papers().save(key, { kind: 'pdf', ...await STORE.put(bytes), pages, ...(thumb && { thumb }), title, named: !!title }, { reason: 'import' }); + await papers().save(key, { kind: 'pdf', ...fields, title, named: !!title }, { reason: 'import' }); return key; }; // The card's picture: the one the record keeps, else drawn once from its bytes here and kept for its revision. @@ -333,13 +347,17 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { if (!doc || saveB.disabled) return; saveB.disabled = true; say(t('Saving…')); + // How far the whole import has gone, by tens (the line is a live region), then Saved. + const progress = GZ_DOC_KINDS.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); try { if (src.bytes.length > STORE.max) throw new RangeError(TOO_BIG_SAVE); - await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' })); + await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); + progress.end(); say(t('Saved to Documents')); } catch (e) { const big = e instanceof RangeError && (e.message === TOO_BIG || e.message === TOO_BIG_SAVE); if (!big) console.error(e); + progress.fail(e); say(t(big ? TOO_BIG_SAVE : 'Not saved. Try again in a moment.')); saveB.disabled = false; } @@ -456,15 +474,11 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { }; // A PDF brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. - const into = async (key, f) => { + const into = async (key, f, { progress = null } = {}) => { if (!Number.isSafeInteger(f?.size) || f.size <= 0 || f.size > STORE.max) throw new RangeError(TOO_BIG); await GZ_WRITER_PAPER.need(['lib/writer-papers.js']); - const bytes = new Uint8Array(await f.arrayBuffer()); - if (bytes.length !== f.size) throw new Error('invalid file read'); - const { task, doc } = await read(bytes); - let thumbUrl = null, pages = 0; - try { pages = doc.numPages; thumbUrl = await picture(doc).catch(() => null); } finally { await task.destroy().catch(() => {}); } - return (await papers().save(key, { kind: 'pdf', doc: null, words: 0, ...await STORE.put(bytes), pages, ...(thumbUrl && { thumb: thumbUrl }) }, { reason: 'import' })).paper; + const fields = await bring(f, progress); + return (await papers().save(key, { kind: 'pdf', doc: null, words: 0, ...fields }, { reason: 'import' })).paper; }; // ---- registration: a PDF already kept lists and opens either way; only Settings → Writer → PDFs on (writer.pdf) lets diff --git a/sheet-kind.js b/sheet-kind.js index 2e30b77a5..c12f538b5 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -97,20 +97,26 @@ // ---- files: .xlsx, .csv and .tsv through GZ_SHEET_XLSX const stem = (name) => (globalThis.GZ_DOC_HOME ? GZ_DOC_HOME.stem(name) : String(name || '').replace(/\.[^./\\]+$/, '').trim().slice(0, 120)); - // A file as a workbook, its formulas calculated; a RangeError (SAYS) when it is too large or can't be read. - const workbookOf = async (file) => { + // A file as a workbook, its formulas calculated, each stage told to progress (lib/doc-kinds.js progress); a RangeError + // (SAYS) when it is too large or can't be read. + const workbookOf = async (file, progress = null) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > DK.KINDS.sheet.max) throw new RangeError(SAYS.size); + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: file.size }); await need([...ENGINE, 'sheet-grid.js']); const raw = await file.arrayBuffer(); if (!(raw instanceof ArrayBuffer) || raw.byteLength !== file.size) throw new Error('invalid file read'); + p({ stage: 'read', done: raw.byteLength, total: raw.byteLength }); + p({ stage: 'prepare' }); const X = await codecs(), bytes = new Uint8Array(raw); const wb = /\.(csv|tsv)$/i.test(file.name) ? X.readDelimited(bytes, { name: file.name }) : await X.read(bytes).catch(() => null); if (!wb?.sheets?.length) throw new RangeError(SAYS.read); GZ_SHEET_CALC.engine(wb).recalc(); return wb; }; - const importFile = async (file) => { - const wb = await workbookOf(file); + const importFile = async (file, { progress = null } = {}) => { + const wb = await workbookOf(file, progress); + progress?.({ stage: 'save' }); const key = GZ_WRITER_PAPERS.newKey(), title = stem(file.name); await saveAll(key, wb, { named: !!title, title }, 'start'); const notice = noticeOf(wb); @@ -150,8 +156,9 @@ // ---- the grid // A spreadsheet brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it // held kept as a version first (the 'import' save), every part it had before dropped. → the record. - const into = async (key, f) => { - const wb = await workbookOf(f), SD = GZ_SHEET_DATA; + const into = async (key, f, { progress = null } = {}) => { + const wb = await workbookOf(f, progress), SD = GZ_SHEET_DATA; + progress?.({ stage: 'save' }); let had = (await get([`writerPaper:${key}`]))[`writerPaper:${key}`]; const prior = had?.kind === 'sheet' ? await SD.read(get, key) : null; if (prior) had = prior.rec; diff --git a/slides-kind.js b/slides-kind.js index 4ba2abbee..70cd83b2a 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -34,10 +34,11 @@ const LEGACY = 3e6; // base64 of 3 MB is the record's 4 MB const STORE = { max: GZ_DOC_KINDS.KINDS.slides.max, - put: async (bytes, ext) => { + // sent(done, total) as the bytes reach the store. + put: async (bytes, ext, sent = null) => { if (!(bytes?.length <= STORE.max)) throw new RangeError(SAYS.size); const x = String(ext || '').toLowerCase() === '.ppsx' ? '.ppsx' : '.pptx', F = await files(); - try { return { file: await F.client().put(bytes, MIME[x]), size: bytes.length, ext: x }; } catch (e) { + try { return { file: await F.client().put(bytes, MIME[x], sent), size: bytes.length, ext: x }; } catch (e) { if (e.why === 'unavailable' && bytes.length <= LEGACY) return { file: b64(bytes), size: bytes.length, ext: x }; throw new FileError(e.why); } @@ -174,19 +175,31 @@ // ---- import and export const stem = (name) => (globalThis.GZ_DOC_HOME ? GZ_DOC_HOME.stem(name) : String(name || '').replace(/\.[^./\\]+$/, '').trim().slice(0, 120)); - // A deck as a new standalone document, titled by its file's name. → its key. - const importFile = async (file) => { - if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); - await need(LIBS); - const raw = await file.arrayBuffer(); - if (!(raw instanceof ArrayBuffer) || raw.byteLength !== file.size) throw new Error('invalid file read'); + // A deck file's bytes, read, its first slide drawn and its words taken, then copied into the store, each stage told to + // progress (lib/doc-kinds.js progress). → the record's fields for it. + const bring = async (f, progress) => { + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: f.size }); + const raw = await f.arrayBuffer(); + if (!(raw instanceof ArrayBuffer) || raw.byteLength !== f.size) throw new Error('invalid file read'); const bytes = new Uint8Array(raw); + p({ stage: 'read', done: bytes.length, total: bytes.length }); + p({ stage: 'prepare' }); const deck = await GZ_PPTX.read(bytes).catch(() => null); if (!deck?.slides.length) throw new RangeError(SAYS.read); - const thumbUrl = await picture(deck, bytes).catch(() => null), words = textOf(deck), title = stem(file.name); + const thumbUrl = await picture(deck, bytes).catch(() => null), words = textOf(deck); + p({ stage: 'copy', done: 0, total: bytes.length }); + const kept = await STORE.put(bytes, /\.ppsx$/i.test(f.name) ? '.ppsx' : '.pptx', (done, total) => p({ stage: 'copy', done, total })); + p({ stage: 'save' }); + return { ...kept, slides: deck.slides.length, text: words, words: measure({ text: words }).words, ...(thumbUrl && { thumb: thumbUrl }) }; + }; + // A deck as a new standalone document, titled by its file's name. → its key. + const importFile = async (file, { progress = null } = {}) => { + if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); + await need(LIBS); + const fields = await bring(file, progress), title = stem(file.name); const key = GZ_WRITER_PAPERS.newKey(); - await papers().save(key, { kind: 'slides', ...await STORE.put(bytes, /\.ppsx$/i.test(file.name) ? '.ppsx' : '.pptx'), slides: deck.slides.length, text: words, - words: measure({ text: words }).words, ...(thumbUrl && { thumb: thumbUrl }), named: !!title, title }, { reason: 'import' }); + await papers().save(key, { kind: 'slides', ...fields, named: !!title, title }, { reason: 'import' }); return key; }; // A deck as its file: the bytes imported, named by its title. → { name, mime, bytes } or null. @@ -541,17 +554,11 @@ }; // A deck brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. - const into = async (key, f) => { + const into = async (key, f, { progress = null } = {}) => { if (!Number.isSafeInteger(f?.size) || f.size <= 0 || f.size > STORE.max) throw new RangeError(SAYS.size); await need(LIBS); - const raw = await f.arrayBuffer(); - if (!(raw instanceof ArrayBuffer) || raw.byteLength !== f.size) throw new Error('invalid file read'); - const bytes = new Uint8Array(raw); - const deck = await GZ_PPTX.read(bytes).catch(() => null); - if (!deck?.slides.length) throw new RangeError(SAYS.read); - const thumbUrl = await picture(deck, bytes).catch(() => null), words2 = textOf(deck); - return (await papers().save(key, { kind: 'slides', doc: null, ...await STORE.put(bytes, /\.ppsx$/i.test(f.name) ? '.ppsx' : '.pptx'), slides: deck.slides.length, text: words2, - words: measure({ text: words2 }).words, ...(thumbUrl && { thumb: thumbUrl }) }, { reason: 'import' })).paper; + const fields = await bring(f, progress); + return (await papers().save(key, { kind: 'slides', doc: null, ...fields }, { reason: 'import' })).paper; }; // Viewing stays registered; only import follows the switch, read through defaults.js as every Writer setting is (a device diff --git a/test/writer-kinds.test.js b/test/writer-kinds.test.js index d6e91ab1f..5c5e7db0f 100644 --- a/test/writer-kinds.test.js +++ b/test/writer-kinds.test.js @@ -148,7 +148,7 @@ const viewer = async (id = 'pdf') => { into: async () => (await P.save(KEY, { kind: 'pdf', file: 'TkVX', size: 3 }, { reason: 'import' })).paper }; const DK = require('../lib/doc-kinds.js'); const ctx = vm.createContext({ console: { error() {} }, document, GZ_WRITER_PAPERS: { open: () => P }, - GZ_DOC_KINDS: { KINDS: DK.KINDS, kindOf: DK.kindOf, settled: async () => {}, get: (id) => ({ ...DK.KINDS[id], ...implementation }) }, + GZ_DOC_KINDS: { KINDS: DK.KINDS, kindOf: DK.kindOf, progress: DK.progress, said: DK.said, settled: async () => {}, get: (id) => ({ ...DK.KINDS[id], ...implementation }) }, GZ_WRITER_FRAME: { t: (s) => s, say() {}, on() {} }, GZ_WRITER_SETTINGS: { now: () => ({ pdf: true }) }, GZ_WRITER_SUBMIT_LIB: L, GZ_WRITER_PAPER: { need: async () => {}, submit() {} } }); vm.runInContext(fs.readFileSync(path.join(__dirname, '..', 'writer-kinds.js'), 'utf8'), ctx); diff --git a/writer-kinds.js b/writer-kinds.js index baebc62cb..afebe09ad 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -89,6 +89,7 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { // ---- Open in Glaze: a brought file of a kind that opens here becomes the attempt's record, and shows. api: the paper // or the viewer that shows now. + let arriving = null; const said = (k, e) => (e instanceof RangeError && Object.values(k?.says || {}).includes(e.message) ? e.message : k?.says?.read || 'That file could not be read.'); const open = async (api, f) => { if (busy || !f) return; @@ -105,7 +106,18 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { if (shown) { if (!await (shown.id === 'sheet' ? shown.surface.version('import') : shown.surface.save())) throw new Error('not saved'); } else if (!await paper.save()) throw new Error('paper could not be saved'); paper.hold(); - const rec = await k.into(key, f); + // The file arriving, stage by stage (lib/doc-kinds.js progress): in the paper's status bar, or the viewer's line, + // until its viewer shows (finally, below). + const steps = id === 'sheet' ? ['read', 'prepare', 'save'] : ['read', 'prepare', 'copy', 'save']; + const progress = GZ_DOC_KINDS.progress((p) => { + const w = GZ_DOC_KINDS.said(p, id); + if (!w || p.end || p.failed) return; + if (shown) shown.viewer?.say?.(t(...w)); + else paper.arriving?.({ name: f.name, text: t(...w), spoken: t(...GZ_DOC_KINDS.said(p, id, { spoken: true })), fraction: p.fraction }); + }, steps); + arriving = paper; + const rec = await k.into(key, f, { progress }); + progress.end(); if (shown) { await unmount(); const s = await mount(id, key, paper); if (!s) throw new Error('no viewer'); paper.take(s); } else if (!await paper.hand(rec)) throw new Error('no viewer'); if (rec?.notice) shown?.surface.notify(rec.notice); @@ -113,7 +125,7 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { console.error(e); paper.release(); (shown?.surface || paper).notify?.(said(k, e)); - } finally { busy = false; } + } finally { busy = false; arriving?.arriving?.(null); arriving = null; } }; // A Word file over a viewer: the record a paper again (its kind's fields cleared), then the file opens as a paper does. const back = async (f) => { diff --git a/writer-paper.css b/writer-paper.css index 06d5abdc6..8e5922e57 100644 --- a/writer-paper.css +++ b/writer-paper.css @@ -191,6 +191,15 @@ .gp-status { height: 34px; flex: none; display: flex; align-items: center; gap: 18px; padding: 0 16px; color: var(--gz-ink-muted); font-size: 12px; background: var(--panel); border-top: 1px solid var(--edge); white-space: nowrap; } .gp-status button { font-size: 12px; color: var(--gz-ink-muted); } .gp-status button:hover { color: var(--gz-ink); } +/* A file arriving (Open in Glaze): its stage and a bar filling as it goes */ +.gp-arrive { display: inline-flex; align-items: center; gap: 8px; color: var(--gz-ink); } +.gp-arrive[hidden] { display: none; } +.gp-progress { width: 120px; height: 4px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--gz-ink-muted) 24%, transparent); } +.gp-fill { display: block; height: 100%; border-radius: inherit; background: var(--gz-accent); transform: scaleX(0); transform-origin: left; transition: transform var(--gz-t-move, .2s) ease-out; } +:root[dir="rtl"] .gp-fill { transform-origin: right; } +html[data-gz-motion="off"] .gp-fill, html[data-gz-no-trans] .gp-fill { transition: none; } +@media (prefers-reduced-motion: reduce) { html:not([data-gz-motion]) .gp-fill { transition: none; } } +.gp-sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* Side panel */ .gp-side { background: var(--panel); border-left: 1px solid var(--edge); display: flex; flex-direction: column; min-height: 0; min-width: 0; } diff --git a/writer-paper.js b/writer-paper.js index 11db181e0..44869c30b 100644 --- a/writer-paper.js +++ b/writer-paper.js @@ -138,7 +138,11 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { const toolGrow = h('span', { class: 'grow' }); // The status bar's word count opens the counts (writer-counts.js, where loaded). wordsL.addEventListener('click', () => globalThis.GZ_WRITER_COUNTS?.open(wordsL)); - const status = h('div', { class: 'gp-status' }, pageL, wordsL, h('span', { class: 'grow' }), fontL, versionsB); + // A file arriving (Open in Glaze, writer-kinds.js): its stage and bytes, a bar filling as it goes, the stage spoken. + const arriveText = h('span', { 'aria-hidden': 'true' }), arriveSay = h('span', { class: 'gp-sr', role: 'status' }), arriveFill = h('span', { class: 'gp-fill' }); + const arriveBar = h('span', { class: 'gp-progress', role: 'progressbar', 'aria-valuemin': '0', 'aria-valuemax': '100', 'aria-valuenow': '0' }, arriveFill); + const arrive = h('span', { class: 'gp-arrive', hidden: true }, arriveText, arriveBar, arriveSay); + const status = h('div', { class: 'gp-status' }, pageL, wordsL, arrive, h('span', { class: 'grow' }), fontL, versionsB); const tabs = {}, panes = {}, badges = {}; for (const [id, label] of [['checks', 'Checks'], ['sources', 'Sources'], ['feedback', 'Feedback'], ['outline', 'Document outline']]) { badges[id] = h('span', { class: 'gp-badge' }); @@ -1725,6 +1729,18 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // over the viewer). hold: () => { if (!parked) { parked = { holding: true }; clearTimeout(saveTimer); } }, release: () => { if (parked?.holding) parked = null; }, hand: (rec) => hand(rec, true), take: (surface) => { parked = surface; current = surface; }, unpark: () => { parked = null; current = api; }, + // arriving(p): the file's stage while it arrives ({ name, text, spoken, fraction }), null once it has or hasn't. In + // the compact box, the bar's line says it. + arriving: (p) => { + if (compact && said) { if (p) tell('importing', p.text); else if (saying === 'importing') tell(''); return; } + arrive.hidden = !p; + if (!p) { arriveSay.textContent = ''; return; } + arriveText.textContent = p.text; + if (arriveSay.textContent !== p.spoken) arriveSay.textContent = p.spoken; + arriveBar.setAttribute('aria-label', p.name || ''); + arriveBar.setAttribute('aria-valuenow', String(Math.round(p.fraction * 100))); + arriveFill.style.transform = `scaleX(${p.fraction})`; + }, // Nothing written past the format's opening yet (an import then needs no word about the paper it replaces). blank: () => { const text = view.state.doc.textContent.trim(); return !text || text === S.paper.nodeFromJSON(F.template(format, values())).textContent.trim(); }, // The page ended Focus (a click on its dim). From 60272de9beca4e3ca3c506650f3de0a7507db1b5 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:04:14 -0400 Subject: [PATCH 004/907] Test the import progress reporter and check the importing card, the frame's arrival and Save to Documents' progress --- scripts/documents-check.js | 121 ++++++++++++++++++++++++++++++++- scripts/ultra-preview-check.js | 5 ++ scripts/writer-kinds-check.js | 12 ++++ test/import-progress.test.js | 102 +++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 test/import-progress.test.js diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 4ec20d64b..458cd8f40 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -114,7 +114,9 @@ const PROBE = ({ lang }) => { const local = (k) => worker.evaluate(async (k) => (await chrome.storage.local.get(k))[k] ?? null, k); const watch = (pg, tag) => { pg.on('pageerror', (e) => errors.push(`${tag}: ${e.message}`)); - pg.on('console', (m) => { if (m.type() === 'error' && !/Failed to load resource|intentional review refusal|Error: not a zip file/.test(m.text())) errors.push(`${tag} console: ${m.text()}`); }); + // The import cases' own refusals (a store with no room, a file that is no PDF) are logged as every failed import is. + const meant = tag === 'import' ? /^RangeError: (There isn't enough room in this browser for this file\.|That file could not be read\.)\n/ : /$^/; + pg.on('console', (m) => { if (m.type() === 'error' && !/Failed to load resource|intentional review refusal|Error: not a zip file/.test(m.text()) && !meant.test(m.text())) errors.push(`${tag} console: ${m.text()}`); }); }; const openHome = async (lang = null, tag = 'home') => { const pg = await context.newPage(); @@ -534,6 +536,10 @@ const PROBE = ({ lang }) => { await drop(home, [{ name: 'Broken.docx', bytes: [1, 2, 3] }]); await until(() => home.evaluate(() => document.getElementById('gd-said').textContent === 'That file could not be read as a Word document.'), 'broken Word refusal'); check((await shown()).length === before + 3, 'a broken .docx reports its failure in the status line and creates no document'); + const brokenCard = await home.evaluate(() => { const el = document.querySelector('#gd-docs > .gd-pending[data-failed]'); return el && `${el.querySelector('.gd-title').textContent}: ${el.querySelector('.gd-meta').textContent}`; }); + check(brokenCard === 'Broken.docx: That file could not be read as a Word document.', `and on its card, which Remove takes away (${brokenCard})`); + await home.click('.gd-pending [data-act="remove"]'); + await until(() => home.evaluate(() => !document.querySelector('#gd-docs > .gd-pending')), 'the broken file\'s card removed'); const beforeBatch = (await shown()).length; await drop(home, Array.from({ length: 23 }, (_, i) => ({ name: `Batch ${i}.docx`, bytes: docx }))); await until(() => home.evaluate(() => document.getElementById('gd-said').textContent === 'Choose up to 20 files at a time.'), 'batch limit'); @@ -1346,6 +1352,119 @@ const PROBE = ({ lang }) => { await mo.close(); } + // ---- an import shows itself (papers.js bring, lib/doc-kinds.js progress): a card at once with the file's name, one + // stage word at a time over a bar fed by the real stages and bytes, its menu off; then the document's own card. A failed + // one says why on its card with Retry and Remove. The same for a deck. + { + const im = await openHome(null, 'import'); + // Every state an importing card shows, and what the count's live region says, as they change. + const recordImports = () => im.evaluate(() => { + const R = globalThis.__imp = { states: [], spoken: [], firstPending: 0, firstCard: {} }; + const look = () => { + for (const el of document.querySelectorAll('#gd-docs > .gd-pending')) { + R.firstPending ||= performance.now(); + const bar = el.querySelector('.gd-progress'); + const s = { name: el.querySelector('.gd-title').textContent, meta: el.querySelector('.gd-meta').textContent, now: Number(bar.getAttribute('aria-valuenow')), failed: el.hasAttribute('data-failed'), + menuOff: el.querySelector('.gd-more').disabled, acts: !el.querySelector('.gd-acts').hidden, bar: !bar.hidden, first: document.querySelector('#gd-docs').firstElementChild === el }; + const l = R.states.at(-1); + if (!l || l.meta !== s.meta || l.now !== s.now || l.failed !== s.failed || l.name !== s.name) R.states.push(s); + } + for (const c of document.querySelectorAll('#gd-docs > .gd-card')) R.firstCard[c.querySelector('.gd-title').textContent] ??= performance.now(); + }; + new MutationObserver(look).observe(document.getElementById('gd-docs'), { subtree: true, childList: true, characterData: true, attributes: true }); + const count = document.getElementById('gd-count'); + new MutationObserver(() => R.spoken.push(count.textContent)).observe(count, { childList: true, characterData: true, subtree: true }); + }); + const imports = () => im.evaluate(() => globalThis.__imp); + const pdfBytes = require('./pdf-fixtures.js').large({ count: 30, size: 12e6 }), pdfPath = path.join(tmp, 'Course reader.pdf'); + fs.writeFileSync(pdfPath, pdfBytes); + await recordImports(); + await im.setInputFiles('#gd-file', pdfPath); + const pdfKey = await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].find((c) => c.querySelector('.gd-title').textContent === 'Course reader')?.dataset.key), 'the 12 MB PDF\'s card', 60000); + const pdfThumb = await until(() => im.evaluate((k) => { const img = document.querySelector(`.gd-card[data-key="${k}"] .gd-pic img`); return img?.complete && img.naturalWidth > 0 ? img.naturalWidth : 0; }, pdfKey), 'the PDF card\'s first page', 30000); + let R = await imports(); + const pdfStates = R.states.filter((x) => x.name === 'Course reader.pdf'), metas = pdfStates.map((x) => x.meta); + const order = ['Reading…', 'Preparing pages…', 'Copying…', 'Saving…'].map((w) => metas.findIndex((m) => m.startsWith(w))); + const copies = metas.filter((m) => /^Copying… [\d.]+ \/ 1[12](\.\d)? MB$/.test(m)); + console.log(` the PDF's importing card: ${metas.length} states (${[...new Set(metas)].slice(0, 6).join(' | ')} …), card after ${Math.round(R.firstCard['Course reader'] - R.firstPending)} ms`); + check(pdfStates.length > 0 && pdfStates[0].first && pdfStates.every((x) => x.menuOff && x.bar && !x.acts && !x.failed) && R.firstPending < R.firstCard['Course reader'], + 'importing a 12 MB PDF shows its card at once, first in the list, with the file\'s name, a bar and its menu off, before the document\'s own card'); + check(order.every((i) => i >= 0) && order.every((i, n) => n === 0 || i > order[n - 1]) && copies.length >= 2, + `one status word at a time in the order the import goes, the bytes counted while copying (${[...new Set(metas)].length} words: ${copies.slice(0, 3).join(', ')} …)`); + const nows = pdfStates.map((x) => x.now); + check(nows.every((v, i) => i === 0 || v >= nows[i - 1]) && nows.at(-1) === 100 && new Set(nows).size >= 4, `the bar only moves forward, by the real stages and bytes, and reaches the end (${[...new Set(nows)].join(' → ')})`); + check(['Reading…', 'Preparing pages…', 'Copying…', 'Saving…'].every((w) => R.spoken.includes(w)) && !R.spoken.some((x) => /MB/.test(x)), `the live region speaks each stage once, without the bytes (${R.spoken.join(' | ')})`); + const pdfRec = await local(`writerPaper:${pdfKey}`); + check(pdfThumb > 0 && /^data:image\/(png|jpeg);base64,/.test(pdfRec?.thumb || '') && pdfRec.thumb.length <= 64e3 && pdfRec.size === pdfBytes.length && !(await im.evaluate(() => document.querySelector('#gd-docs > .gd-pending'))), + `then the document's card, its first page within the thumbnail budget (${(pdfRec?.thumb || '').length} chars), and the importing card gone`); + + // The store refuses the bytes once (no room): the reason on the card, Retry and Remove; Retry imports it. + await im.evaluate(async () => { + await GZ_WRITER_PAPER.need(['lib/doc-files.js']); + const real = GZ_DOC_FILES.client; + GZ_DOC_FILES.client = (...a) => { GZ_DOC_FILES.client = real; const c = real(...a); return { ...c, put: async () => { throw GZ_DOC_FILES.fail('quota'); } }; }; + }); + const fullPath = path.join(tmp, 'Lab manual.pdf'); + fs.writeFileSync(fullPath, require('./pdf-fixtures.js').FIXTURES.three()); + await im.setInputFiles('#gd-file', fullPath); + const room = "There isn't enough room in this browser for this file."; + const failed = await until(() => im.evaluate(() => { const el = [...document.querySelectorAll('#gd-docs > .gd-pending')].find((x) => x.querySelector('.gd-title').textContent === 'Lab manual.pdf'); + return el?.hasAttribute('data-failed') ? { meta: el.querySelector('.gd-meta').textContent, acts: [...el.querySelectorAll('.gd-acts:not([hidden]) button')].map((b) => b.textContent), bar: !el.querySelector('.gd-progress').hidden, said: document.getElementById('gd-said').textContent } : null; }), 'the refused import\'s card'); + check(failed.meta === room && failed.acts.join() === 'Retry,Remove' && !failed.bar && failed.said === room, `a failed import says why on its card, with Retry and Remove (${JSON.stringify(failed)})`); + await pause(300); + await im.screenshot({ path: path.join(out, 'import-failed-1280-en.png') }); + check(!((await local('writerPapers')) || []).some((r) => r.title === 'Lab manual'), 'and keeps no document'); + await pause(300); + await im.click('.gd-pending [data-act="retry"]'); + const retried = await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].find((c) => c.querySelector('.gd-title').textContent === 'Lab manual')?.dataset.key), 'the retried import\'s card', 30000); + check(!!retried && !(await im.evaluate(() => document.querySelector('#gd-docs > .gd-pending'))) && await until(() => im.evaluate(() => document.getElementById('gd-said').textContent === ''), 'Retry clears the reason', 3000), 'Retry imports it: the document\'s card in its place, the reason gone'); + check(await im.evaluate((k) => document.activeElement?.closest('.gd-card')?.dataset.key === k, retried), 'and the focus Retry had goes to the document'); + + // A file that is no PDF: Remove takes its card away and gives the focus to Import. + const brokenPath = path.join(tmp, 'Scan.pdf'); + fs.writeFileSync(brokenPath, 'not a pdf at all'); + await im.setInputFiles('#gd-file', brokenPath); + const broken = await until(() => im.evaluate(() => { const el = document.querySelector('#gd-docs > .gd-pending[data-failed]'); return el && { name: el.querySelector('.gd-title').textContent, meta: el.querySelector('.gd-meta').textContent }; }), 'the broken PDF\'s card'); + check(broken.name === 'Scan.pdf' && broken.meta === 'That file could not be read.', `a file that is no PDF says so on its card (${JSON.stringify(broken)})`); + await pause(300); + await im.focus('.gd-pending [data-act="remove"]'); + await im.keyboard.press('Enter'); + await until(() => im.evaluate(() => !document.querySelector('#gd-docs > .gd-pending')), 'Remove'); + check(await im.evaluate(() => document.activeElement?.id === 'gd-import') && await until(() => im.evaluate(() => document.getElementById('gd-said').textContent === ''), 'Remove clears the reason', 3000), 'Remove takes the card away, its reason with it, and the focus goes to Import'); + + // Held halfway through copying, for a picture of the importing card. + await im.evaluate(async () => { + await GZ_WRITER_PAPER.need(['lib/doc-files.js']); + const real = GZ_DOC_FILES.client, gate = new Promise((r) => { globalThis.__gate = r; }); + GZ_DOC_FILES.client = (...a) => { GZ_DOC_FILES.client = real; const c = real(...a); return { ...c, put: async (bytes, type, sent) => { sent(bytes.length / 2, bytes.length); await gate; return c.put(bytes, type, sent); } }; }; + }); + const heldPath = path.join(tmp, 'Week 4 reading.pdf'); + fs.writeFileSync(heldPath, require('./pdf-fixtures.js').large({ count: 10, size: 6e6 })); + await im.setInputFiles('#gd-file', heldPath); + await until(() => im.evaluate(() => /^Copying… [\d.]+ \/ [\d.]+ MB$/.test(document.querySelector('#gd-docs > .gd-pending .gd-meta')?.textContent || '')), 'the held import halfway'); + await pause(400); + await im.screenshot({ path: path.join(out, 'import-copying-1280-en.png') }); + await im.evaluate(() => globalThis.__gate()); + await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Week 4 reading') && !document.querySelector('#gd-docs > .gd-pending')), 'the held import done', 60000); + + // A deck shows the same, its own word for the middle stage. + const Z = require(path.join(root, 'lib/zip.js')); + const deckDir = path.join(root, 'test/fixtures/pptx/deck'); + const walk = (d, base = '') => fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.name !== '.DS_Store') + .flatMap((e) => (e.isDirectory() ? walk(path.join(d, e.name), `${base}${e.name}/`) : [{ name: `${base}${e.name}`, data: new Uint8Array(fs.readFileSync(path.join(d, e.name))), date: new Date(2026, 9, 29, 12) }])); + const deckPath = path.join(tmp, 'Cell Biology week 3.pptx'); + fs.writeFileSync(deckPath, Buffer.concat(Z.zip([...walk(deckDir), { name: 'ppt/media/filler.bin', data: new Uint8Array(crypto.randomBytes(3e6)), date: new Date(2026, 9, 29, 12) }]))); + await recordImports(); + await im.setInputFiles('#gd-file', deckPath); + await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Cell Biology week 3')), 'the deck\'s card', 60000); + R = await imports(); + const deckMetas = R.states.filter((x) => x.name === 'Cell Biology week 3.pptx').map((x) => x.meta), deckNows = R.states.filter((x) => x.name === 'Cell Biology week 3.pptx').map((x) => x.now); + check(['Reading…', 'Preparing slides…', 'Copying…', 'Saving…'].every((w) => deckMetas.some((m) => m.startsWith(w))) && deckMetas.some((m) => /^Copying… [\d.]+ \/ 3(\.\d)? MB$/.test(m)) && deckNows.at(-1) === 100 && R.firstPending < R.firstCard['Cell Biology week 3'], + `a deck shows the same: its card at once, Preparing slides…, its bytes copied, the bar to the end, then its own card (${[...new Set(deckMetas)].join(' | ')})`); + await shot(im, 'import-done-1280-en.png'); + await im.close(); + } + check(!errors.some((e) => /sender/.test(e)), 'no worker sender refusals'); check(errors.length === 0, `no page errors (${errors.join('; ')})`); check(requests.length === 0, `no network requests (${requests.join(', ')})`); diff --git a/scripts/ultra-preview-check.js b/scripts/ultra-preview-check.js index 562f71852..9bdb3b8e2 100644 --- a/scripts/ultra-preview-check.js +++ b/scripts/ultra-preview-check.js @@ -180,8 +180,13 @@ const quiet = async (fn, label, ms = 1500) => { const end = Date.now() + ms; whi await opened.close().catch(() => {}); // ---- Save to Documents above the old 3 MB cap: the merged file store keeps it + // Each thing the viewer's line says as it saves, kept. + await fr.evaluate(() => { const R = globalThis.__saying = [], st = document.querySelector('.gp-state'); new MutationObserver(() => { if (R.at(-1) !== st.textContent) R.push(st.textContent); }).observe(st, { childList: true, characterData: true, subtree: true }); }); await fr.click('[data-act="save"]'); await until(() => fr.evaluate(() => document.querySelector('.gp-state')?.textContent === 'Saved to Documents'), 'large PDF saved', 20000, () => fr.evaluate(() => document.querySelector('.gp-state')?.textContent)); + const saying = await fr.evaluate(() => globalThis.__saying), pcts = saying.map((x) => /^Saving… (\d+)%$/.exec(x)?.[1]).filter(Boolean).map(Number); + check(saying[0] === 'Saving…' && pcts.length >= 3 && pcts.every((n, i) => i === 0 || n > pcts[i - 1]) && saying.at(-1) === 'Saved to Documents', + `Save to Documents says Saving… and how far it has gone, then Saved to Documents (${saying.join(' | ')})`); const papers = () => worker.evaluate(async () => { const all = await chrome.storage.local.get(null); return Object.entries(all).filter(([k]) => k.startsWith('writerPaper:doc:')).map(([k, v]) => ({ k, kind: v.kind, title: v.title, size: v.size, pages: v.pages, file: v.file })); }); const large = await papers(); check(large.length === 1 && large[0].size === pdfs[SLIDES.hash].length && large[0].file?.size === large[0].size && typeof large[0].file?.hash === 'string', `Save to Documents keeps a ${(pdfs[SLIDES.hash].length / 1e6).toFixed(1)} MB PDF in the file store`); diff --git a/scripts/writer-kinds-check.js b/scripts/writer-kinds-check.js index 8c9d6e0a2..7ac301402 100644 --- a/scripts/writer-kinds-check.js +++ b/scripts/writer-kinds-check.js @@ -175,10 +175,22 @@ const holds = (bytes, value) => { await until(() => cardOf(fr), 'the card'); const card = await cardOf(fr); check(card.tiles.join('|') === 'Open in Glaze|Submit as is' && card.name === 'Field trip form.pdf' && await p.evaluate(() => __uploads.length === 0), `a .pdf on Ultra's file area asks Open in Glaze or Submit as is, Ultra hearing nothing (${JSON.stringify(card)})`); + // The file arriving, as the compact bar's line (or the paper's status bar) says it, each change kept. + await fr.evaluate(() => { + const R = globalThis.__arrive = []; + const look = () => { + const a = document.querySelector('.gp-arrive:not([hidden]) > span'), s = document.querySelector('.gp-said[data-kind="importing"]'), v = a?.textContent || s?.textContent || ''; + if (v && R.at(-1) !== v) R.push(v); + }; + new MutationObserver(look).observe(document.body, { subtree: true, childList: true, characterData: true, attributes: true }); + }); await tap(fr, '.gi-default'); await until(() => fr.evaluate(() => document.documentElement.dataset.doc === 'pdf' && document.querySelectorAll('.gk .pdfViewer .page').length === 3 && !!document.querySelector('.gk .pdfViewer .page[data-page-number="1"] canvas')), 'the PDF viewer', 40000, () => fr.evaluate(() => ({ doc: document.documentElement.dataset.doc, pages: document.querySelectorAll('.pdfViewer .page').length, said: document.querySelector('.gq-said')?.textContent }))); const h1 = await until(async () => { const h = await el.evaluate((f) => f.getBoundingClientRect().height); return h > 600 && h; }, 'a paper\'s height'); + const arrived = await fr.evaluate(() => ({ said: globalThis.__arrive, left: !document.querySelector('.gp-said[data-kind="importing"]') && !document.querySelector('.gp-arrive:not([hidden])') })); + check(['Reading…', 'Preparing pages…', 'Copying…', 'Saving…'].every((w, i, all) => arrived.said.indexOf(w) >= 0 && (i === 0 || arrived.said.indexOf(w) > arrived.said.indexOf(all[i - 1]))) && arrived.left, + `Open in Glaze shows the file arriving, stage by stage, until the viewer does (${arrived.said.join(' | ')})`); let rec = await record(), vs = await versions(); check(rec.kind === 'pdf' && sha((await bytesOf(rec))) === sha(pdfBytes) && rec.pages === 3 && rec.doc == null, `the attempt's record is the PDF, its bytes as brought (${JSON.stringify({ kind: rec.kind, pages: rec.pages, size: rec.size })})`); check(vs.some((v) => v.reason === 'import' && (v.kind || 'paper') === 'paper'), `the paper kept as a version first (${vs.map((v) => `${v.reason}:${v.kind || 'paper'}`).join(', ')})`); diff --git a/test/import-progress.test.js b/test/import-progress.test.js new file mode 100644 index 000000000..2dc74c59f --- /dev/null +++ b/test/import-progress.test.js @@ -0,0 +1,102 @@ +// An import's progress (lib/doc-kinds.js progress and said): what each kind reports as it goes, as the Documents home's +// importing card, the frame's status bar and Save to Documents show it. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const DK = require('../lib/doc-kinds.js'); + +const heard = (stages) => { const got = []; const report = DK.progress((p) => got.push(p), stages); return { got, report }; }; + +test('stages in order fill the bar by their shares, bytes within a stage', () => { + const { got, report } = heard(); + report({ stage: 'read', done: 0, total: 1000 }); + report({ stage: 'read', done: 1000, total: 1000 }); + report({ stage: 'prepare' }); + report({ stage: 'copy', done: 0, total: 1000 }); + report({ stage: 'copy', done: 500, total: 1000 }); + report({ stage: 'copy', done: 1000, total: 1000 }); + report({ stage: 'save' }); + report.end(); + const f = got.map((p) => p.fraction); + const S = DK.STAGES, sum = S.read + S.prepare + S.copy + S.save; + assert.deepEqual(got.map((p) => p.stage), ['read', 'read', 'prepare', 'copy', 'copy', 'copy', 'save', 'save']); + assert.equal(f[0], 0); + assert.equal(f[1], S.read / sum); + assert.equal(f[4], (S.read + S.prepare + S.copy / 2) / sum); + assert.equal(f[6], (S.read + S.prepare + S.copy) / sum); + assert.equal(f.at(-1), 1); + assert.ok(f.every((x, i) => i === 0 || x >= f[i - 1]), `never back: ${f}`); + assert.deepEqual({ done: got[4].done, total: got[4].total }, { done: 500, total: 1000 }); + assert.equal(got.at(-1).end, true); +}); + +test('the copy dominates a PDF import, as measured', () => { + const S = DK.STAGES; + assert.ok(S.copy > S.read + S.prepare + S.save); +}); + +test('a stage only goes forward and its bytes only up; unknown stages and calls after the end are ignored', () => { + const { got, report } = heard(); + report({ stage: 'copy', done: 600, total: 1000 }); + report({ stage: 'copy', done: 400, total: 1000 }); + report({ stage: 'read', done: 1000, total: 1000 }); + report({ stage: 'teleport' }); + report({ stage: 'copy', done: 5000, total: 1000 }); + assert.deepEqual(got.map((p) => [p.stage, p.done]), [['copy', 600], ['copy', 600], ['copy', 1000]]); + report.end(); + report({ stage: 'save' }); + report.fail(new Error('late')); + report.end(); + assert.equal(got.length, 4); +}); + +test('a kind without a copy stage (a workbook, a Word file) spreads its bar over the stages it has', () => { + const { got, report } = heard(['read', 'prepare', 'save']); + report({ stage: 'read', done: 10, total: 10 }); + report({ stage: 'copy', done: 5, total: 10 }); + report({ stage: 'save' }); + const S = DK.STAGES, sum = S.read + S.prepare + S.save; + assert.deepEqual(got.map((p) => p.stage), ['read', 'save']); + assert.equal(got[0].fraction, S.read / sum); + assert.equal(got[1].fraction, (S.read + S.prepare) / sum); +}); + +test('a failure is heard once, with its error, and the bar stays where it stopped', () => { + const { got, report } = heard(); + report({ stage: 'copy', done: 250, total: 1000 }); + const e = new RangeError('There isn\'t enough room in this browser for this file.'); + report.fail(e); + report.fail(new Error('again')); + report({ stage: 'save' }); + assert.equal(got.length, 2); + assert.equal(got[1].failed, e); + assert.equal(got[1].stage, 'copy'); + assert.equal(got[1].fraction, got[0].fraction); + assert.ok(!got[1].end); +}); + +test('a listener that throws does not stop the import', () => { + const report = DK.progress(() => { throw new Error('page gone'); }); + const error = console.error; + console.error = () => {}; + try { assert.doesNotThrow(() => { report({ stage: 'read' }); report.end(); }); } finally { console.error = error; } +}); + +test('said: one status word a stage, the bytes while copying, none of them spoken', () => { + assert.equal(DK.said(null, 'pdf'), null); + assert.deepEqual(DK.said({ stage: 'read' }, 'pdf'), ['Reading…']); + assert.deepEqual(DK.said({ stage: 'prepare' }, 'pdf'), ['Preparing pages…']); + assert.deepEqual(DK.said({ stage: 'prepare' }, 'slides'), ['Preparing slides…']); + assert.deepEqual(DK.said({ stage: 'prepare' }, 'sheet'), ['Preparing sheets…']); + assert.deepEqual(DK.said({ stage: 'copy', done: 12_345_678, total: 49_500_000 }, 'pdf'), ['Copying… {done} / {total} MB', { done: 12.3, total: 49.5 }]); + assert.deepEqual(DK.said({ stage: 'copy', done: 12_345_678, total: 49_500_000 }, 'pdf', { spoken: true }), ['Copying…']); + assert.deepEqual(DK.said({ stage: 'copy', done: 0, total: 0 }, 'pdf'), ['Copying…']); + assert.deepEqual(DK.said({ stage: 'copy', done: 300e3, total: 900e3 }, 'pdf'), ['Copying…']); + assert.deepEqual(DK.said({ stage: 'save' }, 'sheet'), ['Saving…']); +}); + +test('every word said is in every catalogue', () => { + const I = require('../lib/i18n.js'); + const words = ['Reading…', 'Preparing pages…', 'Preparing slides…', 'Preparing sheets…', 'Copying…', 'Copying… {done} / {total} MB', 'Saving…', 'Saving… {n}%', 'Retry', 'Remove']; + for (const code of I.CODES) for (const w of words) assert.ok(typeof globalThis.GZ_LANGS[code][w] === 'string', `${code}: ${w}`); + assert.equal(I.t('Copying… {done} / {total} MB', { done: 12.3, total: 49.5 }, 'de'), 'Wird kopiert… 12,3 / 49,5 MB'); +}); From b2ff7bb3cb1032b344ad4204436e757b967513dd Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:04:38 -0400 Subject: [PATCH 005/907] Cover 3-D chart review regressions --- test/sheet-chart-3d.test.js | 94 ++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/test/sheet-chart-3d.test.js b/test/sheet-chart-3d.test.js index 36cb8bb47..40ed4b35a 100644 --- a/test/sheet-chart-3d.test.js +++ b/test/sheet-chart-3d.test.js @@ -64,9 +64,9 @@ test('3-D pie: an exploded slice moves out along its mid-angle; cut faces turn t assert.deepEqual([by[0].dx, by[0].dy, by[2].dx, by[3].dy].map((v) => v + 0), [0, 0, 0, 0]); // Shrunk to fit: the exploded slice's far edge stays inside the plot. assert.ok(cx + by[1].dx + rx <= 480 && cy - ry - 0.18 * ry >= 0); - // Faces: the first slice's last edge (36°, right half) and the second's last edge (108°), and the fourth's first (216°). + // Faces only at separated boundaries: the first slice's last edge and the exploded second slice's last edge. const faces = sc.marks.sides.filter((x) => x.kind === 'face'); - assert.deepEqual(faces.map((x) => [x.i, x.a]).sort((a, b) => a[0] - b[0]), [[0, 36], [1, 108], [3, 216]]); + assert.deepEqual(faces.map((x) => [x.i, x.a]).sort((a, b) => a[0] - b[0]), [[0, 36], [1, 108]]); // Unexploded: no cut faces at all. const flat = CH.scene(pie3d((x) => x.replace('', '')), 480, 300); assert.equal(flat.marks.sides.filter((x) => x.kind === 'face').length, 0); @@ -152,10 +152,100 @@ test('frames: 3-D line and area charts, and a 3-D chart beside another, name the const both = (a, b) => FX.CHARTS.col3d.replace('', `${a}`).replace('', `${b}`); assert.equal(CX.chart(both('', '')).type, '3-D column chart'); assert.equal(CX.chart(both('', '')).type, '3-D pie chart'); + const pie = FX.CHARTS.pie3d.match(/[\s\S]*?<\/c:pie3DChart>/)[0]; + for (const name of ['col3d', 'line', 'radar']) { + assert.equal(CX.chart(FX.CHARTS[name].replace('', `${pie}`)).type, '3-D pie chart', `pie after ${name}`); + } for (const w of ['3-D pie chart', '3-D column chart', '3-D bar chart', '3-D line chart', '3-D area chart']) assert.ok(CX.WORDS.includes(w), w); assert.ok(!CX.WORDS.includes('3-D chart'), 'no frame says only "3-D"'); }); +test('3-D bars: zero values and zero percent totals have no filled faces', () => { + for (const dir of ['col', 'bar']) for (const grouping of ['clustered', 'stacked', 'percentStacked', 'standard']) { + const d = col3d(), g = d.groups[0]; + g.dir = dir; g.grouping = grouping; + for (const s of g.series) { s.vals = [0, -10, 20, null]; } + const sc = CH.scene(d, 480, 300); + assert.ok(!sc.marks.faces.some((f) => 's' in f && f.i === 0), `${dir}/${grouping}: zero is not a coloured depth plane`); + assert.ok(!/NaN|Infinity/.test(JSON.stringify(sc.prims)), `${dir}/${grouping}: finite geometry`); + assert.ok(sc.marks.bars.filter((b) => b.i === 0).every((b) => b.top === 0)); + } +}); + +test('3-D bar labels: negative endpoints, reversed axes and stacked segment centres, above all faces', () => { + for (const dir of ['col', 'bar']) for (const reverse of [false, true]) { + const d = col3d(), g = d.groups[0]; + g.dir = dir; g.series = [g.series[0]]; + g.series[0].vals = [-10, 20]; + Object.values(d.axes).find((a) => a.kind === 'val').reverse = reverse; + const sc = CH.scene(d, 480, 300), b = sc.marks.bars.find((b) => b.i === 0); + const label = sc.prims.find((p) => p.cls === 'gc-label' && p.text === '-10'); + if (dir === 'col') assert.ok(reverse ? label.a.y < b.y : label.a.y > b.y + b.h, `${dir}/${reverse}: ${JSON.stringify({ b, label })}`); + else assert.ok(reverse ? label.a.x > b.x + b.w : label.a.x < b.x, `${dir}/${reverse}: ${JSON.stringify({ b, label })}`); + } + for (const dir of ['col', 'bar']) { + const d = col3d(grouping('stacked')); d.groups[0].dir = dir; + const sc = CH.scene(d, 480, 300), b = sc.marks.bars.find((b) => b.s === 0 && b.i === 0); + const label = sc.prims.find((p) => p.cls === 'gc-label' && p.text === '10'); + assert.ok(near(label.a.x, b.x + b.w / 2) && near(label.a.y - 3.5, b.y + b.h / 2), `${dir}: the label belongs to its own segment`); + assert.ok(sc.prims.findIndex((p) => p.cls === 'gc-label') > sc.prims.findLastIndex((p) => p.cls === 'gc-bar' || p.cls === 'gc-face'), 'later bars cannot erase a label'); + } +}); + +test('3-D pie: rotations are reduced to one turn before geometry work', () => { + // This finite XML number makes turn++ stop advancing in the old rim loop. + const run = require('node:child_process').spawnSync(process.execPath, ['-e', ` + const CX = require('./lib/sheet-chart-xml.js'), CH = require('./lib/sheet-chart.js'); + const FX = require('./test/fixtures/sheets/charts.js'); + const xml = FX.CHARTS.pie3d.replace('', ''); + console.log(JSON.stringify(CH.scene(CX.resolve(CX.chart(xml), () => null), 480, 300).marks)); + `], { cwd: require('node:path').join(__dirname, '..'), timeout: 2000, encoding: 'utf8' }); + assert.equal(run.error, undefined, `rotation must finish: ${run.error}`); + assert.equal(run.status, 0, run.stderr); + const marks = JSON.parse(run.stdout); + assert.equal(marks.slices.length, 4); + assert.ok(marks.slices.every((s) => s.a0 >= 0 && s.a1 <= 720)); + assert.ok(marks.sides.filter((s) => s.kind === 'rim').length <= 5); +}); + +test('3-D pie: every slice exploded still fits a small plot', () => { + const d = pie3d(); d.title = null; d.legend = null; + d.groups[0].series[0].explosion = 400; + d.groups[0].series[0].explode.clear(); + const { marks } = CH.scene(d, 40, 40), p = marks.pie3d; + for (const s of marks.slices) { + assert.ok(p.cx + s.dx - p.rx >= 8 && p.cx + s.dx + p.rx <= 32, JSON.stringify({ p, s })); + assert.ok(p.cy + s.dy - p.ry >= 8 && p.cy + s.dy + p.ry + p.depth <= 32, JSON.stringify({ p, s })); + } +}); + +test('3-D pie: large finite values keep their proportions when their sum overflows', () => { + const d = pie3d(), s = d.groups[0].series[0]; + s.vals = [Number.MAX_VALUE, -Number.MAX_VALUE / 2, 0, null]; + const sc = CH.scene(d, 480, 300); + assert.deepEqual(sc.marks.slices.map((p) => p.a1 - p.a0).sort((a, b) => a - b), [120, 240]); + assert.deepEqual(sc.marks.labels, ['67%', '33%']); + assert.ok(!/NaN|Infinity/.test(JSON.stringify(sc.prims))); +}); + +test('3-D defaults: missing view3D, all explosions, negative pies and zero totals stay finite', () => { + for (const kind of ['pie3d', 'col3d']) { + const d = data(FX.CHARTS[kind].replace(/[\s\S]*?<\/c:view3D>/, '')); + const s = d.groups[0].series[0]; + s.vals = [-10, 0, 30, null]; s.explosion = 25; s.explode.clear(); + const before = JSON.stringify(d), sc = CH.scene(d, 480, 300); + assert.ok(!/NaN|Infinity/.test(JSON.stringify(sc.prims))); + assert.equal(JSON.stringify(d), before, 'drawing does not mutate the description'); + if (kind === 'pie3d') { + assert.equal(sc.marks.slices.length, 2); + assert.ok(sc.marks.slices.every((p) => p.explosion === 0.25)); + assert.ok(near(sc.marks.pie3d.ry / sc.marks.pie3d.rx, 0.5, 0.005)); + s.vals = [0, 0, null]; + assert.equal(CH.scene(d, 480, 300).marks.slices.length, 0); + } + } +}); + test('bounded: an oversized 3-D chart is a titled frame; a 3-D pie\'s shapes grow by a few per slice', () => { const big = pie3d(); big.groups[0].series[0].vals = new Array(10001).fill(1); From 5fc570e71507c7a7da46ca562bf80f4a01b117af Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:05:05 -0400 Subject: [PATCH 006/907] Hide cut faces between joined pie slices --- lib/sheet-chart.js | 14 +++++++------- scripts/sheets-check.js | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 223e6d20d..5838d0578 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -175,19 +175,19 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { vals.forEach((v, i) => { if (!v) return; const a1 = a + (v / total) * 360, mid = (a + a1) / 2, e = exOf(i); - slices.push({ i, a0: a, a1, mid, v, e, off: [rx * e * Math.sin(mid * RAD), -ry * e * Math.cos(mid * RAD)], color: pointColor(g, s, i) }); + slices.push({ i, index: slices.length, a0: a, a1, mid, v, e, off: [rx * e * Math.sin(mid * RAD), -ry * e * Math.cos(mid * RAD)], color: pointColor(g, s, i) }); a = a1; }); - const exploded = slices.some((x) => x.e > 0); const tipOf = (x) => `${s.cats?.[x.i] ?? x.i + 1}: ${format(s.vals[x.i], s.fmt)}`; // Back (mid-angle at 12 o'clock) to front (6 o'clock). const order = [...slices].sort((p, q) => Math.cos(q.mid * RAD) - Math.cos(p.mid * RAD)); for (const x of order) { - // A cut face shows only when the pie is apart somewhere and the face turns toward the viewer: the slice's first - // edge on the left half, its last edge on the right half. - if (exploded && slices.length > 1) { - for (const [edge, facing] of [[x.a0, Math.sin(x.a0 * RAD) < -1e-9], [x.a1, Math.sin(x.a1 * RAD) > 1e-9]]) { - if (!facing) continue; + // A cut face shows only at a separated boundary and toward the viewer: the slice's first edge on the left + // half, its last edge on the right half. An explosion elsewhere doesn't expose two joined slices' shared edge. + if (slices.length > 1) { + const prev = slices[(x.index + slices.length - 1) % slices.length], next = slices[(x.index + 1) % slices.length]; + for (const [edge, facing, apart] of [[x.a0, Math.sin(x.a0 * RAD) < -1e-9, x.e || prev.e], [x.a1, Math.sin(x.a1 * RAD) > 1e-9, x.e || next.e]]) { + if (!facing || !apart) continue; const c = [cx + x.off[0], cy + x.off[1]], p = at(edge, 1, x.off); add('path', { d: poly([c, p, [p[0], p[1] + depth], [c[0], c[1] + depth]]), fill: darker(x.color, 60000) }, 'gc-side', null, tipOf(x)); marks.sides.push({ i: x.i, kind: 'face', a: round(edge) }); diff --git a/scripts/sheets-check.js b/scripts/sheets-check.js index 6e7f72801..0a82606ed 100644 --- a/scripts/sheets-check.js +++ b/scripts/sheets-check.js @@ -612,7 +612,7 @@ const seeded = (key, wb = M.workbook()) => { // ---- 3-D charts: a workbook with a 3-D pie (its second slice exploded, percent labels) and a 3-D clustered column // chart (value labels, the side and back walls) draws both projected: the pie's four tops, the rim of its three front - // slices and the three cut faces its explosion shows; the columns' eight fronts with a top and a side each; the + // slices and the two cut faces its explosion shows; the columns' eight fronts with a top and a side each; the // dark Look's labels read; 640 px; screenshots of each. const file3d = path.join(tmp, 'Charts 3D.xlsx'); fs.writeFileSync(file3d, FX.workbook({ kinds: ['pie3d', 'col3d'] })); @@ -630,8 +630,8 @@ const seeded = (key, wb = M.workbook()) => { })); const three = await until(async () => { const l = await count3d(); return l.length === 2 ? l : null; }, 'two 3-D charts drawn'); const pie3 = three.find((c) => c.title === 'Share in 3-D'), col3 = three.find((c) => c.title === 'Sales in 3-D'); - check(pie3 && !pie3.kind && pie3.tops === 4 && pie3.sides === 6 && pie3.plabels.join() === '10%,20%,30%,40%', - `a 3-D pie draws its four tops, three front rims and three cut faces, with percent labels (${JSON.stringify(pie3)})`); + check(pie3 && !pie3.kind && pie3.tops === 4 && pie3.sides === 5 && pie3.plabels.join() === '10%,20%,30%,40%', + `a 3-D pie draws its four tops, three front rims and two cut faces, with percent labels (${JSON.stringify(pie3)})`); check(col3 && !col3.kind && col3.bars === 8 && col3.faces === 16 && col3.walls === 2 && col3.labels.join() === '10,20,30,40', `a 3-D clustered column chart draws eight bars, each with a top and a side, on its two walls, with value labels (${JSON.stringify(col3)})`); const ink3 = await page.evaluate(() => { From 5736bf79807cd1f95f1fdd9dfded4b6f2d2ec498 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:05:21 -0400 Subject: [PATCH 007/907] Omit depth faces for zero-length 3-D bars --- lib/sheet-chart.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 5838d0578..905477582 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -485,17 +485,20 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const color = pointColor(g, s, i); const tip = `${s.name ? `${s.name}, ` : ''}${cats[i]}: ${format(p.v, s.fmt)}`; const f = pj(x0, y0, z0); - add('rect', { x: round(f[0]), y: round(f[1]), width: round(box.width), height: round(box.height), fill: color }, 'gc-bar', null, tip); - marks.faces.push({ s: j, i, kind: 'front', pts: pts([pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y1, z0), pj(x0, y1, z0)]) }); - if (sy > 1e-6) { - const q = [pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y0, z1), pj(x0, y0, z1)]; - add('path', { d: poly(q), fill: lighter(color) }, 'gc-face', null, tip); - marks.faces.push({ s: j, i, kind: 'top', pts: pts(q) }); - } - if (Math.abs(sx) > 1e-6) { - const xe = sx > 0 ? x1 : x0, q = [pj(xe, y0, z0), pj(xe, y0, z1), pj(xe, y1, z1), pj(xe, y1, z0)]; - add('path', { d: poly(q), fill: darker(color) }, 'gc-face', null, tip); - marks.faces.push({ s: j, i, kind: 'side', pts: pts(q) }); + // A zero or fully clipped value has no volume: its cap would otherwise look like a nonzero depth plane. + if (it.a !== it.b) { + add('rect', { x: round(f[0]), y: round(f[1]), width: round(box.width), height: round(box.height), fill: color }, 'gc-bar', null, tip); + marks.faces.push({ s: j, i, kind: 'front', pts: pts([pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y1, z0), pj(x0, y1, z0)]) }); + if (sy > 1e-6) { + const q = [pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y0, z1), pj(x0, y0, z1)]; + add('path', { d: poly(q), fill: lighter(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'top', pts: pts(q) }); + } + if (Math.abs(sx) > 1e-6) { + const xe = sx > 0 ? x1 : x0, q = [pj(xe, y0, z0), pj(xe, y0, z1), pj(xe, y1, z1), pj(xe, y1, z0)]; + add('path', { d: poly(q), fill: darker(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'side', pts: pts(q) }); + } } marks.bars.push({ s: j, i, x: round(f[0]), y: round(f[1]), w: round(box.width), h: round(box.height), v: p.v, top: p.hi, color, z0: round(z0), z1: round(z1) }); const lab = s.labels ?? g.labels; From eb03d884d2721cd968d6588f478d268e93189081 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:05:29 -0400 Subject: [PATCH 008/907] Name mixed 3-D pie frames regardless of plot order --- lib/sheet-chart-xml.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index 55cdfb4b0..34d66be11 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -286,9 +286,9 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { // Drawn: one or more of the six on one pair of axes (a pie, doughnut or 3-D chart alone); else the titled frame. const pairs = new Set(groups.map((g) => g.axes.slice(0, 2).join(','))); const round = groups.some((g) => g.type === 'pie' || g.type === 'doughnut' || g.type === 'pie3D'); - const d3 = groups.find((g) => g.type === 'pie3D' || g.type === 'bar3D'); + const d3 = groups.find((g) => g.type === 'pie3D') ?? groups.find((g) => g.type === 'bar3D'); if (other.length || !groups.length || pairs.size > 1 || Object.values(axes).some((a) => a.log) || (round && groups.length > 1) || (d3 && groups.length > 1) || (groups.some((g) => g.type === 'scatter') && groups.length > 1)) { - return { ...out, kind: 'frame', type: other[0] ?? (d3 ? word3D(d3) : groups.length > 1 ? 'Combo chart' : 'Chart') }; + return { ...out, kind: 'frame', type: d3 ? word3D(d3) : other[0] ?? (groups.length > 1 ? 'Combo chart' : 'Chart') }; } return out; }; From 7eb66e00513b1175b1d5eab2a363c89f30a772a6 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:05:47 -0400 Subject: [PATCH 009/907] Place 3-D bar labels at their values and paint them last --- lib/sheet-chart.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 905477582..8ca065273 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -466,7 +466,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const stacked = g.grouping === 'stacked' || g.grouping === 'percentStacked'; const rows = g.grouping === 'standard' ? S : 1, kk = g.grouping === 'clustered' ? S : 1; const barW = band / (kk + clamp(g.gap, 0, 500) / 100), start = (band - barW * kk) / 2; - const items = []; + const items = [], labels = []; g.series.forEach((s, j) => stacks[gi][j].forEach((p, i) => { if (!p) return; const off = start + (kk > 1 ? j * barW : 0); @@ -504,10 +504,18 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const lab = s.labels ?? g.labels; if (lab && !lab.del && lab.val) { const text = format(p.v, lab.fmt || s.fmt), zm = (z0 + z1) / 2; - add('text', horiz ? { x: round(Math.max(it.a, it.b) + sx * z1 + 3), y: round(y0 + box.height / 2 - sy * zm + 3.5) } : { x: round((x0 + x1) / 2 + sx * zm), y: round(Math.min(it.a, it.b) - sy * zm - 3), 'text-anchor': 'middle' }, 'gc-label', text); + // Stacked labels sit on their own front, not the next segment. Outside labels follow the value endpoint, + // including negative values and reversed axes. Paint them after every bar so no later face erases them. + const centre = lab.pos === 'ctr' || (stacked && !lab.pos); + const dir = Math.sign(it.b - it.a) || (horiz ? (vRev ? -1 : 1) : (vRev ? 1 : -1)); + const a = centre ? { x: round((x0 + x1) / 2 + sx * z0), y: round((y0 + y1) / 2 - sy * z0 + 3.5), 'text-anchor': 'middle' } + : horiz ? { x: round(it.b + (dir > 0 ? Math.max(sx * z0, sx * z1) : Math.min(sx * z0, sx * z1)) + dir * 3), y: round((y0 + y1) / 2 - sy * zm + 3.5), 'text-anchor': dir > 0 ? 'start' : 'end' } + : { x: round((x0 + x1) / 2 + sx * (dir < 0 ? zm : z0)), y: round(it.b - sy * (dir < 0 ? zm : z0) + (dir < 0 ? -3 : TICK + 3)), 'text-anchor': 'middle' }; + labels.push({ a, text }); marks.labels.push(text); } } + for (const { a, text } of labels) add('text', a, 'gc-label', text); } else if (g.type === 'line' || g.type === 'area') { const stacked = g.grouping !== 'standard'; for (const j of g.series.keys()) { From ec99b0d664e09fa5060c2fcc318d7405e213d326 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:06:01 -0400 Subject: [PATCH 010/907] Bound 3-D pie rotations before walking rim arcs --- lib/sheet-chart.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 8ca065273..77df950db 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -171,7 +171,8 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { marks.pie3d = { cx: round(cx), cy: round(cy), rx: round(rx), ry: round(ry), depth: round(depth) }; const at = (deg, f, [ox, oy], dz = 0) => [cx + ox + rx * f * Math.sin(deg * RAD), cy + oy - ry * f * Math.cos(deg * RAD) + dz]; const slices = []; - let a = g.first || 0; + // Keep the rim loop within two turns, even for an out-of-range finite rotation in a file. + let a = (((g.first || 0) % 360) + 360) % 360; vals.forEach((v, i) => { if (!v) return; const a1 = a + (v / total) * 360, mid = (a + a1) / 2, e = exOf(i); From ed502320696616ab950d4f95ae810bc497a7b106 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:06:22 -0400 Subject: [PATCH 011/907] Fit exploded 3-D pies within small chart bounds --- lib/sheet-chart.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 77df950db..d3073e227 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -166,7 +166,8 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const maxE = Math.max(0, ...vals.map((v, i) => (v ? exOf(i) : 0))); const thick = 0.18 * Math.cos(tilt); const W = right - left, H = bottom - top; - const rx = Math.max(4, Math.min(W / 2 / (1 + maxE), H / (2 * k * (1 + maxE) + thick)) - 2), ry = rx * k, depth = rx * thick; + const rx = Math.max(0, Math.min(W / 2 / (1 + maxE), H / (2 * k * (1 + maxE) + thick)) - 2), ry = rx * k, depth = rx * thick; + if (!rx) return { prims, marks }; const cx = (left + right) / 2, cy = top + (H - (2 * ry * (1 + maxE) + depth)) / 2 + ry * (1 + maxE); marks.pie3d = { cx: round(cx), cy: round(cy), rx: round(rx), ry: round(ry), depth: round(depth) }; const at = (deg, f, [ox, oy], dz = 0) => [cx + ox + rx * f * Math.sin(deg * RAD), cy + oy - ry * f * Math.cos(deg * RAD) + dz]; From 130a912b03ae213ebf64a0d25c24aa504204f8d4 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:06:31 -0400 Subject: [PATCH 012/907] Keep 3-D pie proportions when finite totals overflow --- lib/sheet-chart.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index d3073e227..324878287 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -159,7 +159,11 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const v3 = data.view3D ?? {}; const tilt = clamp(v3.rotX ?? 30, 10, 90) * RAD; const k = Math.sin(tilt); - const vals = s.vals.map((v) => (v === null ? 0 : Math.abs(v))); + // Sum weights relative to the largest value so finite inputs cannot overflow the total and collapse every arc. + const values = s.vals.map((v) => (Number.isFinite(v) ? Math.abs(v) : 0)); + const maxV = Math.max(0, ...values); + if (!maxV) return { prims, marks }; + const vals = values.map((v) => v / maxV); const total = vals.reduce((n, v) => n + v, 0); if (!total) return { prims, marks }; const exOf = (i) => clamp(s.explode?.get(sourceIndex(s, i)) ?? s.explosion ?? 0, 0, 400) / 100; From 91101fdcf10ba6ab23f6181ac91eca0760e82f5c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:07:37 -0400 Subject: [PATCH 013/907] Check 3-D pie label bounds and raw round-trip bytes --- test/sheet-chart-3d.test.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/sheet-chart-3d.test.js b/test/sheet-chart-3d.test.js index 40ed4b35a..69355de2d 100644 --- a/test/sheet-chart-3d.test.js +++ b/test/sheet-chart-3d.test.js @@ -228,6 +228,18 @@ test('3-D pie: large finite values keep their proportions when their sum overflo assert.ok(!/NaN|Infinity/.test(JSON.stringify(sc.prims))); }); +test('3-D pie labels: a whole pie is centred; text that cannot fit a shallow ellipse is omitted', () => { + const one = pie3d(); one.groups[0].series[0].vals = [5, 0, null, 0]; + const sc = CH.scene(one, 480, 300), p = sc.marks.pie3d, slice = sc.marks.slices[0]; + const label = sc.prims.find((p) => p.cls === 'gc-plabel'); + assert.ok(near(label.a.x, p.cx + slice.dx) && near(label.a.y - 4, p.cy + slice.dy), '100% belongs at the centre of its ellipse'); + const shallow = pie3d(view(10, 0)); shallow.title = null; shallow.legend = null; + const s = shallow.groups[0].series[0]; s.explode.clear(); s.explosion = 400; s.vals = [10, 10, 10, 10]; + const small = CH.scene(shallow, 160, 100); + assert.ok(small.marks.pie3d.ry < 5, 'the ellipse cannot hold a 10px label'); + assert.equal(small.prims.filter((p) => p.cls === 'gc-plabel').length, 0); +}); + test('3-D defaults: missing view3D, all explosions, negative pies and zero totals stay finite', () => { for (const kind of ['pie3d', 'col3d']) { const d = data(FX.CHARTS[kind].replace(/[\s\S]*?<\/c:view3D>/, '')); @@ -265,7 +277,7 @@ test('bounded: an oversized 3-D chart is a titled frame; a 3-D pie\'s shapes gro test('round trip: a workbook with 3-D charts read, drawn and written untouched keeps their parts byte for byte', async () => { const bytes = FX.workbook({ kinds: ['pie3d', 'col3d'] }); const z = D.unzip(bytes), before = {}; - for (const n of z.names) before[n] = await z.text(n); + for (const n of z.names) before[n] = await z.bytes(n); const wb = await X.read(bytes); C.engine(wb).recalc(); const list = CX.charts(wb, wb.sheets[0]).map((c) => CX.resolve(c.chart, CX.lookup(wb))); @@ -275,5 +287,5 @@ test('round trip: a workbook with 3-D charts read, drawn and written untouched k const out = D.unzip(await X.write(wb, { date: new Date(2026, 0, 1) })); const names = Object.keys(before).filter((n) => /^xl\/(charts|drawings)\//.test(n)); assert.equal(names.length, 4); - for (const n of names) assert.equal(await out.text(n), before[n], n); + for (const n of names) assert.deepEqual(await out.bytes(n), before[n], n); }); From f10a2f14e3ac2d1800112c77f1b0cb63a563b7aa Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:07:50 -0400 Subject: [PATCH 014/907] Keep 3-D pie labels within their projected tops --- lib/sheet-chart.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 324878287..5dc3ec8c8 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -225,7 +225,10 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { if (!lab || lab.del || !(lab.val || lab.pct || lab.cat) || x.a1 - x.a0 < 12) continue; const text = [lab.cat ? s.cats?.[x.i] : null, lab.val ? format(s.vals[x.i], lab.fmt || s.fmt) : null, lab.pct ? format(x.v / total, lab.fmt && /%/.test(lab.fmt) ? lab.fmt : '0%') : null].filter(Boolean).join(' '); const out = lab.pos === 'outEnd'; - const [lx, ly] = at(x.mid, out ? 1.16 : 0.62, x.off, out && Math.cos(x.mid * RAD) < 0 ? depth : 0); + const [lx, ly] = at(x.mid, out ? 1.16 : slices.length === 1 ? 0 : 0.62, x.off, out && Math.cos(x.mid * RAD) < 0 ? depth : 0); + // Include the 10px text and its outline, not just its anchor, when deciding whether it fits the tilted top. + if (!out && ((Math.abs(lx - cx - x.off[0]) + width(text, TICK) / 2 + 1) / rx) ** 2 + + ((Math.abs(ly - cy - x.off[1]) + TICK / 2 + 1) / ry) ** 2 > 1) continue; add('text', { x: round(lx), y: round(ly + 4), 'text-anchor': out ? (Math.sin(x.mid * RAD) > 0.2 ? 'start' : Math.sin(x.mid * RAD) < -0.2 ? 'end' : 'middle') : 'middle' }, out ? 'gc-label' : 'gc-plabel', text); marks.labels.push(text); } From 3dfd2a3ceb45b7fd3f5b38ce53a505c568eb6eb4 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:08:43 -0400 Subject: [PATCH 015/907] Compare chart round-trip bytes as Uint8Arrays --- test/sheet-chart-3d.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/sheet-chart-3d.test.js b/test/sheet-chart-3d.test.js index 69355de2d..d7656b6fe 100644 --- a/test/sheet-chart-3d.test.js +++ b/test/sheet-chart-3d.test.js @@ -287,5 +287,5 @@ test('round trip: a workbook with 3-D charts read, drawn and written untouched k const out = D.unzip(await X.write(wb, { date: new Date(2026, 0, 1) })); const names = Object.keys(before).filter((n) => /^xl\/(charts|drawings)\//.test(n)); assert.equal(names.length, 4); - for (const n of names) assert.deepEqual(await out.bytes(n), before[n], n); + for (const n of names) assert.deepEqual(new Uint8Array(await out.bytes(n)), new Uint8Array(before[n]), n); }); From f2dfe375efdd5347bebbbff076200305f468887b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:19:00 -0400 Subject: [PATCH 016/907] Keep up to four chunks on their way when putting a file in the store --- lib/doc-files.js | 28 ++++++++++++++++++++-------- test/doc-files.test.js | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/doc-files.js b/lib/doc-files.js index fabdbbc9a..45798c9c3 100644 --- a/lib/doc-files.js +++ b/lib/doc-files.js @@ -1,15 +1,17 @@ // Documents' files: a PDF's or a deck's bytes (lib/doc-kinds.js), kept by the worker in its own IndexedDB, glaze-docs // (lib/background.js), by their SHA-256. writer.html in a school's page (whose IndexedDB the site around it partitions), // papers.html and Settings' backup reach them over a runtime port (PORT), in chunks of CHUNK bytes, each sent as base64 -// and acknowledged before the next (Safari's message size is the tightest), so neither side holds more than the one copy -// it is building. A record points at its file as { hash, size, type } (lib/writer-papers.js); the worker keeps, per -// document, the hashes its record and versions point at (owners), and a file goes once no document points at it. One -// object store, string keys: f: a file (its chunks' id and count, its owners), c:: a chunk, u: an upload -// under way, o: the hashes a document holds. +// (Safari's message size is the tightest) and acknowledged, a put's at most WINDOW ahead of its acknowledgements and a +// get's each before the next, so neither side holds more than the one copy it is building and a few chunks. A record +// points at its file as { hash, size, type } (lib/writer-papers.js); the worker keeps, per document, the hashes its +// record and versions point at (owners), and a file goes once no document points at it. One object store, string keys: +// f: a file (its chunks' id and count, its owners), c:: a chunk, u: an upload under way, o: +// the hashes a document holds. // Classic script for the worker and the pages; module.exports for node. var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { const PORT = 'glaze:docFiles'; const CHUNK = 384 << 10; // 512 KB as base64 + const WINDOW = 4; // a put's chunks on their way at once (client put) // The largest file (lib/doc-kinds.js FILE; test/doc-files.test.js holds them equal) and what a record held inline before // the store (its base64 within the record's 4 MB), which a record may still hold, and a browser without the store writes. const MAX = 50e6, LEGACY = 3e6; @@ -301,16 +303,26 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { return m; }; const send = (m) => { if (gone) throw fail('interrupted'); try { port.postMessage(m); } catch { throw fail('interrupted'); } }; - return { send, next, close: () => { if (gone) return; gone = true; try { port.disconnect(); } catch { /* gone */ } } }; + // A refusal already here, unread (a put that was still sending when the worker said no). + const refused = () => { const m = inbox.find((x) => x?.error); return m ? fail(WHY.has(m.error) ? m.error : 'unavailable') : null; }; + return { send, next, refused, close: () => { if (gone) return; gone = true; try { port.disconnect(); } catch { /* gone */ } } }; }; - // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent. + // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent, up to WINDOW of them on their way (the + // worker takes a port's messages in order, one at a time): the page encodes and the port carries the next while the + // worker writes this one. A refusal sent while chunks were on their way is the one said. const put = async (bytes, type = '') => { if (!(bytes instanceof Uint8Array) || !bytes.length || bytes.length > MAX) throw fail('size'); const s = session(); try { s.send({ op: 'put', size: bytes.length, type: String(type).slice(0, 200) }); await s.next(); - for (let at = 0; at < bytes.length; at += CHUNK) { s.send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); await s.next(); } + let out = 0; + const send = (m) => { try { s.send(m); } catch (e) { throw s.refused() || e; } }; + for (let at = 0; at < bytes.length; at += CHUNK) { + send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); + if (++out >= WINDOW) { await s.next(); out--; } + } + for (; out > 0; out--) await s.next(); s.send({ op: 'end' }); const { file } = await s.next(); if (!ref(file) || file.size !== bytes.length) throw fail('unavailable'); diff --git a/test/doc-files.test.js b/test/doc-files.test.js index 1c41b034f..b346bbc62 100644 --- a/test/doc-files.test.js +++ b/test/doc-files.test.js @@ -267,6 +267,24 @@ test('quota exceeded while opening IndexedDB is reported as quota', async () => } }); +test('a put keeps up to WINDOW chunks on their way, the bytes kept in order', async () => { + const { st } = make(), { client, log } = wire(st); + const b = bytes(F.CHUNK * 10 + 7, 12), file = await client.put(b, 'application/pdf'); + let out = 0, most = 0; + for (const { from, m } of log[0].sent) { if (from === 'page' && m.op === 'chunk') most = Math.max(most, ++out); if (from === 'worker' && m.ack) out--; } + assert.equal(most, 4); + assert.equal(out, 0); + assert.equal(sha(await client.get(file)), sha(b)); +}); + +test('a refusal while chunks are on their way is the one said', async () => { + const { st } = make(); + const chunk = st.chunk; + let n = 0; + st.chunk = (...a) => (++n === 2 ? Promise.reject(Object.assign(new Error('full'), { name: 'QuotaExceededError' })) : chunk(...a)); + await assert.rejects(wire(st).client.put(bytes(F.CHUNK * 8, 13), 'application/pdf'), (e) => e.why === 'quota'); +}); + test('a transfer cut mid-chunk: interrupted, and the worker drops the half upload', async () => { const { st, data } = make(); // The page goes (its tab closed) after its second chunk. From 52be64395320dd54a5047a088fe2340e079e954b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:26:39 -0400 Subject: [PATCH 017/907] Check every Save to Documents announcement --- scripts/ultra-preview-check.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ultra-preview-check.js b/scripts/ultra-preview-check.js index 9bdb3b8e2..d32b57885 100644 --- a/scripts/ultra-preview-check.js +++ b/scripts/ultra-preview-check.js @@ -181,7 +181,7 @@ const quiet = async (fn, label, ms = 1500) => { const end = Date.now() + ms; whi // ---- Save to Documents above the old 3 MB cap: the merged file store keeps it // Each thing the viewer's line says as it saves, kept. - await fr.evaluate(() => { const R = globalThis.__saying = [], st = document.querySelector('.gp-state'); new MutationObserver(() => { if (R.at(-1) !== st.textContent) R.push(st.textContent); }).observe(st, { childList: true, characterData: true, subtree: true }); }); + await fr.evaluate(() => { const R = globalThis.__saying = [], st = document.querySelector('.gp-state'); new MutationObserver((records) => { for (const r of records) for (const n of r.addedNodes) R.push(n.textContent); }).observe(st, { childList: true, characterData: true, subtree: true }); }); await fr.click('[data-act="save"]'); await until(() => fr.evaluate(() => document.querySelector('.gp-state')?.textContent === 'Saved to Documents'), 'large PDF saved', 20000, () => fr.evaluate(() => document.querySelector('.gp-state')?.textContent)); const saying = await fr.evaluate(() => globalThis.__saying), pcts = saying.map((x) => /^Saving… (\d+)%$/.exec(x)?.[1]).filter(Boolean).map(Number); From 300fbc6d71b600f176bf6403aabc44742d59ef19 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:26:55 -0400 Subject: [PATCH 018/907] Announce each saving percentage once --- pdf-kind.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf-kind.js b/pdf-kind.js index f7174b0e5..dc487d8c9 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -401,7 +401,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { // ---- saving: a field filled (pdf.js's annotation storage) saves a second after the last change, and before the page // leaves for Documents; the bytes pdf.js writes (an incremental update) replace the record's, with page 1 drawn again. - const say = (text) => { state.textContent = text; }; + const say = (text) => { if (state.textContent !== text) state.textContent = text; }; let dirty = false, saving = null, timer = 0; const save = async () => { if (!doc || !dirty) return true; From f7ef20f00b1079264c9dbc4bc718454a0267cc1b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:27:16 -0400 Subject: [PATCH 019/907] Check compact import stage announcements --- scripts/writer-kinds-check.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/writer-kinds-check.js b/scripts/writer-kinds-check.js index 7ac301402..8efdddd72 100644 --- a/scripts/writer-kinds-check.js +++ b/scripts/writer-kinds-check.js @@ -178,11 +178,17 @@ const holds = (bytes, value) => { // The file arriving, as the compact bar's line (or the paper's status bar) says it, each change kept. await fr.evaluate(() => { const R = globalThis.__arrive = []; + const spoken = globalThis.__importSpoken = []; const look = () => { const a = document.querySelector('.gp-arrive:not([hidden]) > span'), s = document.querySelector('.gp-said[data-kind="importing"]'), v = a?.textContent || s?.textContent || ''; if (v && R.at(-1) !== v) R.push(v); }; new MutationObserver(look).observe(document.body, { subtree: true, childList: true, characterData: true, attributes: true }); + new MutationObserver((records) => { + for (const r of records) if (r.target.matches?.('.gp-said[data-kind="importing"], .gp-sr[role="status"]') && r.target.getAttribute('aria-live') !== 'off') { + for (const n of r.addedNodes) if (n.textContent) spoken.push(n.textContent); + } + }).observe(document.body, { subtree: true, childList: true }); }); await tap(fr, '.gi-default'); await until(() => fr.evaluate(() => document.documentElement.dataset.doc === 'pdf' && document.querySelectorAll('.gk .pdfViewer .page').length === 3 && !!document.querySelector('.gk .pdfViewer .page[data-page-number="1"] canvas')), 'the PDF viewer', 40000, @@ -191,6 +197,8 @@ const holds = (bytes, value) => { const arrived = await fr.evaluate(() => ({ said: globalThis.__arrive, left: !document.querySelector('.gp-said[data-kind="importing"]') && !document.querySelector('.gp-arrive:not([hidden])') })); check(['Reading…', 'Preparing pages…', 'Copying…', 'Saving…'].every((w, i, all) => arrived.said.indexOf(w) >= 0 && (i === 0 || arrived.said.indexOf(w) > arrived.said.indexOf(all[i - 1]))) && arrived.left, `Open in Glaze shows the file arriving, stage by stage, until the viewer does (${arrived.said.join(' | ')})`); + const spoken = await fr.evaluate(() => globalThis.__importSpoken); + check(JSON.stringify(spoken) === JSON.stringify(['Reading…', 'Preparing pages…', 'Copying…', 'Saving…']), `the compact import announces each stage once, without chunk counts (${spoken.join(' | ')})`); let rec = await record(), vs = await versions(); check(rec.kind === 'pdf' && sha((await bytesOf(rec))) === sha(pdfBytes) && rec.pages === 3 && rec.doc == null, `the attempt's record is the PDF, its bytes as brought (${JSON.stringify({ kind: rec.kind, pages: rec.pages, size: rec.size })})`); check(vs.some((v) => v.reason === 'import' && (v.kind || 'paper') === 'paper'), `the paper kept as a version first (${vs.map((v) => `${v.reason}:${v.kind || 'paper'}`).join(', ')})`); From 1c5619758850305ec3d37e97e1fa3ec6799402c6 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:27:29 -0400 Subject: [PATCH 020/907] Test a Close button after the pop-up's footer actions --- test/announce-ui.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/announce-ui.test.js b/test/announce-ui.test.js index 68ef2d3b6..dd28da2f4 100644 --- a/test/announce-ui.test.js +++ b/test/announce-ui.test.js @@ -95,6 +95,25 @@ test('the pop-up is a modal dialog named by its title: focus goes in, Escape clo assert.equal(p.doc.activeElement, b); }); +test('a footer with actions ends with a Close button after them; a pop-up without actions has no footer', async () => { + const p = page(); + const a = p.link(); + a.focus(); + const go = p.doc.createElement('a'); go.className = 'go'; go.textContent = 'Open in Canvas'; + p.P.open({ title: 'T', body: 'x', actions: [go], source: a }); + const foot = p.dialog().querySelector('footer'); + assert.deepEqual(foot.children.map((c) => `${c.tagName.toLowerCase()}.${c.className}`), ['a.go', 'button.shut']); + const shut = foot.children[1]; + assert.equal(shut.type, 'button'); + assert.equal(text(shut), 'Close'); + shut.dispatchEvent({ type: 'click' }); + assert.equal(p.doc.activeElement, a, 'focus returns to the link'); + await settle(); + assert.equal(p.host('gz-popup'), null, 'gone'); + p.P.open({ title: 'Rubric', body: 'x', source: a }); + assert.equal(p.dialog().querySelector('footer').children.length, 0, 'no actions, no Close: the footer stays empty and hidden'); +}); + test('an async body shows Loading… (busy) until it comes; a rejected one is the caller\'s to handle', async () => { const p = page(); let give; From d16903276ede72915ae4a17a319b234b952c8497 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:29:13 -0400 Subject: [PATCH 021/907] Validate chunk acknowledgements before advancing the upload window --- lib/doc-files.js | 14 ++++++---- test/doc-files.test.js | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/lib/doc-files.js b/lib/doc-files.js index 45798c9c3..a79e8bd89 100644 --- a/lib/doc-files.js +++ b/lib/doc-files.js @@ -238,11 +238,11 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { }; // ---- the worker's end of a port: one transfer each. put: { op: 'put', size, type } → { ready }; { op: 'chunk', data } - // → { ack } each; { op: 'end' } → { file }. get: { op: 'get', hash } → { data } each, answered { op: 'ack' }, then + // → { ack: 1, 2, … } each; { op: 'end' } → { file }. get: { op: 'get', hash } → { data } each, answered { op: 'ack' }, then // { done, file }. stat: { op: 'stat', hash } → { file } or { file: null }. Any failure: { error: WHY }, and the port // closes. allowed: whether the sender may (a promise), settled before anything runs. const serve = (port, st, allowed = Promise.resolve(true)) => { - let up = null, ack = null, closed = false; + let up = null, ack = null, closed = false, chunks = 0; const say = (m) => { if (!closed) port.postMessage(m); }; const close = () => { if (closed) return; closed = true; try { port.disconnect(); } catch { /* gone */ } }; let queue = Promise.resolve(allowed).then((ok) => { if (!ok) { close(); throw fail('bad'); } }, () => { close(); throw fail('bad'); }); @@ -259,7 +259,7 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { } else if (m?.op === 'chunk') { if (!up) throw fail('interrupted'); await st.chunk(up, unb64(m.data)); - say({ ack: true }); + say({ ack: ++chunks }); } else if (m?.op === 'end') { if (!up) throw fail('interrupted'); const id = up; @@ -316,13 +316,15 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { try { s.send({ op: 'put', size: bytes.length, type: String(type).slice(0, 200) }); await s.next(); - let out = 0; + let out = 0, got = 0; + // A gap, duplicate or reordered acknowledgement cannot advance the window or publish the file. + const acknowledged = async () => { if ((await s.next()).ack !== ++got) throw fail('unavailable'); }; const send = (m) => { try { s.send(m); } catch (e) { throw s.refused() || e; } }; for (let at = 0; at < bytes.length; at += CHUNK) { send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); - if (++out >= WINDOW) { await s.next(); out--; } + if (++out >= WINDOW) { await acknowledged(); out--; } } - for (; out > 0; out--) await s.next(); + for (; out > 0; out--) await acknowledged(); s.send({ op: 'end' }); const { file } = await s.next(); if (!ref(file) || file.size !== bytes.length) throw fail('unavailable'); diff --git a/test/doc-files.test.js b/test/doc-files.test.js index b346bbc62..80b18349e 100644 --- a/test/doc-files.test.js +++ b/test/doc-files.test.js @@ -277,6 +277,69 @@ test('a put keeps up to WINDOW chunks on their way, the bytes kept in order', as assert.equal(sha(await client.get(file)), sha(b)); }); +test('a put refuses chunk acknowledgements delivered out of order', async () => { + const { st, data } = make(); + const { client } = wire(st, { tap: (p) => { + const post = p.postMessage; + let first = null, n = 0; + p.postMessage = (m) => { + if (m.ack && ++n === 1) { first = m; return; } + post(m); + if (m.ack && n === 2) post(first); + }; + return p; + } }); + await assert.rejects(client.put(bytes(F.CHUNK * 8, 14), 'application/pdf'), (e) => e.why === 'unavailable'); + for (let i = 0; i < 50 && [...data().keys()].length; i++) await new Promise(setImmediate); + assert.deepEqual([...data().keys()], [], 'no upload, chunk or published file left'); +}); + +test('a put refuses a gap in chunk acknowledgements before the port disconnects', async () => { + const { st, data } = make(); + const { client } = wire(st, { tap: (p) => { + const post = p.postMessage; + let n = 0; + p.postMessage = (m) => { + if (m.ack && ++n === 1) return; + post(m); + // End the faulty fake's port so a client that misses the gap cannot hang the test. + if (m.ack && n === 4) setImmediate(() => p.disconnect()); + }; + return p; + } }); + await assert.rejects(client.put(bytes(F.CHUNK * 4, 15), 'application/pdf'), (e) => e.why === 'unavailable'); + for (let i = 0; i < 50 && [...data().keys()].length; i++) await new Promise(setImmediate); + assert.deepEqual([...data().keys()], [], 'no upload or chunk left'); +}); + +test('a full window waits for the first write and a page disconnect releases it', async () => { + let release, entered; + const writing = new Promise((r) => { entered = r; }); + const gate = new Promise((r) => { release = r; }); + const { st, db, data } = make({ wrap: (db) => ({ ...db, write: async (puts, dels) => { + if (puts.some(([k]) => k.startsWith('c:'))) { entered(); await gate; } + return db.write(puts, dels); + } }) }); + const p = ports(); + F.serve(p.worker, st); + const ready = new Promise((r) => p.page.onMessage.addListener((m) => { if (m.ready) r(); })); + p.page.postMessage({ op: 'put', size: F.CHUNK * 8, type: 'application/pdf' }); + await ready; + for (let i = 0; i < 4; i++) p.page.postMessage({ op: 'chunk', data: F.b64(bytes(F.CHUNK, 16 + i)) }); + try { + await writing; + await new Promise(setImmediate); + assert.equal(p.sent.filter(({ from, m }) => from === 'worker' && m.ack).length, 0); + } finally { p.page.disconnect(); release(); } + for (let i = 0; i < 50 && [...data().keys()].length; i++) await new Promise(setImmediate); + assert.deepEqual([...data().keys()], [], 'the pending write and queued chunks cannot outlive the upload'); + // A new worker over the same database starts the retry on its own port, never resumes the old window. + const restarted = wire(F.store(db)), b = bytes(F.CHUNK * 4 + 1, 17); + const file = await restarted.client.put(b, 'application/pdf'); + assert.equal(file.hash, sha(b)); + assert.equal(sha(await restarted.client.get(file)), file.hash); +}); + test('a refusal while chunks are on their way is the one said', async () => { const { st } = make(); const chunk = st.chunk; From 97c3667820cf8386a69da1853308a5123ac1b7ad Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:31:36 -0400 Subject: [PATCH 022/907] Announce compact imports once per stage --- writer-paper.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/writer-paper.js b/writer-paper.js index 44869c30b..a4d36bb74 100644 --- a/writer-paper.js +++ b/writer-paper.js @@ -1732,7 +1732,14 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // arriving(p): the file's stage while it arrives ({ name, text, spoken, fraction }), null once it has or hasn't. In // the compact box, the bar's line says it. arriving: (p) => { - if (compact && said) { if (p) tell('importing', p.text); else if (saying === 'importing') tell(''); return; } + if (compact && said) { + // Bytes remain visible; only a change of stage reaches the separate live region. + said.setAttribute('aria-live', p ? 'off' : 'polite'); + if (p) tell('importing', p.text); else if (saying === 'importing') tell(''); + const spoken = p?.spoken || ''; + if (compactArriveSay.textContent !== spoken) compactArriveSay.textContent = spoken; + return; + } arrive.hidden = !p; if (!p) { arriveSay.textContent = ''; return; } arriveText.textContent = p.text; @@ -1770,6 +1777,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // until the next edit; and, while the paper is past what Ultra's text box takes (compose's input limit: project() // null, so nothing more goes into it), that, the Full writer switch beside it marked, whose .docx has no such limit. const said = boxEl ? h('span', { class: 'gp-said', role: 'status' }) : null; + const compactArriveSay = boxEl ? h('span', { class: 'gp-sr', role: 'status' }) : null; let saying = ''; const tell = (kind, text = '') => { if (!said) return; @@ -1896,7 +1904,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // the caret, Versions, Open from Documents and Documents, labelled again in the page's language as it opens. const more = boxEl && document.getElementById('more'); if (more) { - more.before(said, fullSwitch()); + more.before(said, compactArriveSay, fullSwitch()); const panel = async (name) => { await setView(true, { save: true }); if (!compact) { tabs[name].click(); tabs[name].focus(); } }; const items = [['Checks', () => panel('checks')], ['Feedback', () => panel('feedback')], ['Cite', () => globalThis.GZ_WRITER_CITE?.open?.({ anchor: more })], ['Versions', () => versions(more)], ['Open from Documents', () => fromDocsSheet()], ['Documents', () => open2('papers.html')]] .map(([label, run]) => [host.item?.(t(label), run), label]); From a3a4945df65979e342cb0347f6eed2a205dd93bd Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:32:02 -0400 Subject: [PATCH 023/907] Check concurrent imports and Retry focus --- scripts/documents-check.js | 66 +++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 458cd8f40..03c4adaa4 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -115,7 +115,7 @@ const PROBE = ({ lang }) => { const watch = (pg, tag) => { pg.on('pageerror', (e) => errors.push(`${tag}: ${e.message}`)); // The import cases' own refusals (a store with no room, a file that is no PDF) are logged as every failed import is. - const meant = tag === 'import' ? /^RangeError: (There isn't enough room in this browser for this file\.|That file could not be read\.)\n/ : /$^/; + const meant = tag === 'import' ? /^RangeError: (There isn't enough room in this browser for this file\.|That file could not be read\.|The file didn't finish copying\. Try again\.)\n/ : /$^/; pg.on('console', (m) => { if (m.type() === 'error' && !/Failed to load resource|intentional review refusal|Error: not a zip file/.test(m.text()) && !meant.test(m.text())) errors.push(`${tag} console: ${m.text()}`); }); }; const openHome = async (lang = null, tag = 'home') => { @@ -1461,6 +1461,70 @@ const PROBE = ({ lang }) => { const deckMetas = R.states.filter((x) => x.name === 'Cell Biology week 3.pptx').map((x) => x.meta), deckNows = R.states.filter((x) => x.name === 'Cell Biology week 3.pptx').map((x) => x.now); check(['Reading…', 'Preparing slides…', 'Copying…', 'Saving…'].every((w) => deckMetas.some((m) => m.startsWith(w))) && deckMetas.some((m) => /^Copying… [\d.]+ \/ 3(\.\d)? MB$/.test(m)) && deckNows.at(-1) === 100 && R.firstPending < R.firstCard['Cell Biology week 3'], `a deck shows the same: its card at once, Preparing slides…, its bytes copied, the bar to the end, then its own card (${[...new Set(deckMetas)].join(' | ')})`); + + // Two separate drops can copy at once. Hold both at different fractions, then finish the newer one first. + await im.evaluate(() => { + const real = GZ_DOC_FILES.client; + globalThis.__copies = []; + GZ_DOC_FILES.client = (...a) => { + const c = real(...a); + return { ...c, put: async (bytes, type, sent) => { + const n = __copies.length; + if (n === 1) GZ_DOC_FILES.client = real; + sent(bytes.length * (n ? .75 : .25), bytes.length); + await new Promise((r) => __copies.push(r)); + return c.put(bytes, type, sent); + } }; + }; + }); + const parallel = ['Parallel A.pdf', 'Parallel B.pdf'].map((name) => { const p = path.join(tmp, name); fs.copyFileSync(heldPath, p); return p; }); + await recordImports(); + await im.setInputFiles('#gd-file', parallel[0]); + await until(() => im.evaluate(() => __copies.length === 1), 'first copy held'); + await im.setInputFiles('#gd-file', parallel[1]); + await until(() => im.evaluate(() => __copies.length === 2), 'second copy held'); + const together = await im.evaluate(() => [...document.querySelectorAll('.gd-pending')].map((el) => ({ id: el.dataset.import, name: el.querySelector('.gd-title').textContent, now: Number(el.querySelector('[role="progressbar"]').getAttribute('aria-valuenow')) }))); + check(together.length === 2 && new Set(together.map((x) => x.id)).size === 2 && together.find((x) => x.name === 'Parallel A.pdf')?.now === 37 && together.find((x) => x.name === 'Parallel B.pdf')?.now === 76, + `simultaneous drops have separate cards and progress (${JSON.stringify(together)})`); + await im.evaluate(() => document.querySelector('.gd-pending [data-act="remove"]').click()); + check(await im.locator('.gd-pending').count() === 2, 'Remove cannot discard an import while it is running'); + await im.evaluate(() => __copies[1]()); + await until(() => im.evaluate(() => [...document.querySelectorAll('.gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Parallel B') && document.querySelectorAll('.gd-pending').length === 1), 'second copy finishes first'); + check(await im.evaluate(() => document.querySelector('.gd-pending .gd-title').textContent === 'Parallel A.pdf' && document.querySelector('.gd-pending [role="progressbar"]').getAttribute('aria-valuenow') === '37'), 'finishing one import leaves the other card and its progress intact'); + await im.evaluate(() => __copies[0]()); + await until(() => im.evaluate(() => !document.querySelector('.gd-pending')), 'both copies done'); + R = await imports(); + check(['Parallel A.pdf', 'Parallel B.pdf'].every((name) => { const n = R.states.filter((x) => x.name === name).map((x) => x.now); return n.at(-1) === 100 && n.every((v, i) => v <= 100 && (i === 0 || v >= n[i - 1])); }), 'both bars stay bounded and forward when earlier copy acknowledgements arrive after later progress'); + + // A mid-copy failure must settle the card. Retry reads the same File again; moving focus while it copies wins. + await im.evaluate(() => { + const real = GZ_DOC_FILES.client, read = File.prototype.arrayBuffer; + globalThis.__reread = []; + File.prototype.arrayBuffer = function () { if (this.name === 'Interrupted.pdf') __reread.push(this); return read.call(this); }; + let tries = 0; + GZ_DOC_FILES.client = (...a) => { + const c = real(...a); + return { ...c, put: async (bytes, type, sent) => { + sent(bytes.length / 2, bytes.length); + if (++tries === 1) throw GZ_DOC_FILES.fail('interrupted'); + GZ_DOC_FILES.client = real; + await new Promise((r) => { globalThis.__retryCopy = r; }); + return c.put(bytes, type, sent); + } }; + }; + }); + const interruptedPath = path.join(tmp, 'Interrupted.pdf'); + fs.copyFileSync(heldPath, interruptedPath); + await im.setInputFiles('#gd-file', interruptedPath); + await until(() => im.evaluate(() => document.querySelector('.gd-pending[data-failed] .gd-meta')?.textContent === "The file didn't finish copying. Try again."), 'interrupted copy settles'); + check(await im.evaluate(() => !document.querySelector('.gd-pending .gd-acts').hidden && document.querySelector('.gd-pending .gd-progress').hidden), 'a mid-copy interruption leaves a failed card with actions, rather than an endless importing card'); + await im.click('.gd-pending [data-act="retry"]'); + await until(() => im.evaluate(() => typeof __retryCopy === 'function'), 'retry copy held'); + check(await im.evaluate(() => __reread.length === 2 && __reread[0] === __reread[1]), 'Retry reads the same File object a second time'); + await im.focus('#gd-q'); + await im.evaluate(() => __retryCopy()); + await until(() => im.evaluate(() => !document.querySelector('.gd-pending') && [...document.querySelectorAll('.gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Interrupted')), 'interrupted file retried'); + check(await im.evaluate(() => document.activeElement?.id === 'gd-q'), 'Retry preserves focus moved elsewhere while the import was running'); await shot(im, 'import-done-1280-en.png'); await im.close(); } From 96627e324875807c7bf15a3529c75c093fcb21f9 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:32:36 -0400 Subject: [PATCH 024/907] Check stage announcements over an existing viewer --- test/writer-kinds.test.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/writer-kinds.test.js b/test/writer-kinds.test.js index 5c5e7db0f..be0ee9264 100644 --- a/test/writer-kinds.test.js +++ b/test/writer-kinds.test.js @@ -139,13 +139,20 @@ test('a frame without Writer\'s host makes no kinds module', () => { const viewer = async (id = 'pdf') => { const P = PA.open(store(), () => Date.now()); await P.save(KEY, { kind: id, doc: null, file: 'JVBERi0=', size: 5 }); - const calls = { save: 0, dispose: 0, versions: [] }, paper = { key: () => KEY, context: () => ({}), paper: () => ({}), + const calls = { save: 0, dispose: 0, versions: [], said: [] }, paper = { key: () => KEY, context: () => ({}), paper: () => ({}), hold() {}, unpark() {}, take() {}, release() {}, notify() {}, importFile: async () => null }; const el = () => ({ dataset: {}, append() {}, remove() {}, addEventListener() {} }); const document = { documentElement: el(), body: el(), head: { append: (l) => queueMicrotask(() => l.onload()) }, createElement: el }; const implementation = { ...(id === 'sheet' && { version: async (key, reason) => { calls.versions.push([key, reason]); return { version: { id: 'packed', gz: 'workbook' } }; } }), - view: async () => ({ save: async () => { calls.save++; return true; }, file: async () => ({ bytes: new Uint8Array([1]) }), dispose: () => { calls.dispose++; } }), - into: async () => (await P.save(KEY, { kind: 'pdf', file: 'TkVX', size: 3 }, { reason: 'import' })).paper }; + view: async () => ({ say: (text) => calls.said.push(text), save: async () => { calls.save++; return true; }, file: async () => ({ bytes: new Uint8Array([1]) }), dispose: () => { calls.dispose++; } }), + into: async (key, f, { progress } = {}) => { + progress?.({ stage: 'read' }); + progress?.({ stage: 'read', done: 2e6, total: 2e6 }); + progress?.({ stage: 'prepare' }); + for (const done of [0, 1e6, 2e6]) progress?.({ stage: 'copy', done, total: 2e6 }); + progress?.({ stage: 'save' }); + return (await P.save(KEY, { kind: 'pdf', file: 'TkVX', size: 3 }, { reason: 'import' })).paper; + } }; const DK = require('../lib/doc-kinds.js'); const ctx = vm.createContext({ console: { error() {} }, document, GZ_WRITER_PAPERS: { open: () => P }, GZ_DOC_KINDS: { KINDS: DK.KINDS, kindOf: DK.kindOf, progress: DK.progress, said: DK.said, settled: async () => {}, get: (id) => ({ ...DK.KINDS[id], ...implementation }) }, @@ -182,6 +189,12 @@ test('replacing a viewer disposes the previous viewer', async () => { assert.equal(calls.dispose, 1); }); +test('an import over a viewer announces each stage once without chunk counts', async () => { + const { surface, calls } = await viewer(); + await surface.importFile({ name: 'new.pdf' }); + assert.deepEqual(calls.said, ['Reading…', 'Preparing pages…', 'Copying…', 'Saving…']); +}); + test('the grid uses its kind\'s packed version for submit and before a file replaces it', async () => { const { surface, calls } = await viewer('sheet'); assert.equal((await surface.version('sending')).version.gz, 'workbook'); From 584312de8a1a034aeb87b307a8be3aa51a241567 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:33:07 -0400 Subject: [PATCH 025/907] Announce imports over viewers once per stage --- writer-kinds.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/writer-kinds.js b/writer-kinds.js index afebe09ad..96745403b 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -109,10 +109,13 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { // The file arriving, stage by stage (lib/doc-kinds.js progress): in the paper's status bar, or the viewer's line, // until its viewer shows (finally, below). const steps = id === 'sheet' ? ['read', 'prepare', 'save'] : ['read', 'prepare', 'copy', 'save']; + let stage = null; const progress = GZ_DOC_KINDS.progress((p) => { const w = GZ_DOC_KINDS.said(p, id); if (!w || p.end || p.failed) return; - if (shown) shown.viewer?.say?.(t(...w)); + if (shown) { + if (stage !== p.stage) { stage = p.stage; shown.viewer?.say?.(t(...GZ_DOC_KINDS.said(p, id, { spoken: true }))); } + } else paper.arriving?.({ name: f.name, text: t(...w), spoken: t(...GZ_DOC_KINDS.said(p, id, { spoken: true })), fraction: p.fraction }); }, steps); arriving = paper; From d3d41a18897abfe73c8ce81dbb67d66e71f39ba0 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:33:52 -0400 Subject: [PATCH 026/907] Keep Retry focus with the user's current location --- papers.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/papers.js b/papers.js index d31140201..55d6e1007 100644 --- a/papers.js +++ b/papers.js @@ -343,7 +343,7 @@ for (let i = incoming.length; i--;) { const j = incoming[i]; if (!j.key || !listed.has(j.key)) continue; - if (j.refocus || j.ui.el.contains(document.activeElement)) focusNext = j.key; + if (j.ui.el.contains(document.activeElement)) focusNext = j.key; incoming.splice(i, 1); } const els = [...(bin ? [] : incoming.map((j) => j.ui.el)), ...shown.map(cardFor)]; @@ -714,9 +714,10 @@ // Retry keeps the focus with the import: on the document once it is listed, on Retry again if it fails again. const retry = async (j) => { say(''); - j.refocus = j.ui.el.contains(document.activeElement); - const r = await importJob(j); - if (r.failed && j.refocus && j.ui.el.isConnected) j.ui.again.focus(); + const focused = j.ui.el.contains(document.activeElement), pending = importJob(j); + if (focused) j.ui.el.focus({ preventScroll: true }); + const r = await pending; + if (r.failed && j.ui.el.contains(document.activeElement) && j.ui.el.isConnected) j.ui.again.focus(); if (r.failed || r.notice) say(t(r.failed || r.notice)); }; const job = (f, id) => { @@ -727,7 +728,7 @@ const again = btn({ class: 'gd-btn gd-quiet', 'data-act': 'retry', onclick: () => { retry(j).catch((e) => console.error(e)); } }, t('Retry')); const acts = h('div', { class: 'gd-acts', hidden: true }, again, btn({ class: 'gd-btn gd-quiet', 'data-act': 'remove', onclick: () => removeJob(j) }, t('Remove'))); const more = btn({ class: 'gd-more', disabled: true, 'aria-label': t('More'), title: t('More'), tabindex: '-1' }, icon('dots')); - const el = h('li', { class: 'gd-pending', 'data-kind': id, 'data-import': String(j.id) }, + const el = h('li', { class: 'gd-pending', tabindex: '-1', 'data-kind': id, 'data-import': String(j.id) }, h('div', { class: 'gd-pic' }, h('div', { class: 'gd-blank' })), h('div', { class: 'gd-info' }, icon(DK.KINDS[id]?.icon || 'paper', 'gd-kind'), h('div', { class: 'gd-text' }, h('span', { class: 'gd-title', text: f.name }), meta, bar, acts), more)); j.ui = { el, bar, fill, meta, acts, again }; From 38b79c7f04dfca4cf14d4bdaaa17b317e6d2d270 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:34:03 -0400 Subject: [PATCH 027/907] Check import feedback over an open deck --- scripts/writer-kinds-check.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/writer-kinds-check.js b/scripts/writer-kinds-check.js index 8efdddd72..729e68799 100644 --- a/scripts/writer-kinds-check.js +++ b/scripts/writer-kinds-check.js @@ -272,6 +272,23 @@ const holds = (bytes, value) => { check((await cardOf(fr)).tiles.join('|') === 'Open in Glaze|Submit as is', 'a .pptx asks Open in Glaze or Submit as is'); await tap(fr, '.gi-default'); await until(() => fr.evaluate(() => document.documentElement.dataset.doc === 'slides' && document.querySelectorAll('.gk .gs-rail-b').length === 10 && !!document.querySelector('.gk #gs-canvas [role="group"]')), 'the slides viewer', 40000); + // Replacing an already-open deck must show the arriving file's stage in that viewer too. + await fr.evaluate((bytes) => { + const k = GZ_DOC_KINDS.get('slides'); + GZ_DOC_KINDS.register('slides', { ...k, into: async (key, f, opts) => { + GZ_DOC_KINDS.register('slides', k); + opts.progress({ stage: 'read' }); + await new Promise((r) => { globalThis.__deckRead = r; }); + return k.into(key, f, opts); + } }); + globalThis.__deckReplaced = false; + GZ_WRITER_PAPER.current().importFile(new File([new Uint8Array(bytes)], 'Replacement.pptx', { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' })).then(() => { globalThis.__deckReplaced = true; }); + }, Array.from(deckBytes)); + await until(() => fr.evaluate(() => typeof __deckRead === 'function'), 'replacement deck reading'); + const deckStage = await fr.evaluate(() => document.querySelector('.gk .gp-state')?.textContent); + check(deckStage === 'Reading…', `an import over a slide viewer shows its stage (${deckStage})`); + await fr.evaluate(() => __deckRead()); + await until(() => fr.evaluate(() => __deckReplaced && document.querySelectorAll('.gk .gs-rail-b').length === 10), 'replacement deck shown'); rec = await record(); vs = await versions(); check(rec.kind === 'slides' && rec.slides === 10 && sha((await bytesOf(rec))) === sha(deckBytes) && vs.some((v) => v.reason === 'import'), `the record is the deck, its bytes as brought, the paper a version first (${JSON.stringify({ kind: rec.kind, slides: rec.slides })})`); // Ctrl+F (⌘F) over the viewer is the viewer's own find, never the hidden paper's (writer-find.js). From 2c3de868280edc3344e251efb502d0f4043d3ea2 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:33:16 -0400 Subject: [PATCH 028/907] Test that storage.local writes change neither the worker's agent instance nor the mod name the approval window shows --- test/mcp-approve.test.js | 27 ++++++++++++++-- test/mcp-worker.test.js | 70 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/test/mcp-approve.test.js b/test/mcp-approve.test.js index 87254b838..233d98e54 100644 --- a/test/mcp-approve.test.js +++ b/test/mcp-approve.test.js @@ -543,7 +543,7 @@ test('the approval page is not reachable from web pages', () => { }); // Execute the shipped page with a small DOM and controlled focus/clock, including native button default actions. -const pageEnv = async () => { +const pageEnv = async ({ records = { 'mcpAsk:a': { ...ACTION, id: 'a', kind: 'action', at: 1, state: 'waiting' } }, local = {} } = {}) => { const handlers = {}, events = {}, sent = [], due = new Map(), elements = new Map(); let focused = true, seq = 0; const document = { activeElement: null, hasFocus: () => focused, visibilityState: 'visible', documentElement: { style: {} }, @@ -564,7 +564,8 @@ const pageEnv = async () => { GZ_I18N: { page() {}, ui: () => 'en', t: (k, values = {}) => k.replace(/\{(\w+)\}/g, (_, p) => values[p]) }, matchMedia: () => ({ matches: false, addEventListener() {} }), setTimeout: (fn) => { due.set(++seq, fn); return seq; }, clearTimeout: (id) => due.delete(id), - chrome: { storage: { session: { get: async () => ({ 'mcpAsk:a': { ...ACTION, id: 'a', kind: 'action', at: 1, state: 'waiting' } }) }, + chrome: { storage: { session: { get: async () => structuredClone(records) }, + local: { get: async (k) => Object.fromEntries([k].flat().filter((x) => x in local).map((x) => [x, structuredClone(local[x])])) }, onChanged: { addListener() {} }, sync: { get: async () => ({}) } }, runtime: { sendMessage: (m, cb) => { sent.push(m); cb({ ok: true }); } } }, }); @@ -602,3 +603,25 @@ test('held and composing Enter keys cannot activate Allow after the guard expire assert.equal(p.sent[0].answer, 'yes', 'a fresh, intentional key still works'); } }); + +// storage.local is content-script writable: the window names a mod as the worker's ask does, whatever a page wrote there. +test("the page names a mod from the worker's ask, never from storage.local", async () => { + const MODID = 'abcdefghijklmnop', group = `mod:${MODID}`, local = { [group]: { manifest: { name: 'Planted' } } }; + const pair = await pageEnv({ records: { 'mcpAsk:p': { ...PAIR, group, mod: 'Due titles', id: 'p', kind: 'pair', at: 1, state: 'waiting' } }, local }); + assert.equal(pair.el('group').textContent, 'The mod Due titles'); + const grant = await pageEnv({ records: { 'mcpAsk:g': { ...GRANT, group, mod: 'Due titles', id: 'g', kind: 'grant', at: 1, state: 'waiting' } }, local }); + assert.equal(grant.el('title').textContent, 'Claude wants to use the mod Due titles'); + const bare = await pageEnv({ records: { 'mcpAsk:b': { ...GRANT, group, id: 'b', kind: 'grant', at: 1, state: 'waiting' } }, local }); + assert.equal(bare.el('title').textContent, `Claude wants to use the mod ${MODID}`, 'an ask without a name shows the id'); + assert.ok(!/chrome\.storage\.local/.test(read('lib/mcp/approve-page.js')), 'the page reads no storage.local'); +}); + +test("a mod group's ask keeps the worker's name for it, cleaned as the app's", async () => { + const e = env(); + e.a.ask('grant', { ...GRANT, group: 'mod:abcdefghijklmnop', mod: 'Due\u202e titles\n' }); + e.a.ask('grant', { ...GRANT, app: { ...CLAUDE, name: 'Other' }, group: 'grades', mod: 'Ignored' }); + await settle(); + const [m, g] = records(e.data); + assert.equal(m.mod, 'Due titles'); + assert.equal(g.mod, undefined, 'only a mod group carries one'); +}); diff --git a/test/mcp-worker.test.js b/test/mcp-worker.test.js index bd9df6b4f..ff8da1d85 100644 --- a/test/mcp-worker.test.js +++ b/test/mcp-worker.test.js @@ -189,7 +189,7 @@ test('frames in give replies out: initialize, ping and an unknown method, each o const env = boot(); const host = nodeHost(env.w); await connected(env); - assert.deepEqual(host.ports[0].sent[0], { t: 'hello', v: 1, glaze: env.w.manifest.version, instance: env.store.local.mcpInstance }); + assert.deepEqual(host.ports[0].sent[0], { t: 'hello', v: 1, glaze: env.w.manifest.version, instance: kept(env.idb) }); const a = agent(host.link(), 1), b = agent(host.link(), 2); const init = await a.ask(INIT(0)); assert.equal(init.result.protocolVersion, '2025-11-25'); @@ -542,3 +542,71 @@ test('an unused Safari snapshot cannot survive off and give a new connection the assert.equal((await a.ask({ jsonrpc: '2.0', id: 4, method: 'tools/list' })).error?.code, -32602, 'new connections must establish their own identity'); await config(env, { op: 'set', on: false }, { ...EDITOR, url: base + 'options.html', origin: base.slice(0, -1) }); }); + +// Content scripts run on school pages and can write storage.local, so nothing there may pick the instance the helper names +// its run file for, or the mod name the approval window shows. +const PLANTED = 'f'.repeat(20), LEGACY = '0123456789abcdef0123'; +const kept = (idb) => idb.dbs.get('glaze-mcp-settings')?.get('settings')?.get('mcpInstance'); +const hello = (host) => host.ports.at(-1).sent.find((f) => f.t === 'hello')?.instance; + +test("the worker's instance is its own: a content script's storage.local write changes it in neither this worker nor the next", async () => { + const store = store0(), idb = fakeIDB().indexedDB; + const first = boot({ store, idb }), h1 = nodeHost(first.w); + await connected(first); + const mine = hello(h1); + assert.match(mine, /^[0-9a-f]{20}$/); + assert.equal(kept(idb), mine, "kept in the worker's database"); + assert.equal(store.local.mcpInstance, undefined, 'never in storage.local'); + await first.w.context.chrome.storage.local.set({ mcpInstance: PLANTED }); + await config(first, { op: 'set', on: false }); + await config(first, { op: 'set', on: true }); + await connected(first); + assert.equal(hello(h1), mine, 'a reconnect in the same worker'); + delete store.session.mcpLink; + const second = boot({ store, idb }), h2 = nodeHost(second.w); + await connected(second); + assert.equal(hello(h2), mine, 'the next worker'); + await config(second, { op: 'set', on: false }); +}); + +test("a 4.0.0 profile's instance moves once into the worker's database while an app is paired, and is made afresh otherwise", async () => { + const idb = fakeIDB().indexedDB; + idb.dbs.set(G.DB, new Map([[G.STORE, new Map([[MOD, { id: MOD, name: 'Claude', exe: '', verified: false, at: 1, used: 1, grants: {}, declined: {} }]])]])); + const store = store0({ local: { mcpInstance: LEGACY } }); + const env = boot({ store, idb }), host = nodeHost(env.w); + await connected(env); + assert.equal(hello(host), LEGACY); + assert.equal(kept(idb), LEGACY); + assert.equal(store.local.mcpInstance, undefined, 'moved, not copied'); + store.local.mcpInstance = PLANTED; + delete store.session.mcpLink; + const next = boot({ store, idb }), h2 = nodeHost(next.w); + await connected(next); + assert.equal(hello(h2), LEGACY, 'moved once: a later write is not taken'); + await config(next, { op: 'set', on: false }); + + // No app ever used this profile's instance: one a page may have written is not taken. + const fresh = boot({ store: store0({ local: { mcpInstance: PLANTED } }) }), h3 = nodeHost(fresh.w); + await connected(fresh); + assert.match(hello(h3), /^[0-9a-f]{20}$/); + assert.notEqual(hello(h3), PLANTED); + assert.equal(fresh.store.local.mcpInstance, undefined); + await config(fresh, { op: 'set', on: false }); +}); + +test("a mod group's ask carries the name from the worker's checked record, which a later storage.local write cannot change", async () => { + const env = boot({ store: store0({ local: await school({ mods: [MOD], [`mod:${MOD}`]: modRec(true) }) }) }); + const host = nodeHost(env.w); + await connected(env); + const a = agent(host.link(), 1); + await a.ask(INIT(0)); + a.send(INITIALIZED); + a.send(CALL(1, `mod_${MOD}_titles`)); + const rec = await waiting(env, 'pair'); + assert.equal(rec.group, `mod:${MOD}`); + assert.equal(rec.mod, 'Due titles'); + await env.w.context.chrome.storage.local.set({ [`mod:${MOD}`]: { manifest: { name: 'Planted' } } }); + await pause(50); + assert.equal(env.store.session[`mcpAsk:${rec.id}`].mod, 'Due titles'); + await config(env, { op: 'set', on: false }); +}); From 83d2024903ed6e773408fca95b698b2c489b2222 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:34:38 -0400 Subject: [PATCH 029/907] Check immediate feedback for queued imports --- scripts/documents-check.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 03c4adaa4..ab3d6fe3b 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -1525,6 +1525,26 @@ const PROBE = ({ lang }) => { await im.evaluate(() => __retryCopy()); await until(() => im.evaluate(() => !document.querySelector('.gd-pending') && [...document.querySelectorAll('.gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Interrupted')), 'interrupted file retried'); check(await im.evaluate(() => document.activeElement?.id === 'gd-q'), 'Retry preserves focus moved elsewhere while the import was running'); + + // All twenty accepted files get a named, readable progress card immediately, including those still in the queue. + await im.evaluate((bytes) => { + const k = GZ_DOC_KINDS.get('pdf'); + GZ_DOC_KINDS.register('pdf', { ...k, importFile: async (f, opts) => { + GZ_DOC_KINDS.register('pdf', k); + opts.progress({ stage: 'read' }); + await new Promise((r) => { globalThis.__batchRead = r; }); + return k.importFile(f, opts); + } }); + const dt = new DataTransfer(); + for (let i = 1; i <= 20; i++) dt.items.add(new File([new Uint8Array(bytes)], `Batch ${i}.pdf`, { type: 'application/pdf' })); + document.dispatchEvent(new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: dt })); + }, Array.from(require('./pdf-fixtures.js').FIXTURES.three())); + await until(() => im.evaluate(() => typeof __batchRead === 'function'), 'batch reading'); + const queued = await im.evaluate(() => [...document.querySelectorAll('.gd-pending')].map((el) => el.querySelector('.gd-meta').textContent)); + check(queued.length === 20 && queued.every((s) => s === 'Reading…'), `twenty dropped files each show a stage immediately (${JSON.stringify(queued)})`); + await im.evaluate(() => __batchRead()); + await until(() => im.evaluate(() => !document.querySelector('.gd-pending')), 'batch imported', 60000); + check(await im.evaluate(() => [...document.querySelectorAll('.gd-card .gd-title')].filter((el) => /^Batch \d+$/.test(el.textContent)).length === 20), 'all twenty queued imports become their own documents'); await shot(im, 'import-done-1280-en.png'); await im.close(); } From f1b744672c48ad78b1ff03bbe24f4f64c58a9196 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:36:09 -0400 Subject: [PATCH 030/907] Show import stages in the slide viewer --- slides-kind.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/slides-kind.js b/slides-kind.js index 70cd83b2a..ef175d587 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -306,8 +306,9 @@ list.append(h('li', { role: 'presentation' }, b)); return b; }); + const state = h('span', { class: 'gp-state', role: 'status', text: deck ? t('{n} slides', { n }) : '' }); const view = h('div', { class: 'gp gp-standalone gs-view' }, - h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), h('span', { class: 'gp-state', text: deck ? t('{n} slides', { n }) : '' })), + h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), state), h('div', { class: 'gs-tool' }, h('div', { class: 'gs-group' }, prevB, atEl, nextB), h('div', { class: 'gs-group' }, outB, zoomB, inB), h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), moreB), h('div', { class: 'gs-work' }, railEl, h('div', { class: 'gs-main' }, canvasEl, notesEl))); @@ -545,7 +546,7 @@ draw(); globalThis.GZ_PAPERS_MOTION?.shown('paper'); // What an assignment's submit sheet sends (writer-submit.js): the deck as it was brought. - return { pages: () => null, say: () => {}, ready: () => !!deck, save: async () => true, + return { pages: () => null, say: (text) => { if (state.textContent !== text) state.textContent = text || ''; }, ready: () => !!deck, save: async () => true, dispose: () => { life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); for (const u of media.values()) { URL.revokeObjectURL(u); made.delete(u); } From d0c7fde8c1183a559e4451f245810161acb1a4df Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:37:58 -0400 Subject: [PATCH 031/907] Check failed Retry focus without fixed waits --- scripts/documents-check.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index ab3d6fe3b..21548ec93 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -130,7 +130,7 @@ const PROBE = ({ lang }) => { const card = (key) => `.gd-card[data-key="${key}"]`; const said = (pg) => pg.evaluate(() => document.getElementById('gd-count').textContent); const focused = (pg) => pg.evaluate(() => document.activeElement?.closest('.gd-card')?.dataset.key || document.activeElement?.id || document.activeElement?.className || ''); - const shot = async (pg, name) => { await pause(200); const f = path.join(shots, name); await pg.screenshot({ path: f }); saved.push(f); return f; }; + const shot = async (pg, name) => { const f = path.join(shots, name); await pg.screenshot({ path: f, animations: 'disabled' }); saved.push(f); return f; }; const saved = []; const total = seeds.length; @@ -1411,10 +1411,8 @@ const PROBE = ({ lang }) => { const failed = await until(() => im.evaluate(() => { const el = [...document.querySelectorAll('#gd-docs > .gd-pending')].find((x) => x.querySelector('.gd-title').textContent === 'Lab manual.pdf'); return el?.hasAttribute('data-failed') ? { meta: el.querySelector('.gd-meta').textContent, acts: [...el.querySelectorAll('.gd-acts:not([hidden]) button')].map((b) => b.textContent), bar: !el.querySelector('.gd-progress').hidden, said: document.getElementById('gd-said').textContent } : null; }), 'the refused import\'s card'); check(failed.meta === room && failed.acts.join() === 'Retry,Remove' && !failed.bar && failed.said === room, `a failed import says why on its card, with Retry and Remove (${JSON.stringify(failed)})`); - await pause(300); - await im.screenshot({ path: path.join(out, 'import-failed-1280-en.png') }); + await im.screenshot({ path: path.join(out, 'import-failed-1280-en.png'), animations: 'disabled' }); check(!((await local('writerPapers')) || []).some((r) => r.title === 'Lab manual'), 'and keeps no document'); - await pause(300); await im.click('.gd-pending [data-act="retry"]'); const retried = await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].find((c) => c.querySelector('.gd-title').textContent === 'Lab manual')?.dataset.key), 'the retried import\'s card', 30000); check(!!retried && !(await im.evaluate(() => document.querySelector('#gd-docs > .gd-pending'))) && await until(() => im.evaluate(() => document.getElementById('gd-said').textContent === ''), 'Retry clears the reason', 3000), 'Retry imports it: the document\'s card in its place, the reason gone'); @@ -1426,7 +1424,9 @@ const PROBE = ({ lang }) => { await im.setInputFiles('#gd-file', brokenPath); const broken = await until(() => im.evaluate(() => { const el = document.querySelector('#gd-docs > .gd-pending[data-failed]'); return el && { name: el.querySelector('.gd-title').textContent, meta: el.querySelector('.gd-meta').textContent }; }), 'the broken PDF\'s card'); check(broken.name === 'Scan.pdf' && broken.meta === 'That file could not be read.', `a file that is no PDF says so on its card (${JSON.stringify(broken)})`); - await pause(300); + await im.click('.gd-pending [data-act="retry"]'); + await until(() => im.evaluate(() => document.querySelector('.gd-pending[data-failed] [data-act="retry"]') === document.activeElement), 'failed Retry keeps focus'); + check(await im.evaluate(() => document.querySelector('.gd-pending .gd-meta').textContent === 'That file could not be read.'), 'a repeated failure keeps focus on Retry and its reason on the card'); await im.focus('.gd-pending [data-act="remove"]'); await im.keyboard.press('Enter'); await until(() => im.evaluate(() => !document.querySelector('#gd-docs > .gd-pending')), 'Remove'); @@ -1442,8 +1442,7 @@ const PROBE = ({ lang }) => { fs.writeFileSync(heldPath, require('./pdf-fixtures.js').large({ count: 10, size: 6e6 })); await im.setInputFiles('#gd-file', heldPath); await until(() => im.evaluate(() => /^Copying… [\d.]+ \/ [\d.]+ MB$/.test(document.querySelector('#gd-docs > .gd-pending .gd-meta')?.textContent || '')), 'the held import halfway'); - await pause(400); - await im.screenshot({ path: path.join(out, 'import-copying-1280-en.png') }); + await im.screenshot({ path: path.join(out, 'import-copying-1280-en.png'), animations: 'disabled' }); await im.evaluate(() => globalThis.__gate()); await until(() => im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card')].some((c) => c.querySelector('.gd-title').textContent === 'Week 4 reading') && !document.querySelector('#gd-docs > .gd-pending')), 'the held import done', 60000); From 94ec71c0a199377f895cfab9f7a87bab243fc02f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:38:18 -0400 Subject: [PATCH 032/907] Show an initial stage on every queued import --- papers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papers.js b/papers.js index 55d6e1007..f49093db1 100644 --- a/papers.js +++ b/papers.js @@ -682,7 +682,7 @@ if (w) { stage = p.stage; count.textContent = t(...w); } showJob(j); }, STEPS[j.kind] || ['read', 'prepare', 'save']); - showJob(j); + report({ stage: 'read', total: j.file.size }); try { // An import that kept the file with parts left out says so (sheet-kind.js: { key, notice }). const got = await DK.get(j.kind).importFile(j.file, { progress: report }); @@ -721,7 +721,7 @@ if (r.failed || r.notice) say(t(r.failed || r.notice)); }; const job = (f, id) => { - const j = { id: ++jobs, file: f, kind: id, p: null, failed: '', key: null, running: false }; + const j = { id: ++jobs, file: f, kind: id, p: { stage: 'read', fraction: 0 }, failed: '', key: null, running: false }; const fill = h('span', { class: 'gd-fill' }); const bar = h('div', { class: 'gd-progress', role: 'progressbar', 'aria-label': f.name, 'aria-valuemin': '0', 'aria-valuemax': '100', 'aria-valuenow': '0' }, fill); const meta = h('p', { class: 'gd-meta' }); From 79ee8e3c5ac287f3a3b56357c4d71bcfb6e0b9fa Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:38:46 -0400 Subject: [PATCH 033/907] Keep the agent instance and the approval window's mod name in state the worker owns The instance that names the helper's run file moves from storage.local to the worker's glaze-mcp-settings database (a 4.0.0 value moves once, only while an app is paired). The approval window shows the mod name the worker put in the ask from the record it checked, instead of reading storage.local, which content scripts can write. --- PRIVACY.md | 4 +-- lib/agent-link.js | 4 ++- lib/background.js | 35 +++++++++++++++---- lib/mcp/approve-page.js | 14 ++++---- lib/mcp/approve.js | 9 ++--- lib/mcp/calls.js | 10 +++--- .../Resources/lib/agent-link.js | 4 ++- .../Resources/lib/background.js | 35 +++++++++++++++---- .../Resources/lib/mcp/approve-page.js | 14 ++++---- .../Resources/lib/mcp/approve.js | 9 ++--- .../Resources/lib/mcp/calls.js | 10 +++--- scripts/agent-link-check.js | 6 +++- 12 files changed, 103 insertions(+), 51 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 211b7969e..402d4384a 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -145,8 +145,8 @@ Kept on this device: - assignment reads, `mcpRubric:///` with their index `mcpRubrics` in `chrome.storage.local`: the 100 used last, with no time-based expiry, so an app can read them, dated, with no tab open; -- a random id for this browser profile, `mcpInstance` in `chrome.storage.local`, so an app can tell browsers apart. It - stays when agent access is turned off; +- a random id for this browser profile, `mcpInstance` in the worker's IndexedDB `glaze-mcp-settings`, so an app can tell + browsers apart. It stays when agent access is turned off; - for the browser session only, in `chrome.storage.session`: the helper's state (`mcpLink`); approval records (`mcpAsk:`) with app and tool details, the group or action summary, times and answer; the approval window's id (`mcpAskWindow`); on Safari each connection's protocol state (`mcpConn:`); and the salt of Writer documents' keys diff --git a/lib/agent-link.js b/lib/agent-link.js index b90591f7f..9cb347049 100644 --- a/lib/agent-link.js +++ b/lib/agent-link.js @@ -7,6 +7,8 @@ // (local, session), browser ('chrome' | 'firefox' | 'safari'); timers, now() and random(n) default to the real ones. // attach({ open(c, peer), line(c, text), close(c, why), state(status) }). send(c, text) and close(c, why) → whether it went; // the link closes a connection itself as 'gone' (the helper left), 'stopped' or 'too-long' (a line over 64 MiB). +// storage.local here is the worker's: lib/background.js answers mcp and mcpInstance from its own IndexedDB, never from +// chrome.storage.local, which content scripts can write. // ensure() brings the link to what storage.local mcp says (as lib/mcp/grants.js reads it) and runs on every trigger: // worker start, onStartup, onInstalled, an mcp change either way, permissions.onAdded. // stop() says bye, and disconnects the port itself unless the helper ends it within a second, as permissions.remove leaves @@ -330,7 +332,7 @@ var GZ_AGENT_LINK = globalThis.GZ_AGENT_LINK || (() => { return { attach, send, close, ensure, stop, status }; }; - return { HOST, SAFARI, KEY, INSTANCE, STATES, make }; + return { HOST, SAFARI, KEY, INSTANCE, SETTING, ID, STATES, make }; })(); GZ_AGENT_LINK; if (typeof module !== 'undefined') module.exports = GZ_AGENT_LINK; diff --git a/lib/background.js b/lib/background.js index 36e09058e..755b32f2a 100644 --- a/lib/background.js +++ b/lib/background.js @@ -2577,22 +2577,38 @@ const mcpFiles = () => importScripts('ai/request.js', 'ai/schema.js', 'ai/tools. globalThis.addEventListener?.('install', () => { agentFiles(); mcpFiles(); }); // The local setting is a UI mirror, writable by content scripts. Only an options message may persist consent in this // extension-origin database. A forged local on cannot reconnect a previously paired app after the owner switched off. +// The profile's instance (lib/agent-link.js INSTANCE), which names the helper's run file, is kept here too. let mcpConsentDB = null; -const mcpConsent = async (on) => { +// key's value, or with value given, put → what was read or put. +const mcpSetting = async (key, ...value) => { mcpConsentDB ||= new Promise((resolve, reject) => { const r = indexedDB.open('glaze-mcp-settings', 1); r.onupgradeneeded = () => r.result.createObjectStore('settings'); r.onsuccess = () => { r.result.onversionchange = () => { r.result.close(); mcpConsentDB = null; }; resolve(r.result); }; r.onerror = () => reject(r.error); }).catch((e) => { mcpConsentDB = null; throw e; }); - const tx = (await mcpConsentDB).transaction('settings', on === undefined ? 'readonly' : 'readwrite'); - let value = false; + const tx = (await mcpConsentDB).transaction('settings', value.length ? 'readwrite' : 'readonly'); + let got = value[0]; const store = tx.objectStore('settings'); - if (on === undefined) { const r = store.get('on'); r.onsuccess = () => { value = r.result === true; }; } - else { store.put(on === true, 'on'); value = on === true; } - return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(value); tx.onerror = tx.onabort = () => reject(tx.error); }); + if (value.length) store.put(value[0], key); + else { const r = store.get(key); r.onsuccess = () => { got = r.result; }; } + return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(got); tx.onerror = tx.onabort = () => reject(tx.error); }); }; +const mcpConsent = async (on) => (on === undefined ? (await mcpSetting('on')) === true : mcpSetting('on', on === true)); const mcpOn = async () => (await chrome.storage.local.get('mcp')).mcp?.on === true && await mcpConsent().catch(() => false); +// 4.0.0 kept the instance in storage.local. It moves here once, and only while an app is paired (in the worker's own +// glaze-mcp): with none, no app has used it, and a page may have written it. +const mcpInstance = async () => { + const id = await mcpSetting(GZ_AGENT_LINK.INSTANCE); + if (GZ_AGENT_LINK.ID.test(id)) return id; + const legacy = (await chrome.storage.local.get(GZ_AGENT_LINK.INSTANCE))[GZ_AGENT_LINK.INSTANCE]; + if (legacy === undefined) return undefined; + if (typeof GZ_MCP_GRANTS === 'undefined') grantFiles(); + const used = GZ_AGENT_LINK.ID.test(legacy) && (await mcpGrants().list().catch(() => [])).length > 0; + if (used) await mcpSetting(GZ_AGENT_LINK.INSTANCE, legacy); + await chrome.storage.local.remove(GZ_AGENT_LINK.INSTANCE); + return used ? legacy : undefined; +}; let mcpGrantStore = null; const mcpGrants = () => (mcpGrantStore ||= GZ_MCP_GRANTS.open()); // Only Safari's helper, the Glaze app, outlives this worker and announces its connections to the next one, so only there @@ -2736,7 +2752,12 @@ const mcpEnsure = async () => { if (!mcpLink) { if (typeof GZ_AGENT_LINK === 'undefined') agentFiles(); mcpLink = (() => { - const local = { get: async (keys) => ({ ...await chrome.storage.local.get(keys), mcp: { on: await mcpOn() && await glazeOn() } }), set: (v) => chrome.storage.local.set(v) }; + // The link's setting and instance, from the worker's own state only. + const I = GZ_AGENT_LINK.INSTANCE; + const local = { + get: async () => ({ [GZ_AGENT_LINK.SETTING]: { on: await mcpOn() && await glazeOn() }, [I]: await mcpInstance() }), + set: async (v) => { if (GZ_AGENT_LINK.ID.test(v?.[I])) await mcpSetting(I, v[I]); }, + }; const link = GZ_AGENT_LINK.make({ runtime: chrome.runtime, permissions: chrome.permissions, storage: { local, session: chrome.storage.session }, browser: MCP_BROWSER }); link.attach({ open: mcpOpen, line: mcpLine, close: mcpClose }); return link; diff --git a/lib/mcp/approve-page.js b/lib/mcp/approve-page.js index 3d6b95697..8f2295e84 100644 --- a/lib/mcp/approve-page.js +++ b/lib/mcp/approve-page.js @@ -1,7 +1,7 @@ // The approval window's page (lib/mcp/approve.js opens it as one popup for every ask): the oldest waiting ask, as Glaze // wrote it to storage.session, live through storage.onChanged. The app's name and program are the app's own claims and say -// so; every other word is Glaze's, and an action's is the sentence Glaze's summarize built. A mod's name comes from its -// installed record. Allow and Don't allow send glaze:mcp-ask with the shown ask's id, so a decision is only ever for what +// so; every other word is Glaze's, and an action's is the sentence Glaze's summarize built. A mod's name is the one the +// worker put in the ask from the record it checked, never storage.local, which content scripts can write. Allow and Don't allow send glaze:mcp-ask with the shown ask's id, so a decision is only ever for what // was on screen. Focus starts on Don't allow; a new ask or regained focus waits ARM ms, and held keys never answer. (() => { const PREFIX = 'mcpAsk:', ARM = 800, MOD = /^mod:([a-z2-7]{16})$/; @@ -23,10 +23,9 @@ assignments: t('{app} wants to see your assignments and rubrics', { app }), }[group] ?? t('{app} wants to use Glaze', { app })); const ENDED = { cancelled: () => t('Cancelled'), timeout: () => t('Timed out'), interrupted: () => t('Glaze restarted before you answered.') }; - const modName = async (group) => { - const id = MOD.exec(str(group))?.[1]; - if (!id) return ''; - try { return str((await chrome.storage.local.get(`mod:${id}`))[`mod:${id}`]?.manifest?.name).replace(/[\p{Cc}\p{Cf}]+/gu, ' ').trim().slice(0, 60) || id; } catch { return id; } + const modName = (rec) => { + const id = MOD.exec(str(rec.group))?.[1]; + return id ? str(rec.mod).replace(/[\p{Cc}\p{Cf}]+/gu, ' ').trim().slice(0, 60) || id : ''; }; let shown = null, armed = false, sent = false, turn = 0, timer = 0; @@ -67,9 +66,8 @@ try { all = await chrome.storage.session.get(null); } catch { return; } const recs = Object.entries(isObj(all) ? all : {}).filter(([k, v]) => k.startsWith(PREFIX) && isObj(v)).map(([, v]) => v); const next = recs.filter((r) => r.state === 'waiting').sort((a, b) => a.at - b.at)[0]; - const mod = next ? await modName(next.group) : ''; if (mine !== turn) return; - if (next) fill(next, mod); + if (next) fill(next, modName(next)); else end(recs.find((r) => r.id === shown?.id)); }; diff --git a/lib/mcp/approve.js b/lib/mcp/approve.js index b67b69d57..aeb690556 100644 --- a/lib/mcp/approve.js +++ b/lib/mcp/approve.js @@ -4,7 +4,8 @@ // // create({ windows, tabs, storage: { session }, runtime, now, timers, url }) → { ask, decide, sweep, closed } // ask(kind, view, { signal, progress }) → 'yes' | 'no' | 'timeout' | 'cancelled' | 'interrupted' | 'busy' -// kind 'pair' or 'grant': view { app: { id?, name, exe }, tool: { name, title }, group }; 'action': { app, tool, +// kind 'pair' or 'grant': view { app: { id?, name, exe }, tool: { name, title }, group, mod? (a mod group's name, from +// the worker's checked record) }; 'action': { app, tool, // summary } (Glaze's summarize, from trusted data). 'interrupted': no window could show it; 'busy': LIMIT.app asks of // this app (by app.id, else its name and program) or LIMIT.all in all already wait. progress(n, undefined, SAYS) every // TICK, n the seconds that callback has waited across its asks, so a call that asks twice keeps rising. @@ -12,7 +13,7 @@ // ask once. closed(windowId): the student closed the window, which is Don't allow for every ask it holds. // sweep(): the last worker's asks; ask waits for it. // One popup (SIZE, focused) holds every waiting ask and shows the oldest; Glaze closes it when none waits. Each ask is -// storage.session mcpAsk: = { id, kind, app: { name, exe }, tool: { name, title }, group?, summary?, at, until, state }, +// storage.session mcpAsk: = { id, kind, app: { name, exe }, tool: { name, title }, group?, mod?, summary?, at, until, state }, // which the page (lib/mcp/approve.html) renders from. state: waiting until `until` (TIMEOUT), then yes | no | timeout | // cancelled | interrupted; consent records do not assert execution. sweep: waiting → interrupted, running → // interrupted with mayHaveRun, records made over KEEP ago that no longer wait are dropped. mcpAskWindow: { id, tab }. @@ -23,7 +24,7 @@ var GZ_MCP_APPROVE = globalThis.GZ_MCP_APPROVE || (() => { const TIMEOUT = 600e3, TICK = 15e3, KEEP = 3600e3; const GROUPS = ['courses', 'grades', 'writer', 'assignments'], MOD = /^mod:[a-z2-7]{16}$/; // app, all: waiting asks; the rest: characters kept of what the window shows. - const LIMIT = { app: 5, all: 20, name: 100, exe: 1024, tool: 64, title: 120, group: 64, summary: 2000 }; + const LIMIT = { app: 5, all: 20, name: 100, exe: 1024, tool: 64, title: 120, group: 64, mod: 60, summary: 2000 }; const SIZE = { width: 440, height: 340 }; const SAYS = 'Waiting for the student in Glaze'; const isObj = (o) => !!o && typeof o === 'object' && !Array.isArray(o); @@ -101,7 +102,7 @@ var GZ_MCP_APPROVE = globalThis.GZ_MCP_APPROVE || (() => { // Strictly rising, so the queue's order is the order asked. const at = last = Math.max(now(), last + 1); const rec = { id: hex(globalThis.crypto.getRandomValues(new Uint8Array(12))), kind, app, tool, - ...(kind === 'action' ? { summary } : { group }), + ...(kind === 'action' ? { summary } : { group, ...(MOD.test(group) && text(view.mod, LIMIT.mod) && { mod: text(view.mod, LIMIT.mod) }) }), at, until: at + TIMEOUT, state: 'waiting' }; return new Promise((resolve) => { let timeout = 0, tick = 0; diff --git a/lib/mcp/calls.js b/lib/mcp/calls.js index c2ea05fe5..2907ea0ae 100644 --- a/lib/mcp/calls.js +++ b/lib/mcp/calls.js @@ -10,7 +10,8 @@ // { signal, tool }) → { ok, source: 'tab' } | null (null: no tab to read), local, keys (a tool's ctx: storage.local and // its keys), now, record(row) (GZ_MCP_GRANTS.record's), turn(f) (the broker's serializer), timers? ({ setTimeout, // clearTimeout }) }) → { call } for lib/mcp/wire.js create. -// A view: { app: { name (as the app reports itself), exe }, tool: { name, title }, group, summary? (an action's: the tool's +// A view: { app: { name (as the app reports itself), exe }, tool: { name, title }, group, mod? (a mod tool's: its mod's name +// in the state the call was listed from), summary? (an action's: the tool's // summarize, from Glaze's own data, never the model's words) }. progress may be given (message) or (n, total, message). // The answer: structuredContent { note, account, source: 'tab' | 'cache', at, ageSeconds, ...the rest of the tool's }, and // the same JSON in content[0].text; past LIMIT.text characters both hold the harness's omission receipt (lib/ai/loop.js @@ -176,10 +177,11 @@ var GZ_MCP_CALLS = globalThis.GZ_MCP_CALLS || (() => { }; // Steps 3 to 10. → [outcome (null: none logged), the call's answer]. - const serve = async (t, stamp, id, c, signal, tell) => { + const serve = async (t, stamp, id, c, signal, tell, s0) => { if (signal.aborted) return answer('cancelled'); const app = { name: cut(str(c.client?.name) || str(c.peer?.name), G().LIMIT.name), exe: str(c.peer?.exe) || null }; - const view = { app, tool: { name: t.name, title: t.title || t.name }, group: t.group }; + const mod = isMod(t) ? str((Array.isArray(s0?.mods) ? s0.mods : []).find((r) => `mod:${r?.id}` === t.owner)?.manifest?.name) : ''; + const view = { app, tool: { name: t.name, title: t.title || t.name }, group: t.group, ...(mod && { mod }) }; // Paired, and the group granted: asked once, a decline remembered. let rec = await grants.get(id); if (!rec) { @@ -270,7 +272,7 @@ var GZ_MCP_CALLS = globalThis.GZ_MCP_CALLS || (() => { let n = 0; const tell = (message) => { if (typeof c.progress === 'function') c.progress(++n, undefined, typeof message === 'string' ? message : undefined); }; let outcome, result; - try { [outcome, result] = await serve(t, stamp, id, c, signal, tell); } catch { [outcome, result] = signal.aborted ? answer('cancelled') : ['error', fail("Glaze couldn't run this tool.")]; } + try { [outcome, result] = await serve(t, stamp, id, c, signal, tell, s0); } catch { [outcome, result] = signal.aborted ? answer('cancelled') : ['error', fail("Glaze couldn't run this tool.")]; } if (outcome) { try { Promise.resolve(d.record({ at: start, app: id, tool: t.name, group: t.group, kind: t.kind, outcome, ms: Math.max(0, now() - start) })).catch(() => {}); } catch { /* The log never fails a call. */ } } diff --git a/safari/Glaze/Glaze Extension/Resources/lib/agent-link.js b/safari/Glaze/Glaze Extension/Resources/lib/agent-link.js index b90591f7f..9cb347049 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/agent-link.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/agent-link.js @@ -7,6 +7,8 @@ // (local, session), browser ('chrome' | 'firefox' | 'safari'); timers, now() and random(n) default to the real ones. // attach({ open(c, peer), line(c, text), close(c, why), state(status) }). send(c, text) and close(c, why) → whether it went; // the link closes a connection itself as 'gone' (the helper left), 'stopped' or 'too-long' (a line over 64 MiB). +// storage.local here is the worker's: lib/background.js answers mcp and mcpInstance from its own IndexedDB, never from +// chrome.storage.local, which content scripts can write. // ensure() brings the link to what storage.local mcp says (as lib/mcp/grants.js reads it) and runs on every trigger: // worker start, onStartup, onInstalled, an mcp change either way, permissions.onAdded. // stop() says bye, and disconnects the port itself unless the helper ends it within a second, as permissions.remove leaves @@ -330,7 +332,7 @@ var GZ_AGENT_LINK = globalThis.GZ_AGENT_LINK || (() => { return { attach, send, close, ensure, stop, status }; }; - return { HOST, SAFARI, KEY, INSTANCE, STATES, make }; + return { HOST, SAFARI, KEY, INSTANCE, SETTING, ID, STATES, make }; })(); GZ_AGENT_LINK; if (typeof module !== 'undefined') module.exports = GZ_AGENT_LINK; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 36e09058e..755b32f2a 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -2577,22 +2577,38 @@ const mcpFiles = () => importScripts('ai/request.js', 'ai/schema.js', 'ai/tools. globalThis.addEventListener?.('install', () => { agentFiles(); mcpFiles(); }); // The local setting is a UI mirror, writable by content scripts. Only an options message may persist consent in this // extension-origin database. A forged local on cannot reconnect a previously paired app after the owner switched off. +// The profile's instance (lib/agent-link.js INSTANCE), which names the helper's run file, is kept here too. let mcpConsentDB = null; -const mcpConsent = async (on) => { +// key's value, or with value given, put → what was read or put. +const mcpSetting = async (key, ...value) => { mcpConsentDB ||= new Promise((resolve, reject) => { const r = indexedDB.open('glaze-mcp-settings', 1); r.onupgradeneeded = () => r.result.createObjectStore('settings'); r.onsuccess = () => { r.result.onversionchange = () => { r.result.close(); mcpConsentDB = null; }; resolve(r.result); }; r.onerror = () => reject(r.error); }).catch((e) => { mcpConsentDB = null; throw e; }); - const tx = (await mcpConsentDB).transaction('settings', on === undefined ? 'readonly' : 'readwrite'); - let value = false; + const tx = (await mcpConsentDB).transaction('settings', value.length ? 'readwrite' : 'readonly'); + let got = value[0]; const store = tx.objectStore('settings'); - if (on === undefined) { const r = store.get('on'); r.onsuccess = () => { value = r.result === true; }; } - else { store.put(on === true, 'on'); value = on === true; } - return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(value); tx.onerror = tx.onabort = () => reject(tx.error); }); + if (value.length) store.put(value[0], key); + else { const r = store.get(key); r.onsuccess = () => { got = r.result; }; } + return new Promise((resolve, reject) => { tx.oncomplete = () => resolve(got); tx.onerror = tx.onabort = () => reject(tx.error); }); }; +const mcpConsent = async (on) => (on === undefined ? (await mcpSetting('on')) === true : mcpSetting('on', on === true)); const mcpOn = async () => (await chrome.storage.local.get('mcp')).mcp?.on === true && await mcpConsent().catch(() => false); +// 4.0.0 kept the instance in storage.local. It moves here once, and only while an app is paired (in the worker's own +// glaze-mcp): with none, no app has used it, and a page may have written it. +const mcpInstance = async () => { + const id = await mcpSetting(GZ_AGENT_LINK.INSTANCE); + if (GZ_AGENT_LINK.ID.test(id)) return id; + const legacy = (await chrome.storage.local.get(GZ_AGENT_LINK.INSTANCE))[GZ_AGENT_LINK.INSTANCE]; + if (legacy === undefined) return undefined; + if (typeof GZ_MCP_GRANTS === 'undefined') grantFiles(); + const used = GZ_AGENT_LINK.ID.test(legacy) && (await mcpGrants().list().catch(() => [])).length > 0; + if (used) await mcpSetting(GZ_AGENT_LINK.INSTANCE, legacy); + await chrome.storage.local.remove(GZ_AGENT_LINK.INSTANCE); + return used ? legacy : undefined; +}; let mcpGrantStore = null; const mcpGrants = () => (mcpGrantStore ||= GZ_MCP_GRANTS.open()); // Only Safari's helper, the Glaze app, outlives this worker and announces its connections to the next one, so only there @@ -2736,7 +2752,12 @@ const mcpEnsure = async () => { if (!mcpLink) { if (typeof GZ_AGENT_LINK === 'undefined') agentFiles(); mcpLink = (() => { - const local = { get: async (keys) => ({ ...await chrome.storage.local.get(keys), mcp: { on: await mcpOn() && await glazeOn() } }), set: (v) => chrome.storage.local.set(v) }; + // The link's setting and instance, from the worker's own state only. + const I = GZ_AGENT_LINK.INSTANCE; + const local = { + get: async () => ({ [GZ_AGENT_LINK.SETTING]: { on: await mcpOn() && await glazeOn() }, [I]: await mcpInstance() }), + set: async (v) => { if (GZ_AGENT_LINK.ID.test(v?.[I])) await mcpSetting(I, v[I]); }, + }; const link = GZ_AGENT_LINK.make({ runtime: chrome.runtime, permissions: chrome.permissions, storage: { local, session: chrome.storage.session }, browser: MCP_BROWSER }); link.attach({ open: mcpOpen, line: mcpLine, close: mcpClose }); return link; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve-page.js b/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve-page.js index 3d6b95697..8f2295e84 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve-page.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve-page.js @@ -1,7 +1,7 @@ // The approval window's page (lib/mcp/approve.js opens it as one popup for every ask): the oldest waiting ask, as Glaze // wrote it to storage.session, live through storage.onChanged. The app's name and program are the app's own claims and say -// so; every other word is Glaze's, and an action's is the sentence Glaze's summarize built. A mod's name comes from its -// installed record. Allow and Don't allow send glaze:mcp-ask with the shown ask's id, so a decision is only ever for what +// so; every other word is Glaze's, and an action's is the sentence Glaze's summarize built. A mod's name is the one the +// worker put in the ask from the record it checked, never storage.local, which content scripts can write. Allow and Don't allow send glaze:mcp-ask with the shown ask's id, so a decision is only ever for what // was on screen. Focus starts on Don't allow; a new ask or regained focus waits ARM ms, and held keys never answer. (() => { const PREFIX = 'mcpAsk:', ARM = 800, MOD = /^mod:([a-z2-7]{16})$/; @@ -23,10 +23,9 @@ assignments: t('{app} wants to see your assignments and rubrics', { app }), }[group] ?? t('{app} wants to use Glaze', { app })); const ENDED = { cancelled: () => t('Cancelled'), timeout: () => t('Timed out'), interrupted: () => t('Glaze restarted before you answered.') }; - const modName = async (group) => { - const id = MOD.exec(str(group))?.[1]; - if (!id) return ''; - try { return str((await chrome.storage.local.get(`mod:${id}`))[`mod:${id}`]?.manifest?.name).replace(/[\p{Cc}\p{Cf}]+/gu, ' ').trim().slice(0, 60) || id; } catch { return id; } + const modName = (rec) => { + const id = MOD.exec(str(rec.group))?.[1]; + return id ? str(rec.mod).replace(/[\p{Cc}\p{Cf}]+/gu, ' ').trim().slice(0, 60) || id : ''; }; let shown = null, armed = false, sent = false, turn = 0, timer = 0; @@ -67,9 +66,8 @@ try { all = await chrome.storage.session.get(null); } catch { return; } const recs = Object.entries(isObj(all) ? all : {}).filter(([k, v]) => k.startsWith(PREFIX) && isObj(v)).map(([, v]) => v); const next = recs.filter((r) => r.state === 'waiting').sort((a, b) => a.at - b.at)[0]; - const mod = next ? await modName(next.group) : ''; if (mine !== turn) return; - if (next) fill(next, mod); + if (next) fill(next, modName(next)); else end(recs.find((r) => r.id === shown?.id)); }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve.js b/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve.js index b67b69d57..aeb690556 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/mcp/approve.js @@ -4,7 +4,8 @@ // // create({ windows, tabs, storage: { session }, runtime, now, timers, url }) → { ask, decide, sweep, closed } // ask(kind, view, { signal, progress }) → 'yes' | 'no' | 'timeout' | 'cancelled' | 'interrupted' | 'busy' -// kind 'pair' or 'grant': view { app: { id?, name, exe }, tool: { name, title }, group }; 'action': { app, tool, +// kind 'pair' or 'grant': view { app: { id?, name, exe }, tool: { name, title }, group, mod? (a mod group's name, from +// the worker's checked record) }; 'action': { app, tool, // summary } (Glaze's summarize, from trusted data). 'interrupted': no window could show it; 'busy': LIMIT.app asks of // this app (by app.id, else its name and program) or LIMIT.all in all already wait. progress(n, undefined, SAYS) every // TICK, n the seconds that callback has waited across its asks, so a call that asks twice keeps rising. @@ -12,7 +13,7 @@ // ask once. closed(windowId): the student closed the window, which is Don't allow for every ask it holds. // sweep(): the last worker's asks; ask waits for it. // One popup (SIZE, focused) holds every waiting ask and shows the oldest; Glaze closes it when none waits. Each ask is -// storage.session mcpAsk: = { id, kind, app: { name, exe }, tool: { name, title }, group?, summary?, at, until, state }, +// storage.session mcpAsk: = { id, kind, app: { name, exe }, tool: { name, title }, group?, mod?, summary?, at, until, state }, // which the page (lib/mcp/approve.html) renders from. state: waiting until `until` (TIMEOUT), then yes | no | timeout | // cancelled | interrupted; consent records do not assert execution. sweep: waiting → interrupted, running → // interrupted with mayHaveRun, records made over KEEP ago that no longer wait are dropped. mcpAskWindow: { id, tab }. @@ -23,7 +24,7 @@ var GZ_MCP_APPROVE = globalThis.GZ_MCP_APPROVE || (() => { const TIMEOUT = 600e3, TICK = 15e3, KEEP = 3600e3; const GROUPS = ['courses', 'grades', 'writer', 'assignments'], MOD = /^mod:[a-z2-7]{16}$/; // app, all: waiting asks; the rest: characters kept of what the window shows. - const LIMIT = { app: 5, all: 20, name: 100, exe: 1024, tool: 64, title: 120, group: 64, summary: 2000 }; + const LIMIT = { app: 5, all: 20, name: 100, exe: 1024, tool: 64, title: 120, group: 64, mod: 60, summary: 2000 }; const SIZE = { width: 440, height: 340 }; const SAYS = 'Waiting for the student in Glaze'; const isObj = (o) => !!o && typeof o === 'object' && !Array.isArray(o); @@ -101,7 +102,7 @@ var GZ_MCP_APPROVE = globalThis.GZ_MCP_APPROVE || (() => { // Strictly rising, so the queue's order is the order asked. const at = last = Math.max(now(), last + 1); const rec = { id: hex(globalThis.crypto.getRandomValues(new Uint8Array(12))), kind, app, tool, - ...(kind === 'action' ? { summary } : { group }), + ...(kind === 'action' ? { summary } : { group, ...(MOD.test(group) && text(view.mod, LIMIT.mod) && { mod: text(view.mod, LIMIT.mod) }) }), at, until: at + TIMEOUT, state: 'waiting' }; return new Promise((resolve) => { let timeout = 0, tick = 0; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/mcp/calls.js b/safari/Glaze/Glaze Extension/Resources/lib/mcp/calls.js index c2ea05fe5..2907ea0ae 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/mcp/calls.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/mcp/calls.js @@ -10,7 +10,8 @@ // { signal, tool }) → { ok, source: 'tab' } | null (null: no tab to read), local, keys (a tool's ctx: storage.local and // its keys), now, record(row) (GZ_MCP_GRANTS.record's), turn(f) (the broker's serializer), timers? ({ setTimeout, // clearTimeout }) }) → { call } for lib/mcp/wire.js create. -// A view: { app: { name (as the app reports itself), exe }, tool: { name, title }, group, summary? (an action's: the tool's +// A view: { app: { name (as the app reports itself), exe }, tool: { name, title }, group, mod? (a mod tool's: its mod's name +// in the state the call was listed from), summary? (an action's: the tool's // summarize, from Glaze's own data, never the model's words) }. progress may be given (message) or (n, total, message). // The answer: structuredContent { note, account, source: 'tab' | 'cache', at, ageSeconds, ...the rest of the tool's }, and // the same JSON in content[0].text; past LIMIT.text characters both hold the harness's omission receipt (lib/ai/loop.js @@ -176,10 +177,11 @@ var GZ_MCP_CALLS = globalThis.GZ_MCP_CALLS || (() => { }; // Steps 3 to 10. → [outcome (null: none logged), the call's answer]. - const serve = async (t, stamp, id, c, signal, tell) => { + const serve = async (t, stamp, id, c, signal, tell, s0) => { if (signal.aborted) return answer('cancelled'); const app = { name: cut(str(c.client?.name) || str(c.peer?.name), G().LIMIT.name), exe: str(c.peer?.exe) || null }; - const view = { app, tool: { name: t.name, title: t.title || t.name }, group: t.group }; + const mod = isMod(t) ? str((Array.isArray(s0?.mods) ? s0.mods : []).find((r) => `mod:${r?.id}` === t.owner)?.manifest?.name) : ''; + const view = { app, tool: { name: t.name, title: t.title || t.name }, group: t.group, ...(mod && { mod }) }; // Paired, and the group granted: asked once, a decline remembered. let rec = await grants.get(id); if (!rec) { @@ -270,7 +272,7 @@ var GZ_MCP_CALLS = globalThis.GZ_MCP_CALLS || (() => { let n = 0; const tell = (message) => { if (typeof c.progress === 'function') c.progress(++n, undefined, typeof message === 'string' ? message : undefined); }; let outcome, result; - try { [outcome, result] = await serve(t, stamp, id, c, signal, tell); } catch { [outcome, result] = signal.aborted ? answer('cancelled') : ['error', fail("Glaze couldn't run this tool.")]; } + try { [outcome, result] = await serve(t, stamp, id, c, signal, tell, s0); } catch { [outcome, result] = signal.aborted ? answer('cancelled') : ['error', fail("Glaze couldn't run this tool.")]; } if (outcome) { try { Promise.resolve(d.record({ at: start, app: id, tool: t.name, group: t.group, kind: t.kind, outcome, ms: Math.max(0, now() - start) })).catch(() => {}); } catch { /* The log never fails a call. */ } } diff --git a/scripts/agent-link-check.js b/scripts/agent-link-check.js index 78d818cd7..f45f6a62c 100644 --- a/scripts/agent-link-check.js +++ b/scripts/agent-link-check.js @@ -407,7 +407,11 @@ async function onAndAgents() { ph.host = { rec: host && { ...host.rec, token: '…' }, argv, run: runFolder(P1.home) }; ok('2', 'the link is connected to the helper setup installed, which Chromium started', up?.host === VERSION && up.browser === 'chromium' && hosts(P1.home).length === 1 && !!argv && same(argv.slice(1), [L.parse(R.phases.setup.launcher)?.script, 'host', '--browser=chromium', IDS.origin(E1.id)]), { up, ...ph.host }); - const instance = ph.instance = (await opt.evaluate(() => chrome.storage.local.get('mcpInstance'))).mcpInstance; + const instance = ph.instance = await opt.evaluate(() => new Promise((resolve, reject) => { + const r = indexedDB.open('glaze-mcp-settings', 1); + r.onerror = () => reject(r.error); + r.onsuccess = () => { const g = r.result.transaction('settings').objectStore('settings').get('mcpInstance'); g.onsuccess = () => { r.result.close(); resolve(g.result); }; g.onerror = () => reject(g.error); }; + })); ok('2', 'the run file is named for the worker\'s instance', host?.rec.instance === instance && runFolder(P1.home)?.join() === `chromium.${instance}.json,chromium.${instance}.sock`, { instance, run: runFolder(P1.home) }); const doctor = (() => { try { return execFileSync(process.execPath, [BIN, 'doctor'], { env: envFor(P1.home), encoding: 'utf8' }); } catch (e) { return `${e.stdout}${e.stderr}`; } })(); From 12e3a637b3b4c9fb523d55865b5b51374a50ddbf Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:38:50 -0400 Subject: [PATCH 034/907] Close the announcement pop-up from its footer too, after Open in {site} --- features/popup.js | 14 ++++++++++---- scripts/announce-check.js | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/features/popup.js b/features/popup.js index 486e14dfc..1c0fa7824 100644 --- a/features/popup.js +++ b/features/popup.js @@ -7,7 +7,7 @@ // label?, meta?, // meta: one line under the title (course · author · date) // body, // a Node, an array of them, a string, or an async () => any of those: // // until it settles the pop-up says Loading… (aria-busy) -// actions?, // Nodes for its footer (an "Open in …" link) +// actions?, // Nodes for its footer (an "Open in …" link), then a Close button // source?, // the Element (or () => Element) it came from: it grows out of it, and // // focus goes back to it when it closes // wide?, // a wider pop-up (tables) @@ -20,7 +20,7 @@ // GZ_POPUP.current() // the open handle, or null; GZ_POPUP.kind() the motion it would use // // A real modal (the rest of the page inert, focus inside it from the start), named by its title, closed by Escape, -// its Close button or a click outside it, focus then going back where it came from at once, before the closing motion. +// either Close button or a click outside it, focus then going back where it came from at once, before the closing motion. // It lives in a closed shadow root, so the school's styles and scripts can't reach in, and wears the Look's tokens (the // page's --gz-* custom properties), light and dark. Motion (lib/popup.js motion): the genie out of `source` on Expressive, // a scale and fade on Subtle, a fade or nothing as the "Pop-up animation" setting says, nothing with motion off or reduced. @@ -76,11 +76,14 @@ h2 { margin: 0; font: 600 1.15em/1.3 var(--gz-font-head, var(--gz-font, system-u a { color: var(--gz-accent, #0b6bcb); text-underline-offset: 0.15em; } .wait { margin: 0; color: var(--gz-ink-muted, #5b6876); } .body .files { display: flex; flex-wrap: wrap; gap: 0.35em 0.9em; margin: 1em 0 0; padding: 0.7em 0 0; list-style: none; font-size: 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } -footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } +footer { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } footer:empty { display: none; } .go { display: inline-flex; align-items: center; min-height: 2.25em; padding: 0.3em 1em; font-weight: 600; text-decoration: none; color: var(--gz-accent-ink, #fff); background: var(--gz-accent, #0b6bcb); border: 1px solid var(--gz-accent, #0b6bcb); border-radius: 999px; } .go:hover { filter: brightness(1.08); } +.shut { min-height: 2.25em; padding: 0.3em 1em; font: inherit; font-weight: 600; color: var(--gz-ink, #1d2733); background: transparent; + border: 1px solid var(--gz-border-strong, var(--gz-line-strong, #9aa7b4)); border-radius: 999px; cursor: pointer; } +.shut:hover { background: var(--gz-row-hover, rgb(0 0 0 / 0.06)); } :focus-visible { outline: 2px solid var(--gz-accent, #0b6bcb); outline-offset: 2px; } h2:focus-visible { outline-offset: 4px; }`; @@ -135,7 +138,9 @@ h2:focus-visible { outline-offset: 4px; }`; const x = make('button', { type: 'button', className: 'x', textContent: '×', title: t('Close') }); x.setAttribute('aria-label', t('Close')); const body = make('div', { className: 'body', dir: 'auto' }); - const foot = make('footer', {}, ...nodes(o.actions)); + const acts = nodes(o.actions); + const shut = acts.length ? make('button', { type: 'button', className: 'shut', textContent: t('Close') }) : null; + const foot = make('footer', {}, ...acts, shut); const box = make('dialog', { className: o.wide ? 'wide' : '' }, make('header', {}, title, meta, x), body, foot); if (o.label) box.setAttribute('aria-label', o.label); else box.setAttribute('aria-labelledby', 'gz-popup-title'); root.append(veil, box); @@ -285,6 +290,7 @@ h2:focus-visible { outline-offset: 4px; }`; // The browser closing it by itself (a repeated Escape it no longer lets the page refuse). box.addEventListener('close', () => { if (live && !box.open) close(true); }); x.addEventListener('click', () => close()); + shut?.addEventListener('click', () => close()); box.addEventListener('click', (e) => { if (e.target !== box) return; const r = box.getBoundingClientRect(); diff --git a/scripts/announce-check.js b/scripts/announce-check.js index 92f6709cf..7493f951f 100644 --- a/scripts/announce-check.js +++ b/scripts/announce-check.js @@ -191,6 +191,8 @@ const ok = (v, label) => { checks++; if (!v) { failures.push(label); console.err ok(loadedImage?.result.value, 'pop-up: the same-origin picture still loads'); const d = await box('DIALOG'); ok(d.left >= 0 && d.right <= 1440, 'pop-up: inside the viewport at 1440'); + const [goBox, shutBox] = [await box('A', 'go'), await box('BUTTON', 'shut')]; + ok(goBox && shutBox && shutBox.left >= goBox.right && Math.abs(d.right - shutBox.right) < 40, 'pop-up: Close sits bottom-right, Open in Canvas to its left'); await shot('popup-light-1440'); let trapped = true; for (const key of ['Tab', 'Shift+Tab']) for (let i = 0; i < 12; i++) { @@ -228,7 +230,17 @@ const ok = (v, label) => { checks++; if (!v) { failures.push(label); console.err const b = await box('DIALOG'); ok(b.left >= 0 && b.right <= width + 0.5 && b.bottom <= height + 0.5, `${name}: inside the viewport (${Math.round(b.left)}..${Math.round(b.right)} of ${width})`); ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), `${name}: no sideways scroll`); + const [g, c] = [await box('A', 'go'), await box('BUTTON', 'shut')]; + ok(c && c.bottom <= b.bottom + 0.5, `${name}: the footer's Close is whole inside the pop-up`); + ok(g && c && (c.top > g.top || (rtl ? c.right <= g.left : c.left >= g.right)), `${name}: Close after Open in Canvas${rtl ? ', mirrored' : ''}`); await shot(`popup-${name}`); + if (name === 'dark-1440') { + await page.mouse.click(c.left + c.width / 2, c.top + c.height / 2); + await page.waitForTimeout(300); + ok(!(await popupOpen()), `${name}: the footer's Close closes it`); + ok(await page.evaluate(() => document.activeElement?.matches('#gz-todo a[data-ann="announcement:7"]')), `${name}: focus back on the link after Close`); + continue; + } await page.keyboard.press('Escape'); await page.waitForTimeout(200); } From 0f5986f2a452b97388fb1dfc03265812738ede23 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:38:50 -0400 Subject: [PATCH 035/907] Refresh the Safari resource mirror for the pop-up's footer Close --- .../Glaze Extension/Resources/features/popup.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/features/popup.js b/safari/Glaze/Glaze Extension/Resources/features/popup.js index 486e14dfc..1c0fa7824 100644 --- a/safari/Glaze/Glaze Extension/Resources/features/popup.js +++ b/safari/Glaze/Glaze Extension/Resources/features/popup.js @@ -7,7 +7,7 @@ // label?, meta?, // meta: one line under the title (course · author · date) // body, // a Node, an array of them, a string, or an async () => any of those: // // until it settles the pop-up says Loading… (aria-busy) -// actions?, // Nodes for its footer (an "Open in …" link) +// actions?, // Nodes for its footer (an "Open in …" link), then a Close button // source?, // the Element (or () => Element) it came from: it grows out of it, and // // focus goes back to it when it closes // wide?, // a wider pop-up (tables) @@ -20,7 +20,7 @@ // GZ_POPUP.current() // the open handle, or null; GZ_POPUP.kind() the motion it would use // // A real modal (the rest of the page inert, focus inside it from the start), named by its title, closed by Escape, -// its Close button or a click outside it, focus then going back where it came from at once, before the closing motion. +// either Close button or a click outside it, focus then going back where it came from at once, before the closing motion. // It lives in a closed shadow root, so the school's styles and scripts can't reach in, and wears the Look's tokens (the // page's --gz-* custom properties), light and dark. Motion (lib/popup.js motion): the genie out of `source` on Expressive, // a scale and fade on Subtle, a fade or nothing as the "Pop-up animation" setting says, nothing with motion off or reduced. @@ -76,11 +76,14 @@ h2 { margin: 0; font: 600 1.15em/1.3 var(--gz-font-head, var(--gz-font, system-u a { color: var(--gz-accent, #0b6bcb); text-underline-offset: 0.15em; } .wait { margin: 0; color: var(--gz-ink-muted, #5b6876); } .body .files { display: flex; flex-wrap: wrap; gap: 0.35em 0.9em; margin: 1em 0 0; padding: 0.7em 0 0; list-style: none; font-size: 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } -footer { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } +footer { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } footer:empty { display: none; } .go { display: inline-flex; align-items: center; min-height: 2.25em; padding: 0.3em 1em; font-weight: 600; text-decoration: none; color: var(--gz-accent-ink, #fff); background: var(--gz-accent, #0b6bcb); border: 1px solid var(--gz-accent, #0b6bcb); border-radius: 999px; } .go:hover { filter: brightness(1.08); } +.shut { min-height: 2.25em; padding: 0.3em 1em; font: inherit; font-weight: 600; color: var(--gz-ink, #1d2733); background: transparent; + border: 1px solid var(--gz-border-strong, var(--gz-line-strong, #9aa7b4)); border-radius: 999px; cursor: pointer; } +.shut:hover { background: var(--gz-row-hover, rgb(0 0 0 / 0.06)); } :focus-visible { outline: 2px solid var(--gz-accent, #0b6bcb); outline-offset: 2px; } h2:focus-visible { outline-offset: 4px; }`; @@ -135,7 +138,9 @@ h2:focus-visible { outline-offset: 4px; }`; const x = make('button', { type: 'button', className: 'x', textContent: '×', title: t('Close') }); x.setAttribute('aria-label', t('Close')); const body = make('div', { className: 'body', dir: 'auto' }); - const foot = make('footer', {}, ...nodes(o.actions)); + const acts = nodes(o.actions); + const shut = acts.length ? make('button', { type: 'button', className: 'shut', textContent: t('Close') }) : null; + const foot = make('footer', {}, ...acts, shut); const box = make('dialog', { className: o.wide ? 'wide' : '' }, make('header', {}, title, meta, x), body, foot); if (o.label) box.setAttribute('aria-label', o.label); else box.setAttribute('aria-labelledby', 'gz-popup-title'); root.append(veil, box); @@ -285,6 +290,7 @@ h2:focus-visible { outline-offset: 4px; }`; // The browser closing it by itself (a repeated Escape it no longer lets the page refuse). box.addEventListener('close', () => { if (live && !box.open) close(true); }); x.addEventListener('click', () => close()); + shut?.addEventListener('click', () => close()); box.addEventListener('click', (e) => { if (e.target !== box) return; const r = box.getBoundingClientRect(); From 4047ca369d39804d417b7780e521351df77c64ab Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:41:16 -0400 Subject: [PATCH 036/907] Include visible batch titles in a failed import check --- scripts/documents-check.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 21548ec93..520d46fa9 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -1543,7 +1543,8 @@ const PROBE = ({ lang }) => { check(queued.length === 20 && queued.every((s) => s === 'Reading…'), `twenty dropped files each show a stage immediately (${JSON.stringify(queued)})`); await im.evaluate(() => __batchRead()); await until(() => im.evaluate(() => !document.querySelector('.gd-pending')), 'batch imported', 60000); - check(await im.evaluate(() => [...document.querySelectorAll('.gd-card .gd-title')].filter((el) => /^Batch \d+$/.test(el.textContent)).length === 20), 'all twenty queued imports become their own documents'); + const batch = await im.evaluate(() => ({ titles: [...document.querySelectorAll('.gd-card .gd-title')].map((el) => el.textContent).filter((title) => title.startsWith('Batch')), search: document.getElementById('gd-q').value })); + check(batch.titles.filter((title) => /^Batch \d+$/.test(title)).length === 20, `all twenty queued imports become their own documents (${JSON.stringify(batch)})`); await shot(im, 'import-done-1280-en.png'); await im.close(); } From 32c90d2cf0d395ec9b5e7d58a2197ff5ae5eb425 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:42:58 -0400 Subject: [PATCH 037/907] Distinguish PDF batch cards from earlier Word imports --- scripts/documents-check.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 520d46fa9..9e341aa8f 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -1535,7 +1535,7 @@ const PROBE = ({ lang }) => { return k.importFile(f, opts); } }); const dt = new DataTransfer(); - for (let i = 1; i <= 20; i++) dt.items.add(new File([new Uint8Array(bytes)], `Batch ${i}.pdf`, { type: 'application/pdf' })); + for (let i = 1; i <= 20; i++) dt.items.add(new File([new Uint8Array(bytes)], `Queued ${i}.pdf`, { type: 'application/pdf' })); document.dispatchEvent(new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: dt })); }, Array.from(require('./pdf-fixtures.js').FIXTURES.three())); await until(() => im.evaluate(() => typeof __batchRead === 'function'), 'batch reading'); @@ -1543,8 +1543,8 @@ const PROBE = ({ lang }) => { check(queued.length === 20 && queued.every((s) => s === 'Reading…'), `twenty dropped files each show a stage immediately (${JSON.stringify(queued)})`); await im.evaluate(() => __batchRead()); await until(() => im.evaluate(() => !document.querySelector('.gd-pending')), 'batch imported', 60000); - const batch = await im.evaluate(() => ({ titles: [...document.querySelectorAll('.gd-card .gd-title')].map((el) => el.textContent).filter((title) => title.startsWith('Batch')), search: document.getElementById('gd-q').value })); - check(batch.titles.filter((title) => /^Batch \d+$/.test(title)).length === 20, `all twenty queued imports become their own documents (${JSON.stringify(batch)})`); + const batch = await im.evaluate(() => [...document.querySelectorAll('#gd-docs > .gd-card .gd-title')].map((el) => el.textContent).filter((title) => /^Queued \d+$/.test(title))); + check(batch.length === 20, `all twenty queued imports become their own documents (${batch.length})`); await shot(im, 'import-done-1280-en.png'); await im.close(); } From 7bf8b2ec99a69e0cae5c2cae05319f4c468f4cfe Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:44:26 -0400 Subject: [PATCH 038/907] Keep the pop-up's footer whole at narrow widths with large text, and show what Tab focuses in it --- features/popup.js | 7 +++++-- .../Glaze/Glaze Extension/Resources/features/popup.js | 7 +++++-- scripts/announce-check.js | 11 ++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/features/popup.js b/features/popup.js index 1c0fa7824..36baf547d 100644 --- a/features/popup.js +++ b/features/popup.js @@ -43,6 +43,7 @@ dialog, .genie-face { position: fixed; inset: 0; box-sizing: border-box; display color: var(--gz-ink, #1d2733); background: var(--gz-surface-grain, none), var(--gz-surface-fill, var(--gz-surface, #fff)); border: 1px solid var(--gz-border-strong, var(--gz-line-strong, #9aa7b4)); border-radius: var(--gz-radius, 12px); box-shadow: var(--gz-shadow, 0 18px 50px rgb(0 0 0 / 0.3)); } dialog.wide { width: min(64em, calc(100vw - 1rem)); } +dialog { overflow: auto; } /* the body scrolls; the whole pop-up only when its title, body and footer can't all fit */ dialog:not([open]) { display: none; } dialog::backdrop { background: transparent; } dialog[data-genie] { background: transparent; border-color: transparent; box-shadow: none; overflow: visible; } @@ -76,7 +77,7 @@ h2 { margin: 0; font: 600 1.15em/1.3 var(--gz-font-head, var(--gz-font, system-u a { color: var(--gz-accent, #0b6bcb); text-underline-offset: 0.15em; } .wait { margin: 0; color: var(--gz-ink-muted, #5b6876); } .body .files { display: flex; flex-wrap: wrap; gap: 0.35em 0.9em; margin: 1em 0 0; padding: 0.7em 0 0; list-style: none; font-size: 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } -footer { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } +footer { flex: none; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } footer:empty { display: none; } .go { display: inline-flex; align-items: center; min-height: 2.25em; padding: 0.3em 1em; font-weight: 600; text-decoration: none; color: var(--gz-accent-ink, #fff); background: var(--gz-accent, #0b6bcb); border: 1px solid var(--gz-accent, #0b6bcb); border-radius: 999px; } @@ -285,7 +286,9 @@ h2:focus-visible { outline-offset: 4px; }`; .filter((el) => !el.disabled && el.tabIndex >= 0 && !el.closest('[inert]') && el.getClientRects().length) .sort((a, b) => (a.tabIndex || Infinity) - (b.tabIndex || Infinity)); const at = all.indexOf(root.activeElement), next = e.shiftKey ? (at <= 0 ? all.length - 1 : at - 1) : (at + 1) % all.length; - e.preventDefault(); (all[next] || title).focus({ preventScroll: true }); + // Focus without the browser's scroll (it would move the page behind), then just enough to show it in the pop-up. + const to = all[next] || title; + e.preventDefault(); to.focus({ preventScroll: true }); to.scrollIntoView?.({ block: 'nearest', inline: 'nearest' }); }); // The browser closing it by itself (a repeated Escape it no longer lets the page refuse). box.addEventListener('close', () => { if (live && !box.open) close(true); }); diff --git a/safari/Glaze/Glaze Extension/Resources/features/popup.js b/safari/Glaze/Glaze Extension/Resources/features/popup.js index 1c0fa7824..36baf547d 100644 --- a/safari/Glaze/Glaze Extension/Resources/features/popup.js +++ b/safari/Glaze/Glaze Extension/Resources/features/popup.js @@ -43,6 +43,7 @@ dialog, .genie-face { position: fixed; inset: 0; box-sizing: border-box; display color: var(--gz-ink, #1d2733); background: var(--gz-surface-grain, none), var(--gz-surface-fill, var(--gz-surface, #fff)); border: 1px solid var(--gz-border-strong, var(--gz-line-strong, #9aa7b4)); border-radius: var(--gz-radius, 12px); box-shadow: var(--gz-shadow, 0 18px 50px rgb(0 0 0 / 0.3)); } dialog.wide { width: min(64em, calc(100vw - 1rem)); } +dialog { overflow: auto; } /* the body scrolls; the whole pop-up only when its title, body and footer can't all fit */ dialog:not([open]) { display: none; } dialog::backdrop { background: transparent; } dialog[data-genie] { background: transparent; border-color: transparent; box-shadow: none; overflow: visible; } @@ -76,7 +77,7 @@ h2 { margin: 0; font: 600 1.15em/1.3 var(--gz-font-head, var(--gz-font, system-u a { color: var(--gz-accent, #0b6bcb); text-underline-offset: 0.15em; } .wait { margin: 0; color: var(--gz-ink-muted, #5b6876); } .body .files { display: flex; flex-wrap: wrap; gap: 0.35em 0.9em; margin: 1em 0 0; padding: 0.7em 0 0; list-style: none; font-size: 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } -footer { flex: none; display: flex; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } +footer { flex: none; display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 0.5em; padding: 0.7em 1.1em 0.9em; border-top: 1px solid var(--gz-line, #d5dbe1); } footer:empty { display: none; } .go { display: inline-flex; align-items: center; min-height: 2.25em; padding: 0.3em 1em; font-weight: 600; text-decoration: none; color: var(--gz-accent-ink, #fff); background: var(--gz-accent, #0b6bcb); border: 1px solid var(--gz-accent, #0b6bcb); border-radius: 999px; } @@ -285,7 +286,9 @@ h2:focus-visible { outline-offset: 4px; }`; .filter((el) => !el.disabled && el.tabIndex >= 0 && !el.closest('[inert]') && el.getClientRects().length) .sort((a, b) => (a.tabIndex || Infinity) - (b.tabIndex || Infinity)); const at = all.indexOf(root.activeElement), next = e.shiftKey ? (at <= 0 ? all.length - 1 : at - 1) : (at + 1) % all.length; - e.preventDefault(); (all[next] || title).focus({ preventScroll: true }); + // Focus without the browser's scroll (it would move the page behind), then just enough to show it in the pop-up. + const to = all[next] || title; + e.preventDefault(); to.focus({ preventScroll: true }); to.scrollIntoView?.({ block: 'nearest', inline: 'nearest' }); }); // The browser closing it by itself (a repeated Escape it no longer lets the page refuse). box.addEventListener('close', () => { if (live && !box.open) close(true); }); diff --git a/scripts/announce-check.js b/scripts/announce-check.js index 7493f951f..f1e961af2 100644 --- a/scripts/announce-check.js +++ b/scripts/announce-check.js @@ -230,8 +230,17 @@ const ok = (v, label) => { checks++; if (!v) { failures.push(label); console.err const b = await box('DIALOG'); ok(b.left >= 0 && b.right <= width + 0.5 && b.bottom <= height + 0.5, `${name}: inside the viewport (${Math.round(b.left)}..${Math.round(b.right)} of ${width})`); ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), `${name}: no sideways scroll`); + if (rtl) { // Blackboard's longer name, the widest footer the pop-up gets + const { root } = await cdp.send('DOM.getDocument', { depth: -1, pierce: true }); + const n = find(root, (x) => x.nodeName === 'A' && (x.attributes || []).some((v, i, a) => i % 2 === 0 && v === 'class' && a[i + 1] === 'go')); + const { object } = await cdp.send('DOM.resolveNode', { nodeId: n.nodeId }); + await cdp.send('Runtime.callFunctionOn', { objectId: object.objectId, functionDeclaration: 'function() { this.textContent = "Open in Blackboard"; }' }); + } + await page.keyboard.press('Shift+Tab'); + ok(await focusedName() === 'button:Close', `${name}: Shift+Tab from the title reaches the footer's Close`); const [g, c] = [await box('A', 'go'), await box('BUTTON', 'shut')]; - ok(c && c.bottom <= b.bottom + 0.5, `${name}: the footer's Close is whole inside the pop-up`); + const inside = (r) => r && r.left >= b.left - 0.5 && r.right <= b.right + 0.5 && r.bottom <= b.bottom + 0.5; + ok(inside(g) && inside(c), `${name}: Open and Close are whole inside the pop-up (${JSON.stringify([g, c].map((r) => r && [Math.round(r.left), Math.round(r.right), Math.round(r.bottom)]))})`); ok(g && c && (c.top > g.top || (rtl ? c.right <= g.left : c.left >= g.right)), `${name}: Close after Open in Canvas${rtl ? ', mirrored' : ''}`); await shot(`popup-${name}`); if (name === 'dark-1440') { From ebf8979e8ec66f605ed71e25a91ad8daa791b573 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:52:34 -0400 Subject: [PATCH 039/907] Add Glaze's own tokenizer for local LFM3 models lib/ai/local/tokenizer.js loads a tokenizer.json or a GGUF's tokenizer keys and encodes as LFM3's byte-level BPE does: its Split regex with onig's character tables pinned, ignore_merges, added tokens only when asked, and a streaming decoder. Page text never yields a control token; Glaze's own specials go after the vocabulary. scripts/local-tokenizer-check.js holds it to HF tokenizers line for line. Not in the worker's imports yet. --- lib/ai/local/tokenizer.js | 355 ++++++++++++++++++ .../Resources/lib/ai/local/tokenizer.js | 355 ++++++++++++++++++ scripts/local-tokenizer-check.js | 66 ++++ test/ai-local-tokenizer.test.js | 222 +++++++++++ test/ai-worker.test.js | 4 +- 5 files changed, 1001 insertions(+), 1 deletion(-) create mode 100644 lib/ai/local/tokenizer.js create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js create mode 100644 scripts/local-tokenizer-check.js create mode 100644 test/ai-local-tokenizer.test.js diff --git a/lib/ai/local/tokenizer.js b/lib/ai/local/tokenizer.js new file mode 100644 index 000000000..511f75faa --- /dev/null +++ b/lib/ai/local/tokenizer.js @@ -0,0 +1,355 @@ +// Glaze's own tokenizer for the local engine's models: byte-level BPE as LiquidAI's LFM3 specifies it (tokenizer.json at +// huggingface.co/LiquidAI/LFM3-1.7B, commit ac2586a3): no normalizer; the pre-tokenizer's one Split regex (Isolated), +// then the GPT-2 byte → character table; BPE by merge rank with ignore_merges (a piece the vocabulary holds whole is +// one token); added tokens; the ByteLevel decoder. A tokenizer.json or a GGUF's tokenizer.ggml.* keys load the same +// neutral vocabulary, and anything they say that this file doesn't do is refused rather than ignored. +// Page text can never produce a control token: encode() reads added tokens only when asked, and Glaze's own specials +// (add) only ever come from encodeWith. scripts/local-tokenizer-check.js holds it to HF tokenizers line for line. +// Classic script; node exports. +var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { + // GPT-2's table: printable Latin-1 bytes are themselves, the other 68 are U+0100 onwards in byte order. + const BYTE_CHAR = new Uint16Array(256); + const CHAR_BYTE = new Int16Array(0x144).fill(-1); + for (let b = 0, n = 0; b < 256; b++) { + const keep = (b >= 33 && b <= 126) || (b >= 161 && b <= 172) || b >= 174; + BYTE_CHAR[b] = keep ? b : 256 + n++; + CHAR_BYTE[BYTE_CHAR[b]] = b; + } + + // HF tokenizers' regex engine (onig) has its own Unicode tables, older than any browser's: to it 17,144 of today's letters + // (U+0C5C, U+A7CE…) and 336 digits are neither, and those decide where a word ends. Its classes are pinned here, read + // off HF tokenizers 0.22.2's own splits of every code point ("a"+c one piece: a letter; "1"+c: a digit; c+"!" two + // pieces and neither: White_Space), so every runtime splits as the model's tokenizer did. Ranges, each as base-36 + // "gap" or "gap.length" from the last one's end. + const ONIG = { + L: '1t.p,7.p,1c,b,5,6.m,2.u,2.cp,5.b,f.4,8,2,3m.4,2.1,3.3,2,7,2.2,2,2.j,2.2a,2.3u,9.4l,2.11,3,7.14,20.q,5.3,1a.16,10.1,2.2q,2,g.1,8.1,b.2,3,h,2.t,u.2g,c,p.w,a.1,5,6.l,5,a,4,o.o,8.a,6.n,2.5,i.15,1n.1h,4,j,8.9,g.f,5.7,3.1,3.l,2.6,2,4.3,4,h,e.1,2.2,f.1,b,9.5,5.1,3.l,2.6,2.1,2.1,2.1,w.3,2,k.2,h.8,2.2,2.l,2.6,2.1,2.4,4,j,g.1,o,c.7,3.1,3.l,2.6,2.1,2.4,4,v.1,2.2,g,i,2.5,4.2,2.3,4.1,2,2.1,4.1,4.2,4.b,n,1h.7,2.2,2.m,2.f,4,r.2,3,3.1,v,5.7,2.2,2.m,2.9,2.4,4,w.1,2.1,g.1,i.8,2.2,2.14,3,h,6.2,9.2,p.5,6.h,4.n,2.8,2,3.6,1n.1b,2.1,d.6,1n.1,2,2.4,2.n,2,2.9,2.1,a,3.4,2,m.3,x,1s.7,2.z,s.4,38.16,l,h.5,5.3,4,4.1,8.2,5.c,d,i.11,2,6,3.16,2.98,2.3,3.6,2,2.3,3.14,2.3,3.w,2.3,3.6,2,2.3,3.e,2.1k,2.3,3.1u,12.f,h.2d,3.5,4.h7,3.g,2.p,6.22,7.7,8.h,e.i,f.h,f.c,2.2,g.1f,10,5,1w.2g,8.4,3.x,2,6.1x,b.u,1e.t,3.4,c.17,5.p,1j.m,a.1g,2b,2m.1a,i.7,1j.t,e.1,b.17,r.z,16.2,b.z,3.a,6.16,3.2,16.3,2.5,2.1,4,6.5b,1t.7p,3.5,3.11,3.5,3.7,2,2,2,2.u,3.1g,2.6,2,4.2,2.6,4.3,3.5,5.c,6.2,2.6,39,e,h.c,2u,5,3.9,2,4.4,7,2,2,2.3,2.a,3.3,6.4,5,1h.1,22k.6c,7.3,4.1,d.11,2,6,3.1j,8,h.m,a.6,2.6,2.6,2.6,2.6,2.6,2.6,2.6,29,d2.1,17.4,6.1,5.2d,7.2,2.2h,2.3,6.16,2.2l,i.v,1d.f,e9.533,1t.h3g,1w.19,3.7g,4.f,b.1,l.1a,h.u,3.1x,1e.8,3.2u,3.1u,3.1,2,2.7,m.f,2.2,2.3,2.m,u.1f,f.1d,1r.5,4,2.1,c.r,b.m,q.s,8.1a,t,h.4,2.9,b.4,2.14,o.2,2.7,l.m,4,4.1d,2,4.1,3.4,3,2,p.2,3.a,8.2,d.5,3.5,3.5,a.6,2.6,2.16,2.d,7.36,u.8mb,d.m,5.1c,6it.a5,3.2x,13.6,d.4,6,2.9,2.c,2.4,2,2.1,2.1,2.2z,y.a2,j.1r,3.1h,15.b,39.4,2.3q,11.p,7.p,c.2g,4.5,3.5,3.5,3.2,10.b,2.p,2.i,2.1,2.e,3.d,z.3e,au.s,4.1c,1c.v,e.j,2.7,7.11,b.t,3.z,5.7,1d.4d,j.z,5.z,5.13,9.1f,d.a,2.e,2.6,2.1,2.a,2.e,2.6,2.1,4.1f,d.8m,a.l,b.7,p.5,2.15,2.8,1y.5,3,2.17,2.1,4,3.m,b.m,a.u,1u.i,2.1,b.l,b.p,1z.1j,7.1,1t,g.3,2.2,2.s,17.s,4.s,10.7,2.r,s.1h,b.l,b.i,e.h,33.20,1k.1e,e.1e,e.z,13.r,a.m,6z.15,7.1,h.2,1o.s,b,9.l,17.h,1b.k,s.m,d.1g,1m.1,3,e.18,x.o,r.z,u,3,9.y,4,d.1b,f.3,m,2,10.h,2.o,k.1,1s.6,2,2.3,2.e,2.9,8.1a,13.7,3.1,3.l,2.6,2.1,2.4,4,j,d.4,v.9,2,3,2.11,2,q,2,19.1g,j.3,l.2,v.1b,l.1,2,55.1a,16.3,11.1b,l,1o.16,e,20.q,12.6,56.17,39.1r,w.7,3,3.7,2.1,2.n,g,2,2n.7,3.12,h,2,t,b.13,8,m,c.19,k,j.20,5k.w,w.8,2.10,i,1e.t,35.6,2.1,2.11,m,q.5,2.1,2.v,f,94.i,g,2.c,2.x,3h,28.pl,6f.5f,219.2o,g.tr,i.5,q.32y,6.g6,5a2.t,1cz.fs,8.u,i.26,i.t,j.1b,h.3,w.k,6.i,c1.18,5w.1r,3l.22,6,1v.c,1t.1,2,t.4qf,9.yd,16.9,6w8.3,2.6,2.1,2.82,g,u.2,3,f.3,9.az,1s5.2y,6.c,4.8,8.9,4mf.2c,2.1y,2.1,3,3.1,3.3,2.b,2,2.6,2.1s,2.3,3.7,2.6,2.r,2.3,2.4,2,4.6,2.9f,3.o,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.7,1f9.u,7.5,7a.1p,43.18,b.6,h,8y.t,j.17,dh.r,6d.t,3,ds.6,2.3,2.1,2.e,2.5g,1o.1v,8,xh.3,2.q,2.1,2,3,2.9,2.3,2,2,7,5,2,2,2.2,2.1,2,3,2,2,2,2,2.1,2,3.3,2.6,2.3,2.3,2,2.9,2.g,6.2,2.4,2.g,3et.wyn,x.37d,7.65,3.4g1,f.5rk,g.h9,1wj.f1,15v.3t6,6.38f', + N: '1c.9,3d.1,6,3.2,142.9,3r.9,5j.9,bh.9,3b.9,5.5,31.9,3b.9,3b.9,3.5,33.c,38.9,9.6,2w.9,2x.6,8.i,32.9,2p.9,3b.9,1z.j,7h.9,1z.9,k0.j,oi.2,6o.9,7.9,n.9,8d.9,3l.a,4m.9,7.9,53.9,2f.9,3r.9,7.9,t3,4.5,7.9,5j.1e,3.4,k7.1n,27.l,hj.t,12i,lm,q.8,f.2,9k.3,3v.9,v.7,2.e,x.9,14.e,msh.9,59.9,8x.5,4b.9,13.9,5j.9,n.9,2f.9,bb.9,gev.9,dq.18,d.1k,i.1,9i.q,11.3,u,9,3r.4,5n.9,q7.7,q.6,14.8,24.4,n.5,4h.1,3.f,3.19,1t.8,1h.1,v.2,24.4,2x.7,p.7,16.6,97.5,1d.9,7.9,7r.u,4f.9,17.3,35.6,3r.t,3l.9,1p.9,41.9,8.j,70.9,9j.9,3b.9,af.9,2v.9,7.j,25.b,bp.i,2m.9,if.9,2f.s,6c.9,1z.9,br.9,2v.k,to.32,c02.9,1t3.9,2f.9,3r.9,2.6,en.9,7b.m,imy.9,153.j,d.j,31.o,uu.1d,1tt.9,br.9,dz.9,6w.9,jx.8,3l.9,m0.1m,2.2,2.3,25.18,2.e,qr.c,25g.9', + W: '9.4,j,2t,r,4bk,1vk.a,u.1,6,1c,335', + }; + const cls = (packed) => { + let at = 0, out = ''; + for (const r of packed.split(',')) { + const [g, n = '0'] = r.split('.'); + const a = at + parseInt(g, 36), b = a + parseInt(n, 36); + out += a === b ? `\\u{${a.toString(16)}}` : `\\u{${a.toString(16)}}-\\u{${b.toString(16)}}`; + at = b; + } + return out; + }; + // Pre-tokenizers by GGUF name, with the onig regex a tokenizer.json writes for each and its JS (made on first use). + // onig's \s is Unicode White_Space (U+0085 in, U+FEFF out), and its (?i:'s) also takes ſ, the one letter that + // case-folds onto those (HF: 'ſt is 'ſ, t). + const PRE = { + lfm2: { + hf: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+", + js: () => { + const L = cls(ONIG.L), N = cls(ONIG.N), W = cls(ONIG.W); + return `'(?:[sS\\u017f]|[tT]|[rR][eE]|[vV][eE]|[mM]|[lL][lL]|[dD])|[^\\r\\n${L}${N}]?[${L}]+|[${N}]{1,3}| ?[^${W}${L}${N}]+[\\r\\n]*|[${W}]*[\\r\\n]+|[${W}]+(?![^${W}])|[${W}]+`; + }, + ignoreMerges: true, + }, + }; + // GGUF token types. + const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; + const GLAZE = /^<\|\/?gz_[a-z][\w:.-]{0,60}\|>$/; + const enc = new TextEncoder(); + const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; + // Lone surrogates → U+FFFD, as TextEncoder makes them. + const well = (s) => (s.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g, (m) => (m.length === 2 ? m : '\ufffd'))); + const pair = (m) => { + if (Array.isArray(m) && m.length === 2) return m; + const s = String(m), i = s.indexOf(' '); + if (i <= 0 || i !== s.lastIndexOf(' ') || i === s.length - 1) fail(`merge ${JSON.stringify(s)}`); + return [s.slice(0, i), s.slice(i + 1)]; + }; + + // A token's bytes as the ByteLevel decoder gives them: each character back through the table, or, when one isn't in + // it, the token's own UTF-8 (HF's rule, per token). + const tokenBytes = (s) => { + const out = new Uint8Array(s.length); + for (let i = 0; i < s.length; i++) { + const c = s.charCodeAt(i), b = c < CHAR_BYTE.length ? CHAR_BYTE[c] : -1; + if (b < 0) return enc.encode(s); + out[i] = b; + } + return out; + }; + + // Merge ranks in an open-addressed table of (left id, right id) → rank, and rank → the merged id. + const mergeTable = (merges, vocab) => { + const cap = 1 << Math.max(10, Math.ceil(Math.log2(merges.length * 2 + 1))); + const ka = new Int32Array(cap).fill(-1), kb = new Int32Array(cap), rk = new Int32Array(cap), to = new Int32Array(merges.length); + const mask = cap - 1; + const slot = (a, b) => (Math.imul(a, 0x9e3779b1) ^ Math.imul(b, 0x85ebca6b)) & mask; + merges.forEach((m, r) => { + const [x, y] = pair(m); + const a = vocab.get(x), b = vocab.get(y), c = vocab.get(x + y); + if (a === undefined || b === undefined || c === undefined) fail(`merge ${JSON.stringify(`${x} ${y}`)} is not in the vocabulary`); + let i = slot(a, b); + while (ka[i] !== -1 && !(ka[i] === a && kb[i] === b)) i = (i + 1) & mask; + ka[i] = a; kb[i] = b; rk[i] = r; to[r] = c; // a merge listed twice ranks where it is last, as in HF's map + }); + const rank = (a, b) => { + for (let i = slot(a, b); ; i = (i + 1) & mask) { + const k = ka[i]; + if (k === -1) return -1; + if (k === a && kb[i] === b) return rk[i]; + } + }; + return { rank, to }; + }; + + // v: { tokens, types, merges, pre, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them (byte-level + // strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order. opts.cache: words kept. + const load = (v, opts = {}) => { + const pre = PRE[v.pre]; + if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); + const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); + if (types.length !== tokens.length) fail('types and tokens differ in length'); + const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; + const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(tokens.length); + let longest = 0; + tokens.forEach((t, id) => { + const ty = types[id]; + kinds[id] = ty; + if (ty === T.control || ty === T.user) { if (!specials.has(t)) specials.set(t, id); } + else if (ty !== T.unused) { if (!vocab.has(t)) vocab.set(t, id); longest = Math.max(longest, t.length); } + }); + const byteId = new Int32Array(256); + for (let b = 0; b < 256; b++) { + const id = vocab.get(String.fromCharCode(BYTE_CHAR[b])); + if (id === undefined) fail(`byte 0x${b.toString(16)} has no token`); + byteId[b] = id; + } + const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); + const re = new RegExp((pre.src ||= pre.js()), 'gu'); + // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher. + const addedRe = specials.size ? new RegExp([...specials.keys()].sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; + const glaze = []; // Glaze's own specials, ids from tokens.length on + const size = () => tokens.length + glaze.length; + const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; + + // ---- BPE --------------------------------------------------------------------------------------------------------- + let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); + const grow = (n) => { + if (buf.length < n * 3) buf = new Uint8Array(n * 3 + 64); + if (sym.length < n * 3) { const m = n * 3 + 64; sym = new Int32Array(m); next = new Int32Array(m); prev = new Int32Array(m); heap = new Float64Array(m * 2); } + }; + const P = 2 ** 32; + // A min-heap of rank·2³² + left position: the lowest rank merges first, the leftmost on a tie. + let h = 0; + const push = (key) => { + if (h === heap.length) { const g = new Float64Array(h * 2); g.set(heap); heap = g; } + let i = h++; + while (i > 0) { const p = (i - 1) >> 1; if (heap[p] <= key) break; heap[i] = heap[p]; i = p; } + heap[i] = key; + }; + const pop = () => { + const top = heap[0], last = heap[--h]; + let i = 0; + for (;;) { + let c = 2 * i + 1; + if (c >= h) break; + if (c + 1 < h && heap[c + 1] < heap[c]) c++; + if (heap[c] >= last) break; + heap[i] = heap[c]; i = c; + } + heap[i] = last; + return top; + }; + const bpe = (piece) => { + grow(piece.length); + const n = enc.encodeInto(piece, buf).written; + if (ignoreMerges && n <= longest) { + if (chars.length < n) chars = new Uint16Array(longest); + for (let i = 0; i < n; i++) chars[i] = BYTE_CHAR[buf[i]]; + const whole = vocab.get(String.fromCharCode.apply(null, chars.subarray(0, n))); + if (whole !== undefined) return [whole]; + } + for (let i = 0; i < n; i++) { sym[i] = byteId[buf[i]]; prev[i] = i - 1; next[i] = i + 1 < n ? i + 1 : -1; } + h = 0; + for (let i = 0; i + 1 < n; i++) { const r = rank(sym[i], sym[i + 1]); if (r >= 0) push(r * P + i); } + while (h > 0) { + const key = pop(), r = Math.floor(key / P), i = key - r * P; + const j = next[i]; + if (sym[i] < 0 || j < 0 || rank(sym[i], sym[j]) !== r) continue; + sym[i] = to[r]; sym[j] = -1; + const k = next[j]; + next[i] = k; + if (k >= 0) prev[k] = i; + if (prev[i] >= 0) { const q = rank(sym[prev[i]], sym[i]); if (q >= 0) push(q * P + prev[i]); } + if (k >= 0) { const q = rank(sym[i], sym[k]); if (q >= 0) push(q * P + i); } + } + const out = []; + for (let i = 0; i >= 0; i = next[i]) out.push(sym[i]); + return out; + }; + + // Words (pre-tokenizer pieces) → ids. Two generations of opts.cache words: a word found in the older one moves to the + // newer, and a full newer one becomes the older (deleting a Map's oldest entry one at a time is slow in V8). + const CACHE = opts.cache ?? 16384; + let cache = new Map(), older = new Map(); + const word = (piece) => { + let ids = cache.get(piece); + if (ids) return ids; + if (piece.length > 256) return bpe(piece); + ids = older.get(piece) || bpe(piece); + if (cache.size >= CACHE) { older = cache; cache = new Map(); } + cache.set(piece, ids); + return ids; + }; + + // Ids are collected in one growing Uint32Array. + const sink = () => { + let a = new Uint32Array(256), n = 0; + return { + add(ids) { + if (n + ids.length > a.length) { const g = new Uint32Array(Math.max(a.length * 2, n + ids.length)); g.set(a.subarray(0, n)); a = g; } + for (let i = 0; i < ids.length; i++) a[n++] = ids[i]; + }, + get n() { return n; }, + done: () => a.slice(0, n), + }; + }; + const pieces = (text, each) => { + re.lastIndex = 0; + let last = 0, m; + while ((m = re.exec(text))) { + if (m.index > last) each(text.slice(last, m.index)); + each(m[0]); + last = re.lastIndex; + } + if (last < text.length) each(text.slice(last)); + }; + const plainText = (text, out) => { if (text) pieces(text, (p) => out.add(word(p))); }; + const run = (text, specialsOn, out) => { + const s = well(String(text)); + if (!specialsOn || !addedRe) return plainText(s, out); + addedRe.lastIndex = 0; + let last = 0, m; + while ((m = addedRe.exec(s))) { + plainText(s.slice(last, m.index), out); + out.add([specials.get(m[0])]); + last = addedRe.lastIndex; + } + plainText(s.slice(last), out); + }; + + // ---- decoding ---------------------------------------------------------------------------------------------------- + // Each model token's bytes, made once; null for added and unused ones. + let base = null; + const baseBytes = () => (base ||= tokens.map((t, id) => { const k = kinds[id]; return k === T.control || k === T.user || k === T.unused ? null : tokenBytes(t); })); + const EMPTY = new Uint8Array(0); + const added = new Map(); // id → bytes of an added token's text, for decoding + const idBytes = (id, specialsOn) => { + idOk(id); + const k = id < tokens.length ? kinds[id] : T.control; + if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; + if (k === T.control || k === T.user) { + let b = added.get(id); + if (!b) { b = id < tokens.length ? tokenBytes(tokens[id]) : enc.encode(glaze[id - tokens.length]); added.set(id, b); } + return b; + } + return baseBytes()[id]; + }; + + return { + get size() { return size(); }, + modelSize: tokens.length, + get cached() { return cache.size + older.size; }, + bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, + // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. + encode(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.done(); }, + count(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.n; }, + // parts: [{ text } | { special: name }], name an added token's text or one add() registered. + encodeWith(parts) { + const out = sink(); + for (const p of parts) { + const keys = p && typeof p === 'object' ? Object.keys(p) : []; + if (keys.length !== 1 || !(keys[0] === 'text' || keys[0] === 'special')) fail('a part is { text } or { special }'); + if (keys[0] === 'text') { run(p.text, false, out); continue; } + const id = this.id(p.special); + if (id === undefined) fail(`unknown special ${JSON.stringify(p.special)}`); + out.add([id]); + } + return out.done(); + }, + id(name) { + const id = specials.get(name); + if (id !== undefined) return id; + const g = glaze.indexOf(name); + return g < 0 ? undefined : tokens.length + g; + }, + // Glaze's own special, at the next id after the vocabulary (the same name keeps its id). → its id. + add(name) { + const had = this.id(name); + if (had !== undefined && had >= tokens.length) return had; + if (typeof name !== 'string' || !GLAZE.test(name) || had !== undefined || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); + glaze.push(name); + return size() - 1; + }, + // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are + // never text. + byteStrings: () => [...baseBytes().map((b) => b && b.slice()), ...glaze.map(() => null)], + // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). + decode(ids, { specials: sp = false } = {}) { + const parts = []; + let n = 0; + for (const id of ids) { const b = idBytes(id, sp); if (b.length) { parts.push(b); n += b.length; } } + const all = new Uint8Array(n); + let o = 0; + for (const b of parts) { all.set(b, o); o += b.length; } + return new TextDecoder('utf-8', { ignoreBOM: true }).decode(all); + }, + // push(id) → the text that is complete so far; end() → the rest (an unfinished character as U+FFFD). + decoder({ specials: sp = false } = {}) { + const td = new TextDecoder('utf-8', { ignoreBOM: true }); + return { push: (id) => td.decode(idBytes(id, sp), { stream: true }), end: () => td.decode() }; + }, + // The pre-tokenizer's pieces (for tests and the check). + split(text) { const out = []; pieces(well(String(text)), (p) => out.push(p)); return out; }, + }; + }; + + // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), and + // load()'s cache. + const fromHF = (json, opts = {}) => { + const m = json && json.model; + if (!m || m.type !== 'BPE') fail('model: only BPE'); + if (m.byte_fallback) fail('byte_fallback'); + if (m.dropout) fail('dropout'); + if (m.unk_token != null) fail('unk_token'); + if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); + if (json.normalizer != null) fail('normalizer'); + const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; + const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; + const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; + const pre = Object.keys(PRE).find((k) => PRE[k].hf === split); + if (!pre || !bl) fail('pre_tokenizer'); + if (!json.decoder || json.decoder.type !== 'ByteLevel') fail('decoder'); + if (json.post_processor != null && json.post_processor.type !== 'ByteLevel') fail('post_processor'); + const tokens = [], types = []; + for (const [t, id] of Object.entries(m.vocab)) { tokens[id] = t; types[id] = T.normal; } + for (const a of json.added_tokens || []) { + if (a.lstrip || a.rstrip || a.single_word) fail(`added token ${JSON.stringify(a.content)}: lstrip, rstrip or single_word`); + if (types[a.id] !== undefined) fail(`added token ${JSON.stringify(a.content)} is in the vocabulary`); + tokens[a.id] = a.content; types[a.id] = a.special ? T.control : T.user; + } + for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } + const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; + return load({ tokens, types, merges: m.merges, pre, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); + }; + + // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. + const fromGGUF = (kv, opts) => { + if (!kv || !('tokenizer.ggml.tokens' in kv)) return load(kv, opts); + const g = (k) => kv[`tokenizer.ggml.${k}`]; + if (g('model') !== 'gpt2') fail(`model ${JSON.stringify(g('model'))}: only gpt2 (byte-level BPE)`); + if (!PRE[g('pre')]) fail(`pre-tokenizer ${JSON.stringify(g('pre'))}`); + const id = (k) => (g(k) == null ? null : Number(g(k))); + return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); + }; + + return { load, fromHF, fromGGUF, PRE, TYPES: T }; +})(); +if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js new file mode 100644 index 000000000..511f75faa --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js @@ -0,0 +1,355 @@ +// Glaze's own tokenizer for the local engine's models: byte-level BPE as LiquidAI's LFM3 specifies it (tokenizer.json at +// huggingface.co/LiquidAI/LFM3-1.7B, commit ac2586a3): no normalizer; the pre-tokenizer's one Split regex (Isolated), +// then the GPT-2 byte → character table; BPE by merge rank with ignore_merges (a piece the vocabulary holds whole is +// one token); added tokens; the ByteLevel decoder. A tokenizer.json or a GGUF's tokenizer.ggml.* keys load the same +// neutral vocabulary, and anything they say that this file doesn't do is refused rather than ignored. +// Page text can never produce a control token: encode() reads added tokens only when asked, and Glaze's own specials +// (add) only ever come from encodeWith. scripts/local-tokenizer-check.js holds it to HF tokenizers line for line. +// Classic script; node exports. +var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { + // GPT-2's table: printable Latin-1 bytes are themselves, the other 68 are U+0100 onwards in byte order. + const BYTE_CHAR = new Uint16Array(256); + const CHAR_BYTE = new Int16Array(0x144).fill(-1); + for (let b = 0, n = 0; b < 256; b++) { + const keep = (b >= 33 && b <= 126) || (b >= 161 && b <= 172) || b >= 174; + BYTE_CHAR[b] = keep ? b : 256 + n++; + CHAR_BYTE[BYTE_CHAR[b]] = b; + } + + // HF tokenizers' regex engine (onig) has its own Unicode tables, older than any browser's: to it 17,144 of today's letters + // (U+0C5C, U+A7CE…) and 336 digits are neither, and those decide where a word ends. Its classes are pinned here, read + // off HF tokenizers 0.22.2's own splits of every code point ("a"+c one piece: a letter; "1"+c: a digit; c+"!" two + // pieces and neither: White_Space), so every runtime splits as the model's tokenizer did. Ranges, each as base-36 + // "gap" or "gap.length" from the last one's end. + const ONIG = { + L: '1t.p,7.p,1c,b,5,6.m,2.u,2.cp,5.b,f.4,8,2,3m.4,2.1,3.3,2,7,2.2,2,2.j,2.2a,2.3u,9.4l,2.11,3,7.14,20.q,5.3,1a.16,10.1,2.2q,2,g.1,8.1,b.2,3,h,2.t,u.2g,c,p.w,a.1,5,6.l,5,a,4,o.o,8.a,6.n,2.5,i.15,1n.1h,4,j,8.9,g.f,5.7,3.1,3.l,2.6,2,4.3,4,h,e.1,2.2,f.1,b,9.5,5.1,3.l,2.6,2.1,2.1,2.1,w.3,2,k.2,h.8,2.2,2.l,2.6,2.1,2.4,4,j,g.1,o,c.7,3.1,3.l,2.6,2.1,2.4,4,v.1,2.2,g,i,2.5,4.2,2.3,4.1,2,2.1,4.1,4.2,4.b,n,1h.7,2.2,2.m,2.f,4,r.2,3,3.1,v,5.7,2.2,2.m,2.9,2.4,4,w.1,2.1,g.1,i.8,2.2,2.14,3,h,6.2,9.2,p.5,6.h,4.n,2.8,2,3.6,1n.1b,2.1,d.6,1n.1,2,2.4,2.n,2,2.9,2.1,a,3.4,2,m.3,x,1s.7,2.z,s.4,38.16,l,h.5,5.3,4,4.1,8.2,5.c,d,i.11,2,6,3.16,2.98,2.3,3.6,2,2.3,3.14,2.3,3.w,2.3,3.6,2,2.3,3.e,2.1k,2.3,3.1u,12.f,h.2d,3.5,4.h7,3.g,2.p,6.22,7.7,8.h,e.i,f.h,f.c,2.2,g.1f,10,5,1w.2g,8.4,3.x,2,6.1x,b.u,1e.t,3.4,c.17,5.p,1j.m,a.1g,2b,2m.1a,i.7,1j.t,e.1,b.17,r.z,16.2,b.z,3.a,6.16,3.2,16.3,2.5,2.1,4,6.5b,1t.7p,3.5,3.11,3.5,3.7,2,2,2,2.u,3.1g,2.6,2,4.2,2.6,4.3,3.5,5.c,6.2,2.6,39,e,h.c,2u,5,3.9,2,4.4,7,2,2,2.3,2.a,3.3,6.4,5,1h.1,22k.6c,7.3,4.1,d.11,2,6,3.1j,8,h.m,a.6,2.6,2.6,2.6,2.6,2.6,2.6,2.6,29,d2.1,17.4,6.1,5.2d,7.2,2.2h,2.3,6.16,2.2l,i.v,1d.f,e9.533,1t.h3g,1w.19,3.7g,4.f,b.1,l.1a,h.u,3.1x,1e.8,3.2u,3.1u,3.1,2,2.7,m.f,2.2,2.3,2.m,u.1f,f.1d,1r.5,4,2.1,c.r,b.m,q.s,8.1a,t,h.4,2.9,b.4,2.14,o.2,2.7,l.m,4,4.1d,2,4.1,3.4,3,2,p.2,3.a,8.2,d.5,3.5,3.5,a.6,2.6,2.16,2.d,7.36,u.8mb,d.m,5.1c,6it.a5,3.2x,13.6,d.4,6,2.9,2.c,2.4,2,2.1,2.1,2.2z,y.a2,j.1r,3.1h,15.b,39.4,2.3q,11.p,7.p,c.2g,4.5,3.5,3.5,3.2,10.b,2.p,2.i,2.1,2.e,3.d,z.3e,au.s,4.1c,1c.v,e.j,2.7,7.11,b.t,3.z,5.7,1d.4d,j.z,5.z,5.13,9.1f,d.a,2.e,2.6,2.1,2.a,2.e,2.6,2.1,4.1f,d.8m,a.l,b.7,p.5,2.15,2.8,1y.5,3,2.17,2.1,4,3.m,b.m,a.u,1u.i,2.1,b.l,b.p,1z.1j,7.1,1t,g.3,2.2,2.s,17.s,4.s,10.7,2.r,s.1h,b.l,b.i,e.h,33.20,1k.1e,e.1e,e.z,13.r,a.m,6z.15,7.1,h.2,1o.s,b,9.l,17.h,1b.k,s.m,d.1g,1m.1,3,e.18,x.o,r.z,u,3,9.y,4,d.1b,f.3,m,2,10.h,2.o,k.1,1s.6,2,2.3,2.e,2.9,8.1a,13.7,3.1,3.l,2.6,2.1,2.4,4,j,d.4,v.9,2,3,2.11,2,q,2,19.1g,j.3,l.2,v.1b,l.1,2,55.1a,16.3,11.1b,l,1o.16,e,20.q,12.6,56.17,39.1r,w.7,3,3.7,2.1,2.n,g,2,2n.7,3.12,h,2,t,b.13,8,m,c.19,k,j.20,5k.w,w.8,2.10,i,1e.t,35.6,2.1,2.11,m,q.5,2.1,2.v,f,94.i,g,2.c,2.x,3h,28.pl,6f.5f,219.2o,g.tr,i.5,q.32y,6.g6,5a2.t,1cz.fs,8.u,i.26,i.t,j.1b,h.3,w.k,6.i,c1.18,5w.1r,3l.22,6,1v.c,1t.1,2,t.4qf,9.yd,16.9,6w8.3,2.6,2.1,2.82,g,u.2,3,f.3,9.az,1s5.2y,6.c,4.8,8.9,4mf.2c,2.1y,2.1,3,3.1,3.3,2.b,2,2.6,2.1s,2.3,3.7,2.6,2.r,2.3,2.4,2,4.6,2.9f,3.o,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.7,1f9.u,7.5,7a.1p,43.18,b.6,h,8y.t,j.17,dh.r,6d.t,3,ds.6,2.3,2.1,2.e,2.5g,1o.1v,8,xh.3,2.q,2.1,2,3,2.9,2.3,2,2,7,5,2,2,2.2,2.1,2,3,2,2,2,2,2.1,2,3.3,2.6,2.3,2.3,2,2.9,2.g,6.2,2.4,2.g,3et.wyn,x.37d,7.65,3.4g1,f.5rk,g.h9,1wj.f1,15v.3t6,6.38f', + N: '1c.9,3d.1,6,3.2,142.9,3r.9,5j.9,bh.9,3b.9,5.5,31.9,3b.9,3b.9,3.5,33.c,38.9,9.6,2w.9,2x.6,8.i,32.9,2p.9,3b.9,1z.j,7h.9,1z.9,k0.j,oi.2,6o.9,7.9,n.9,8d.9,3l.a,4m.9,7.9,53.9,2f.9,3r.9,7.9,t3,4.5,7.9,5j.1e,3.4,k7.1n,27.l,hj.t,12i,lm,q.8,f.2,9k.3,3v.9,v.7,2.e,x.9,14.e,msh.9,59.9,8x.5,4b.9,13.9,5j.9,n.9,2f.9,bb.9,gev.9,dq.18,d.1k,i.1,9i.q,11.3,u,9,3r.4,5n.9,q7.7,q.6,14.8,24.4,n.5,4h.1,3.f,3.19,1t.8,1h.1,v.2,24.4,2x.7,p.7,16.6,97.5,1d.9,7.9,7r.u,4f.9,17.3,35.6,3r.t,3l.9,1p.9,41.9,8.j,70.9,9j.9,3b.9,af.9,2v.9,7.j,25.b,bp.i,2m.9,if.9,2f.s,6c.9,1z.9,br.9,2v.k,to.32,c02.9,1t3.9,2f.9,3r.9,2.6,en.9,7b.m,imy.9,153.j,d.j,31.o,uu.1d,1tt.9,br.9,dz.9,6w.9,jx.8,3l.9,m0.1m,2.2,2.3,25.18,2.e,qr.c,25g.9', + W: '9.4,j,2t,r,4bk,1vk.a,u.1,6,1c,335', + }; + const cls = (packed) => { + let at = 0, out = ''; + for (const r of packed.split(',')) { + const [g, n = '0'] = r.split('.'); + const a = at + parseInt(g, 36), b = a + parseInt(n, 36); + out += a === b ? `\\u{${a.toString(16)}}` : `\\u{${a.toString(16)}}-\\u{${b.toString(16)}}`; + at = b; + } + return out; + }; + // Pre-tokenizers by GGUF name, with the onig regex a tokenizer.json writes for each and its JS (made on first use). + // onig's \s is Unicode White_Space (U+0085 in, U+FEFF out), and its (?i:'s) also takes ſ, the one letter that + // case-folds onto those (HF: 'ſt is 'ſ, t). + const PRE = { + lfm2: { + hf: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+", + js: () => { + const L = cls(ONIG.L), N = cls(ONIG.N), W = cls(ONIG.W); + return `'(?:[sS\\u017f]|[tT]|[rR][eE]|[vV][eE]|[mM]|[lL][lL]|[dD])|[^\\r\\n${L}${N}]?[${L}]+|[${N}]{1,3}| ?[^${W}${L}${N}]+[\\r\\n]*|[${W}]*[\\r\\n]+|[${W}]+(?![^${W}])|[${W}]+`; + }, + ignoreMerges: true, + }, + }; + // GGUF token types. + const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; + const GLAZE = /^<\|\/?gz_[a-z][\w:.-]{0,60}\|>$/; + const enc = new TextEncoder(); + const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; + // Lone surrogates → U+FFFD, as TextEncoder makes them. + const well = (s) => (s.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g, (m) => (m.length === 2 ? m : '\ufffd'))); + const pair = (m) => { + if (Array.isArray(m) && m.length === 2) return m; + const s = String(m), i = s.indexOf(' '); + if (i <= 0 || i !== s.lastIndexOf(' ') || i === s.length - 1) fail(`merge ${JSON.stringify(s)}`); + return [s.slice(0, i), s.slice(i + 1)]; + }; + + // A token's bytes as the ByteLevel decoder gives them: each character back through the table, or, when one isn't in + // it, the token's own UTF-8 (HF's rule, per token). + const tokenBytes = (s) => { + const out = new Uint8Array(s.length); + for (let i = 0; i < s.length; i++) { + const c = s.charCodeAt(i), b = c < CHAR_BYTE.length ? CHAR_BYTE[c] : -1; + if (b < 0) return enc.encode(s); + out[i] = b; + } + return out; + }; + + // Merge ranks in an open-addressed table of (left id, right id) → rank, and rank → the merged id. + const mergeTable = (merges, vocab) => { + const cap = 1 << Math.max(10, Math.ceil(Math.log2(merges.length * 2 + 1))); + const ka = new Int32Array(cap).fill(-1), kb = new Int32Array(cap), rk = new Int32Array(cap), to = new Int32Array(merges.length); + const mask = cap - 1; + const slot = (a, b) => (Math.imul(a, 0x9e3779b1) ^ Math.imul(b, 0x85ebca6b)) & mask; + merges.forEach((m, r) => { + const [x, y] = pair(m); + const a = vocab.get(x), b = vocab.get(y), c = vocab.get(x + y); + if (a === undefined || b === undefined || c === undefined) fail(`merge ${JSON.stringify(`${x} ${y}`)} is not in the vocabulary`); + let i = slot(a, b); + while (ka[i] !== -1 && !(ka[i] === a && kb[i] === b)) i = (i + 1) & mask; + ka[i] = a; kb[i] = b; rk[i] = r; to[r] = c; // a merge listed twice ranks where it is last, as in HF's map + }); + const rank = (a, b) => { + for (let i = slot(a, b); ; i = (i + 1) & mask) { + const k = ka[i]; + if (k === -1) return -1; + if (k === a && kb[i] === b) return rk[i]; + } + }; + return { rank, to }; + }; + + // v: { tokens, types, merges, pre, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them (byte-level + // strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order. opts.cache: words kept. + const load = (v, opts = {}) => { + const pre = PRE[v.pre]; + if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); + const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); + if (types.length !== tokens.length) fail('types and tokens differ in length'); + const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; + const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(tokens.length); + let longest = 0; + tokens.forEach((t, id) => { + const ty = types[id]; + kinds[id] = ty; + if (ty === T.control || ty === T.user) { if (!specials.has(t)) specials.set(t, id); } + else if (ty !== T.unused) { if (!vocab.has(t)) vocab.set(t, id); longest = Math.max(longest, t.length); } + }); + const byteId = new Int32Array(256); + for (let b = 0; b < 256; b++) { + const id = vocab.get(String.fromCharCode(BYTE_CHAR[b])); + if (id === undefined) fail(`byte 0x${b.toString(16)} has no token`); + byteId[b] = id; + } + const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); + const re = new RegExp((pre.src ||= pre.js()), 'gu'); + // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher. + const addedRe = specials.size ? new RegExp([...specials.keys()].sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; + const glaze = []; // Glaze's own specials, ids from tokens.length on + const size = () => tokens.length + glaze.length; + const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; + + // ---- BPE --------------------------------------------------------------------------------------------------------- + let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); + const grow = (n) => { + if (buf.length < n * 3) buf = new Uint8Array(n * 3 + 64); + if (sym.length < n * 3) { const m = n * 3 + 64; sym = new Int32Array(m); next = new Int32Array(m); prev = new Int32Array(m); heap = new Float64Array(m * 2); } + }; + const P = 2 ** 32; + // A min-heap of rank·2³² + left position: the lowest rank merges first, the leftmost on a tie. + let h = 0; + const push = (key) => { + if (h === heap.length) { const g = new Float64Array(h * 2); g.set(heap); heap = g; } + let i = h++; + while (i > 0) { const p = (i - 1) >> 1; if (heap[p] <= key) break; heap[i] = heap[p]; i = p; } + heap[i] = key; + }; + const pop = () => { + const top = heap[0], last = heap[--h]; + let i = 0; + for (;;) { + let c = 2 * i + 1; + if (c >= h) break; + if (c + 1 < h && heap[c + 1] < heap[c]) c++; + if (heap[c] >= last) break; + heap[i] = heap[c]; i = c; + } + heap[i] = last; + return top; + }; + const bpe = (piece) => { + grow(piece.length); + const n = enc.encodeInto(piece, buf).written; + if (ignoreMerges && n <= longest) { + if (chars.length < n) chars = new Uint16Array(longest); + for (let i = 0; i < n; i++) chars[i] = BYTE_CHAR[buf[i]]; + const whole = vocab.get(String.fromCharCode.apply(null, chars.subarray(0, n))); + if (whole !== undefined) return [whole]; + } + for (let i = 0; i < n; i++) { sym[i] = byteId[buf[i]]; prev[i] = i - 1; next[i] = i + 1 < n ? i + 1 : -1; } + h = 0; + for (let i = 0; i + 1 < n; i++) { const r = rank(sym[i], sym[i + 1]); if (r >= 0) push(r * P + i); } + while (h > 0) { + const key = pop(), r = Math.floor(key / P), i = key - r * P; + const j = next[i]; + if (sym[i] < 0 || j < 0 || rank(sym[i], sym[j]) !== r) continue; + sym[i] = to[r]; sym[j] = -1; + const k = next[j]; + next[i] = k; + if (k >= 0) prev[k] = i; + if (prev[i] >= 0) { const q = rank(sym[prev[i]], sym[i]); if (q >= 0) push(q * P + prev[i]); } + if (k >= 0) { const q = rank(sym[i], sym[k]); if (q >= 0) push(q * P + i); } + } + const out = []; + for (let i = 0; i >= 0; i = next[i]) out.push(sym[i]); + return out; + }; + + // Words (pre-tokenizer pieces) → ids. Two generations of opts.cache words: a word found in the older one moves to the + // newer, and a full newer one becomes the older (deleting a Map's oldest entry one at a time is slow in V8). + const CACHE = opts.cache ?? 16384; + let cache = new Map(), older = new Map(); + const word = (piece) => { + let ids = cache.get(piece); + if (ids) return ids; + if (piece.length > 256) return bpe(piece); + ids = older.get(piece) || bpe(piece); + if (cache.size >= CACHE) { older = cache; cache = new Map(); } + cache.set(piece, ids); + return ids; + }; + + // Ids are collected in one growing Uint32Array. + const sink = () => { + let a = new Uint32Array(256), n = 0; + return { + add(ids) { + if (n + ids.length > a.length) { const g = new Uint32Array(Math.max(a.length * 2, n + ids.length)); g.set(a.subarray(0, n)); a = g; } + for (let i = 0; i < ids.length; i++) a[n++] = ids[i]; + }, + get n() { return n; }, + done: () => a.slice(0, n), + }; + }; + const pieces = (text, each) => { + re.lastIndex = 0; + let last = 0, m; + while ((m = re.exec(text))) { + if (m.index > last) each(text.slice(last, m.index)); + each(m[0]); + last = re.lastIndex; + } + if (last < text.length) each(text.slice(last)); + }; + const plainText = (text, out) => { if (text) pieces(text, (p) => out.add(word(p))); }; + const run = (text, specialsOn, out) => { + const s = well(String(text)); + if (!specialsOn || !addedRe) return plainText(s, out); + addedRe.lastIndex = 0; + let last = 0, m; + while ((m = addedRe.exec(s))) { + plainText(s.slice(last, m.index), out); + out.add([specials.get(m[0])]); + last = addedRe.lastIndex; + } + plainText(s.slice(last), out); + }; + + // ---- decoding ---------------------------------------------------------------------------------------------------- + // Each model token's bytes, made once; null for added and unused ones. + let base = null; + const baseBytes = () => (base ||= tokens.map((t, id) => { const k = kinds[id]; return k === T.control || k === T.user || k === T.unused ? null : tokenBytes(t); })); + const EMPTY = new Uint8Array(0); + const added = new Map(); // id → bytes of an added token's text, for decoding + const idBytes = (id, specialsOn) => { + idOk(id); + const k = id < tokens.length ? kinds[id] : T.control; + if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; + if (k === T.control || k === T.user) { + let b = added.get(id); + if (!b) { b = id < tokens.length ? tokenBytes(tokens[id]) : enc.encode(glaze[id - tokens.length]); added.set(id, b); } + return b; + } + return baseBytes()[id]; + }; + + return { + get size() { return size(); }, + modelSize: tokens.length, + get cached() { return cache.size + older.size; }, + bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, + // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. + encode(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.done(); }, + count(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.n; }, + // parts: [{ text } | { special: name }], name an added token's text or one add() registered. + encodeWith(parts) { + const out = sink(); + for (const p of parts) { + const keys = p && typeof p === 'object' ? Object.keys(p) : []; + if (keys.length !== 1 || !(keys[0] === 'text' || keys[0] === 'special')) fail('a part is { text } or { special }'); + if (keys[0] === 'text') { run(p.text, false, out); continue; } + const id = this.id(p.special); + if (id === undefined) fail(`unknown special ${JSON.stringify(p.special)}`); + out.add([id]); + } + return out.done(); + }, + id(name) { + const id = specials.get(name); + if (id !== undefined) return id; + const g = glaze.indexOf(name); + return g < 0 ? undefined : tokens.length + g; + }, + // Glaze's own special, at the next id after the vocabulary (the same name keeps its id). → its id. + add(name) { + const had = this.id(name); + if (had !== undefined && had >= tokens.length) return had; + if (typeof name !== 'string' || !GLAZE.test(name) || had !== undefined || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); + glaze.push(name); + return size() - 1; + }, + // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are + // never text. + byteStrings: () => [...baseBytes().map((b) => b && b.slice()), ...glaze.map(() => null)], + // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). + decode(ids, { specials: sp = false } = {}) { + const parts = []; + let n = 0; + for (const id of ids) { const b = idBytes(id, sp); if (b.length) { parts.push(b); n += b.length; } } + const all = new Uint8Array(n); + let o = 0; + for (const b of parts) { all.set(b, o); o += b.length; } + return new TextDecoder('utf-8', { ignoreBOM: true }).decode(all); + }, + // push(id) → the text that is complete so far; end() → the rest (an unfinished character as U+FFFD). + decoder({ specials: sp = false } = {}) { + const td = new TextDecoder('utf-8', { ignoreBOM: true }); + return { push: (id) => td.decode(idBytes(id, sp), { stream: true }), end: () => td.decode() }; + }, + // The pre-tokenizer's pieces (for tests and the check). + split(text) { const out = []; pieces(well(String(text)), (p) => out.push(p)); return out; }, + }; + }; + + // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), and + // load()'s cache. + const fromHF = (json, opts = {}) => { + const m = json && json.model; + if (!m || m.type !== 'BPE') fail('model: only BPE'); + if (m.byte_fallback) fail('byte_fallback'); + if (m.dropout) fail('dropout'); + if (m.unk_token != null) fail('unk_token'); + if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); + if (json.normalizer != null) fail('normalizer'); + const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; + const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; + const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; + const pre = Object.keys(PRE).find((k) => PRE[k].hf === split); + if (!pre || !bl) fail('pre_tokenizer'); + if (!json.decoder || json.decoder.type !== 'ByteLevel') fail('decoder'); + if (json.post_processor != null && json.post_processor.type !== 'ByteLevel') fail('post_processor'); + const tokens = [], types = []; + for (const [t, id] of Object.entries(m.vocab)) { tokens[id] = t; types[id] = T.normal; } + for (const a of json.added_tokens || []) { + if (a.lstrip || a.rstrip || a.single_word) fail(`added token ${JSON.stringify(a.content)}: lstrip, rstrip or single_word`); + if (types[a.id] !== undefined) fail(`added token ${JSON.stringify(a.content)} is in the vocabulary`); + tokens[a.id] = a.content; types[a.id] = a.special ? T.control : T.user; + } + for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } + const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; + return load({ tokens, types, merges: m.merges, pre, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); + }; + + // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. + const fromGGUF = (kv, opts) => { + if (!kv || !('tokenizer.ggml.tokens' in kv)) return load(kv, opts); + const g = (k) => kv[`tokenizer.ggml.${k}`]; + if (g('model') !== 'gpt2') fail(`model ${JSON.stringify(g('model'))}: only gpt2 (byte-level BPE)`); + if (!PRE[g('pre')]) fail(`pre-tokenizer ${JSON.stringify(g('pre'))}`); + const id = (k) => (g(k) == null ? null : Number(g(k))); + return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); + }; + + return { load, fromHF, fromGGUF, PRE, TYPES: T }; +})(); +if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/scripts/local-tokenizer-check.js b/scripts/local-tokenizer-check.js new file mode 100644 index 000000000..3ade61693 --- /dev/null +++ b/scripts/local-tokenizer-check.js @@ -0,0 +1,66 @@ +// Glaze's local tokenizer (lib/ai/local/tokenizer.js) against a model's own: every line of a JSONL of references +// { text, ids, plain } made with HF tokenizers must give exactly `ids` with added tokens parsed (specials: true), and +// exactly `plain` (HF with its added tokens taken out) the default way, and decode back to the text. Then encode speed. +// Not in the suite: it needs the model's tokenizer.json and the references, which stay out of the repo. +// node scripts/local-tokenizer-check.js [--config tokenizer_config.json] +// [--gguf kv.json (the GGUF's tokenizer.* keys as JSON: checked the same way)] [--out results.json] +const fs = require('node:fs'); +const K = require('../lib/ai/local/tokenizer.js'); + +const args = process.argv.slice(2); +const flag = (name) => { const i = args.indexOf(name); return i < 0 ? null : args.splice(i, 2)[1]; }; +const config = flag('--config'); +const gguf = flag('--gguf'); +const out = flag('--out'); +const [tokPath, refPath] = args; +if (!tokPath || !refPath) { console.error('usage: node scripts/local-tokenizer-check.js [--config f] [--gguf f] [--out f]'); process.exit(2); } + +const cfg = config ? JSON.parse(fs.readFileSync(config, 'utf8')) : {}; +const name = (t) => (t && typeof t === 'object' ? t.content : t); +const loaders = { hf: () => K.fromHF(JSON.parse(fs.readFileSync(tokPath, 'utf8')), { bos: name(cfg.bos_token), eos: name(cfg.eos_token), pad: name(cfg.pad_token) }) }; +if (gguf) loaders.gguf = () => K.fromGGUF(JSON.parse(fs.readFileSync(gguf, 'utf8'))); +const refs = fs.readFileSync(refPath, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)); +const well = (s) => (String.prototype.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff](?![\udc00-\udfff])|(? n + Buffer.byteLength(well(r.text)), 0); + +const firstDiff = (a, b) => { let i = 0; while (i < a.length && i < b.length && a[i] === b[i]) i++; return i; }; +const show = (r, ours, want, what) => { + const i = firstDiff(ours, want); + return `${what} ${r.src || ''}: first difference at id ${i} of ${want.length}; ours ${JSON.stringify(ours.slice(Math.max(0, i - 3), i + 5))} want ${JSON.stringify(want.slice(Math.max(0, i - 3), i + 5))}; text ${JSON.stringify(r.text.slice(0, 160))}`; +}; + +const results = { tokenizer: tokPath, references: refPath, lines: refs.length, bytes, runs: {} }; +let failed = false; +for (const [label, make] of Object.entries(loaders)) { + let t0 = performance.now(); + const tk = make(); + const loadMs = performance.now() - t0; + const bad = []; + let ok = 0; + for (const r of refs) { + const errs = []; + const sp = [...tk.encode(r.text, { specials: true })]; + if (sp.length !== r.ids.length || firstDiff(sp, r.ids) !== sp.length) errs.push(show(r, sp, r.ids, 'specials')); + if (r.plain) { + const pl = [...tk.encode(r.text)]; + if (pl.length !== r.plain.length || firstDiff(pl, r.plain) !== pl.length) errs.push(show(r, pl, r.plain, 'plain')); + if (tk.decode(pl) !== well(r.text)) errs.push(`decode ${r.src || ''}: ${JSON.stringify(r.text.slice(0, 160))}`); + } + if (tk.decode(sp, { specials: true }) !== well(r.text)) errs.push(`decode specials ${r.src || ''}: ${JSON.stringify(r.text.slice(0, 160))}`); + if (errs.length) bad.push(...errs); else ok++; + } + // Speed: a fresh tokenizer (cold word cache), then the same text again (warm). + const fresh = make(); + const time = (tk2) => { const s = performance.now(); let n = 0; for (const r of refs) n += tk2.encode(r.text).length; return [performance.now() - s, n]; }; + const [coldMs, ids] = time(fresh); + const [warmMs] = time(fresh); + const mbs = (ms) => +(bytes / 1e6 / (ms / 1000)).toFixed(2); + results.runs[label] = { match: ok, of: refs.length, mismatches: bad.slice(0, 50), loadMs: Math.round(loadMs), ids, coldMBs: mbs(coldMs), warmMBs: mbs(warmMs) }; + console.log(`${label}: ${ok}/${refs.length} lines match exactly; load ${Math.round(loadMs)} ms; encode ${(bytes / 1e6).toFixed(2)} MB → ${ids} ids: cold ${mbs(coldMs)} MB/s, warm ${mbs(warmMs)} MB/s`); + for (const m of bad.slice(0, 20)) console.log(` ${m}`); + if (ok !== refs.length) failed = true; + if (mbs(coldMs) < 1) { console.log(` cold encode under 1 MB/s`); failed = true; } +} +if (out) fs.writeFileSync(out, JSON.stringify(results, null, 2)); +console.log(failed ? 'FAIL' : 'PASS'); +process.exit(failed ? 1 : 0); diff --git a/test/ai-local-tokenizer.test.js b/test/ai-local-tokenizer.test.js new file mode 100644 index 000000000..9f79368cd --- /dev/null +++ b/test/ai-local-tokenizer.test.js @@ -0,0 +1,222 @@ +// Glaze's own tokenizer for local models (lib/ai/local/tokenizer.js), on a hand-made byte-level BPE vocabulary: merges +// in rank order, bytes with no merge as their own tokens, added tokens only when asked for, page text never producing a +// control token, prompts from text and named specials, Glaze's own specials after the vocabulary, a streaming decoder that +// never splits a character, and decode(encode(x)) === x. scripts/local-tokenizer-check.js holds it to LFM3's real one. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); +const K = require('../lib/ai/local/tokenizer.js'); + +// GPT-2's byte → character table, written out here so the test doesn't trust the module's own. +const BYTE = (() => { + const keep = []; + for (let b = 33; b <= 126; b++) keep.push(b); + for (let b = 161; b <= 172; b++) keep.push(b); + for (let b = 174; b <= 255; b++) keep.push(b); + const out = new Array(256); + let n = 0; + for (let b = 0; b < 256; b++) out[b] = String.fromCharCode(keep.includes(b) ? b : 256 + n++); + return out; +})(); +const bl = (s) => [...new TextEncoder().encode(s)].map((b) => BYTE[b]).join(''); + +// ids 0–255 are the bytes in order; then merged tokens; then added tokens. +const vocab = (merges, extra = []) => { + const tokens = [...BYTE]; + for (const [a, b] of merges) if (!tokens.includes(a + b)) tokens.push(a + b); + for (const t of extra) if (!tokens.includes(t)) tokens.push(t); + return tokens; +}; +const MERGES = [['h', 'e'], ['l', 'l'], ['he', 'll'], ['hell', 'o'], ['Ġ', 'w'], ['o', 'r'], ['Ġw', 'or'], ['Ġwor', 'ld']]; +const neutral = (o = {}) => { + const merges = o.merges || [...MERGES, ['l', 'd']]; + const tokens = vocab(merges, o.extra); + const types = tokens.map(() => 1); + for (const [content, type] of o.added || []) { tokens.push(content); types.push(type); } + return { tokens, types, merges: merges.map((m) => m.join(' ')), pre: 'lfm2', bos: o.bos, eos: o.eos, ...o.more }; +}; +const ADDED = [['<|startoftext|>', 3], ['<|im_start|>', 3], ['<|im_end|>', 3], ['', 4], ['<|a|>', 3], ['<|a|>x', 3]]; +const id = (tk, s) => tk.byteStrings().findIndex((b) => b && Buffer.from(b).toString('latin1') === Buffer.from(s, 'utf8').toString('latin1')); + +test('merges apply in rank order, leftmost first on a tie, and every byte is its own token', () => { + const tk = K.load(neutral()); + const strs = (ids) => [...ids].map((i) => new TextDecoder().decode(tk.byteStrings()[i])); + assert.deepEqual(strs(tk.encode('hello world')), ['hello', ' world']); + // b+c ranks before a+b: abc → a, bc; the other way round → ab, c. + const bc = K.load(neutral({ merges: [['b', 'c'], ['a', 'b']] })); + const ab = K.load(neutral({ merges: [['a', 'b'], ['b', 'c']] })); + assert.deepEqual([...bc.encode('abc')], [97, id(bc, 'bc')]); + assert.deepEqual([...ab.encode('abc')], [id(ab, 'ab'), 99]); + // A merge listed twice ranks where it is listed last (HF's map keeps the last). + const dup = K.load(neutral({ merges: [['a', 'b'], ['b', 'c'], ['a', 'b']] })); + assert.deepEqual([...dup.encode('abc')], [97, id(dup, 'bc')]); + // aaa with a+a: the leftmost pair merges. + const aa = K.load(neutral({ merges: [['a', 'a']] })); + assert.deepEqual([...aa.encode('aaa')], [id(aa, 'aa'), 97]); + // A character no merge covers comes out as its UTF-8 bytes, each a token. + const euro = [...new TextEncoder().encode('€')]; + assert.deepEqual([...tk.encode('€')], euro); + assert.deepEqual([...tk.encode('\0')], [0]); + assert.equal(tk.decode(tk.encode('€\0')), '€\0'); +}); + +test('ignore_merges takes a whole piece that is in the vocabulary, and only when the tokenizer says so', () => { + const extra = [bl('xyz')]; + const on = K.load(neutral({ extra })); + assert.deepEqual(strsOf(on, on.encode('xyz')), ['xyz']); + const hf = (ignore) => ({ model: { type: 'BPE', vocab: Object.fromEntries(vocab([...MERGES, ['l', 'd']], extra).map((t, i) => [t, i])), merges: [...MERGES, ['l', 'd']], ignore_merges: ignore, byte_fallback: false, dropout: null, unk_token: null, continuing_subword_prefix: null, end_of_word_suffix: null, fuse_unk: false }, normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, added_tokens: [] }); + assert.deepEqual(strsOf(K.fromHF(hf(true)), K.fromHF(hf(true)).encode('xyz')), ['xyz']); + const off = K.fromHF(hf(false)); + assert.deepEqual(strsOf(off, off.encode('xyz')), ['x', 'y', 'z']); +}); +const strsOf = (tk, ids) => [...ids].map((i) => new TextDecoder().decode(tk.byteStrings()[i])); +const LFM_PRE = { type: 'Sequence', pretokenizers: [{ type: 'Split', pattern: { Regex: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" }, behavior: 'Isolated', invert: false }, { type: 'ByteLevel', add_prefix_space: false, trim_offsets: true, use_regex: false }] }; + +test('the pre-tokenizer splits as LFM3\'s onig regex does and its character tables (cases measured with HF tokenizers 0.22.2)', () => { + const tk = K.load(neutral()); + const cases = [ + ['1234567', ['123', '456', '7']], [' \n x', [' \n', ' ', ' x']], ['a\r\n\r\nb', ['a', '\r\n\r\n', 'b']], + ["ab'LL", ['ab', "'LL"]], ["'ſt", ["'ſ", 't']], ['\x85!', ['\x85', '!']], ['\ufeff!', ['\ufeff!']], ['\u3000!', ['\u3000', '!']], + ['\u200b!', ['\u200b!']], ['a \u3000b', ['a', ' ', '\u3000b']], ['x\0y', ['x', '\0y']], ['e\u0301', ['e', '\u0301']], + // Letters and digits newer than onig's tables are not letters or digits to it, whatever the runtime's Unicode says. + ['a\u0c5cb', ['a', '\u0c5cb']], ['\u0c5c(y', ['\u0c5c(', 'y']], ['1\u{11de0}', ['1', '\u{11de0}']], + ]; + for (const [text, want] of cases) assert.deepEqual(tk.split(text), want, JSON.stringify(text)); +}); + +test('added tokens split out only with specials: true, leftmost and longest', () => { + const tk = K.load(neutral({ added: ADDED })); + const [sot, ims, ime, think, a, ax] = ADDED.map(([c]) => tk.id(c)); + assert.ok(sot >= 256 && ax === sot + 5, 'added tokens keep their ids'); + const text = 'hi<|im_start|>user<|a|>x<|a|>'; + assert.deepEqual([...tk.encode(text, { specials: true })], [...tk.encode('hi'), ims, ...tk.encode('user'), think, ax, a]); + // Page text, by default: never a control or added token, whatever it spells. + for (const s of [text, '<|im_end|>', '<|startoftext|>', '']) { + const ids = tk.encode(s); + assert.ok(ids instanceof Uint32Array); + assert.ok([...ids].every((i) => i < sot), s); + assert.equal(tk.decode(ids), s); + } + assert.equal(tk.count(text), tk.encode(text).length); + void ime; +}); + +test('encodeWith builds a prompt from text and named specials; text parts stay text', () => { + const v = neutral({ added: ADDED }); + const tk = K.load({ ...v, bos: v.tokens.indexOf('<|startoftext|>') }); + const ids = tk.encodeWith([{ special: '<|startoftext|>' }, { special: '<|im_start|>' }, { text: 'user\n<|im_end|>' }, { special: '<|im_end|>' }]); + assert.deepEqual([...ids], [tk.bos, tk.id('<|im_start|>'), ...tk.encode('user\n<|im_end|>'), tk.id('<|im_end|>')]); + assert.throws(() => tk.encodeWith([{ special: '<|nope|>' }]), /special/); + assert.throws(() => tk.encodeWith([{ special: 'he' }]), /special/, 'an ordinary token is not a special'); + assert.throws(() => tk.encodeWith([{ text: 'a', special: '<|im_end|>' }]), /part/); +}); + +test('Glaze specials go after the vocabulary, once, and are never text', () => { + const tk = K.load(neutral({ added: ADDED })); + const n = tk.size; + const ctx = tk.add('<|gz_ctx|>'); + assert.equal(ctx, n); + assert.equal(tk.add('<|gz_ctx|>'), ctx, 'the same name keeps its id'); + const task = tk.add('<|gz_task:syllabus|>'); + assert.equal(task, n + 1); + assert.equal(tk.size, n + 2); + assert.throws(() => tk.add('gz_ctx'), /name/); + assert.throws(() => tk.add('<|im_start|>'), /name/, 'not a model token'); + const ids = tk.encodeWith([{ special: '<|gz_ctx|>' }, { text: 'hello' }, { special: '<|gz_task:syllabus|>' }]); + assert.deepEqual([...ids], [ctx, ...tk.encode('hello'), task]); + assert.ok(![...tk.encode('<|gz_ctx|>', { specials: true })].includes(ctx), 'text never spells a Glaze special'); + assert.equal(tk.decode(ids), 'hello'); + assert.equal(tk.decode(ids, { specials: true }), '<|gz_ctx|>hello<|gz_task:syllabus|>'); + const bytes = tk.byteStrings(); + assert.equal(bytes.length, tk.size); + assert.equal(bytes[ctx], null); + assert.equal(bytes[tk.id('<|im_start|>')], null); + assert.equal(bytes[tk.id('')], null); + assert.deepEqual([...bytes[0x41]], [0x41]); + assert.deepEqual([...bytes[id(tk, ' world')]], [...Buffer.from(' world')]); +}); + +test('decode skips control tokens unless asked and keeps user-defined ones, as HF does', () => { + const tk = K.load(neutral({ added: ADDED })); + const ids = [tk.id('<|im_start|>'), ...tk.encode('hi'), tk.id('')]; + assert.equal(tk.decode(ids), 'hi'); + assert.equal(tk.decode(ids, { specials: true }), '<|im_start|>hi'); + assert.throws(() => tk.decode([tk.size]), /id/); +}); + +test('the streaming decoder emits only complete characters', () => { + const tk = K.load(neutral({ added: ADDED })); + const text = 'é€😀 👩\u200d👩\u200d👧 hello'; + const ids = [...tk.encode(text)]; + const d = tk.decoder(); + const outs = ids.map((i) => d.push(i)); + assert.deepEqual(outs.slice(0, 2), ['', 'é'], 'é is two byte tokens: nothing until the second'); + assert.ok(outs.every((s) => !/[\ud800-\udbff]$/.test(s) && !s.includes('\ufffd')), 'never half a character'); + assert.equal(outs.join('') + d.end(), text); + const bom = tk.decoder(); + assert.equal([...tk.encode('\ufeffx')].map((i) => bom.push(i)).join('') + bom.end(), '\ufeffx', 'a leading BOM is text'); + const cut = tk.decoder(); + assert.equal(cut.push(0xe2), ''); + assert.equal(cut.end(), '\ufffd', 'an unfinished character at the end becomes U+FFFD'); + const sp = tk.decoder({ specials: true }); + assert.equal(sp.push(0xc3) + sp.push(tk.id('<|im_end|>')) + sp.push(0xa9) + sp.end(), '\ufffd<|im_end|>\ufffd', 'a special between bytes ends the character'); +}); + +test('decode(encode(x)) === x, with any cache size', () => { + const texts = ['hello world', '', ' ', '\r\n\r\n\t x', 'Ünïcödé — 日本語 العربية עברית हिन्दी', '👩\u200d👩\u200d👧\u200d👦🏳\ufe0f\u200d🌈', 'x\0y', '\ufeffa leading BOM', 'a'.repeat(5000), ' '.repeat(300) + 'end', '1234567890']; + for (const cache of [1, 4, undefined]) { + const tk = K.load(neutral({ added: ADDED }), cache && { cache }); + for (const t of texts) { + assert.equal(tk.decode(tk.encode(t)), t, `${JSON.stringify(t.slice(0, 20))} cache ${cache}`); + assert.deepEqual([...tk.encode(t)], [...tk.encode(t)], 'cached the same'); + } + } + const small = K.load(neutral(), { cache: 4 }); + for (let i = 0; i < 100; i++) small.encode(`word${i} `); + assert.ok(small.cached > 0 && small.cached <= 8, `the word cache stays bounded (${small.cached})`); + // A lone surrogate has no UTF-8: it becomes U+FFFD, as TextEncoder makes it (HF refuses such a string). + const tk = K.load(neutral()); + assert.deepEqual([...tk.encode('a\ud800b')], [...tk.encode('a\ufffdb')]); +}); + +test('loaders: HF tokenizer.json and GGUF metadata give the same tokenizer; anything else is refused', () => { + const v = neutral({ added: ADDED }); + const hf = { + model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i]).filter(([, i]) => v.types[i] === 1)), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false, dropout: null, unk_token: null, continuing_subword_prefix: null, end_of_word_suffix: null, fuse_unk: false }, + normalizer: null, pre_tokenizer: LFM_PRE, post_processor: { type: 'ByteLevel' }, decoder: { type: 'ByteLevel' }, + added_tokens: v.tokens.map((content, id) => ({ id, content, special: v.types[id] === 3, normalized: false, lstrip: false, rstrip: false, single_word: false })).filter((t) => v.types[t.id] !== 1), + }; + const gguf = { 'tokenizer.ggml.model': 'gpt2', 'tokenizer.ggml.pre': 'lfm2', 'tokenizer.ggml.tokens': v.tokens, 'tokenizer.ggml.token_type': Int32Array.from(v.types), 'tokenizer.ggml.merges': v.merges, 'tokenizer.ggml.bos_token_id': v.tokens.length - 6, 'tokenizer.ggml.eos_token_id': v.tokens.length - 4, 'tokenizer.ggml.add_bos_token': false }; + const a = K.fromHF(hf, { bos: '<|startoftext|>', eos: '<|im_end|>' }); + const b = K.fromGGUF(gguf); + const c = K.load(v); + const text = 'hello world <|im_start|>€ 12345 '; + for (const tk of [b, c]) { + assert.deepEqual([...tk.encode(text)], [...a.encode(text)]); + assert.deepEqual([...tk.encode(text, { specials: true })], [...a.encode(text, { specials: true })]); + } + assert.equal(a.bos, b.bos); + assert.equal(a.eos, b.eos); + assert.equal(b.addBos, false); + const bad = (patch) => () => K.fromHF({ ...hf, ...patch(hf) }); + assert.throws(bad((h) => ({ model: { ...h.model, byte_fallback: true } })), /byte_fallback/); + assert.throws(bad((h) => ({ model: { ...h.model, type: 'Unigram' } })), /model/); + assert.throws(bad(() => ({ normalizer: { type: 'NFC' } })), /normalizer/); + assert.throws(bad(() => ({ pre_tokenizer: { type: 'Whitespace' } })), /pre_tokenizer/); + assert.throws(bad(() => ({ decoder: { type: 'Metaspace' } })), /decoder/); + assert.throws(bad((h) => ({ added_tokens: [{ ...h.added_tokens[0], lstrip: true }] })), /added/); + assert.throws(() => K.fromGGUF({ ...gguf, 'tokenizer.ggml.pre': 'qwen2' }), /pre/); + assert.throws(() => K.fromGGUF({ ...gguf, 'tokenizer.ggml.model': 'llama' }), /model/); + assert.throws(() => K.load({ ...v, tokens: v.tokens.slice(1), types: v.types.slice(1) }), /byte/); +}); + +test('a classic script, as importScripts loads it: one global, and it works without toWellFormed', () => { + const ctx = vm.createContext({ TextEncoder, TextDecoder }); + vm.runInContext(fs.readFileSync(path.join(__dirname, '../lib/ai/local/tokenizer.js'), 'utf8'), ctx); + assert.equal(typeof ctx.GZ_AI_TOKENIZER.load, 'function'); + vm.runInContext('delete String.prototype.toWellFormed', ctx); + const tk = ctx.GZ_AI_TOKENIZER.load(neutral()); + assert.equal(tk.decode(tk.encode('a\ud800b\udc00😀')), 'a\ufffdb\ufffd😀'); +}); diff --git a/test/ai-worker.test.js b/test/ai-worker.test.js index cf3396ae6..b14f6c5c2 100644 --- a/test/ai-worker.test.js +++ b/test/ai-worker.test.js @@ -155,7 +155,9 @@ test('Firefox\'s event page loads every file of the harness the worker would imp // scripts/package-firefox.sh: the files named in the worker's importScripts calls, as it reads them. const bg = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); const named = new Set([...bg.matchAll(/importScripts\(([^)]*)\)/g)].flatMap(([, a]) => [...a.matchAll(/'([^']+)'/g)].map(([, f]) => `lib/${f}`))); - const shipped = fs.readdirSync(path.join(__dirname, '..', 'lib/ai'), { recursive: true }).filter((f) => f.endsWith('.js') && f !== 'providers/mock.js').map((f) => `lib/ai/${f}`); + // The local engine's files before its provider step names them (4.0.1): the worker doesn't load them yet. + const unwired = ['local/tokenizer.js']; + const shipped = fs.readdirSync(path.join(__dirname, '..', 'lib/ai'), { recursive: true }).filter((f) => f.endsWith('.js') && f !== 'providers/mock.js' && !unwired.includes(f)).map((f) => `lib/ai/${f}`); assert.ok(shipped.length >= 14); for (const f of shipped) assert.ok(named.has(f), `${f} is named in an importScripts call`); assert.ok(!named.has('lib/ai/providers/mock.js'), 'the mock is for tests and the eval'); From 1a745bc48909f23120ed6cd753dc9565d401a34f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:57:29 -0400 Subject: [PATCH 040/907] Test that lists taken away and put back keep every registration through the worker's pass --- scripts/register-update-check.js | 25 +++++++++++++++++++++++++ test/register-update.test.js | 13 +++++++++++++ 2 files changed, 38 insertions(+) diff --git a/scripts/register-update-check.js b/scripts/register-update-check.js index ceca564e2..d47b25161 100644 --- a/scripts/register-update-check.js +++ b/scripts/register-update-check.js @@ -92,6 +92,31 @@ const worker = async (ctx) => until(async () => ctx.serviceWorkers().find((w) => check(canon(await registered(sw)) === canon(want), 'the replacement restores the execution world'); check(JSON.stringify(await pass(sw)) === '[]', 'the next pass leaves the repaired registrations alone'); + // The lists taken away and put back (a restore, an undo): the pass the first write started has read them empty by the + // time the second lands. Held at its read of the registrations until then, it must not unregister the sites, as a page + // loading before the second write's own pass would get no Glaze. + const churn = await sw.evaluate(async (lists) => { + const s = chrome.scripting, calls = [], real = {}; + let open, reads = 0, read; + const held = new Promise((r) => { open = r; }), reading = (n) => new Promise((r) => { read = () => { if (reads >= n) r(); }; read(); }); + for (const k of ['registerContentScripts', 'unregisterContentScripts', 'updateContentScripts', 'getRegisteredContentScripts']) real[k] = s[k]; + for (const k of ['registerContentScripts', 'unregisterContentScripts', 'updateContentScripts']) s[k] = (o, ...rest) => { calls.push([k, (o?.ids || o || []).map((x) => x.id || x)]); return real[k].call(s, o, ...rest); }; + s.getRegisteredContentScripts = async (...a) => { reads++; read?.(); await held; return real.getRegisteredContentScripts.apply(s, a); }; + try { + let first = reading(1); + await chrome.storage.local.remove(Object.keys(lists)); + await first; + first = reading(2); + await chrome.storage.local.set(lists); + open(); + await first; + await serial(() => {}); + } finally { Object.assign(s, real); } + return calls; + }, { canvas: [`${C}/*`, `${A}/*`], ultra: [B] }); + check(JSON.stringify(churn) === '[]', `the lists taken away and put back: no registration is taken away or changed (${JSON.stringify(churn)})`); + check(canon(await registered(sw)) === canon(want), 'and they are as before'); + const page = await ctx.newPage(); page.on('pageerror', (e) => errors.push(String(e))); await page.goto(`${C}/`); check(await until(async () => (await sheets(page)).includes('canvas.css')), `the Canvas site's page gets Glaze (${(await sheets(page)).join(', ')})`); diff --git a/test/register-update.test.js b/test/register-update.test.js index be5057383..feae409ec 100644 --- a/test/register-update.test.js +++ b/test/register-update.test.js @@ -189,6 +189,19 @@ test('a site added: it registers, the others stay through it, and Notes\' hook t }); +test('the lists taken away and put back (a restore, an undo): the pass the first write started takes nothing away', async () => { + const was = await before(); + const w = worker({ registered: was, local, sync }); + // The second write lands while the first write's pass, which read the lists empty, asks what is registered. + let back = false; + w.read = async () => { if (!back) { back = true; w.change('local', local); } }; + w.change('local', { canvas: undefined, moodle: undefined, ultra: undefined }); + await settle(); + assert.ok(back, 'the second write landed during the first pass'); + neverGone(w, was.map((s) => s.id)); + assert.deepEqual(w.calls, [], 'the registrations are left as they were'); +}); + for (const [id, key, value, expected] of [ [`${C}/*`, 'matchOriginAsFallback', true, false], ['gz-notes-home', 'allFrames', true, false], From d48255fe9469ff301160dd87bb5927a449db3228 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:57:29 -0400 Subject: [PATCH 041/907] announce-check waits for the worker's registrations from Node, not with an async waitForFunction --- scripts/announce-check-sites.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/announce-check-sites.js b/scripts/announce-check-sites.js index 8c3db0fd6..99f8ad739 100644 --- a/scripts/announce-check-sites.js +++ b/scripts/announce-check-sites.js @@ -59,10 +59,13 @@ module.exports = async ({ ctx, opts, O, ok, out }) => { await opts.evaluate(async ([s, l]) => { for (const [area, v] of [[chrome.storage.sync, s], [chrome.storage.local, l]]) { await area.remove(Object.keys(await area.get(null)).filter((k) => !(k in v))); await area.set(v); } }, [sync, local]); - await opts.waitForFunction(async ([want]) => { + // Polled from Node: waitForFunction resolves at once on an async predicate (its promise is truthy). + const want = [[`${MD}/*`, !!local.moodle?.length], [`${UB}/ultra/*`, !!local.ultra?.length]]; + const done = () => opts.evaluate(async (w) => { const scripts = await chrome.scripting.getRegisteredContentScripts(); - return want.every(([pattern, on]) => scripts.some((s) => s.matches?.includes(pattern)) === on); - }, [[[`${MD}/*`, !!local.moodle?.length], [`${UB}/ultra/*`, !!local.ultra?.length]]], { timeout: 10000 }); + return w.every(([pattern, on]) => scripts.some((s) => s.matches?.includes(pattern)) === on); + }, want); + ok(await until(done, 10000), `the worker has registered what the lists name (${JSON.stringify(want)})`); }; const settings = (extra = {}) => G.GZ_SANITIZE({ navbar: { on: false }, motion: { level: 'off' }, announce: { delay: 300 }, ...extra, enhance: { ...off, ...extra.enhance } }); await store(settings(), { ultra: [UB], moodle: [`${MD}/*`], freshDefaults: { fresh: false } }); From 8de896232b55406f258cb7e8c778d450567f854d Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:59:16 -0400 Subject: [PATCH 042/907] Test Google sign-in for Drive and the Picker hand-off before they exist --- test/google.test.js | 351 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) create mode 100644 test/google.test.js diff --git a/test/google.test.js b/test/google.test.js new file mode 100644 index 000000000..8eca3bdab --- /dev/null +++ b/test/google.test.js @@ -0,0 +1,351 @@ +// Google sign-in for Drive (lib/google.js, lib/google-config.js) and the Picker hand-off, with chrome.identity, permissions, +// storage and fetch stubbed: connect on each flow, silent renewal, a state that isn't ours refused, Disconnect revoking, no +// token ever in storage, the picker's fragment and the picker page's fallback order. The packaging side +// (scripts/oauth-manifest.js) is at the end. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const G = require('../lib/google.js'); +const C = require('../lib/google-config.js'); +const { oauth } = require('../scripts/oauth-manifest.js'); + +const REDIRECT = 'https://abcdefghijklmnopabcdefghijklmnop.chromiumapp.org/'; +const DRIVE = 'https://www.googleapis.com/auth/drive.file'; +const REAL = { ...C, chrome: '111-chrome.apps.googleusercontent.com', web: '222-web.apps.googleusercontent.com', ios: '333-ios.apps.googleusercontent.com', apiKey: 'AIzaTestKey', appId: '123456789012' }; + +// A worker: flow, config, what Google answers (google(details) → the redirect URL; userinfo), which picker pages load. +const harness = ({ flow = 'chrome', config = REAL, granted = true, firefox = false, client = REAL.chrome, keep = false, up = [true, true], email = 'student@school.edu' } = {}) => { + const local = {}, sync = {}; + const log = { fetch: [], launch: [], auth: [], removed: [], cleared: 0, permRemoved: [], native: [] }; + let clock = 1_000_000; + let n = 0; + const identity = { + getAuthToken: async (d) => { + log.auth.push(d); + if (identity.fail) throw new Error(identity.fail); + return { token: `chrome-tok-${++n}`, grantedScopes: identity.scopes || d.scopes }; + }, + launchWebAuthFlow: async (d) => { log.launch.push(d); return identity.answer(d); }, + getRedirectURL: () => (firefox ? 'https://0123456789abcdef0123456789abcdef01234567.extensions.allizom.org/' : REDIRECT), + removeCachedAuthToken: async ({ token }) => { log.removed.push(token); }, + clearAllCachedAuthTokens: async () => { log.cleared++; }, + // Google's account screen, answering for our state (the default), with the scopes asked for. + answer: (d) => { + const u = new URL(d.url); + if (u.origin === 'https://accounts.google.com') { + const q = u.searchParams; + return `${q.get('redirect_uri')}#${new URLSearchParams({ access_token: `web-tok-${++n}`, token_type: 'Bearer', expires_in: '3599', scope: `email openid ${DRIVE}`, state: q.get('state') })}`; + } + const f = new URLSearchParams(u.hash.slice(1)); + return `${f.get('redirect_uri')}#${new URLSearchParams({ state: f.get('state'), action: 'picked', files: JSON.stringify([{ id: '1AbC_d-EfGh', name: 'Essay draft.docx', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }]) })}`; + }, + }; + const area = (s) => ({ + get: async (k) => structuredClone(Object.fromEntries([].concat(k).filter((x) => x in s).map((x) => [x, s[x]]))), + set: async (v) => { Object.assign(s, structuredClone(v)); }, + remove: async (k) => { for (const x of [].concat(k)) delete s[x]; }, + }); + const fetch = async (url, init = {}) => { + log.fetch.push({ url, init }); + if (url === G.USERINFO) { + return init.headers?.authorization?.startsWith('Bearer ') ? new Response(JSON.stringify({ sub: '1', email, email_verified: true }), { status: 200 }) : new Response('', { status: 401 }); + } + if (url === G.REVOKE) return new Response('', { status: 200 }); + const i = config.picker.indexOf(url); + if (i >= 0) { if (up[i]) return new Response('', { status: 200 }); throw new TypeError('Failed to fetch'); } + throw new Error(`unexpected request: ${url}`); + }; + const g = G.create({ + config, flow, firefox, + identity: () => (granted ? identity : undefined), + permissions: { contains: async ({ permissions }) => granted && permissions.join() === 'identity', remove: async (p) => { log.permRemoved.push(p); return true; } }, + manifest: () => ({ oauth2: { client_id: client } }), + storage: area(local), + fetch, + native: async (m) => { log.native.push(m); return null; }, + keep: async () => keep, + now: () => clock, + }); + return { g, local, sync, log, identity, tick: (ms) => { clock += ms; } }; +}; +// Every token that ever existed, and storage: none may be in it. +const noToken = (h) => { + const text = JSON.stringify(h.local) + JSON.stringify(h.sync); + assert.ok(!/tok-\d/.test(text), `no token in storage: ${text}`); + assert.deepEqual(Object.keys(h.local).filter((k) => k !== 'google'), [], 'storage.local holds only google'); + if (h.local.google) assert.deepEqual(Object.keys(h.local.google).sort(), ['at', 'email', 'flow']); +}; +const careful = (h) => { + for (const { init } of h.log.fetch) assert.deepEqual([init.credentials, init.redirect, init.cache], ['omit', 'error', 'no-store']); +}; + +test('the config: three scopes, the two picker pages in order, placeholders that report setup', () => { + assert.deepEqual(C.scopes, [DRIVE, 'openid', 'email']); + assert.deepEqual(C.picker, ['https://frostdev.io/glaze/drive-picker/', 'https://frostdev-ops.github.io/glaze-drive-picker/']); + for (const k of ['chrome', 'web', 'ios', 'apiKey', 'appId']) assert.match(C[k], /PLACEHOLDER/, `${k} is marked as a placeholder`); + assert.equal(C.native, false, 'Safari waits for the app'); + for (const f of ['chrome', 'web', 'native']) assert.equal(G.ready(C, f), false, f); + assert.equal(G.ready(REAL, 'chrome'), true); + assert.equal(G.ready(REAL, 'web'), true); + assert.equal(G.ready(REAL, 'native'), false, 'native also needs config.native'); + assert.equal(G.ready({ ...REAL, apiKey: '' }, 'web'), false); +}); + +test('placeholders: status says setup on every browser, and connect does nothing', async () => { + for (const flow of ['chrome', 'web']) { + const h = harness({ flow, config: C }); + assert.deepEqual(await h.g.status(), { ok: false, why: 'setup' }); + assert.deepEqual(await h.g.connect(), { ok: false, why: 'setup' }); + assert.deepEqual([h.log.auth, h.log.launch, h.log.fetch], [[], [], []]); + } + const s = harness({ flow: 'native' }); + assert.deepEqual(await s.g.status(), { ok: false, why: 'browser' }, 'Safari: no native sign-in yet'); +}); + +test('which flow: Google Chrome and Chromium use getAuthToken; Edge, Brave and Firefox the web flow; Safari the app', () => { + const brands = (...b) => b.map((brand) => ({ brand, version: '130' })); + assert.equal(G.flowOf({ protocol: 'chrome-extension:', brands: brands('Google Chrome', 'Chromium', 'Not_A Brand') }), 'chrome'); + assert.equal(G.flowOf({ protocol: 'chrome-extension:', brands: brands('Chromium', 'Not=A?Brand') }), 'chrome'); + assert.equal(G.flowOf({ protocol: 'chrome-extension:', brands: brands('Microsoft Edge', 'Chromium', 'Not_A Brand') }), 'web'); + assert.equal(G.flowOf({ protocol: 'chrome-extension:', brands: brands('Brave', 'Chromium', 'Not_A Brand') }), 'web'); + assert.equal(G.flowOf({ protocol: 'chrome-extension:', brands: undefined }), 'web'); + assert.equal(G.flowOf({ protocol: 'moz-extension:' }), 'web'); + assert.equal(G.flowOf({ protocol: 'safari-web-extension:' }), 'native'); +}); + +test('Chrome: connect needs the identity permission, then signs in with getAuthToken for exactly the three scopes', async () => { + const no = harness({ granted: false }); + assert.deepEqual(await no.g.connect(), { ok: false, why: 'permission' }); + assert.deepEqual(no.local, {}); + const h = harness(); + assert.deepEqual(await h.g.status(), { ok: true, flow: 'chrome', email: null }); + assert.deepEqual(await h.g.connect(), { ok: true, email: 'student@school.edu' }); + assert.deepEqual(h.log.auth, [{ interactive: true, scopes: C.scopes }]); + assert.equal(h.log.fetch[0].url, G.USERINFO); + assert.equal(h.log.fetch[0].init.headers.authorization, 'Bearer chrome-tok-1'); + assert.deepEqual(h.local.google, { email: 'student@school.edu', flow: 'chrome', at: 1_000_000 }); + assert.equal(await h.g.account(), 'student@school.edu'); + assert.deepEqual(await h.g.status(), { ok: true, flow: 'chrome', email: 'student@school.edu' }); + // Later, the worker's token never prompts. + assert.equal(await h.g.token(), 'chrome-tok-2'); + assert.deepEqual(h.log.auth[1], { interactive: false, scopes: C.scopes }); + careful(h); + noToken(h); +}); + +test('Chrome: Drive unticked on Google\'s screen is no connection, and Chrome forgets that token', async () => { + const h = harness(); + h.identity.scopes = ['openid', 'email']; + assert.deepEqual(await h.g.connect(), { ok: false, why: 'scope' }); + assert.deepEqual(h.log.removed, ['chrome-tok-1']); + assert.deepEqual(h.local, {}); +}); + +test('Chrome with another manifest client (a dev copy) uses the web flow; a browser without getAuthToken support falls back to it', async () => { + const dev = harness({ client: '999-other.apps.googleusercontent.com' }); + dev.identity.answer = harness().identity.answer; + assert.deepEqual(await dev.g.status(), { ok: true, flow: 'web', email: null }); + assert.equal((await dev.g.connect()).ok, true); + assert.deepEqual([dev.log.auth.length, dev.log.launch.length, dev.local.google.flow], [0, 1, 'web']); + const edge = harness(); + edge.identity.fail = 'This API is not supported on Microsoft Edge.'; + assert.equal((await edge.g.connect()).ok, true); + assert.deepEqual([edge.log.auth.length, edge.log.launch.length, edge.local.google.flow], [1, 1, 'web']); + noToken(edge); +}); + +test('web flow: the implicit flow with the Web client, its state checked, the token only in memory', async () => { + const h = harness({ flow: 'web' }); + assert.deepEqual(await h.g.connect(), { ok: true, email: 'student@school.edu' }); + assert.equal(h.log.launch.length, 1); + const d = h.log.launch[0]; + assert.equal(d.interactive, true); + assert.equal(d.redirect_uri, undefined, 'Chromium: its own redirect'); + const u = new URL(d.url); + assert.equal(`${u.origin}${u.pathname}`, G.AUTH); + assert.deepEqual(Object.fromEntries([...u.searchParams].filter(([k]) => k !== 'state')), { + client_id: REAL.web, response_type: 'token', redirect_uri: REDIRECT, scope: `${DRIVE} openid email`, include_granted_scopes: 'true', prompt: 'select_account', + }); + assert.match(u.searchParams.get('state'), /^[\w-]{43}$/); + assert.equal(await h.g.token(), 'web-tok-1', 'held in this worker until it expires'); + assert.equal(h.log.launch.length, 1); + careful(h); + noToken(h); +}); + +test('web flow: silent renewal with prompt=none and the account as login_hint, never interactive', async () => { + const h = harness({ flow: 'web' }); + await h.g.connect(); + h.tick(3600 * 1000); + assert.equal(await h.g.token(), 'web-tok-2'); + const d = h.log.launch[1]; + assert.deepEqual([d.interactive, d.abortOnLoadForNonInteractive, d.timeoutMsForNonInteractive], [false, false, 10000]); + const q = new URL(d.url).searchParams; + assert.deepEqual([q.get('prompt'), q.get('login_hint')], ['none', 'student@school.edu']); + // Google says interaction is needed: a silent call fails, it doesn't prompt. + h.tick(3600 * 1000); + h.identity.answer = (x) => `${REDIRECT}#${new URLSearchParams({ error: 'interaction_required', state: new URL(x.url).searchParams.get('state') })}`; + await assert.rejects(h.g.token(), (e) => e.why === 'auth'); + assert.equal(h.log.launch.at(-1).interactive, false); + // A new worker (nothing in memory) renews silently too. + noToken(h); +}); + +test('Firefox: the loopback redirect Google accepts, passed as redirect_uri, and no Chromium-only options', async () => { + const h = harness({ flow: 'web', firefox: true }); + await h.g.connect(); + const d = h.log.launch[0]; + assert.equal(d.redirect_uri, 'http://127.0.0.1/mozoauth2/0123456789abcdef0123456789abcdef01234567'); + assert.equal(new URL(d.url).searchParams.get('redirect_uri'), d.redirect_uri); + h.tick(3600 * 1000); + await h.g.token(); + assert.deepEqual(Object.keys(h.log.launch[1]).sort(), ['interactive', 'redirect_uri', 'url']); +}); + +test('a reply that isn\'t for our state is refused, and nothing is kept', async () => { + for (const bad of [ + (d) => `${REDIRECT}#access_token=evil&token_type=Bearer&expires_in=3599&scope=${encodeURIComponent(DRIVE)}&state=someone-else`, + () => `${REDIRECT}#access_token=evil&token_type=Bearer&expires_in=3599&scope=${encodeURIComponent(DRIVE)}`, + (d) => { const s = new URL(d.url).searchParams.get('state'); return `${REDIRECT}#access_token=evil&token_type=Bearer&scope=${encodeURIComponent(DRIVE)}&state=${s}&state=${s}`; }, + (d) => `${REDIRECT}?access_token=evil&token_type=Bearer&scope=${encodeURIComponent(DRIVE)}&state=${new URL(d.url).searchParams.get('state')}`, + ]) { + const h = harness({ flow: 'web' }); + h.identity.answer = bad; + assert.deepEqual(await h.g.connect(), { ok: false, why: 'state' }); + assert.deepEqual(h.local, {}); + assert.ok(!h.log.fetch.some((f) => f.init.headers?.authorization?.includes('evil')), 'the stray token is never used'); + } + const h = harness({ flow: 'web' }); + h.identity.answer = (d) => `${REDIRECT}#${new URLSearchParams({ access_token: 'x', token_type: 'Bearer', scope: 'openid email', state: new URL(d.url).searchParams.get('state') })}`; + assert.deepEqual(await h.g.connect(), { ok: false, why: 'scope' }, 'Drive unticked'); + const c = harness({ flow: 'web' }); + c.identity.launchWebAuthFlow = async () => { throw new Error('The user did not approve access.'); }; + assert.deepEqual(await c.g.connect(), { ok: false, why: 'cancel' }); +}); + +test('disconnect revokes the grant, clears the cached tokens, gives identity back and forgets the account', async () => { + const h = harness(); + await h.g.connect(); + assert.deepEqual(await h.g.disconnect(), { ok: true, revoked: true, had: true }); + const r = h.log.fetch.find((f) => f.url === G.REVOKE); + assert.deepEqual([r.init.method, r.init.headers['content-type'], r.init.body], ['POST', 'application/x-www-form-urlencoded', 'token=chrome-tok-2']); + assert.deepEqual([h.log.removed, h.log.cleared, h.log.permRemoved], [['chrome-tok-2'], 1, [{ permissions: ['identity'] }]]); + assert.deepEqual(h.local, {}); + assert.equal(await h.g.account(), null); + careful(h); + + const w = harness({ flow: 'web' }); + await w.g.connect(); + assert.deepEqual(await w.g.disconnect(), { ok: true, revoked: true, had: true }); + assert.equal(w.log.fetch.find((f) => f.url === G.REVOKE).init.body, 'token=web-tok-1'); + await assert.rejects(w.g.token(), (e) => e.why === 'connect', 'the token is gone from memory'); + assert.deepEqual(w.log.permRemoved, [{ permissions: ['identity'] }]); + + // Calendar sync connected: its permission and Chrome's cache stay. + const k = harness({ keep: true }); + await k.g.connect(); + await k.g.disconnect(); + assert.deepEqual([k.log.cleared, k.log.permRemoved], [0, []]); + assert.deepEqual(k.local, {}); +}); + +test('the picker\'s answer: our state, files with an id, a name and a type, or a cancel', () => { + const s = 'S'.repeat(43); + const file = { id: '1AbC_d-EfGh', name: 'Notes.pdf', mimeType: 'application/pdf' }; + const at = (p) => `${REDIRECT}#${new URLSearchParams(p)}`; + assert.deepEqual(G.picked(at({ state: s, action: 'picked', files: JSON.stringify([file, { ...file, id: 'x2', mimeType: 'application/vnd.google-apps.document', extra: 'dropped' }]) }), s), + [file, { ...file, id: 'x2', mimeType: 'application/vnd.google-apps.document' }]); + assert.deepEqual(G.picked(at({ state: s, action: 'cancel' }), s), []); + const why = (url, st = s) => { try { G.picked(url, st); } catch (e) { return e.why; } }; + assert.equal(why(at({ state: 'other', action: 'cancel' })), 'state'); + assert.equal(why(at({ action: 'cancel' })), 'state'); + assert.equal(why(`${at({ state: s, action: 'cancel' })}&state=${s}`), 'state'); + assert.equal(why(`${REDIRECT}?state=${s}&action=cancel`), 'state', 'the fragment only'); + assert.equal(why(at({ state: s, action: 'picked', files: '[' })), 'picker'); + assert.equal(why(at({ state: s, action: 'picked', files: '[]' })), 'picker'); + assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, id: '../x' }]) })), 'picker'); + assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, mimeType: 'pdf' }]) })), 'picker'); + assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, name: '' }]) })), 'picker'); + assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify(Array(101).fill(file)) })), 'picker'); + assert.equal(why(at({ state: s, error: 'popup_closed' })), 'picker'); +}); + +test('pick: frostdev.io first, its GitHub Pages copy when it doesn\'t load, the token and state in the fragment', async () => { + const h = harness(); + await h.g.connect(); + const r = await h.g.pick(); + assert.deepEqual(r, { ok: true, files: [{ id: '1AbC_d-EfGh', name: 'Essay draft.docx', mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }] }); + const probes = h.log.fetch.filter((f) => C.picker.includes(f.url)); + assert.deepEqual(probes.map((f) => [f.url, f.init.method]), [[C.picker[0], 'HEAD']]); + const d = h.log.launch.at(-1); + assert.equal(d.interactive, true); + const u = new URL(d.url); + assert.equal(`${u.origin}${u.pathname}`, C.picker[0]); + assert.equal(u.search, '', 'nothing in the query'); + const f = new URLSearchParams(u.hash.slice(1)); + assert.deepEqual([f.get('access_token'), f.get('redirect_uri'), f.get('key'), f.get('app_id')], ['chrome-tok-2', REDIRECT, REAL.apiKey, REAL.appId]); + assert.match(f.get('state'), /^[\w-]{43}$/); + careful(h); + noToken(h); + + const down = harness({ up: [false, true] }); + await down.g.connect(); + assert.equal((await down.g.pick()).ok, true); + assert.deepEqual(down.log.fetch.filter((x) => C.picker.includes(x.url)).map((x) => x.url), C.picker); + assert.equal(new URL(down.log.launch.at(-1).url).origin, 'https://frostdev-ops.github.io'); + + const off = harness({ up: [false, false] }); + await off.g.connect(); + assert.deepEqual(await off.g.pick(), { ok: false, why: 'offline' }); + assert.equal(off.log.launch.length, 0); + + const stray = harness(); + await stray.g.connect(); + stray.identity.answer = () => `${REDIRECT}#state=nope&action=picked&files=${encodeURIComponent('[{"id":"a","name":"b","mimeType":"a/b"}]')}`; + assert.deepEqual(await stray.g.pick(), { ok: false, why: 'state' }); + + assert.deepEqual(await harness().g.pick(), { ok: false, why: 'connect' }); +}); + +test('the worker answers glaze:google from Settings only (pick from Glaze\'s own pages), loading lib/google.js on first use', () => { + const src = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); + assert.match(src, /globalThis\.addEventListener\?\.\('install', \(\) => importScripts\('oauth\.js', 'google-config\.js', 'google\.js'\)\)/); + const at = src.indexOf("m?.type !== 'glaze:google'"); + assert.ok(at > 0); + const body = src.slice(at, src.indexOf('return true;', at)); + assert.match(body, /m\.op === 'pick' \? googlePage\(s\) : fromEditor\(s\)/); + // Nothing about Google in any site's first script. + const m = require('../manifest.json'); + for (const c of m.content_scripts) for (const f of c.js) assert.doesNotMatch(fs.readFileSync(path.join(__dirname, '..', f), 'utf8'), /GZ_GOOGLE|glaze:google|GZ_OAUTH|google-config/, f); +}); + +// ---- packaging: what each package's manifest carries for Google ---- +const repo = require('../manifest.json'); +test('packages: placeholders leave Drive out, so a released build asks for no identity and carries no oauth2', () => { + const chrome = oauth(repo, { target: 'chrome', calendar: false, config: C }); + assert.equal(chrome.oauth2, undefined); + assert.ok(!(chrome.optional_permissions || []).includes('identity')); + const firefox = oauth(repo, { target: 'firefox', calendar: false, config: C }); + assert.equal(firefox.oauth2, undefined); + assert.ok(!(firefox.optional_permissions || []).includes('identity')); + const { oauth2, optional_permissions: optional, ...rest } = repo; + assert.deepEqual(chrome, { ...rest, optional_permissions: optional.filter((p) => p !== 'identity') }, 'nothing else changes'); +}); + +test('packages: real ids give Chrome the Drive client and its three scopes, without Calendar\'s while Calendar is off', () => { + const chrome = oauth(repo, { target: 'chrome', calendar: false, config: REAL }); + assert.deepEqual(chrome.oauth2, { client_id: REAL.chrome, scopes: C.scopes }); + assert.deepEqual(chrome.optional_permissions, repo.optional_permissions, 'identity stays optional'); + assert.ok(!chrome.permissions.includes('identity')); + const both = oauth(repo, { target: 'chrome', calendar: true, config: REAL }); + assert.deepEqual(both.oauth2.scopes, ['https://www.googleapis.com/auth/calendar.app.created', ...C.scopes]); + const cal = oauth(repo, { target: 'chrome', calendar: true, config: C }); + assert.deepEqual(cal.oauth2, repo.oauth2, 'Calendar alone: the manifest as it is'); + const web = oauth(repo, { target: 'chrome', calendar: false, config: { ...REAL, chrome: C.chrome } }); + assert.equal(web.oauth2, undefined, 'only the Web client: Edge and Brave sign in, Chrome waits'); + assert.ok(web.optional_permissions.includes('identity')); + const firefox = oauth(repo, { target: 'firefox', calendar: false, config: REAL }); + assert.equal(firefox.oauth2, undefined); + assert.ok(firefox.optional_permissions.includes('identity'), 'Firefox signs in with the Web client'); +}); From c22809e9e462806dd19e11574783b2494b25cedb Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:59:19 -0400 Subject: [PATCH 043/907] Leave registrations to the next pass when the lists changed after a pass read them --- lib/background.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/background.js b/lib/background.js index 36e09058e..bd2c9f160 100644 --- a/lib/background.js +++ b/lib/background.js @@ -1366,7 +1366,7 @@ const shape = (s) => JSON.stringify([[...s.matches || []].sort(), [...s.excludeM // The registrations become `want` (`have`: what is registered, by id) with none of them missing at any moment, as a page can // load between any two calls: a changed one is updated in place, then the missing ones added, then the rest removed. A // browser without updateContentScripts replaces each changed one on its own. -const reconcile = async (want, have) => { +const reconcile = async (want, have, still = async () => true) => { const changed = want.filter((s) => have.has(s.id) && shape(have.get(s.id)) !== shape(s)); let replace = changed, current = have; if (changed.length && chrome.scripting.updateContentScripts) { @@ -1389,10 +1389,15 @@ const reconcile = async (want, have) => { const todo = want.filter((s) => !have.has(s.id)); if (todo.length) await chrome.scripting.registerContentScripts(todo); const gone = [...have.keys()].filter((id) => !want.some((s) => s.id === id)); - if (gone.length) await chrome.scripting.unregisterContentScripts({ ids: gone }); + if (gone.length && await still()) await chrome.scripting.unregisterContentScripts({ ids: gone }); }; +// What a pass decides from: the lists and the switches whose changes start one (below). +const passInputs = () => Promise.all([chrome.storage.local.get(['canvas', 'moodle', 'ultra']), chrome.storage.sync.get(['enabled', 'enhance']).catch(() => null)]); const ensure = async () => { - const stored = await chrome.storage.local.get(['canvas', 'moodle', 'ultra']); + const [stored, switches] = await passInputs(), read = JSON.stringify([stored, switches]); + // A write since this pass read them (the second of a restore's two writes) has a pass of its own coming. Acting on the + // earlier read would unregister sites the lists name again, and a page loading before that pass would get no Glaze. + const still = async () => JSON.stringify(await passInputs()) === read; const on = await glazeOn(); // Only sites whose host Glaze holds: without each is a grant the student can take back (on Firefox a built-in // host too). The lists stay, so a grant later brings everything back. @@ -1418,7 +1423,8 @@ const ensure = async () => { excludeMatches: first.exclude_matches || [], matches: [...new Set([...first.matches, ...canvas, ...ultraEntry().matches, ...ultra.flatMap((s) => s.matches)])] }; // A site taken off its list (Settings' Sites, Reset everything), or whose host was taken back, loses its registration here. const have = new Map((await chrome.scripting.getRegisteredContentScripts()).map((s) => [s.id, s])); - await reconcile([...sites.map((id) => script(id, canvas.includes(id) ? 'canvas' : 'moodle')), ...ultra, ...detect ? [detector()] : [], ...gs ? [GRADESCOPE] : [], ...notes ? [notes] : [], ...gcal ? [gcal] : []], have); + if (!await still()) return; + await reconcile([...sites.map((id) => script(id, canvas.includes(id) ? 'canvas' : 'moodle')), ...ultra, ...detect ? [detector()] : [], ...gs ? [GRADESCOPE] : [], ...notes ? [notes] : [], ...gcal ? [gcal] : []], have, still); // Notes turned on: course homes already open get its button now (notesHomeInto), not at their next load. if (notes && !have.has(NOTES_HOME)) await notesHomeInto(notes); }; From d320a8f42e35991cb4c747c0a75b44417951152b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 13:59:26 -0400 Subject: [PATCH 044/907] Refresh the Safari resource mirror for the registration pass fix --- .../Glaze Extension/Resources/lib/background.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 36e09058e..bd2c9f160 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -1366,7 +1366,7 @@ const shape = (s) => JSON.stringify([[...s.matches || []].sort(), [...s.excludeM // The registrations become `want` (`have`: what is registered, by id) with none of them missing at any moment, as a page can // load between any two calls: a changed one is updated in place, then the missing ones added, then the rest removed. A // browser without updateContentScripts replaces each changed one on its own. -const reconcile = async (want, have) => { +const reconcile = async (want, have, still = async () => true) => { const changed = want.filter((s) => have.has(s.id) && shape(have.get(s.id)) !== shape(s)); let replace = changed, current = have; if (changed.length && chrome.scripting.updateContentScripts) { @@ -1389,10 +1389,15 @@ const reconcile = async (want, have) => { const todo = want.filter((s) => !have.has(s.id)); if (todo.length) await chrome.scripting.registerContentScripts(todo); const gone = [...have.keys()].filter((id) => !want.some((s) => s.id === id)); - if (gone.length) await chrome.scripting.unregisterContentScripts({ ids: gone }); + if (gone.length && await still()) await chrome.scripting.unregisterContentScripts({ ids: gone }); }; +// What a pass decides from: the lists and the switches whose changes start one (below). +const passInputs = () => Promise.all([chrome.storage.local.get(['canvas', 'moodle', 'ultra']), chrome.storage.sync.get(['enabled', 'enhance']).catch(() => null)]); const ensure = async () => { - const stored = await chrome.storage.local.get(['canvas', 'moodle', 'ultra']); + const [stored, switches] = await passInputs(), read = JSON.stringify([stored, switches]); + // A write since this pass read them (the second of a restore's two writes) has a pass of its own coming. Acting on the + // earlier read would unregister sites the lists name again, and a page loading before that pass would get no Glaze. + const still = async () => JSON.stringify(await passInputs()) === read; const on = await glazeOn(); // Only sites whose host Glaze holds: without each is a grant the student can take back (on Firefox a built-in // host too). The lists stay, so a grant later brings everything back. @@ -1418,7 +1423,8 @@ const ensure = async () => { excludeMatches: first.exclude_matches || [], matches: [...new Set([...first.matches, ...canvas, ...ultraEntry().matches, ...ultra.flatMap((s) => s.matches)])] }; // A site taken off its list (Settings' Sites, Reset everything), or whose host was taken back, loses its registration here. const have = new Map((await chrome.scripting.getRegisteredContentScripts()).map((s) => [s.id, s])); - await reconcile([...sites.map((id) => script(id, canvas.includes(id) ? 'canvas' : 'moodle')), ...ultra, ...detect ? [detector()] : [], ...gs ? [GRADESCOPE] : [], ...notes ? [notes] : [], ...gcal ? [gcal] : []], have); + if (!await still()) return; + await reconcile([...sites.map((id) => script(id, canvas.includes(id) ? 'canvas' : 'moodle')), ...ultra, ...detect ? [detector()] : [], ...gs ? [GRADESCOPE] : [], ...notes ? [notes] : [], ...gcal ? [gcal] : []], have, still); // Notes turned on: course homes already open get its button now (notesHomeInto), not at their next load. if (notes && !have.has(NOTES_HOME)) await notesHomeInto(notes); }; From a3e041929f94280cc87be3727989d8168e79da38 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:02:25 -0400 Subject: [PATCH 045/907] Add an optional Google sign-in for Drive and the Picker hand-off, off until Google's clients are set up --- i18n/de.js | 6 +- i18n/es.js | 6 +- i18n/fr.js | 6 +- i18n/nl.js | 6 +- i18n/pt-BR.js | 6 +- lib/background.js | 27 ++++ lib/google-config.js | 22 +++ lib/google.js | 236 +++++++++++++++++++++++++++++++ lib/oauth.js | 22 +-- options.html | 3 + options.js | 39 +++++ package.sh | 25 ++-- scripts/oauth-manifest.js | 33 +++++ scripts/package-firefox.sh | 9 +- test/audit-documentation.test.js | 12 +- test/fetch-hosts.test.js | 4 + 16 files changed, 427 insertions(+), 35 deletions(-) create mode 100644 lib/google-config.js create mode 100644 lib/google.js create mode 100644 scripts/oauth-manifest.js diff --git a/i18n/de.js b/i18n/de.js index 691f5601b..2650c3fa1 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "nach Angaben der App", "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", - "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast." + "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze braucht die Anmeldeberechtigung des Browsers, um sich zu verbinden.", + "Connect again and allow Google Drive access.": "Verbinde dich erneut und erlaube den Zugriff auf Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal." }; diff --git a/i18n/es.js b/i18n/es.js index 6a4de5868..ff1611f72 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "según la propia app", "Don't allow": "No permitir", "Unnamed app": "App sin nombre", - "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras." + "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze necesita el permiso de inicio de sesión del navegador para conectarse.", + "Connect again and allow Google Drive access.": "Vuelve a conectar y permite el acceso a Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento." }; diff --git a/i18n/fr.js b/i18n/fr.js index b4c680545..4b9bf36f7 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "selon l’app elle-même", "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", - "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse." + "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze a besoin de l’autorisation de connexion du navigateur pour se connecter.", + "Connect again and allow Google Drive access.": "Reconnecte-toi et autorise l’accès à Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant." }; diff --git a/i18n/nl.js b/i18n/nl.js index 82d2b918d..d42c0c216 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "zoals de app zichzelf noemt", "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", - "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde." + "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze heeft de aanmeldtoestemming van de browser nodig om te verbinden.", + "Connect again and allow Google Drive access.": "Verbind opnieuw en sta toegang tot Google Drive toe.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 36abac48d..fbc4f83be 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "segundo o próprio app", "Don't allow": "Não permitir", "Unnamed app": "App sem nome", - "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta." + "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "O Glaze precisa da permissão de login do navegador para se conectar.", + "Connect again and allow Google Drive access.": "Conecte de novo e permita o acesso ao Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco." }; diff --git a/lib/background.js b/lib/background.js index 36e09058e..fd6d5562f 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3229,6 +3229,33 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { return true; }); +// Google sign-in for Drive (lib/google.js): Settings' Connect, its account and Disconnect, and the Picker for Glaze's own +// pages. Loaded the first time one asks; nothing runs for someone who never connects. The token stays here. +globalThis.addEventListener?.('install', () => importScripts('oauth.js', 'google-config.js', 'google.js')); +let google = null; +const openGoogle = () => { + if (!google) { + if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); + const protocol = new URL(chrome.runtime.getURL('')).protocol; + google = GZ_GOOGLE.create({ + config: GZ_GOOGLE_CONFIG, flow: GZ_GOOGLE.flowOf({ protocol, brands: navigator.userAgentData?.brands }), firefox: protocol === 'moz-extension:', + identity: () => chrome.identity, permissions: chrome.permissions, manifest: () => chrome.runtime.getManifest(), storage: chrome.storage.local, + fetch: (u, init) => fetch(u, init), + native: (message) => chrome.runtime.sendNativeMessage(GZ_GOOGLE.APP, message), + // Calendar sync signs in with the same permission and Chrome's same cache: while it's connected, Disconnect leaves both. + keep: async () => GZ_GCAL.ON && !!(await chrome.storage.local.get('gcal')).gcal?.cal, + }); + } + return google; +}; +const googlePage = (s) => s.id === chrome.runtime.id && typeof s.url === 'string' && ['options.html', 'papers.html', 'writer.html'].some((p) => s.url.split(/[?#]/)[0] === chrome.runtime.getURL(p)); +chrome.runtime.onMessage.addListener((m, s, reply) => { + if (m?.type !== 'glaze:google' || !['status', 'connect', 'disconnect', 'pick'].includes(m.op)) return; + if (!(m.op === 'pick' ? googlePage(s) : fromEditor(s))) { reply({ ok: false, why: 'sender' }); return; } + Promise.resolve().then(() => openGoogle()[m.op]()).then(reply, () => reply({ ok: false, why: 'network' })); + return true; +}); + // A Download all wait owns a private ticket, sent over its byte port and the frame's MessageChannel, never a URL. // A public Allow page's hash is display text, not authority. Only that wait's frame and the tab opened for it may ask. // Download all pings its byte port every 15 s; those onMessage events reset Firefox's idle timer while these waits live. diff --git a/lib/google-config.js b/lib/google-config.js new file mode 100644 index 000000000..602d83383 --- /dev/null +++ b/lib/google-config.js @@ -0,0 +1,22 @@ +// Google sign-in for Drive (lib/google.js): the OAuth clients, the Picker's key and project, the scopes and the picker pages. +// Every PLACEHOLDER is a value the owner creates in Google Cloud. Until the ones a flow needs are real (GZ_GOOGLE.ready), +// the worker answers why: 'setup', Settings shows no Google Drive row, and the packages carry no Drive oauth2 key or +// identity permission (scripts/oauth-manifest.js). Worker only; classic script, module.exports for node. +var GZ_GOOGLE_CONFIG = globalThis.GZ_GOOGLE_CONFIG || { + // "Chrome extension" client, tied to the store item's id: Google Chrome's getAuthToken. The package writes it into the + // manifest's oauth2.client_id; an unpacked copy signs in this way only when its manifest holds the same id. + chrome: 'PLACEHOLDER-chrome.apps.googleusercontent.com', + // "Web application" client: launchWebAuthFlow's implicit flow on Edge, Brave, other Chromium browsers and Firefox. + web: 'PLACEHOLDER-web.apps.googleusercontent.com', + // "iOS" client: Safari, through the app (lib/google.js native; not wired yet). + ios: 'PLACEHOLDER-ios.apps.googleusercontent.com', + // The Picker's browser API key and the Cloud project number (Picker setAppId, which lets drive.file open what's picked). + apiKey: 'PLACEHOLDER', + appId: 'PLACEHOLDER', + // Safari: true once the app answers 'google-auth' (lib/google.js native). + native: false, + scopes: ['https://www.googleapis.com/auth/drive.file', 'openid', 'email'], + // The picker page, then its copy: Glaze works without frostdev.io. + picker: ['https://frostdev.io/glaze/drive-picker/', 'https://frostdev-ops.github.io/glaze-drive-picker/'], +}; +if (typeof module !== 'undefined') module.exports = GZ_GOOGLE_CONFIG; diff --git a/lib/google.js b/lib/google.js new file mode 100644 index 000000000..16e3d8599 --- /dev/null +++ b/lib/google.js @@ -0,0 +1,236 @@ +// Google sign-in for Drive, in the worker only (lib/background.js 'glaze:google'), optional: nothing runs until the student +// presses Connect in Settings → Writer → Papers, whose click asks the browser for the identity permission first. Scopes: +// drive.file, openid and email (lib/google-config.js). The access token never leaves the worker and is never stored: +// Chrome's own cache (getAuthToken) or this worker's memory (launchWebAuthFlow). storage.local google holds { email, flow, at }. +// Flows (flowOf): +// chrome Google Chrome and Chromium: getAuthToken with the browser's Google account and the "Chrome extension" client +// (the packaged manifest's oauth2). +// web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow +// (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). +// native Safari, through the app (native(); not wired yet). +// The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with +// launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and +// the files picked (picked()). Requests carry no cookies, follow no redirect and skip the cache. +// env: { config, flow, firefox, identity() (chrome.identity, which exists once the permission is held), permissions, manifest(), +// storage (storage.local), fetch, native(message), keep() (Calendar sync holds identity too: Disconnect leaves it), now() }. +// Classic script; module.exports for node. +var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { + const O = typeof GZ_OAUTH !== 'undefined' ? GZ_OAUTH : require('./oauth.js'); + const AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'; + const USERINFO = 'https://openidconnect.googleapis.com/v1/userinfo'; + const REVOKE = 'https://oauth2.googleapis.com/revoke'; + const DRIVE = 'https://www.googleapis.com/auth/drive.file'; + const APP = 'io.frostdev.Glaze'; // Safari's native handler + const EARLY = 60e3; // a held token is renewed this long before Google's expiry + + class Fail extends Error { constructor(why) { super(why); this.why = why; } } + + // What each flow needs from the config before it's offered. + const NEEDS = { chrome: ['chrome', 'apiKey', 'appId'], web: ['web', 'apiKey', 'appId'], native: ['ios', 'apiKey', 'appId'] }; + const real = (v) => typeof v === 'string' && /\S/.test(v) && !v.includes('PLACEHOLDER'); + const ready = (c, flow) => !!c && Object.hasOwn(NEEDS, flow) && NEEDS[flow].every((k) => real(c[k])) + && Array.isArray(c.scopes) && c.scopes.includes(DRIVE) && Array.isArray(c.picker) && c.picker.length > 0 && (flow !== 'native' || c.native === true); + + // getAuthToken signs in with the browser's own Google account only in Google Chrome and Chromium: Edge refuses it and Brave + // has no Google sign-in, so those (and anything that doesn't say) take the web flow. + const CHROME = /^(Google Chrome|Chromium|HeadlessChrome)$|Not.?A.?Brand/i; + const flowOf = ({ protocol, brands }) => (protocol === 'safari-web-extension:' ? 'native' + : protocol === 'chrome-extension:' && Array.isArray(brands) && brands.length > 0 && brands.every((b) => CHROME.test(b?.brand)) ? 'chrome' : 'web'); + + const authUrl = (c, { redirect, state, prompt, hint }) => `${AUTH}?${new URLSearchParams({ + client_id: c.web, response_type: 'token', redirect_uri: redirect, scope: c.scopes.join(' '), state, include_granted_scopes: 'true', + ...(prompt && { prompt }), ...(hint && { login_hint: hint }), + })}`; + const pickerUrl = (base, { token, state, redirect, key, appId }) => `${base}#${new URLSearchParams({ access_token: token, state, redirect_uri: redirect, key, app_id: appId })}`; + + // The implicit flow's answer, from the fragment only: our state, a bearer token and the Drive scope (the consent screen + // lets a student untick it). → { token, exp } + const grant = (url, state, now) => { + let r; + try { r = O.reply(url, state, 'hash'); } catch (e) { throw new Fail(e.error === 'state' ? 'state' : e.error === 'access_denied' ? 'cancel' : 'auth'); } + if (typeof r.access_token !== 'string' || !r.access_token || !/^bearer$/i.test(r.token_type || '')) throw new Fail('auth'); + if (!String(r.scope || '').split(/\s+/).includes(DRIVE)) throw new Fail('scope'); + const s = Number(r.expires_in); + return { token: r.access_token, exp: now + (s > 0 ? Math.min(s, 86400) : 3600) * 1000 }; + }; + + // The picker page's answer, from the fragment only: our state, then action=cancel, or action=picked with files, a JSON list + // of at most 100 { id, name, mimeType }. Anything else is 'picker'. + const ID = /^[\w-]{1,256}$/, MIME = /^[\w.+-]{1,127}\/[\w.+-]{1,127}$/; + const picked = (url, state) => { + let r; + try { r = O.reply(url, state, 'hash'); } catch (e) { throw new Fail(e.error === 'state' ? 'state' : 'picker'); } + if (r.action === 'cancel') return []; + let list = null; + try { list = r.action === 'picked' ? JSON.parse(r.files) : null; } catch { /* not JSON */ } + if (!Array.isArray(list) || !list.length || list.length > 100) throw new Fail('picker'); + return list.map((f) => { + if (!ID.test(f?.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); + return { id: f.id, name: f.name, mimeType: f.mimeType }; + }); + }; + + const create = (env) => { + const c = () => env.config; + const now = env.now || Date.now; + const id = () => env.identity(); + let held = null; // the web flow's { token, exp }: this worker's memory only + let queue = Promise.resolve(); + const turn = (f) => { const p = queue.then(f); queue = p.catch(() => {}); return p; }; + const send = (url, init = {}) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); + const saved = async () => { + const g = (await env.storage.get('google')).google; + return typeof g?.email === 'string' && Object.hasOwn(NEEDS, g.flow) ? g : null; + }; + // Chrome signs in with its own client only when the manifest holds it; a copy with another (a dev build) uses the web flow. + const usable = (flow) => ready(c(), flow) && (flow !== 'chrome' || env.manifest()?.oauth2?.client_id === c().chrome); + const flowNow = () => (env.flow === 'chrome' && !usable('chrome') && usable('web') ? 'web' : env.flow); + const fails = (f) => async (...a) => { + try { return await f(...a); } catch (e) { if (e instanceof Fail) return { ok: false, why: e.why }; throw e; } + }; + + // Safari: the app signs in. TODO(Safari): the extension handler (Swift) answers { type: 'google-auth', url } by running + // ASWebAuthenticationSession on url with the iOS client's callback scheme and replying { url } with the URL it came back + // to; the iOS client takes the code flow with PKCE (lib/oauth.js challenge, code). Until then config.native stays false. + const native = async (url) => { + const r = await Promise.resolve().then(() => env.native({ type: 'google-auth', url })).catch(() => null); + if (typeof r?.url !== 'string') throw new Fail('cancel'); + return r.url; + }; + const launch = async (url, interactive, redirect) => { + if (env.flow === 'native') return native(url); + const i = id(); + if (typeof i?.launchWebAuthFlow !== 'function') throw new Fail('permission'); + const details = { url, interactive }; + if (env.firefox && redirect) details.redirect_uri = redirect; + // Google's prompt=none answer can take a page or two: Chromium waits for it rather than giving up at the first load. + if (!env.firefox && !interactive) Object.assign(details, { abortOnLoadForNonInteractive: false, timeoutMsForNonInteractive: 10000 }); + try { return await i.launchWebAuthFlow(details); } catch { throw new Fail(interactive ? 'cancel' : 'auth'); } + }; + // Google accepts no redirect on Firefox's own domain; Firefox's loopback form for this add-on is the one it allows. + const redirect = () => { + const r = id().getRedirectURL(); + return env.firefox ? `http://127.0.0.1/mozoauth2/${new URL(r).hostname.split('.')[0]}` : r; + }; + + const chromeToken = async (interactive) => { + const i = id(); + if (typeof i?.getAuthToken !== 'function') throw new Fail('permission'); + let r; + try { r = await i.getAuthToken({ interactive, scopes: c().scopes }); } catch (e) { + throw new Fail(/support/i.test(String(e?.message || e)) ? 'unsupported' : interactive ? 'cancel' : 'auth'); + } + const token = typeof r === 'string' ? r : r?.token; + if (typeof token !== 'string' || !token) throw new Fail('auth'); + if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(DRIVE)) { + await Promise.resolve().then(() => i.removeCachedAuthToken({ token })).catch(() => {}); // or Chrome hands it back next time + throw new Fail('scope'); + } + return token; + }; + // hint: the connected account, for a silent renewal first; without one only an interactive sign-in can answer. + const webToken = async (interactive, hint) => { + if (held && held.exp - EARLY > now()) return held.token; + held = null; + const once = async (prompt, inter) => { + const state = O.random(), back = redirect(); + return grant(await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter, back), state, now()); + }; + let g = null; + if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state') throw e; return null; }); + else if (!interactive) throw new Fail('auth'); + g ||= await once(hint ? '' : 'select_account', true); + held = g; + return g.token; + }; + const tokenFor = (flow, interactive, hint) => (flow === 'chrome' ? chromeToken(interactive) : webToken(interactive, hint)); + const drop = async (flow, token) => { + if (flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token })).catch(() => {}); + else held = null; + }; + const who = async (flow, token) => { + const r = await send(USERINFO, { headers: { authorization: `Bearer ${token}` } }).catch(() => null); + if (r?.status === 401) { await drop(flow, token); throw new Fail('auth'); } + const j = r?.ok ? await r.json().catch(() => null) : null; + if (typeof j?.email !== 'string' || !/^[^@\s]+@[^@\s]+$/.test(j.email) || j.email.length > 320) throw new Fail('network'); + return j.email; + }; + + // → { ok: true, flow, email (null until connected) } or { ok: false, why: 'setup' (placeholders) | 'browser' (Safari, for now) } + const status = async () => { + const flow = flowNow(); + if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; + if (!usable(flow)) return { ok: false, why: 'setup' }; + return { ok: true, flow, email: (await saved())?.email || null }; + }; + // From Settings' click, once the page holds the identity permission. → { ok, email } or { ok: false, why } + const connect = () => turn(fails(async () => { + const s = await status(); + if (!s.ok) return s; + if (!await Promise.resolve(env.permissions.contains({ permissions: ['identity'] })).catch(() => false)) return { ok: false, why: 'permission' }; + if (s.flow === 'native') return { ok: false, why: 'browser' }; + let flow = s.flow, token; + held = null; + if (flow === 'chrome') { + try { token = await chromeToken(true); } catch (e) { if (e.why !== 'unsupported' || !usable('web')) throw e.why === 'unsupported' ? new Fail('browser') : e; flow = 'web'; } + } + if (flow === 'web') token = await webToken(true, ''); + try { + const email = await who(flow, token); + await env.storage.set({ google: { email, flow, at: now() } }); + return { ok: true, email }; + } catch (e) { held = null; throw e; } + })); + // The worker's token for a Drive request; interactive only for something the student pressed. + const token = async ({ interactive = false } = {}) => { + const g = await saved(); + if (!g) throw new Fail('connect'); + if (!usable(g.flow)) throw new Fail('setup'); + return tokenFor(g.flow, interactive, g.email); + }; + const account = async () => (await saved())?.email || null; + // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and + // the identity permission dropped, the account forgotten. → { ok, revoked: whether Google confirmed, had: a token to revoke } + const disconnect = () => turn(async () => { + const g = await saved(); + const t = g && usable(g.flow) ? await tokenFor(g.flow, false, g.email).catch(() => null) : null; + const r = t && await send(REVOKE, { + method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), + }).catch(() => null); + held = null; + const i = id(); + if (t && g.flow === 'chrome') await Promise.resolve().then(() => i?.removeCachedAuthToken({ token: t })).catch(() => {}); + if (!await Promise.resolve().then(() => env.keep?.()).catch(() => false)) { + if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); + } + await env.storage.remove('google'); + return { ok: true, revoked: !!r?.ok, had: !!t }; + }); + // The first picker page that answers, else null: frostdev.io's, then its GitHub Pages copy (which answers every origin). + const reach = async () => { + for (const base of c().picker) { + const r = await send(base, { method: 'HEAD', signal: AbortSignal.timeout(8000) }).catch(() => null); + if (r?.ok) return base; + } + return null; + }; + // From a click on a Glaze page. → { ok: true, files: [{ id, name, mimeType }] (none: cancelled) } or { ok: false, why } + const pick = fails(async () => { + const g = await saved(); + if (!g) return { ok: false, why: 'connect' }; + if (env.flow === 'native') return { ok: false, why: 'browser' }; + const t = await token({ interactive: true }); + const base = await reach(); + if (!base) return { ok: false, why: 'offline' }; + const state = O.random(); + const url = await launch(pickerUrl(base, { token: t, state, redirect: id().getRedirectURL(), key: c().apiKey, appId: c().appId }), true); + return { ok: true, files: picked(url, state) }; + }); + + return { status, connect, token, account, disconnect, pick }; + }; + + return { AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, flowOf, authUrl, pickerUrl, grant, picked, create }; +})(); +if (typeof module !== 'undefined') module.exports = GZ_GOOGLE; diff --git a/lib/oauth.js b/lib/oauth.js index e1172bc92..69ba6dc6e 100644 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -1,5 +1,5 @@ -// PKCE (RFC 7636) and the redirect check for calendar sign-in. Pure: no endpoints, -// no client IDs, no requests. Nothing loads it yet; W13 does, from the service worker only. +// PKCE (RFC 7636), states and the redirect check for sign-in. Pure: no endpoints, no client IDs, no requests. The worker +// loads it for Google Drive (lib/google.js); never a page or a content script. const GZ_OAUTH = (() => { const b64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const random = () => b64url(crypto.getRandomValues(new Uint8Array(32))); // 43 characters, 256 bits @@ -10,16 +10,22 @@ const GZ_OAUTH = (() => { // state, a missing or different one, or any repeated response parameter (RFC 6749 §3.1) is 'state'. // It checks state only: the caller must know the URL came back to its own redirect URI, as // chrome.identity.launchWebAuthFlow guarantees for https://.chromiumapp.org/*. - const code = (redirect, state) => { + const fail = (error, description = '') => Object.assign(new Error(description || error), { error }); + // The redirect's parameters as an object, under the same checks: from the query, else the fragment; `from` 'hash' reads + // the fragment only (an implicit-flow token, or the Drive picker's answer, never travels in a query). + const reply = (redirect, state, from) => { const u = new URL(redirect); - const p = new URLSearchParams(u.search || u.hash.slice(1)); // Microsoft can answer in the fragment - const fail = (error, description = '') => Object.assign(new Error(description || error), { error }); + const p = new URLSearchParams(from === 'hash' ? u.hash.slice(1) : u.search || u.hash.slice(1)); // Microsoft can answer in the fragment const keys = [...p.keys()]; if (typeof state !== 'string' || !state || keys.length !== new Set(keys).size || p.get('state') !== state) throw fail('state'); if (p.get('error')) throw fail(p.get('error'), p.get('error_description') || ''); - if (!p.get('code')) throw fail('code'); - return p.get('code'); + return Object.fromEntries(p); + }; + const code = (redirect, state) => { + const p = reply(redirect, state); + if (!p.code) throw fail('code'); + return p.code; }; - return { random, challenge, code }; + return { random, challenge, reply, code }; })(); if (typeof module !== 'undefined') module.exports = GZ_OAUTH; diff --git a/options.html b/options.html index 408e6c6f1..ef28ce379 100644 --- a/options.html +++ b/options.html @@ -270,6 +270,8 @@ .wr-rows > .wr-row:first-child { border-block-start: 0; padding-block-start: .1rem; } .wr-name { display: flex; align-items: center; gap: .5rem; min-width: 0; font-size: calc(14px * var(--gz-scale, 1)); } .wr-row > select { min-inline-size: 11rem; } + .wr-acts { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: .6rem; min-width: 0; } + .wr-acts .desc { overflow-wrap: anywhere; } .wr-sub { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; padding-block-start: .9rem; border-block-start: 1px solid var(--gz-border-line, var(--gz-line)); } .wr-sub h3 { margin: 0; font-size: calc(13px * var(--gz-scale, 1)); } .wr-sub .desc { margin-inline-end: auto; } @@ -1527,6 +1529,7 @@

Preview on hover

Slides
Versions kept
How many earlier versions each paper keeps on this device, the oldest going first. The last version you submitted always stays.
+

Saved papers

Documents
    diff --git a/options.js b/options.js index d8cd19727..c7052a3b8 100644 --- a/options.js +++ b/options.js @@ -1157,6 +1157,45 @@ GZ_UI.load().then((s) => { chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.gcal) gcalRender(changes.gcal.newValue); }); } + // ---- Google Drive (lib/google.js, through the worker: 'glaze:google'): Connect, the account, Disconnect ---- + // Hidden until Google's clients are set up for this browser (the worker says why: 'setup' or 'browser'). + { + const gdOp = (op) => new Promise((res) => chrome.runtime.sendMessage({ type: 'glaze:google', op }, (r) => res(chrome.runtime.lastError ? { ok: false, why: 'network' } : r || { ok: false }))); + const gdRender = (s) => { + $('#gdrive').hidden = !s?.ok; + $('#gdrive-who').textContent = s?.email || ''; + $('#gdrive-connect').hidden = !!s?.email; + $('#gdrive-disconnect').hidden = !s?.email; + }; + const gdLoad = () => gdOp('status').then(gdRender); + gdLoad(); + let gdBusy = false; + // Inside the student's click: the browser's identity permission first (optional; nothing is asked before this), then + // Google's own screen, from the worker. + $('#gdrive-connect').addEventListener('click', async (e) => { + if (!e.isTrusted || gdBusy) return; + gdBusy = true; + try { + const asked = chrome.permissions.request({ permissions: ['identity'] }); + if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); + const r = await gdOp('connect'); + if (r.ok) { await gdLoad(); $('#gdrive-disconnect').focus(); return hint(t('Connected.')); } + hint(r.why === 'scope' ? t('Connect again and allow Google Drive access.') : r.why === 'cancel' ? '' : t("Google Drive didn't connect. Try again in a moment."), r.why !== 'cancel'); + } finally { gdBusy = false; } + }); + $('#gdrive-disconnect').addEventListener('click', async (e) => { + if (!e.isTrusted || gdBusy) return; + gdBusy = true; + try { + const r = await gdOp('disconnect'); + await gdLoad(); + $('#gdrive-connect').focus(); + hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), !!r.had && !r.revoked); + } finally { gdBusy = false; } + }); + chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.google) gdLoad(); }); + } + // Another surface changed something: read after our queued saves, and never apply a read older than an edit. const syncSettings = () => { if (pending.size || saving) return; diff --git a/package.sh b/package.sh index 94df0a9ae..7bbdc78f6 100755 --- a/package.sh +++ b/package.sh @@ -17,21 +17,14 @@ chmod -R a+rX manifest.json content.js content-ultra.js content-canvas.js defaul zip -q -r -X "$OUT" manifest.json content.js content-ultra.js content-canvas.js defaults.js lib i18n _locales theme.css motion.css extras.css canvas.css moodle.css features \ popup.html popup.js options.html options.js preview.html reader.html reader.js writer.html writer.js writer.css writer-ai.js writer-ai.css writer-mcp.js writer-compose.js writer-compose.css writer-proof.js writer-proof.css writer-cite.js writer-cite.css writer-paper.js writer-paper.css writer-submit.js writer-submit.css writer-import.js writer-import.css writer-kinds.js writer-objects.js writer-objects.css writer-find.js writer-print.js writer-counts.js writer-edit.css writer-paste.js writer-ruler.js writer-ruler.css papers.html papers.js papers.css papers-motion.js sheet-kind.js sheet-grid.js sheet-data.js sheet.css pdf-kind.js pdf-kind.css slides-kind.js slides-kind.css sharing mods ui.js ui.css fonts icons assets/logo.svg assets/logo-dark.svg assets/art/pets assets/art/stickers assets/art/cursors assets/shaders -x '*.DS_Store' -# Google Calendar sync waits behind its build-time switch (lib/gcal.js ON) until Google verifies the app. While it's -# off, the packaged manifest carries neither its oauth2 client nor the optional identity permission it would ask for. -if [ "$(node -p 'require("./lib/gcal.js").ON')" != true ]; then - stage="$(mktemp -d)" - trap 'rm -rf "$stage"' EXIT - node -e ' - const m = require("./manifest.json"); - delete m.oauth2; - m.optional_permissions = (m.optional_permissions || []).filter((p) => p !== "identity"); - if (!m.optional_permissions.length) delete m.optional_permissions; - process.stdout.write(JSON.stringify(m, null, 2) + "\n"); - ' > "$stage/manifest.json" - chmod a+r "$stage/manifest.json" - zip_abs="$(cd "$dist" && pwd)/glaze-v$version.zip" - (cd "$stage" && zip -q -X "$zip_abs" manifest.json) -fi +# Google: the oauth2 key and the optional identity permission go in only for what uses them (scripts/oauth-manifest.js): +# Calendar sync behind its build-time switch (lib/gcal.js ON, off until Google verifies the app), Drive once its clients +# in lib/google-config.js are real. Without either, the packaged manifest carries neither. +stage="$(mktemp -d)" +trap 'rm -rf "$stage"' EXIT +node scripts/oauth-manifest.js chrome manifest.json > "$stage/manifest.json" +chmod a+r "$stage/manifest.json" +zip_abs="$(cd "$dist" && pwd)/glaze-v$version.zip" +(cd "$stage" && zip -q -X "$zip_abs" manifest.json) echo "packaged Glaze v$version -> $OUT ($(du -h "$OUT" | cut -f1))" diff --git a/scripts/oauth-manifest.js b/scripts/oauth-manifest.js new file mode 100644 index 000000000..c5442b1d0 --- /dev/null +++ b/scripts/oauth-manifest.js @@ -0,0 +1,33 @@ +// What a package's manifest carries for Google (package.sh, scripts/package-firefox.sh): the oauth2 key and the optional +// identity permission, each only while a feature that uses it is on. Google Calendar sync: lib/gcal.js ON, off until Google +// verifies the app. Drive: lib/google-config.js, until its clients are real (lib/google.js ready). Safari carries neither +// (scripts/safari-manifest.sh): its sign-in goes through the app. +// node scripts/oauth-manifest.js → the manifest, to stdout +const CALENDAR = 'https://www.googleapis.com/auth/calendar.app.created'; + +// target 'chrome' (Chrome, Edge and other Chromium stores) or 'firefox'; calendar: lib/gcal.js ON; config: lib/google-config.js. +function oauth(manifest, { target, calendar, config }) { + const { ready } = require('../lib/google.js'); + const m = structuredClone(manifest); + const chrome = target === 'chrome' && ready(config, 'chrome'), web = ready(config, 'web'); + const scopes = [...(target === 'chrome' && calendar ? [CALENDAR] : []), ...(chrome ? config.scopes : [])]; + if (scopes.length) m.oauth2 = { client_id: chrome ? config.chrome : manifest.oauth2.client_id, scopes }; + else delete m.oauth2; + const identity = target === 'chrome' ? calendar || chrome || web : web; + const optional = (manifest.optional_permissions || []).filter((p) => identity || p !== 'identity'); + if (identity && !optional.includes('identity')) optional.unshift('identity'); + if (optional.length) m.optional_permissions = optional; + else delete m.optional_permissions; + return m; +} + +if (require.main === module) { + const [target, file] = process.argv.slice(2); + if (!['chrome', 'firefox'].includes(target) || !file) throw new Error('usage: node scripts/oauth-manifest.js '); + const m = JSON.parse(require('node:fs').readFileSync(file, 'utf8')); + // The Chrome package keeps Calendar's client while Calendar is on; Firefox's starts from that package. + const calendar = require('../lib/gcal.js').ON === true; + process.stdout.write(`${JSON.stringify(oauth(m, { target, calendar, config: require('../lib/google-config.js') }), null, 2)}\n`); +} + +module.exports = { CALENDAR, oauth }; diff --git a/scripts/package-firefox.sh b/scripts/package-firefox.sh index 5429e4c71..368ae6125 100644 --- a/scripts/package-firefox.sh +++ b/scripts/package-firefox.sh @@ -2,7 +2,8 @@ # Package Glaze for Firefox (addons.mozilla.org) from the Chrome package: dist/glaze-v-firefox.zip. # Firefox has no extension service worker: the background runs as an event page from every script the worker # imports, listed in order, and importScripts becomes a no-op. Google Calendar sync needs Chrome's identity API, -# so its oauth2 key and permission go, as in Safari. DIST: the folder the zips go in (default dist). +# so its oauth2 key goes, as in Safari; identity stays only for Drive's web sign-in, once its client is real +# (scripts/oauth-manifest.js). DIST: the folder the zips go in (default dist). set -eu root=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) @@ -48,9 +49,9 @@ const imports = [...fs.readFileSync(background, 'utf8').matchAll(/importScripts\ .flatMap(([, args]) => [...args.matchAll(/'([^']+)'/g)].map(([, f]) => path.posix.join('lib', f))); m.background = { scripts: ['lib/firefox-background.js', ...new Set(imports), 'lib/background.js'] }; delete m.minimum_chrome_version; -delete m.oauth2; -m.optional_permissions = (m.optional_permissions || []).filter((p) => p !== 'identity'); -if (!m.optional_permissions.length) delete m.optional_permissions; +// Google (scripts/oauth-manifest.js): no oauth2 key, which is getAuthToken's; identity only once Drive's Web client is real. +const google = require(path.join(process.cwd(), 'scripts/oauth-manifest.js')).oauth(m, { target: 'firefox', calendar: false, config: require(path.join(process.cwd(), 'lib/google-config.js')) }); +for (const k of ['oauth2', 'optional_permissions']) if (k in google) m[k] = google[k]; else delete m[k]; for (const entry of m.web_accessible_resources || []) delete entry.use_dynamic_url; // Firefox has no declarativeContent (Chrome's lit button) and no addHostAccessRequest; AMO's linter flags both, the second // even behind popup.js's check, so the Firefox popup goes without that line. The button starts on the toolbar for new diff --git a/test/audit-documentation.test.js b/test/audit-documentation.test.js index 7adda2ac6..dfa66363e 100644 --- a/test/audit-documentation.test.js +++ b/test/audit-documentation.test.js @@ -93,8 +93,16 @@ test('BLD-5: the permission text matches the manifest and the packaging scripts' assert.ok(!/doesn't ask for unlimited/i.test(policy)); assert.match(policy, /Glaze asks the browser for\s+`unlimitedStorage`/); assert.equal(G.ON, false, 'Google Calendar sync is built off, so released builds omit identity'); - assert.match(code('package.sh'), /filter\(\(p\) => p !== "identity"\)/); - assert.match(code('scripts/package-firefox.sh'), /filter\(\(p\) => p !== 'identity'\)/); + // Drive's Google clients are placeholders until the owner creates them: once one is real, the package asks for identity, + // and PRIVACY.md must name Drive's sign-in before this changes. + const drive = require('../lib/google-config.js'), { oauth } = require('../scripts/oauth-manifest.js'); + assert.ok(!['chrome', 'web'].some((f) => require('../lib/google.js').ready(drive, f)), 'Drive sign-in is not set up, so released builds omit identity'); + assert.match(code('package.sh'), /node scripts\/oauth-manifest\.js chrome manifest\.json > "\$stage\/manifest\.json"/); + assert.match(code('scripts/package-firefox.sh'), /oauth-manifest\.js'\)\)\.oauth\(m, \{ target: 'firefox'/); + for (const target of ['chrome', 'firefox']) { + const packed = oauth(manifest, { target, calendar: G.ON, config: drive }); + assert.ok(!packed.oauth2 && !(packed.optional_permissions || []).includes('identity'), `${target}: no oauth2, no identity`); + } assert.match(code('scripts/safari-manifest.sh'), /p != 'identity'/); assert.match(policy, /`identity` \(Chrome only, optional\) — not requested by released builds/); assert.match(policy, /Chrome sync \(or Firefox Sync\)/); diff --git a/test/fetch-hosts.test.js b/test/fetch-hosts.test.js index 6179ebe56..4886961d3 100644 --- a/test/fetch-hosts.test.js +++ b/test/fetch-hosts.test.js @@ -51,6 +51,7 @@ const SITES = { { at: 'reach: reachable, fetch: (u, i) => fetch(u, i), task:', kind: 'via', to: 'lib/ai/broker.js' }, { at: 'const channelEnv = () => ({', kind: 'asked', flow: 'a mod channel\'s relay: share.frostdev.io, or one the editor asked for (lib/editor-mods.js Read code)', guard: /reachable\(u\)/, near: 0 }, { at: 'fetch: (url, init) => fetch(url, init),', kind: 'via', to: 'lib/gcal.js', exact: true }, + { at: 'fetch: (u, init) => fetch(u, init),', kind: 'via', to: 'lib/google.js', exact: true }, { at: 'fetch(chrome.runtime.getURL(`icons/icon', kind: 'extension' }, { at: "credentials: 'include', redirect: 'error'", kind: 'asked', flow: 'Gradescope: the Settings switch asks for www.gradescope.com', guard: /reachable\(GZ_GRADESCOPE\.HOST\)/ }, { at: "redirect: wide ? 'follow' : 'manual', cache: 'no-store', signal: ctl.signal", kind: 'asked', flow: 'Canvas Download all: the page\'s Allow frame (features/allow.html) for the file server', guard: /if \(!await reachable\(url\)\)/ }, @@ -90,6 +91,9 @@ const SITES = { { at: 'env.fetch(`${env.api || API}', kind: 'open', host: 'https://www.googleapis.com', why: 'Google Calendar API, CORS for any origin; behind GZ_GCAL.ON (off)' }, { at: 'env.fetch(env.revoke || REVOKE', kind: 'open', host: 'https://oauth2.googleapis.com', why: 'token revocation, CORS for any origin; behind GZ_GCAL.ON (off)' }, ], + // Drive's sign-in (behind lib/google-config.js's placeholders until its clients are real): Google's userinfo and revoke, + // and the HEAD that finds the picker page that loads. + 'lib/google.js': [{ at: 'const send = (url, init = {}) => env.fetch(url', kind: 'open', why: 'openidconnect.googleapis.com and oauth2.googleapis.com answer every origin (CORS); of the picker pages, frostdev.io is a required host and its GitHub Pages copy answers every origin' }], 'lib/fonts.js': [{ at: 'const get = (url, as) => fetch(url', kind: 'open', host: 'https://fonts.googleapis.com', hosts: ['https://fonts.googleapis.com', 'https://fonts.gstatic.com', 'https://cdn.jsdelivr.net'], why: 'Google Fonts CSS, fonts.gstatic.com and the jsDelivr catalog files answer every origin' }], 'options.js': [{ at: 'fetch(GZ_FONTS.cssUrl(name, false)', kind: 'open', host: 'https://fonts.googleapis.com', why: 'whether a typed Google font exists' }], 'lib/site.js': [ From 8fdb60cc38b96550bacb7a1f7468d21dfba273df Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:04:48 -0400 Subject: [PATCH 046/907] Tokenizer: never match an empty added token, and refuse truncation and padding An empty added token made encode with specials loop forever on empty matches; HF ignores it. fromHF took a tokenizer.json's truncation or padding and applied neither. --- lib/ai/local/tokenizer.js | 7 +++++-- .../Resources/lib/ai/local/tokenizer.js | 7 +++++-- test/ai-local-tokenizer.test.js | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/ai/local/tokenizer.js b/lib/ai/local/tokenizer.js index 511f75faa..14834ec0b 100644 --- a/lib/ai/local/tokenizer.js +++ b/lib/ai/local/tokenizer.js @@ -123,8 +123,9 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); const re = new RegExp((pre.src ||= pre.js()), 'gu'); - // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher. - const addedRe = specials.size ? new RegExp([...specials.keys()].sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; + // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. + const matched = [...specials.keys()].filter(Boolean); + const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; const glaze = []; // Glaze's own specials, ids from tokens.length on const size = () => tokens.length + glaze.length; const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; @@ -321,6 +322,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { if (m.unk_token != null) fail('unk_token'); if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); if (json.normalizer != null) fail('normalizer'); + if (json.truncation != null) fail('truncation'); + if (json.padding != null) fail('padding'); const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js index 511f75faa..14834ec0b 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js @@ -123,8 +123,9 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); const re = new RegExp((pre.src ||= pre.js()), 'gu'); - // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher. - const addedRe = specials.size ? new RegExp([...specials.keys()].sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; + // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. + const matched = [...specials.keys()].filter(Boolean); + const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; const glaze = []; // Glaze's own specials, ids from tokens.length on const size = () => tokens.length + glaze.length; const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; @@ -321,6 +322,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { if (m.unk_token != null) fail('unk_token'); if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); if (json.normalizer != null) fail('normalizer'); + if (json.truncation != null) fail('truncation'); + if (json.padding != null) fail('padding'); const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; diff --git a/test/ai-local-tokenizer.test.js b/test/ai-local-tokenizer.test.js index 9f79368cd..789bf3985 100644 --- a/test/ai-local-tokenizer.test.js +++ b/test/ai-local-tokenizer.test.js @@ -220,3 +220,21 @@ test('a classic script, as importScripts loads it: one global, and it works with const tk = ctx.GZ_AI_TOKENIZER.load(neutral()); assert.equal(tk.decode(tk.encode('a\ud800b\udc00😀')), 'a\ufffdb\ufffd😀'); }); + +test('an empty added token is never matched in text, as HF ignores it', () => { + // In a context of its own with a timeout: a matcher that loops on empty matches never returns to the event loop. + const ctx = vm.createContext({ TextEncoder, TextDecoder, v: neutral({ added: [['', 3], ...ADDED] }) }); + vm.runInContext(fs.readFileSync(path.join(__dirname, '../lib/ai/local/tokenizer.js'), 'utf8'), ctx); + const [got, want] = JSON.parse(vm.runInContext(`const tk = GZ_AI_TOKENIZER.load(v); + JSON.stringify([[...tk.encode('hi<|im_start|>', { specials: true }), -1, ...tk.encode('hello', { specials: true })], + [...tk.encode('hi'), tk.id('<|im_start|>'), -1, ...tk.encode('hello')]])`, ctx, { timeout: 2000 })); + assert.deepEqual(got, want); +}); + +test('fromHF refuses truncation and padding, which it would not apply', () => { + const v = neutral(); + const hf = { model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i])), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false }, normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, added_tokens: [], truncation: null, padding: null }; + assert.equal(K.fromHF(hf).decode(K.fromHF(hf).encode('hello world')), 'hello world'); + assert.throws(() => K.fromHF({ ...hf, truncation: { max_length: 8, strategy: 'LongestFirst' } }), /truncation/); + assert.throws(() => K.fromHF({ ...hf, padding: { strategy: 'BatchLongest', pad_id: 0 } }), /padding/); +}); From aeb2b0d3830702a51f1f30b9c8914423a250ebc6 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:11:51 -0400 Subject: [PATCH 047/907] Check Drive's sign-in, the Picker hand-off and Settings' Google Drive row in Chromium against stand-ins for Google --- scripts/google-check.js | 240 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 scripts/google-check.js diff --git a/scripts/google-check.js b/scripts/google-check.js new file mode 100644 index 000000000..2740e5675 --- /dev/null +++ b/scripts/google-check.js @@ -0,0 +1,240 @@ +// Google sign-in for Drive and the Picker hand-off, headless, in the real extension (a staged copy) with its real worker +// and Settings page. One stand-in answers for Google (accounts.google.com's implicit flow, userinfo, revoke) and for the +// picker pages (frostdev.io's and its GitHub Pages copy), through Playwright's route() and, for what route() can't see +// (Chrome loads launchWebAuthFlow's page in a WebContents of its own), through a local HTTPS server those hosts resolve to; +// every other host is refused. Each request is logged with which of the two served it. The worker's config is given +// stand-in ids (the shipped one is placeholders, which hide the row: checked first). The staged manifest holds identity +// (headless can't answer the permission prompt): the page's request is recorded and answered yes, and the worker's +// permissions.remove is recorded. +// web flow: Connect from the click, the real launchWebAuthFlow through the stand-in, the row with the account; a reply +// for another state refused; Pick with frostdev.io down goes to the copy, the token and state in the fragment only, the +// files back; with frostdev.io up it goes there; Disconnect revokes, gives identity back, the row returns to Connect. +// chrome flow: getAuthToken (the check's stand-in for Chrome's account) with the three scopes. +// No token in storage.local, sync or session at any point. +// NODE_PATH= nice node scripts/google-check.js [evidence directory] +const { chromium } = require('playwright'); +const fs = require('node:fs'), os = require('node:os'), path = require('node:path'), crypto = require('node:crypto'); +const env = require('./check-env.js'); + +const CHROME_CLIENT = '111-chrome.apps.googleusercontent.com'; +const ext = env.staged(process.env.EXT || path.join(__dirname, '..'), []); +{ + const file = path.join(ext, 'manifest.json'), m = JSON.parse(fs.readFileSync(file, 'utf8')); + m.permissions = [...m.permissions, 'identity']; + m.optional_permissions = m.optional_permissions.filter((p) => p !== 'identity'); + m.oauth2 = { client_id: CHROME_CLIENT, scopes: require('../lib/google-config.js').scopes }; + fs.writeFileSync(file, JSON.stringify(m, null, 2)); +} +const id = [...crypto.createHash('sha256').update(ext).digest('hex').slice(0, 32)].map((c) => String.fromCharCode(97 + parseInt(c, 16))).join(''); +const REDIRECT = `https://${id}.chromiumapp.org/`; +const REAL = { chrome: CHROME_CLIENT, web: '222-web.apps.googleusercontent.com', apiKey: 'AIzaCheckKey0123456789', appId: '123456789012' }; +const PICKER = require('../lib/google-config.js').picker; +const DRIVE = 'https://www.googleapis.com/auth/drive.file'; +const TOKEN = 'ya29.check-web-token'; +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-google-')); +const out = path.resolve(process.argv[2] || path.join(tmp, 'shots')); +fs.mkdirSync(out, { recursive: true }); +let checks = 0; +const ok = (value, label) => { if (!value) throw new Error(`FAIL ${label}`); checks++; console.log(`ok ${label}`); }; +const pause = (ms) => new Promise((r) => setTimeout(r, ms)); +const until = async (fn, label, ms = 15000) => { const end = Date.now() + ms; for (;;) { const v = await fn().catch(() => null); if (v) return v; if (Date.now() > end) throw new Error(`timed out: ${label}`); await pause(100); } }; + +// The stand-in picker page: the contract of glaze-plans' drive-picker (fragment in, the state and files back). It reports +// what it was handed in the file's name, so the check reads it from what comes back. +const pickerPage = ``; + +// The stand-in, for route() and the server alike: { method, url, headers, body } → { status, headers, body }, or null to +// refuse the connection (frostdev.io while it's down, and anything not named here). +const log = { auth: [], userinfo: [], revoke: [], picker: [], other: [] }; +const google = { stray: false, frostdev: false }; +const CORS = { 'access-control-allow-origin': '*', 'access-control-allow-headers': 'authorization, content-type', 'access-control-allow-methods': 'GET, HEAD, POST' }; +const answer = ({ method, url, headers, body }, via) => { + const u = new URL(url); + const base = PICKER.find((p) => u.href.startsWith(p)); + if (method === 'OPTIONS') return base && base === PICKER[0] && !google.frostdev ? null : { status: 204, headers: CORS, body: '' }; + if (u.origin === 'https://accounts.google.com' && u.pathname === '/o/oauth2/v2/auth') { + const q = Object.fromEntries(u.searchParams); + log.auth.push({ ...q, via }); + const state = google.stray ? 'not-glaze-s-state' : q.state; + return { status: 302, headers: { location: `${q.redirect_uri}#${new URLSearchParams({ access_token: TOKEN, token_type: 'Bearer', expires_in: '3599', scope: `email openid ${DRIVE}`, state })}` }, body: '' }; + } + if (u.href === 'https://openidconnect.googleapis.com/v1/userinfo') { + log.userinfo.push([headers.authorization, via]); + return { status: 200, headers: { ...CORS, 'content-type': 'application/json' }, body: JSON.stringify({ sub: '42', email: 'student@school.edu', email_verified: true }) }; + } + if (u.href === 'https://oauth2.googleapis.com/revoke') { + log.revoke.push([method, body, via]); + return { status: 200, headers: CORS, body: '' }; + } + if (base) { + log.picker.push([method, u.origin, u.search, via]); + if (base === PICKER[0] && !google.frostdev) return null; + return { status: 200, headers: { ...CORS, 'content-type': 'text/html' }, body: method === 'HEAD' ? '' : pickerPage }; + } + // Chromium's own account check (its browser sign-in), now that accounts.google.com resolves here: the browser's, not Glaze's. + if (u.origin === 'https://accounts.google.com' && u.pathname === '/ListAccounts' && u.searchParams.get('source') === 'ChromiumBrowser') return null; + log.other.push(`${via} ${u.href}`); + return null; +}; +const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.googleapis.com', ...PICKER.map((p) => new URL(p).hostname)]; + +(async () => { + let ctx, server; + const errors = []; + try { + await require('./wait-for-browser.js')(); + // The server's certificate: made here, for this run; the browser is told to accept it (--ignore-certificate-errors). + const { execFileSync } = require('node:child_process'); + execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', path.join(tmp, 'key.pem'), '-out', path.join(tmp, 'cert.pem'), '-days', '1', '-subj', '/CN=glaze-google-check'], { stdio: 'ignore' }); + server = require('node:https').createServer({ key: fs.readFileSync(path.join(tmp, 'key.pem')), cert: fs.readFileSync(path.join(tmp, 'cert.pem')) }, (req, res) => { + const chunks = []; + req.on('data', (c) => chunks.push(c)); + req.on('end', () => { + const a = answer({ method: req.method, url: `https://${req.headers.host.replace(/:\d+$/, '')}${req.url}`, headers: req.headers, body: Buffer.concat(chunks).toString() }, 'server'); + if (!a) return req.socket.destroy(); + res.writeHead(a.status, a.headers); + res.end(a.body); + }); + }); + await new Promise((r) => server.listen(0, '127.0.0.1', r)); + const port = server.address().port; + const rules = [...HOSTS.map((h) => `MAP ${h} 127.0.0.1:${port}`), 'MAP * ~NOTFOUND'].join(', '); + ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...env.chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, locale: 'en-US', reducedMotion: 'reduce', + args: [`--disable-extensions-except=${ext}`, `--load-extension=${ext}`, `--host-resolver-rules=${rules}`, '--ignore-certificate-errors', '--disable-background-networking', '--renderer-process-limit=1'] }); + env.noFirstRun(ctx); + await ctx.route('**/*', async (route) => { + const r = route.request(), u = new URL(r.url()); + if (u.protocol === 'chrome-extension:') return route.continue(); + if (u.hostname.endsWith('.chromiumapp.org')) return route.abort(); // Chrome's auth flow takes the address before it loads + const a = answer({ method: r.method(), url: r.url(), headers: r.headers(), body: r.postData() || '' }, 'route'); + return a ? route.fulfill({ status: a.status, headers: a.headers, body: a.body }) : route.abort('connectionrefused'); + }); + const sw = ctx.serviceWorkers().find((w) => w.url().startsWith(`chrome-extension://${id}/`)) || await ctx.waitForEvent('serviceworker', { predicate: (w) => w.url().startsWith(`chrome-extension://${id}/`) }); + const page = await ctx.newPage(); + page.on('pageerror', (e) => errors.push(String(e))); + await page.goto(`chrome-extension://${id}/options.html`); + await env.installed(page); + // Every token the stand-ins hand out, looked for in all three storage areas. + const noToken = async (label) => { + const all = await page.evaluate(async () => JSON.stringify(await Promise.all(['local', 'sync', 'session'].map((a) => chrome.storage[a].get(null))))); + ok(!all.includes(TOKEN) && !all.includes('ya29.check-chrome-token'), `${label}: no token in storage.local, sync or session`); + }; + const row = page.locator('#gdrive'); + // A fresh Settings each time (a goto to the same address and hash would only move to the fragment). + const open = async () => { + await page.goto('about:blank'); + await page.goto(`chrome-extension://${id}/options.html#writer/papers`); + await page.evaluate(() => { + globalThis.__gz = { asked: [] }; + chrome.permissions.request = (o) => { __gz.asked.push([o.permissions, navigator.userActivation.isActive]); return Promise.resolve(true); }; + }); + }; + + // Placeholders: the shipped config. No row, nothing asked of Google. + await open(); + ok(await until(() => sw.evaluate(() => typeof GZ_GOOGLE_CONFIG === 'object' && typeof google === 'object' && google !== null), 'Settings asked the worker'), 'Settings asks the worker for Drive\'s status'); + ok(await sw.evaluate(() => GZ_GOOGLE_CONFIG.chrome.includes('PLACEHOLDER')), 'the shipped config is placeholders'); + await pause(300); + ok(await row.isHidden(), 'placeholders: Settings shows no Google Drive row'); + ok(await page.evaluate(() => chrome.runtime.sendMessage({ type: 'glaze:google', op: 'status' })).then((s) => s.why === 'setup'), 'and the worker says why: setup'); + ok(log.auth.length + log.userinfo.length + log.picker.length === 0, 'and nothing reached Google or the picker'); + + // ---- web flow: the Chrome client left a placeholder, so this Chromium signs in as Edge and Brave do ---- + await sw.evaluate((c) => Object.assign(GZ_GOOGLE_CONFIG, c), { ...REAL, chrome: 'PLACEHOLDER-chrome.apps.googleusercontent.com' }); + // Worker stand-in for removing the required identity of this staged copy: recorded. + await sw.evaluate(() => { globalThis.__gzRemoved = []; const P = chrome.permissions, remove = P.remove.bind(P); P.remove = (o) => (o.permissions?.includes('identity') ? (__gzRemoved.push(o), Promise.resolve(true)) : remove(o)); }); + await open(); + await until(() => row.isVisible(), 'the row'); + ok(await page.locator('#gdrive-connect').isVisible() && await page.locator('#gdrive-disconnect').isHidden() && (await page.locator('#gdrive-who').textContent()) === '', 'set up: the row offers Connect, off until connected'); + await row.scrollIntoViewIfNeeded(); + await page.screenshot({ path: path.join(out, 'google-1-connect.png') }); + + google.stray = true; + await page.locator('#gdrive-connect').click(); + await until(async () => (await page.locator('#hint').textContent()).includes("didn't connect"), 'refused'); + ok(log.auth.length === 1, 'a reply for another state: Google was asked once'); + ok(await page.locator('#gdrive-connect').isVisible() && !await page.evaluate(() => chrome.storage.local.get('google').then((r) => r.google)), 'and refused: nothing kept, Connect still offered'); + ok(log.userinfo.length === 0, 'the stray token was never used'); + google.stray = false; + + await page.locator('#gdrive-connect').click(); + await until(async () => (await page.locator('#gdrive-who').textContent()) === 'student@school.edu', 'connected'); + const q = log.auth.at(-1); + ok(q.client_id === REAL.web && q.response_type === 'token' && q.redirect_uri === REDIRECT && q.scope === `${DRIVE} openid email` && /^[\w-]{43}$/.test(q.state) && q.prompt === 'select_account', 'Connect: the Web client\'s implicit flow, our redirect, the three scopes, a state'); + ok(JSON.stringify(await page.evaluate(() => __gz.asked)) === JSON.stringify([[['identity'], true], [['identity'], true]]), 'each Connect asks for identity alone, inside the click'); + ok(log.userinfo.at(-1)[0] === `Bearer ${TOKEN}`, 'the account is read with the token'); + ok(await page.locator('#gdrive-disconnect').isVisible() && await page.locator('#gdrive-connect').isHidden(), 'connected: the account and Disconnect'); + const saved = await page.evaluate(() => chrome.storage.local.get('google').then((r) => r.google)); + ok(JSON.stringify(Object.keys(saved).sort()) === '["at","email","flow"]' && saved.flow === 'web', 'storage.local google: the account, the flow and when'); + await noToken('connected'); + await page.screenshot({ path: path.join(out, 'google-2-connected.png') }); + + // Pick, frostdev.io down: its copy on GitHub Pages. + const pick = () => page.evaluate(() => chrome.runtime.sendMessage({ type: 'glaze:google', op: 'pick' })); + let r = await pick(); + ok(r.ok && r.files.length === 1 && r.files[0].id === '1AbC_d-EfGh' && r.files[0].mimeType === 'application/pdf', `Pick: the file comes back (${JSON.stringify(r)})`); + ok(r.files[0].name === `true,true,true,true@${new URL(PICKER[1]).host}`, 'from the GitHub Pages copy, handed the token, key and project in the fragment, nothing in the query'); + ok(JSON.stringify(log.picker.slice(0, 2).map(([m, o]) => [m, o])) === JSON.stringify([['HEAD', 'https://frostdev.io'], ['HEAD', 'https://frostdev-ops.github.io']]), 'frostdev.io probed first, then its copy'); + ok(log.picker.every(([, , search]) => search === ''), 'no picker request carries a query'); + google.frostdev = true; + log.picker.length = 0; + r = await pick(); + ok(r.ok && r.files[0].name.endsWith('@frostdev.io'), 'with frostdev.io up, the picker is frostdev.io\'s'); + ok(log.picker.every(([, origin]) => origin === 'https://frostdev.io'), 'and its copy isn\'t asked'); + ok(log.auth.length === 2, 'the held token served both picks: no new sign-in'); + await noToken('after picking'); + + // Disconnect. + await page.locator('#gdrive-disconnect').click(); + await until(async () => await page.locator('#gdrive-connect').isVisible(), 'disconnected'); + ok(JSON.stringify(log.revoke.map(([m, b]) => [m, b])) === JSON.stringify([['POST', `token=${encodeURIComponent(TOKEN)}`]]), 'Disconnect revokes the token with Google'); + ok(await sw.evaluate(() => JSON.stringify(__gzRemoved)) === JSON.stringify([{ permissions: ['identity'] }]), 'and gives identity back'); + ok(!await page.evaluate(() => chrome.storage.local.get('google').then((x) => x.google)) && (await page.locator('#gdrive-who').textContent()) === '', 'and forgets the account: Connect again'); + ok((await page.locator('#hint').textContent()) === 'Disconnected.', 'Disconnected.'); + ok(await page.evaluate(() => chrome.runtime.sendMessage({ type: 'glaze:google', op: 'pick' })).then((x) => x.why === 'connect'), 'Pick after Disconnect: not connected'); + await page.screenshot({ path: path.join(out, 'google-3-disconnected.png') }); + + // ---- chrome flow: the manifest's client is the config's, so this Chromium uses getAuthToken (the check's stand-in) ---- + await sw.evaluate((c) => { + Object.assign(GZ_GOOGLE_CONFIG, c); + globalThis.__gzAuth = []; + chrome.identity.getAuthToken = async (d) => { __gzAuth.push(d); return { token: 'ya29.check-chrome-token', grantedScopes: d.scopes }; }; + chrome.identity.removeCachedAuthToken = async () => {}; + }, REAL); + await open(); + await until(() => row.isVisible(), 'the row, chrome flow'); + const before = log.auth.length; + await page.locator('#gdrive-connect').click(); + await until(async () => (await page.locator('#gdrive-who').textContent()) === 'student@school.edu', 'connected, chrome flow'); + ok(await sw.evaluate(() => JSON.stringify(__gzAuth[0])) === JSON.stringify({ interactive: true, scopes: [DRIVE, 'openid', 'email'] }), 'Chrome: getAuthToken, interactive, for exactly the three scopes'); + ok(log.auth.length === before && log.userinfo.at(-1)[0] === 'Bearer ya29.check-chrome-token', 'and no web flow'); + await noToken('chrome flow'); + await page.locator('#gdrive-disconnect').click(); + await until(async () => await page.locator('#gdrive-connect').isVisible(), 'disconnected, chrome flow'); + ok(log.revoke.at(-1)[1] === 'token=ya29.check-chrome-token', 'Disconnect revokes Chrome\'s token'); + + // Who may ask: a page Glaze doesn't trust for this (the pop-up) is refused. + const popup = await ctx.newPage(); + await popup.goto(`chrome-extension://${id}/popup.html`); + ok(await popup.evaluate(() => chrome.runtime.sendMessage({ type: 'glaze:google', op: 'connect' })).then((x) => x.why === 'sender'), 'the pop-up can\'t connect'); + await popup.close(); + + ok(log.other.length === 0, `no other address asked (${log.other.join(' ')})`); + ok(errors.length === 0, `no page errors (${errors.join(' | ')})`); + const via = (rows, at) => [...new Set(rows.map(at))].join('+'); + console.log(`served by: auth ${via(log.auth, (x) => x.via)}, userinfo ${via(log.userinfo, (x) => x[1])}, revoke ${via(log.revoke, (x) => x[2])}, picker ${via(log.picker, (x) => x[3])}`); + console.log(`${checks} checks passed; screenshots in ${out}`); + } catch (e) { + console.error(e.stack || String(e)); + process.exitCode = 1; + } finally { + await ctx?.close().catch(() => {}); + await new Promise((r) => (server ? server.close(r) : r())); + fs.rmSync(out.startsWith(`${tmp}${path.sep}`) ? path.join(tmp, 'profile') : tmp, { recursive: true, force: true }); + } +})(); From 8aea38efb0f1d52cf11d0ef11114cd73a564e2fa Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:12:19 -0400 Subject: [PATCH 048/907] Tokenizer: Glaze specials take the model's reserved rows GLAZE_TOKENS is a fixed, append-only table; each entry takes the model's next reserved row (a GGUF's [PAD] rows, or the rows after a tokenizer.json's last token up to the model's), so both loaders and training give the same ids (125033 on for LFM3). Nothing grows past the model's rows. --- lib/ai/local/tokenizer.js | 65 ++++++++++--------- .../Resources/lib/ai/local/tokenizer.js | 65 ++++++++++--------- test/ai-local-tokenizer.test.js | 62 ++++++++++++++---- 3 files changed, 123 insertions(+), 69 deletions(-) diff --git a/lib/ai/local/tokenizer.js b/lib/ai/local/tokenizer.js index 14834ec0b..4b70107c5 100644 --- a/lib/ai/local/tokenizer.js +++ b/lib/ai/local/tokenizer.js @@ -51,7 +51,11 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { }; // GGUF token types. const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; - const GLAZE = /^<\|\/?gz_[a-z][\w:.-]{0,60}\|>$/; + // Glaze's own specials. Append-only: their ids are trained into the published weights (each takes the model's next + // reserved row, in this order), so never reorder or remove one. + const GLAZE_TOKENS = Object.freeze(['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>', + ...['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', + 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)]); const enc = new TextEncoder(); const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; // Lone surrogates → U+FFFD, as TextEncoder makes them. @@ -99,15 +103,18 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { return { rank, to }; }; - // v: { tokens, types, merges, pre, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them (byte-level - // strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order. opts.cache: words kept. + // v: { tokens, types, merges, pre, rows?, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them + // (byte-level strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order, rows the + // model's embedding rows (default: the tokens). opts.cache: words kept. const load = (v, opts = {}) => { const pre = PRE[v.pre]; if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); if (types.length !== tokens.length) fail('types and tokens differ in length'); const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; - const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(tokens.length); + const rows = v.rows ?? tokens.length; + if (!Number.isInteger(rows) || rows < tokens.length) fail(`rows ${rows}`); + const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(rows).fill(T.unused); let longest = 0; tokens.forEach((t, id) => { const ty = types[id]; @@ -126,9 +133,12 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. const matched = [...specials.keys()].filter(Boolean); const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; - const glaze = []; // Glaze's own specials, ids from tokens.length on - const size = () => tokens.length + glaze.length; - const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; + // Reserved rows, in id order: the unused ones (a GGUF's [PAD…]), then any rows past the tokens. GLAZE_TOKENS[i] takes + // the i-th, so a GGUF and a tokenizer.json with the model's rows give the same ids. + const reserved = []; + for (let id = 0; id < rows; id++) if (kinds[id] === T.unused) reserved.push(id); + const glaze = new Map(), glazeName = new Map(); // name ↔ id of the Glaze specials added + const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= rows) throw new RangeError(`tokenizer: id ${id}`); return id; }; // ---- BPE --------------------------------------------------------------------------------------------------------- let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); @@ -239,24 +249,23 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { // ---- decoding ---------------------------------------------------------------------------------------------------- // Each model token's bytes, made once; null for added and unused ones. let base = null; - const baseBytes = () => (base ||= tokens.map((t, id) => { const k = kinds[id]; return k === T.control || k === T.user || k === T.unused ? null : tokenBytes(t); })); + const baseBytes = () => (base ||= Array.from({ length: rows }, (_, id) => (kinds[id] === T.normal || kinds[id] === T.unknown || kinds[id] === T.byte ? tokenBytes(tokens[id]) : null))); const EMPTY = new Uint8Array(0); const added = new Map(); // id → bytes of an added token's text, for decoding const idBytes = (id, specialsOn) => { idOk(id); - const k = id < tokens.length ? kinds[id] : T.control; + const k = kinds[id]; if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; if (k === T.control || k === T.user) { let b = added.get(id); - if (!b) { b = id < tokens.length ? tokenBytes(tokens[id]) : enc.encode(glaze[id - tokens.length]); added.set(id, b); } + if (!b) { b = glazeName.has(id) ? enc.encode(glazeName.get(id)) : tokenBytes(tokens[id]); added.set(id, b); } return b; } return baseBytes()[id]; }; return { - get size() { return size(); }, - modelSize: tokens.length, + size: rows, get cached() { return cache.size + older.size; }, bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. @@ -275,23 +284,20 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } return out.done(); }, - id(name) { - const id = specials.get(name); - if (id !== undefined) return id; - const g = glaze.indexOf(name); - return g < 0 ? undefined : tokens.length + g; - }, - // Glaze's own special, at the next id after the vocabulary (the same name keeps its id). → its id. + id(name) { return specials.get(name) ?? glaze.get(name); }, + // A Glaze special (one of GLAZE_TOKENS), in its reserved row; the same name keeps its id. → its id. add(name) { - const had = this.id(name); - if (had !== undefined && had >= tokens.length) return had; - if (typeof name !== 'string' || !GLAZE.test(name) || had !== undefined || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); - glaze.push(name); - return size() - 1; + if (glaze.has(name)) return glaze.get(name); + const i = GLAZE_TOKENS.indexOf(name); + if (i < 0 || specials.has(name) || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); + if (i >= reserved.length) fail(`no reserved row for ${name}: the model has ${reserved.length}`); + const id = reserved[i]; + glaze.set(name, id); glazeName.set(id, name); kinds[id] = T.control; + return id; }, // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are // never text. - byteStrings: () => [...baseBytes().map((b) => b && b.slice()), ...glaze.map(() => null)], + byteStrings: () => baseBytes().map((b) => b && b.slice()), // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). decode(ids, { specials: sp = false } = {}) { const parts = []; @@ -312,8 +318,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { }; }; - // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), and - // load()'s cache. + // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), rows, + // and load()'s cache. const fromHF = (json, opts = {}) => { const m = json && json.model; if (!m || m.type !== 'BPE') fail('model: only BPE'); @@ -340,7 +346,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; - return load({ tokens, types, merges: m.merges, pre, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); + // A tokenizer.json doesn't say how many rows the model has (its config's vocab_size does): opts.rows. + return load({ tokens, types, merges: m.merges, pre, rows: opts.rows, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); }; // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. @@ -353,6 +360,6 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); }; - return { load, fromHF, fromGGUF, PRE, TYPES: T }; + return { load, fromHF, fromGGUF, PRE, TYPES: T, GLAZE_TOKENS }; })(); if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js index 14834ec0b..4b70107c5 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js @@ -51,7 +51,11 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { }; // GGUF token types. const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; - const GLAZE = /^<\|\/?gz_[a-z][\w:.-]{0,60}\|>$/; + // Glaze's own specials. Append-only: their ids are trained into the published weights (each takes the model's next + // reserved row, in this order), so never reorder or remove one. + const GLAZE_TOKENS = Object.freeze(['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>', + ...['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', + 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)]); const enc = new TextEncoder(); const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; // Lone surrogates → U+FFFD, as TextEncoder makes them. @@ -99,15 +103,18 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { return { rank, to }; }; - // v: { tokens, types, merges, pre, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them (byte-level - // strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order. opts.cache: words kept. + // v: { tokens, types, merges, pre, rows?, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them + // (byte-level strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order, rows the + // model's embedding rows (default: the tokens). opts.cache: words kept. const load = (v, opts = {}) => { const pre = PRE[v.pre]; if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); if (types.length !== tokens.length) fail('types and tokens differ in length'); const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; - const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(tokens.length); + const rows = v.rows ?? tokens.length; + if (!Number.isInteger(rows) || rows < tokens.length) fail(`rows ${rows}`); + const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(rows).fill(T.unused); let longest = 0; tokens.forEach((t, id) => { const ty = types[id]; @@ -126,9 +133,12 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. const matched = [...specials.keys()].filter(Boolean); const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; - const glaze = []; // Glaze's own specials, ids from tokens.length on - const size = () => tokens.length + glaze.length; - const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= size()) throw new RangeError(`tokenizer: id ${id}`); return id; }; + // Reserved rows, in id order: the unused ones (a GGUF's [PAD…]), then any rows past the tokens. GLAZE_TOKENS[i] takes + // the i-th, so a GGUF and a tokenizer.json with the model's rows give the same ids. + const reserved = []; + for (let id = 0; id < rows; id++) if (kinds[id] === T.unused) reserved.push(id); + const glaze = new Map(), glazeName = new Map(); // name ↔ id of the Glaze specials added + const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= rows) throw new RangeError(`tokenizer: id ${id}`); return id; }; // ---- BPE --------------------------------------------------------------------------------------------------------- let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); @@ -239,24 +249,23 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { // ---- decoding ---------------------------------------------------------------------------------------------------- // Each model token's bytes, made once; null for added and unused ones. let base = null; - const baseBytes = () => (base ||= tokens.map((t, id) => { const k = kinds[id]; return k === T.control || k === T.user || k === T.unused ? null : tokenBytes(t); })); + const baseBytes = () => (base ||= Array.from({ length: rows }, (_, id) => (kinds[id] === T.normal || kinds[id] === T.unknown || kinds[id] === T.byte ? tokenBytes(tokens[id]) : null))); const EMPTY = new Uint8Array(0); const added = new Map(); // id → bytes of an added token's text, for decoding const idBytes = (id, specialsOn) => { idOk(id); - const k = id < tokens.length ? kinds[id] : T.control; + const k = kinds[id]; if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; if (k === T.control || k === T.user) { let b = added.get(id); - if (!b) { b = id < tokens.length ? tokenBytes(tokens[id]) : enc.encode(glaze[id - tokens.length]); added.set(id, b); } + if (!b) { b = glazeName.has(id) ? enc.encode(glazeName.get(id)) : tokenBytes(tokens[id]); added.set(id, b); } return b; } return baseBytes()[id]; }; return { - get size() { return size(); }, - modelSize: tokens.length, + size: rows, get cached() { return cache.size + older.size; }, bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. @@ -275,23 +284,20 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } return out.done(); }, - id(name) { - const id = specials.get(name); - if (id !== undefined) return id; - const g = glaze.indexOf(name); - return g < 0 ? undefined : tokens.length + g; - }, - // Glaze's own special, at the next id after the vocabulary (the same name keeps its id). → its id. + id(name) { return specials.get(name) ?? glaze.get(name); }, + // A Glaze special (one of GLAZE_TOKENS), in its reserved row; the same name keeps its id. → its id. add(name) { - const had = this.id(name); - if (had !== undefined && had >= tokens.length) return had; - if (typeof name !== 'string' || !GLAZE.test(name) || had !== undefined || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); - glaze.push(name); - return size() - 1; + if (glaze.has(name)) return glaze.get(name); + const i = GLAZE_TOKENS.indexOf(name); + if (i < 0 || specials.has(name) || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); + if (i >= reserved.length) fail(`no reserved row for ${name}: the model has ${reserved.length}`); + const id = reserved[i]; + glaze.set(name, id); glazeName.set(id, name); kinds[id] = T.control; + return id; }, // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are // never text. - byteStrings: () => [...baseBytes().map((b) => b && b.slice()), ...glaze.map(() => null)], + byteStrings: () => baseBytes().map((b) => b && b.slice()), // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). decode(ids, { specials: sp = false } = {}) { const parts = []; @@ -312,8 +318,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { }; }; - // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), and - // load()'s cache. + // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), rows, + // and load()'s cache. const fromHF = (json, opts = {}) => { const m = json && json.model; if (!m || m.type !== 'BPE') fail('model: only BPE'); @@ -340,7 +346,8 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { } for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; - return load({ tokens, types, merges: m.merges, pre, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); + // A tokenizer.json doesn't say how many rows the model has (its config's vocab_size does): opts.rows. + return load({ tokens, types, merges: m.merges, pre, rows: opts.rows, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); }; // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. @@ -353,6 +360,6 @@ var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); }; - return { load, fromHF, fromGGUF, PRE, TYPES: T }; + return { load, fromHF, fromGGUF, PRE, TYPES: T, GLAZE_TOKENS }; })(); if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/test/ai-local-tokenizer.test.js b/test/ai-local-tokenizer.test.js index 789bf3985..a1f01402a 100644 --- a/test/ai-local-tokenizer.test.js +++ b/test/ai-local-tokenizer.test.js @@ -1,6 +1,6 @@ // Glaze's own tokenizer for local models (lib/ai/local/tokenizer.js), on a hand-made byte-level BPE vocabulary: merges // in rank order, bytes with no merge as their own tokens, added tokens only when asked for, page text never producing a -// control token, prompts from text and named specials, Glaze's own specials after the vocabulary, a streaming decoder that +// control token, prompts from text and named specials, Glaze's own specials in the model's reserved rows, a streaming decoder that // never splits a character, and decode(encode(x)) === x. scripts/local-tokenizer-check.js holds it to LFM3's real one. const test = require('node:test'); const assert = require('node:assert/strict'); @@ -113,29 +113,69 @@ test('encodeWith builds a prompt from text and named specials; text parts stay t assert.throws(() => tk.encodeWith([{ text: 'a', special: '<|im_end|>' }]), /part/); }); -test('Glaze specials go after the vocabulary, once, and are never text', () => { - const tk = K.load(neutral({ added: ADDED })); - const n = tk.size; +test('Glaze specials take the model\'s reserved rows, once, and are never text', () => { + const v = neutral({ added: ADDED }); + const n = v.tokens.length; + const rows = n + K.GLAZE_TOKENS.length + 2; + const tk = K.load({ ...v, rows }); + assert.equal(tk.size, rows, 'the model\'s rows'); + const task = tk.add('<|gz_task:syllabus.read|>'); const ctx = tk.add('<|gz_ctx|>'); - assert.equal(ctx, n); + assert.equal(ctx, n, 'its place in the table, whatever order they are added in'); + assert.equal(task, n + K.GLAZE_TOKENS.indexOf('<|gz_task:syllabus.read|>')); assert.equal(tk.add('<|gz_ctx|>'), ctx, 'the same name keeps its id'); - const task = tk.add('<|gz_task:syllabus|>'); - assert.equal(task, n + 1); - assert.equal(tk.size, n + 2); - assert.throws(() => tk.add('gz_ctx'), /name/); + assert.equal(tk.size, rows, 'never grows'); + assert.throws(() => tk.add('<|gz_task:syllabus|>'), /name/, 'not in the table'); assert.throws(() => tk.add('<|im_start|>'), /name/, 'not a model token'); - const ids = tk.encodeWith([{ special: '<|gz_ctx|>' }, { text: 'hello' }, { special: '<|gz_task:syllabus|>' }]); + const ids = tk.encodeWith([{ special: '<|gz_ctx|>' }, { text: 'hello' }, { special: '<|gz_task:syllabus.read|>' }]); assert.deepEqual([...ids], [ctx, ...tk.encode('hello'), task]); assert.ok(![...tk.encode('<|gz_ctx|>', { specials: true })].includes(ctx), 'text never spells a Glaze special'); assert.equal(tk.decode(ids), 'hello'); - assert.equal(tk.decode(ids, { specials: true }), '<|gz_ctx|>hello<|gz_task:syllabus|>'); + assert.equal(tk.decode(ids, { specials: true }), '<|gz_ctx|>hello<|gz_task:syllabus.read|>'); + assert.equal(tk.decode([n + 1, n + 2], { specials: true }), '', 'a reserved row nothing has taken is no text'); const bytes = tk.byteStrings(); assert.equal(bytes.length, tk.size); assert.equal(bytes[ctx], null); + assert.equal(bytes[n + 1], null); assert.equal(bytes[tk.id('<|im_start|>')], null); assert.equal(bytes[tk.id('')], null); assert.deepEqual([...bytes[0x41]], [0x41]); assert.deepEqual([...bytes[id(tk, ' world')]], [...Buffer.from(' world')]); + assert.throws(() => tk.decode([tk.size]), /id/); +}); + +test('the Glaze table: fixed order, and the same ids from a GGUF\'s [PAD] rows and after a tokenizer.json\'s last token', () => { + assert.deepEqual(K.GLAZE_TOKENS.slice(0, 4), ['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>']); + assert.deepEqual(K.GLAZE_TOKENS.slice(4), ['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)); + assert.ok(Object.isFrozen(K.GLAZE_TOKENS)); + const v = neutral({ added: ADDED }); + const n = v.tokens.length, rows = n + 20; + // LFM3's layout: tokenizer.json ends at its last added token; the GGUF lists the rest of the rows as [PADi], unused. + const hf = { + model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i]).filter(([, i]) => v.types[i] === 1)), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false }, + normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, + added_tokens: v.tokens.map((content, i) => ({ id: i, content, special: v.types[i] === 3 })).filter((t) => v.types[t.id] !== 1), + }; + const pads = Array.from({ length: rows - n }, (_, i) => `[PAD${n + i}]`); + const gguf = { 'tokenizer.ggml.model': 'gpt2', 'tokenizer.ggml.pre': 'lfm2', 'tokenizer.ggml.tokens': [...v.tokens, ...pads], 'tokenizer.ggml.token_type': [...v.types, ...pads.map(() => 5)], 'tokenizer.ggml.merges': v.merges }; + const a = K.fromHF(hf, { rows }), b = K.fromGGUF(gguf); + assert.equal(a.size, rows); + assert.equal(b.size, rows); + const order = [...K.GLAZE_TOKENS].reverse(); + const ia = order.map((t) => a.add(t)), ib = order.map((t) => b.add(t)); + assert.deepEqual(ia, ib); + assert.deepEqual(K.GLAZE_TOKENS.map((t) => a.id(t)), K.GLAZE_TOKENS.map((_, i) => n + i), 'from the first free row, in table order'); + for (const tk of [a, b]) { + assert.equal(tk.decode([a.id('<|gz_data|>')]), ''); + assert.equal(tk.decode([a.id('<|gz_data|>')], { specials: true }), '<|gz_data|>'); + assert.equal(tk.byteStrings()[a.id('<|gz_data|>')], null); + } + // Fewer reserved rows than the table: the rest are refused, and nothing grows past the model. + const few = K.load({ ...v, rows: n + 3 }); + assert.equal(few.add('<|gz_data|>'), n + 2); + assert.throws(() => few.add('<|/gz_data|>'), /reserved/); + assert.equal(few.size, n + 3); + assert.throws(() => K.fromHF(hf).add('<|gz_ctx|>'), /reserved/, 'a tokenizer.json alone says nothing of the model\'s rows'); }); test('decode skips control tokens unless asked and keeps user-defined ones, as HF does', () => { From 2244d0bc2703c59f35b175fed35d9b1360b0f21b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:15:03 -0400 Subject: [PATCH 049/907] Refresh the Safari resource mirror for import progress, import speed and 3-D charts --- .../Glaze Extension/Resources/i18n/de.js | 12 +- .../Glaze Extension/Resources/i18n/es.js | 12 +- .../Glaze Extension/Resources/i18n/fr.js | 12 +- .../Glaze Extension/Resources/i18n/nl.js | 12 +- .../Glaze Extension/Resources/i18n/pt-BR.js | 12 +- .../Resources/lib/doc-files.js | 42 ++-- .../Resources/lib/doc-kinds.js | 36 +++- .../Resources/lib/sheet-chart-xml.js | 50 +++-- .../Resources/lib/sheet-chart.js | 203 +++++++++++++++++- .../Glaze Extension/Resources/papers.css | 20 +- .../Glaze/Glaze Extension/Resources/papers.js | 110 +++++++++- .../Glaze Extension/Resources/pdf-kind.js | 52 +++-- .../Glaze Extension/Resources/sheet-kind.js | 19 +- .../Glaze/Glaze Extension/Resources/sheet.css | 2 + .../Glaze Extension/Resources/slides-kind.js | 52 +++-- .../Glaze Extension/Resources/writer-kinds.js | 19 +- .../Resources/writer-paper.css | 9 + .../Glaze Extension/Resources/writer-paper.js | 28 ++- 18 files changed, 592 insertions(+), 110 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/de.js b/safari/Glaze/Glaze Extension/Resources/i18n/de.js index 691f5601b..de78f17ff 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/de.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/de.js @@ -3505,6 +3505,12 @@ "Open original": "Original öffnen", "Saved to Documents": "In Dokumenten gespeichert", "This PDF is too big to save.": "Diese PDF-Datei ist zu groß zum Speichern.", + "Preparing pages…": "Seiten werden vorbereitet…", + "Preparing slides…": "Folien werden vorbereitet…", + "Preparing sheets…": "Blätter werden vorbereitet…", + "Copying…": "Wird kopiert…", + "Copying… {done} / {total} MB": "Wird kopiert… {done} / {total} MB", + "Saving… {n}%": "Wird gespeichert… {n} %", "Spreadsheets": "Tabellen", "Spreadsheet": "Tabelle", "Number format": "Zahlenformat", @@ -3562,7 +3568,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Auf diesem Tabellenblatt können keine Zeilen und Spalten eingefügt oder gelöscht werden.", "Series {n}": "Reihe {n}", "Combo chart": "Kombinationsdiagramm", - "3-D chart": "3D-Diagramm", + "3-D pie chart": "3D-Kreisdiagramm", + "3-D column chart": "3D-Säulendiagramm", + "3-D bar chart": "3D-Balkendiagramm", + "3-D line chart": "3D-Liniendiagramm", + "3-D area chart": "3D-Flächendiagramm", "Surface chart": "Oberflächendiagramm", "Radar chart": "Netzdiagramm", "Bubble chart": "Blasendiagramm", diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/es.js b/safari/Glaze/Glaze Extension/Resources/i18n/es.js index 6a4de5868..eabe7a199 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/es.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/es.js @@ -3505,6 +3505,12 @@ "Open original": "Abrir original", "Saved to Documents": "Guardado en Documentos", "This PDF is too big to save.": "Este PDF es demasiado grande para guardarlo.", + "Preparing pages…": "Preparando páginas…", + "Preparing slides…": "Preparando diapositivas…", + "Preparing sheets…": "Preparando hojas…", + "Copying…": "Copiando…", + "Copying… {done} / {total} MB": "Copiando… {done} / {total} MB", + "Saving… {n}%": "Guardando… {n} %", "Spreadsheets": "Hojas de cálculo", "Spreadsheet": "Hoja de cálculo", "Number format": "Formato de número", @@ -3562,7 +3568,11 @@ "Rows and columns can’t be added or removed on this sheet.": "En esta hoja no se pueden insertar ni eliminar filas o columnas.", "Series {n}": "Serie {n}", "Combo chart": "Gráfico combinado", - "3-D chart": "Gráfico 3D", + "3-D pie chart": "Gráfico circular 3D", + "3-D column chart": "Gráfico de columnas 3D", + "3-D bar chart": "Gráfico de barras 3D", + "3-D line chart": "Gráfico de líneas 3D", + "3-D area chart": "Gráfico de áreas 3D", "Surface chart": "Gráfico de superficie", "Radar chart": "Gráfico radial", "Bubble chart": "Gráfico de burbujas", diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js index b4c680545..c2b2a093b 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js @@ -3505,6 +3505,12 @@ "Open original": "Ouvrir l’original", "Saved to Documents": "Enregistré dans Documents", "This PDF is too big to save.": "Ce PDF est trop volumineux pour être enregistré.", + "Preparing pages…": "Préparation des pages…", + "Preparing slides…": "Préparation des diapositives…", + "Preparing sheets…": "Préparation des feuilles…", + "Copying…": "Copie…", + "Copying… {done} / {total} MB": "Copie… {done} / {total} Mo", + "Saving… {n}%": "Enregistrement… {n} %", "Spreadsheets": "Feuilles de calcul", "Spreadsheet": "Feuille de calcul", "Number format": "Format de nombre", @@ -3562,7 +3568,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Impossible d’ajouter ou de supprimer des lignes et des colonnes sur cette feuille.", "Series {n}": "Série {n}", "Combo chart": "Graphique combiné", - "3-D chart": "Graphique 3D", + "3-D pie chart": "Graphique en secteurs 3D", + "3-D column chart": "Histogramme 3D", + "3-D bar chart": "Graphique à barres 3D", + "3-D line chart": "Graphique en courbes 3D", + "3-D area chart": "Graphique en aires 3D", "Surface chart": "Graphique en surface", "Radar chart": "Graphique en radar", "Bubble chart": "Graphique en bulles", diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js index 82d2b918d..5bb584391 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js @@ -3505,6 +3505,12 @@ "Open original": "Origineel openen", "Saved to Documents": "Opgeslagen in Documenten", "This PDF is too big to save.": "Deze pdf is te groot om op te slaan.", + "Preparing pages…": "Pagina's voorbereiden…", + "Preparing slides…": "Dia's voorbereiden…", + "Preparing sheets…": "Bladen voorbereiden…", + "Copying…": "Kopiëren…", + "Copying… {done} / {total} MB": "Kopiëren… {done} / {total} MB", + "Saving… {n}%": "Opslaan… {n}%", "Spreadsheets": "Spreadsheets", "Spreadsheet": "Spreadsheet", "Number format": "Getalnotatie", @@ -3562,7 +3568,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Op dit blad kunnen geen rijen en kolommen worden ingevoegd of verwijderd.", "Series {n}": "Reeks {n}", "Combo chart": "Combinatiegrafiek", - "3-D chart": "3D-grafiek", + "3-D pie chart": "3D-cirkelgrafiek", + "3-D column chart": "3D-kolomgrafiek", + "3-D bar chart": "3D-staafgrafiek", + "3-D line chart": "3D-lijngrafiek", + "3-D area chart": "3D-vlakgrafiek", "Surface chart": "Oppervlaktegrafiek", "Radar chart": "Radargrafiek", "Bubble chart": "Bellengrafiek", diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js index 36abac48d..f76333eaf 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js @@ -3505,6 +3505,12 @@ "Open original": "Abrir original", "Saved to Documents": "Salvo em Documentos", "This PDF is too big to save.": "Este PDF é grande demais para salvar.", + "Preparing pages…": "Preparando páginas…", + "Preparing slides…": "Preparando slides…", + "Preparing sheets…": "Preparando planilhas…", + "Copying…": "Copiando…", + "Copying… {done} / {total} MB": "Copiando… {done} / {total} MB", + "Saving… {n}%": "Salvando… {n}%", "Spreadsheets": "Planilhas", "Spreadsheet": "Planilha", "Number format": "Formato de número", @@ -3562,7 +3568,11 @@ "Rows and columns can’t be added or removed on this sheet.": "Não é possível inserir ou excluir linhas e colunas nesta planilha.", "Series {n}": "Série {n}", "Combo chart": "Gráfico de combinação", - "3-D chart": "Gráfico 3D", + "3-D pie chart": "Gráfico de pizza 3D", + "3-D column chart": "Gráfico de colunas 3D", + "3-D bar chart": "Gráfico de barras 3D", + "3-D line chart": "Gráfico de linhas 3D", + "3-D area chart": "Gráfico de área 3D", "Surface chart": "Gráfico de superfície", "Radar chart": "Gráfico de radar", "Bubble chart": "Gráfico de bolhas", diff --git a/safari/Glaze/Glaze Extension/Resources/lib/doc-files.js b/safari/Glaze/Glaze Extension/Resources/lib/doc-files.js index fabdbbc9a..46979dcf2 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/doc-files.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/doc-files.js @@ -1,15 +1,17 @@ // Documents' files: a PDF's or a deck's bytes (lib/doc-kinds.js), kept by the worker in its own IndexedDB, glaze-docs // (lib/background.js), by their SHA-256. writer.html in a school's page (whose IndexedDB the site around it partitions), // papers.html and Settings' backup reach them over a runtime port (PORT), in chunks of CHUNK bytes, each sent as base64 -// and acknowledged before the next (Safari's message size is the tightest), so neither side holds more than the one copy -// it is building. A record points at its file as { hash, size, type } (lib/writer-papers.js); the worker keeps, per -// document, the hashes its record and versions point at (owners), and a file goes once no document points at it. One -// object store, string keys: f: a file (its chunks' id and count, its owners), c:: a chunk, u: an upload -// under way, o: the hashes a document holds. +// (Safari's message size is the tightest) and acknowledged, a put's at most WINDOW ahead of its acknowledgements and a +// get's each before the next, so neither side holds more than the one copy it is building and a few chunks. A record +// points at its file as { hash, size, type } (lib/writer-papers.js); the worker keeps, per document, the hashes its +// record and versions point at (owners), and a file goes once no document points at it. One object store, string keys: +// f: a file (its chunks' id and count, its owners), c:: a chunk, u: an upload under way, o: +// the hashes a document holds. // Classic script for the worker and the pages; module.exports for node. var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { const PORT = 'glaze:docFiles'; const CHUNK = 384 << 10; // 512 KB as base64 + const WINDOW = 4; // a put's chunks on their way at once (client put) // The largest file (lib/doc-kinds.js FILE; test/doc-files.test.js holds them equal) and what a record held inline before // the store (its base64 within the record's 4 MB), which a record may still hold, and a browser without the store writes. const MAX = 50e6, LEGACY = 3e6; @@ -236,11 +238,11 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { }; // ---- the worker's end of a port: one transfer each. put: { op: 'put', size, type } → { ready }; { op: 'chunk', data } - // → { ack } each; { op: 'end' } → { file }. get: { op: 'get', hash } → { data } each, answered { op: 'ack' }, then + // → { ack: 1, 2, … } each; { op: 'end' } → { file }. get: { op: 'get', hash } → { data } each, answered { op: 'ack' }, then // { done, file }. stat: { op: 'stat', hash } → { file } or { file: null }. Any failure: { error: WHY }, and the port // closes. allowed: whether the sender may (a promise), settled before anything runs. const serve = (port, st, allowed = Promise.resolve(true)) => { - let up = null, ack = null, closed = false; + let up = null, ack = null, closed = false, chunks = 0; const say = (m) => { if (!closed) port.postMessage(m); }; const close = () => { if (closed) return; closed = true; try { port.disconnect(); } catch { /* gone */ } }; let queue = Promise.resolve(allowed).then((ok) => { if (!ok) { close(); throw fail('bad'); } }, () => { close(); throw fail('bad'); }); @@ -257,7 +259,7 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { } else if (m?.op === 'chunk') { if (!up) throw fail('interrupted'); await st.chunk(up, unb64(m.data)); - say({ ack: true }); + say({ ack: ++chunks }); } else if (m?.op === 'end') { if (!up) throw fail('interrupted'); const id = up; @@ -301,16 +303,32 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { return m; }; const send = (m) => { if (gone) throw fail('interrupted'); try { port.postMessage(m); } catch { throw fail('interrupted'); } }; - return { send, next, close: () => { if (gone) return; gone = true; try { port.disconnect(); } catch { /* gone */ } } }; + // A refusal already here, unread (a put that was still sending when the worker said no). + const refused = () => { const m = inbox.find((x) => x?.error); return m ? fail(WHY.has(m.error) ? m.error : 'unavailable') : null; }; + return { send, next, refused, close: () => { if (gone) return; gone = true; try { port.disconnect(); } catch { /* gone */ } } }; }; - // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent. - const put = async (bytes, type = '') => { + // bytes (Uint8Array) → the kept file's ref, each chunk encoded as it is sent, up to WINDOW of them on their way (the + // worker takes a port's messages in order, one at a time): the page encodes and the port carries the next while the + // worker writes this one. A refusal sent while chunks were on their way is the one said. sent(done, total) once the + // worker has each one. + const put = async (bytes, type = '', sent = null) => { if (!(bytes instanceof Uint8Array) || !bytes.length || bytes.length > MAX) throw fail('size'); const s = session(); try { s.send({ op: 'put', size: bytes.length, type: String(type).slice(0, 200) }); await s.next(); - for (let at = 0; at < bytes.length; at += CHUNK) { s.send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); await s.next(); } + let out = 0, got = 0; + // A gap, duplicate or reordered acknowledgement cannot advance the window or publish the file. + const acknowledged = async () => { + if ((await s.next()).ack !== ++got) throw fail('unavailable'); + try { sent?.(Math.min(got * CHUNK, bytes.length), bytes.length); } catch (e) { console.error(e); } + }; + const send = (m) => { try { s.send(m); } catch (e) { throw s.refused() || e; } }; + for (let at = 0; at < bytes.length; at += CHUNK) { + send({ op: 'chunk', data: b64(bytes.subarray(at, at + CHUNK)) }); + if (++out >= WINDOW) { await acknowledged(); out--; } + } + for (; out > 0; out--) await acknowledged(); s.send({ op: 'end' }); const { file } = await s.next(); if (!ref(file) || file.size !== bytes.length) throw fail('unavailable'); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/doc-kinds.js b/safari/Glaze/Glaze Extension/Resources/lib/doc-kinds.js index ae2643dec..d71f6ba90 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/doc-kinds.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/doc-kinds.js @@ -47,7 +47,41 @@ var GZ_DOC_KINDS = globalThis.GZ_DOC_KINDS || (() => { const pending = new Set(); const wait = (p) => { const q = Promise.resolve(p).catch((e) => console.error(e)); pending.add(q); q.then(() => pending.delete(q)); return q; }; const settled = async () => { while (pending.size) await Promise.all([...pending]); }; - return { KINDS, FILE, SAYS, kindOf, forFile, register, get, taking, opens, accept, wait, settled }; + // ---- an import's progress: the kind doing it reports { stage, done, total } as each stage starts and as its bytes go; + // the page showing it hears { stage, done, total, fraction } and, at the end, end or failed. stages: the ones the kind + // goes through, in order; each its share of the bar by what it took when measured (copying a 50 MB PDF into the file + // store is most of an import). A stage only goes forward, its bytes only up, the bar never back. + const STAGES = freeze({ read: 3, prepare: 15, copy: 77, save: 5 }); + const progress = (listen, stages = Object.keys(STAGES)) => { + const order = stages.filter((s) => Object.hasOwn(STAGES, s)), sum = order.reduce((a, s) => a + STAGES[s], 0) || 1; + let at = -1, done = 0, total = 0, fraction = 0, over = false; + const tell = (more) => { try { listen?.({ stage: order[at] ?? null, done, total, fraction, ...more }); } catch (e) { console.error(e); } }; + const report = ({ stage, done: d = 0, total: n = 0 } = {}) => { + const i = order.indexOf(stage); + if (over || i < 0 || i < at) return; + if (i > at) { at = i; done = 0; total = 0; } + if (Number.isFinite(n) && n > 0) total = n; + if (Number.isFinite(d) && d > done) done = total ? Math.min(d, total) : d; + const before = order.slice(0, at).reduce((a, s) => a + STAGES[s], 0); + fraction = Math.max(fraction, Math.min(1, (before + STAGES[stage] * (total ? done / total : 0)) / sum)); + tell(); + }; + report.end = () => { if (over) return; over = true; fraction = 1; tell({ end: true }); }; + report.fail = (error) => { if (over) return; over = true; tell({ failed: error ?? true }); }; + return report; + }; + // What a page says of a progress state: [English words, their values], or null before the first stage. The bytes show + // from 1 MB (a smaller file is copied at once); spoken: the words without them (a live region hears each stage once). + const PREPARING = freeze({ paper: 'Preparing pages…', pdf: 'Preparing pages…', slides: 'Preparing slides…', sheet: 'Preparing sheets…' }); + const mb = (n) => Math.round(n / 1e5) / 10; + const said = (p, id, { spoken = false } = {}) => { + if (p?.stage === 'read') return ['Reading…']; + if (p?.stage === 'prepare') return [PREPARING[id] || PREPARING.pdf]; + if (p?.stage === 'copy') return spoken || !(p.total >= 1e6) ? ['Copying…'] : ['Copying… {done} / {total} MB', { done: mb(p.done), total: mb(p.total) }]; + if (p?.stage === 'save') return ['Saving…']; + return null; + }; + return { KINDS, FILE, SAYS, STAGES, kindOf, forFile, register, get, taking, opens, accept, wait, settled, progress, said }; })(); if (typeof module !== 'undefined') module.exports = GZ_DOC_KINDS; else GZ_DOC_KINDS; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart-xml.js b/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart-xml.js index acdbfb024..34d66be11 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart-xml.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart-xml.js @@ -2,9 +2,9 @@ // drawing's anchors (two-cell, one-cell, absolute) and each chart part read into a plain description, never written back. // charts(wb, sheet) → [{ id, path, anchor, chart }] with each anchor and every c:f reference moved through wb.keep.log // (the rows and columns inserted or deleted, the sheets renamed since the file was read), as the writer moves the parts. -// A chart: { kind: 'plot' | 'frame', type (the frame's word), title, legend, groups: [{ type: bar | line | pie | doughnut | -// scatter | area, dir, grouping, varyColors, gap, overlap, hole, first, series: [{ name, cat, val, x, y, color, line, -// marker, points, labels }] }], axes: { cat, val, x, y } }. A series' name, cat and val are { f, cache: { pts, fmt } }: +// A chart: { kind: 'plot' | 'frame', type (the frame's word), title, legend, view3D, walls, groups: [{ type: bar | line | pie | +// doughnut | scatter | area | pie3D | bar3D, dir, grouping, varyColors, gap, gapDepth, shape, overlap, hole, first, series: +// [{ name, cat, val, x, y, color, line, marker, points, explosion, explode, labels }] }], axes: { cat, val, x, y } }. A series' name, cat and val are { f, cache: { pts, fmt } }: // resolve(chart, lookup) reads each f live (lookup: lib/sheet-model's cells through the engine's values) and falls back // to the cache. Colours are '#RRGGBB' from the theme (wb.keep.theme), the series' own, or Excel's automatic order. // Classic script; doctext, the model and the formula module are looked up when a function runs. module.exports for node. @@ -195,10 +195,16 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { return null; }; const labelsOf = (d) => d && { val: flag(d, 'showVal'), pct: flag(d, 'showPercent'), cat: flag(d, 'showCatName'), fmt: kid(d, 'numFmt')?.a.formatCode ?? null, pos: val(d, 'dLblPos') ?? null, del: flag(d, 'delete') }; - const TYPES = { barChart: 'bar', lineChart: 'line', pieChart: 'pie', doughnutChart: 'doughnut', scatterChart: 'scatter', areaChart: 'area' }; + const TYPES = { barChart: 'bar', lineChart: 'line', pieChart: 'pie', doughnutChart: 'doughnut', scatterChart: 'scatter', areaChart: 'area', pie3DChart: 'pie3D', bar3DChart: 'bar3D' }; // Words for the frame a chart Glaze doesn't draw shows (lib/i18n catalogues). - const OTHER = { bar3DChart: '3-D chart', line3DChart: '3-D chart', pie3DChart: '3-D chart', area3DChart: '3-D chart', surfaceChart: 'Surface chart', surface3DChart: 'Surface chart', + const OTHER = { line3DChart: '3-D line chart', area3DChart: '3-D area chart', surfaceChart: 'Surface chart', surface3DChart: 'Surface chart', radarChart: 'Radar chart', bubbleChart: 'Bubble chart', stockChart: 'Stock chart', ofPieChart: 'Pie chart' }; + // A 3-D pie or column chart Glaze draws, framed when it can't be (beside another chart, on a second axis). + const word3D = (g) => (g.type === 'pie3D' ? '3-D pie chart' : g.dir === 'bar' ? '3-D bar chart' : '3-D column chart'); + // c:view3D as written (null where it says nothing: the drawer has each type's default) and the walls' fills. + const view3DOf = (n) => n && { rotX: num(val(n, 'rotX')), rotY: num(val(n, 'rotY')), perspective: num(val(n, 'perspective')), depthPercent: num(val(n, 'depthPercent')), + hPercent: num(val(n, 'hPercent')), rAngAx: kid(n, 'rAngAx') ? flag(n, 'rAngAx') : null }; + const wallOf = (n, theme) => n && { fill: shapeOf(kid(n, 'spPr'), theme, null).fill }; const EX = { waterfall: 'Waterfall chart', funnel: 'Funnel chart', treemap: 'Treemap chart', sunburst: 'Sunburst chart', boxWhisker: 'Box and whisker chart', clusteredColumn: 'Histogram', paretoLine: 'Pareto chart', regionMap: 'Map chart' }; const axisOf = (n, theme, log) => { @@ -233,6 +239,8 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const autoDeleted = flag(ch, 'autoTitleDeleted'); const legendEl = kid(ch, 'legend'); const legend = legendEl ? { pos: val(legendEl, 'legendPos') ?? 'r', overlay: flag(legendEl, 'overlay'), hidden: kids(legendEl, 'legendEntry').filter((e) => flag(e, 'delete')).map((e) => num(val(e, 'idx'), -1)) } : null; + const view3D = view3DOf(kid(ch, 'view3D')); + const walls = { floor: wallOf(kid(ch, 'floor'), theme), side: wallOf(kid(ch, 'sideWall'), theme), back: wallOf(kid(ch, 'backWall'), theme) }; const groups = [], other = []; let auto = 0; for (const g of plot.kids) { @@ -249,30 +257,38 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const mk = kid(s, 'marker'); const mkSp = shapeOf(kid(mk, 'spPr'), theme, color); const points = new Map(); - for (const p of kids(s, 'dPt')) { const i = num(val(p, 'idx'), -1); const c = shapeOf(kid(p, 'spPr'), theme, null); if (i >= 0 && (c.fill || c.line)) points.set(i, c.fill && c.fill !== 'none' ? c.fill : c.line); } + const explode = new Map(); + for (const p of kids(s, 'dPt')) { + const i = num(val(p, 'idx'), -1); const c = shapeOf(kid(p, 'spPr'), theme, null); + if (i >= 0 && (c.fill || c.line)) points.set(i, c.fill && c.fill !== 'none' ? c.fill : c.line); + if (i >= 0 && kid(p, 'explosion')) explode.set(i, num(val(p, 'explosion'), 0)); + } const dl = labelsOf(kid(s, 'dLbls')); return { idx, order: num(val(s, 'order'), idx), name: dataOf(kid(s, 'tx'), log), color, fill: sp.fill, line: sp.line, width: sp.width, cat: dataOf(kid(s, 'cat') ?? kid(s, 'xVal'), log), val: dataOf(kid(s, 'val') ?? kid(s, 'yVal'), log), marker: mk ? { symbol: val(mk, 'symbol') ?? null, size: num(val(mk, 'size'), 5), fill: mkSp.fill, line: mkSp.line } : null, - smooth: flag(s, 'smooth'), points, labels: dl, + smooth: flag(s, 'smooth'), points, explosion: num(val(s, 'explosion'), 0), explode, labels: dl, }; }).sort((a, b) => a.order - b.order); groups.push({ - type, dir: val(g, 'barDir') === 'bar' ? 'bar' : 'col', grouping: val(g, 'grouping') ?? (type === 'bar' ? 'clustered' : 'standard'), vary, - gap: num(val(g, 'gapWidth'), 150), overlap: num(val(g, 'overlap'), null), hole: num(val(g, 'holeSize'), 50), first: num(val(g, 'firstSliceAng'), 0), + type, dir: val(g, 'barDir') === 'bar' ? 'bar' : 'col', grouping: val(g, 'grouping') ?? (type === 'bar' || type === 'bar3D' ? 'clustered' : 'standard'), vary, + gap: num(val(g, 'gapWidth'), 150), overlap: num(val(g, 'overlap'), null), hole: num(val(g, 'holeSize'), 50), + // A 3-D pie has no c:firstSliceAng: Excel turns it with c:view3D's rotY. + first: num(val(g, 'firstSliceAng'), type === 'pie3D' ? (view3D?.rotY ?? 0) : 0), gapDepth: num(val(g, 'gapDepth'), 150), shape: val(g, 'shape') ?? 'box', style: val(g, 'scatterStyle') ?? null, markers: kid(g, 'marker') ? flag(g, 'marker') : null, axes: kids(g, 'axId').map((a) => a.a.val), series, labels: labelsOf(kid(g, 'dLbls')), }); } const axes = {}; for (const n of plot.kids) if (/^(valAx|catAx|dateAx|serAx)$/.test(local(n.name))) { const a = axisOf(n, theme, log); axes[a.id] = a; } const blanks = val(ch, 'dispBlanksAs') ?? 'gap'; - const out = { kind: 'plot', title, autoDeleted, legend, groups, axes, blanks, visible: flag(ch, 'plotVisOnly', true) }; - // Drawn: one or more of the six on one pair of axes (a pie or doughnut alone); else the titled frame. + const out = { kind: 'plot', title, autoDeleted, legend, view3D, walls, groups, axes, blanks, visible: flag(ch, 'plotVisOnly', true) }; + // Drawn: one or more of the six on one pair of axes (a pie, doughnut or 3-D chart alone); else the titled frame. const pairs = new Set(groups.map((g) => g.axes.slice(0, 2).join(','))); - const round = groups.some((g) => g.type === 'pie' || g.type === 'doughnut'); - if (other.length || !groups.length || pairs.size > 1 || Object.values(axes).some((a) => a.log) || (round && groups.length > 1) || (groups.some((g) => g.type === 'scatter') && groups.length > 1)) { - return { ...out, kind: 'frame', type: other[0] ?? (groups.length > 1 ? 'Combo chart' : 'Chart') }; + const round = groups.some((g) => g.type === 'pie' || g.type === 'doughnut' || g.type === 'pie3D'); + const d3 = groups.find((g) => g.type === 'pie3D') ?? groups.find((g) => g.type === 'bar3D'); + if (other.length || !groups.length || pairs.size > 1 || Object.values(axes).some((a) => a.log) || (round && groups.length > 1) || (d3 && groups.length > 1) || (groups.some((g) => g.type === 'scatter') && groups.length > 1)) { + return { ...out, kind: 'frame', type: d3 ? word3D(d3) : other[0] ?? (groups.length > 1 ? 'Combo chart' : 'Chart') }; } return out; }; @@ -428,8 +444,8 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { let title = null; if (c.title?.text !== undefined) title = c.title.text; else if (c.title?.f) title = read({ f: c.title.f, cache: c.title.cache, kind: 'str' }, look, show)?.texts.join(' ') ?? ''; - else if (!c.autoDeleted && groups.length === 1 && groups[0].series.length === 1 && (c.title?.auto || ['pie', 'doughnut'].includes(groups[0].type) || c.title)) title = first?.name || null; - return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks }; + else if (!c.autoDeleted && groups.length === 1 && groups[0].series.length === 1 && (c.title?.auto || ['pie', 'doughnut', 'pie3D'].includes(groups[0].type) || c.title)) title = first?.name || null; + return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks, view3D: c.view3D ?? null, walls: c.walls ?? null }; }; // ── Writing a chart (Insert): the parts in Excel's element order ─────────────────────────────────────────────────── @@ -594,7 +610,7 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { }; // Every word a frame can show, for the catalogues. - const WORDS = [...new Set(['Chart', 'Combo chart', ...Object.values(OTHER), ...Object.values(EX)])]; + const WORDS = [...new Set(['Chart', 'Combo chart', ...Object.values(OTHER), '3-D pie chart', '3-D column chart', '3-D bar chart', ...Object.values(EX)])]; return { EMU, MAX_POINTS, DEFAULT_THEME, WORDS, logFor, owner, TYPE_CHART, TYPE_RELS, build, plan, spans, addAnchor, setAnchor, relsXml, relList, tree, anchors, chart, chartEx, charts, lookup, resolve, autoColor, transform, moveFormula, moveAnchor, relsOf, partText }; })(); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart.js b/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart.js index 1638c2d96..5dc3ec8c8 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/sheet-chart.js @@ -1,6 +1,6 @@ // A sheet's charts drawn on the grid (sheet-grid.js): scene() lays a resolved chart (lib/sheet-chart-xml.js resolve) out -// in a w×h box as plain shapes (pure, for node: bars, lines, areas, pies and doughnuts, scatter, axes with nice ticks, -// titles, the legend), svg() builds them with DOM calls, and layer() keeps a chart layer in each of the grid's panes, each +// in a w×h box as plain shapes (pure, for node: bars, lines, areas, pies and doughnuts, scatter, 3-D pies and columns, +// axes with nice ticks, titles, the legend), svg() builds them with DOM calls, and layer() keeps a chart layer in each of the grid's panes, each // chart at its anchor's cells, moving and sizing with them and with the frozen panes, drawn again a moment after the // cells it reads change. The chart's own colours draw its data; its text, axes and ground take the Look (sheet.css // .gc-*). A chart Glaze doesn't draw is a frame with its title and kind. Nothing here writes the file's chart parts. @@ -25,6 +25,12 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const round = (x) => Math.round(x * 100) / 100; const clean = (x) => Number(x.toPrecision(12)); const formatNumber = (v, code, opts = {}) => { if (v === null || v === undefined) return ''; try { return NF().format(v, code || 'General', opts) ?? String(v); } catch { return String(v); } }; + const clamp = (x, lo, hi) => Math.max(lo, Math.min(hi, x)); + const RAD = Math.PI / 180; + // A 3-D face's shade of its data colour: tops lighter, sides darker (no palette of their own). + const lighter = (hex) => CX().transform(hex, [{ name: 'tint', a: { val: '75000' } }]); + const darker = (hex, k = 72000) => CX().transform(hex, [{ name: 'shade', a: { val: String(k) } }]); + const poly = (pts) => `M${pts.map(([x, y]) => `${round(x)} ${round(y)}`).join('L')}Z`; // An axis's scale: Excel's rule (from 0 unless the values sit within a sixth of their top, 5% of room past them), steps // of 1, 2 or 5 a power of ten as many as `len` px holds at `gap` px apart, the file's min, max and major unit where set. @@ -61,7 +67,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return scene({ kind: 'frame', type: 'Chart', title: data.title }, w, h, opts); } const t = opts.t || ((s) => s); - const prims = [], marks = { bars: [], points: [], slices: [], ticks: { val: [], cat: [], x: [] }, legend: [], title: null, areas: [], lines: [] }; + const prims = [], marks = { bars: [], points: [], slices: [], sides: [], faces: [], labels: [], ticks: { val: [], cat: [], x: [] }, legend: [], title: null, areas: [], lines: [] }; const add = (el, a, cls, text, tip) => { prims.push({ el, a, cls: cls || null, text: text ?? null, tip: tip ?? null }); return prims[prims.length - 1]; }; add('rect', { x: 0.5, y: 0.5, width: Math.max(0, w - 1), height: Math.max(0, h - 1), rx: 3 }, data.kind === 'frame' ? 'gc-bg gc-frame' : 'gc-bg'); let top = P, left = P, right = w - P, bottom = h - P; @@ -86,7 +92,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { } const groups = data.groups.filter((g) => g.series.length); if (!groups.length) return { prims, marks }; - const round1 = groups[0].type === 'pie' || groups[0].type === 'doughnut'; + const round1 = groups[0].type === 'pie' || groups[0].type === 'doughnut' || groups[0].type === 'pie3D'; const sourceIndex = (s, i) => s.indices?.[i] ?? i; const pointColor = (g, s, i) => s.points?.get(sourceIndex(s, i)) ?? (g.vary || round1 ? CX().autoColor(sourceIndex(s, i), opts.theme || CX().DEFAULT_THEME) : null) ?? fillOf(s); const fillOf = (s) => (s.fill && s.fill !== 'none' ? s.fill : s.color); @@ -143,6 +149,92 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { } if (right - left < 16 || bottom - top < 16) return { prims, marks }; + // ── A 3-D pie, as Excel projects it: the pie tilted back by view3D's rotX (default 30°) so its top is an ellipse + // sin(rotX) as tall as wide, extruded down by a thickness 18% of its radius (projected by cos(rotX)). Painter's order: + // each slice's sides (the cut faces an explosion shows, the rim of the front half) back to front by its mid-angle, + // then the tops. An exploded slice (c:explosion, % of the radius) moves out along its mid-angle; the pie shrinks so + // it still fits. Perspective is not drawn: the projection is parallel. ── + if (groups[0].type === 'pie3D') { + const g = groups[0], s = g.series[0]; + const v3 = data.view3D ?? {}; + const tilt = clamp(v3.rotX ?? 30, 10, 90) * RAD; + const k = Math.sin(tilt); + // Sum weights relative to the largest value so finite inputs cannot overflow the total and collapse every arc. + const values = s.vals.map((v) => (Number.isFinite(v) ? Math.abs(v) : 0)); + const maxV = Math.max(0, ...values); + if (!maxV) return { prims, marks }; + const vals = values.map((v) => v / maxV); + const total = vals.reduce((n, v) => n + v, 0); + if (!total) return { prims, marks }; + const exOf = (i) => clamp(s.explode?.get(sourceIndex(s, i)) ?? s.explosion ?? 0, 0, 400) / 100; + const maxE = Math.max(0, ...vals.map((v, i) => (v ? exOf(i) : 0))); + const thick = 0.18 * Math.cos(tilt); + const W = right - left, H = bottom - top; + const rx = Math.max(0, Math.min(W / 2 / (1 + maxE), H / (2 * k * (1 + maxE) + thick)) - 2), ry = rx * k, depth = rx * thick; + if (!rx) return { prims, marks }; + const cx = (left + right) / 2, cy = top + (H - (2 * ry * (1 + maxE) + depth)) / 2 + ry * (1 + maxE); + marks.pie3d = { cx: round(cx), cy: round(cy), rx: round(rx), ry: round(ry), depth: round(depth) }; + const at = (deg, f, [ox, oy], dz = 0) => [cx + ox + rx * f * Math.sin(deg * RAD), cy + oy - ry * f * Math.cos(deg * RAD) + dz]; + const slices = []; + // Keep the rim loop within two turns, even for an out-of-range finite rotation in a file. + let a = (((g.first || 0) % 360) + 360) % 360; + vals.forEach((v, i) => { + if (!v) return; + const a1 = a + (v / total) * 360, mid = (a + a1) / 2, e = exOf(i); + slices.push({ i, index: slices.length, a0: a, a1, mid, v, e, off: [rx * e * Math.sin(mid * RAD), -ry * e * Math.cos(mid * RAD)], color: pointColor(g, s, i) }); + a = a1; + }); + const tipOf = (x) => `${s.cats?.[x.i] ?? x.i + 1}: ${format(s.vals[x.i], s.fmt)}`; + // Back (mid-angle at 12 o'clock) to front (6 o'clock). + const order = [...slices].sort((p, q) => Math.cos(q.mid * RAD) - Math.cos(p.mid * RAD)); + for (const x of order) { + // A cut face shows only at a separated boundary and toward the viewer: the slice's first edge on the left + // half, its last edge on the right half. An explosion elsewhere doesn't expose two joined slices' shared edge. + if (slices.length > 1) { + const prev = slices[(x.index + slices.length - 1) % slices.length], next = slices[(x.index + 1) % slices.length]; + for (const [edge, facing, apart] of [[x.a0, Math.sin(x.a0 * RAD) < -1e-9, x.e || prev.e], [x.a1, Math.sin(x.a1 * RAD) > 1e-9, x.e || next.e]]) { + if (!facing || !apart) continue; + const c = [cx + x.off[0], cy + x.off[1]], p = at(edge, 1, x.off); + add('path', { d: poly([c, p, [p[0], p[1] + depth], [c[0], c[1] + depth]]), fill: darker(x.color, 60000) }, 'gc-side', null, tipOf(x)); + marks.sides.push({ i: x.i, kind: 'face', a: round(edge) }); + } + } + // The rim: the part of the slice's arc on the front half (90° to 270°, every turn), as a band `depth` tall. + for (let turn = Math.floor((x.a0 - 270) / 360); turn * 360 + 90 < x.a1; turn++) { + const b0 = Math.max(x.a0, turn * 360 + 90), b1 = Math.min(x.a1, turn * 360 + 270); + if (b1 - b0 < 1e-6) continue; + const p0 = at(b0, 1, x.off), p1 = at(b1, 1, x.off); + const d = `M${round(p0[0])} ${round(p0[1])}A${round(rx)} ${round(ry)} 0 0 1 ${round(p1[0])} ${round(p1[1])}L${round(p1[0])} ${round(p1[1] + depth)}` + + `A${round(rx)} ${round(ry)} 0 0 0 ${round(p0[0])} ${round(p0[1] + depth)}Z`; + add('path', { d, fill: darker(x.color) }, 'gc-side', null, tipOf(x)); + marks.sides.push({ i: x.i, kind: 'rim', a0: round(b0), a1: round(b1) }); + } + } + for (const x of order) { + const c = [cx + x.off[0], cy + x.off[1]]; + if (slices.length === 1) add('ellipse', { cx: round(c[0]), cy: round(c[1]), rx: round(rx), ry: round(ry), fill: x.color }, 'gc-slice', null, tipOf(x)); + else { + const p0 = at(x.a0, 1, x.off), p1 = at(x.a1, 1, x.off); + add('path', { d: `M${round(c[0])} ${round(c[1])}L${round(p0[0])} ${round(p0[1])}A${round(rx)} ${round(ry)} 0 ${x.a1 - x.a0 > 180 ? 1 : 0} 1 ${round(p1[0])} ${round(p1[1])}Z`, fill: x.color }, 'gc-slice', null, tipOf(x)); + } + marks.slices.push({ i: x.i, a0: round(x.a0), a1: round(x.a1), color: x.color, explosion: x.e, dx: round(x.off[0]), dy: round(x.off[1]) }); + } + // Labels on the slice's top (outside past its edge for outEnd), in slice order; leader lines are not drawn. + for (const x of slices) { + const lab = s.labels ?? g.labels; + if (!lab || lab.del || !(lab.val || lab.pct || lab.cat) || x.a1 - x.a0 < 12) continue; + const text = [lab.cat ? s.cats?.[x.i] : null, lab.val ? format(s.vals[x.i], lab.fmt || s.fmt) : null, lab.pct ? format(x.v / total, lab.fmt && /%/.test(lab.fmt) ? lab.fmt : '0%') : null].filter(Boolean).join(' '); + const out = lab.pos === 'outEnd'; + const [lx, ly] = at(x.mid, out ? 1.16 : slices.length === 1 ? 0 : 0.62, x.off, out && Math.cos(x.mid * RAD) < 0 ? depth : 0); + // Include the 10px text and its outline, not just its anchor, when deciding whether it fits the tilted top. + if (!out && ((Math.abs(lx - cx - x.off[0]) + width(text, TICK) / 2 + 1) / rx) ** 2 + + ((Math.abs(ly - cy - x.off[1]) + TICK / 2 + 1) / ry) ** 2 > 1) continue; + add('text', { x: round(lx), y: round(ly + 4), 'text-anchor': out ? (Math.sin(x.mid * RAD) > 0.2 ? 'start' : Math.sin(x.mid * RAD) < -0.2 ? 'end' : 'middle') : 'middle' }, out ? 'gc-label' : 'gc-plabel', text); + marks.labels.push(text); + } + return { prims, marks }; + } + // ── Pies and doughnuts: the first series a pie, every series a ring (the first innermost) ── if (round1) { const g = groups[0]; @@ -188,7 +280,9 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { // ── Cartesian: one pair of axes ── const axList = Object.values(data.axes || {}); const scatter = groups[0].type === 'scatter'; - const horiz = groups[0].type === 'bar' && groups[0].dir === 'bar'; + const isBar = (g) => g.type === 'bar' || g.type === 'bar3D'; + const d3 = groups[0].type === 'bar3D'; + const horiz = isBar(groups[0]) && groups[0].dir === 'bar'; const valAx = axList.find((a) => a.kind === 'val' && (!scatter || a.pos === 'l' || a.pos === 'r')) ?? axList.find((a) => a.kind === 'val') ?? { kind: 'val', grid: true }; const catAx = scatter ? (axList.find((a) => a.kind === 'val' && a !== valAx) ?? { kind: 'val' }) : (axList.find((a) => a.kind === 'cat' || a.kind === 'date') ?? { kind: 'cat' }); const n = Math.max(1, ...groups.flatMap((g) => g.series.map((s) => s.vals.length))); @@ -219,7 +313,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { const stacked = groups[gi].grouping !== 'standard' && groups[gi].grouping !== 'clustered'; for (const v of stacked ? [p.lo, p.hi] : [p.hi]) { lo = Math.min(lo, v); hi = Math.max(hi, v); } }))); - if (groups.some((g) => g.type === 'bar' || g.type === 'area') && Number.isFinite(lo)) { lo = Math.min(lo, 0); hi = Math.max(hi, 0); } + if (groups.some((g) => isBar(g) || g.type === 'area') && Number.isFinite(lo)) { lo = Math.min(lo, 0); hi = Math.max(hi, 0); } if (anyPct && Number.isFinite(hi)) { hi = Math.max(hi, 1e-9) > 0 ? 1 : hi; lo = lo < 0 ? -1 : 0; } const valFmt = valAx.fmt && !valAx.fmt.linked ? valAx.fmt.code : anyPct ? '0%' : (groups[0].series[0].fmt || valAx.fmt?.code || 'General'); const catFmt = catAx.fmt && !catAx.fmt.linked ? catAx.fmt.code : (groups[0].series[0].catFmt || catAx.fmt?.code || 'General'); @@ -246,6 +340,28 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { if (showC) bottom -= TICK + 6; } if (right - left < 12 || bottom - top < 12) return { prims, marks }; + // ── 3-D columns and bars: Excel's right-angle axes (c:rAngAx, its default for columns) as an oblique projection. The + // plot's front stays a flat rectangle and depth z recedes by (sin rotY, −sin rotX·|cos rotY|) per px (rotY 20°, rotX 15° + // by default). A file with rAngAx 0 and a perspective is drawn the same way: parallel, no vanishing point. A bar is as + // deep as wide (× depthPercent), a row of them gapDepth % deeper; series stand side by side (clustered, stacked) or one + // row each, first in front (standard). Cylinders, cones and pyramids (c:shape) are drawn as boxes. The plot's room + // gives up the depth's projection, at most 35% each way. ── + let sx = 0, sy = 0, depth = 0, rowDepth = 0, barDepth = 0; + if (d3) { + const g = groups[0], v3 = data.view3D ?? {}; + const turn = ((((v3.rotY ?? 20) % 360) + 360) % 360) * RAD; + sx = Math.sin(turn); sy = Math.sin(clamp(v3.rotX ?? 15, 0, 90) * RAD) * Math.abs(Math.cos(turn)); + const rows = g.grouping === 'standard' ? g.series.length : 1; + const kk = g.grouping === 'clustered' ? g.series.length : 1; + const gap = clamp(g.gap, 0, 500) / 100, gd = clamp(g.gapDepth ?? 150, 0, 500) / 100, dp = clamp(v3.depthPercent ?? 100, 20, 2000) / 100; + const per = rows * dp * (1 + gd); + depth = ((horiz ? bottom - top : right - left) / (n * (kk + gap) + (horiz ? sy : Math.abs(sx)) * per)) * per; + depth = Math.min(depth, Math.abs(sx) > 1e-6 ? ((right - left) * 0.35) / Math.abs(sx) : Infinity, sy > 1e-6 ? ((bottom - top) * 0.35) / sy : Infinity); + rowDepth = depth / rows; barDepth = rowDepth / (1 + gd); + if (sx > 0) right -= sx * depth; else left -= sx * depth; + top += sy * depth; + } + const pj = (x, y, z) => [x + sx * z, y - sy * z]; vs = scale(lo, hi, horiz ? right - left : bottom - top, horiz ? 64 : 28, anyPct ? { ...valAx, min: valAx.min ?? lo, max: valAx.max ?? hi } : valAx); if (scatter) xScale = scale(xLo, xHi, right - left, 56, catAx); const vRev = !!valAx.reverse, cRev = !!catAx.reverse; @@ -255,7 +371,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return horiz ? left + (vRev ? 1 - f : f) * (right - left) : bottom - (vRev ? 1 - f : f) * (bottom - top); }; const cLen = horiz ? bottom - top : right - left; - const mid = !scatter && !groups.some((g) => g.type === 'bar') && (valAx.between === 'midCat' || (!valAx.between && groups.every((g) => g.type === 'area'))); + const mid = !scatter && !groups.some(isBar) && (valAx.between === 'midCat' || (!valAx.between && groups.every((g) => g.type === 'area'))); const band = mid && n > 1 ? cLen / (n - 1) : cLen / n; const cpx = (i, f = 0.5) => { const k = cRev ? n - 1 - i : i; @@ -263,10 +379,21 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { return horiz ? bottom - along : left + along; }; const xpx = (x) => { const f = (x - xScale.min) / (xScale.max - xScale.min); return left + (cRev ? 1 - f : f) * (right - left); }; - // Gridlines and the value axis's labels. + // A 3-D plot's walls behind it: the back, the side seen from inside, the floor (their file's fill; none said, the Look's). + const xs3 = sx >= 0 ? left : right, xo3 = sx >= 0 ? right : left; + if (d3) { + const wl = data.walls ?? {}; + const wall = (w, pts, name) => { if (w?.fill === 'none') return; add('path', { d: poly(pts), ...(w?.fill && { fill: w.fill }) }, 'gc-wall'); marks.faces.push({ kind: name }); }; + wall(wl.back, [pj(left, top, depth), pj(right, top, depth), pj(right, bottom, depth), pj(left, bottom, depth)], 'back'); + wall(wl.side, [pj(xs3, top, 0), pj(xs3, top, depth), pj(xs3, bottom, depth), pj(xs3, bottom, 0)], 'side'); + wall(wl.floor, [pj(left, bottom, 0), pj(right, bottom, 0), pj(right, bottom, depth), pj(left, bottom, depth)], 'floor'); + } + // Gridlines (a 3-D plot's across its back wall and along the side wall or floor) and the value axis's labels. vs.ticks.forEach((v) => { const p = round(vpx(v)); - if (valAx.grid !== false && (valAx.grid || !data.axes || !Object.keys(data.axes).length)) add('line', horiz ? { x1: p, y1: round(top), x2: p, y2: round(bottom) } : { x1: round(left), y1: p, x2: round(right), y2: p }, 'gc-grid'); + const gridOn = valAx.grid !== false && (valAx.grid || !data.axes || !Object.keys(data.axes).length); + if (gridOn && d3) add('path', { d: (horiz ? [pj(p, top, depth), pj(p, bottom, depth), pj(p, bottom, 0)] : [pj(xs3, p, 0), pj(xs3, p, depth), pj(xo3, p, depth)]).map(([x, y], i) => `${i ? 'L' : 'M'}${round(x)} ${round(y)}`).join(''), fill: 'none' }, 'gc-grid'); + else if (gridOn) add('line', horiz ? { x1: p, y1: round(top), x2: p, y2: round(bottom) } : { x1: round(left), y1: p, x2: round(right), y2: p }, 'gc-grid'); if (showV) add('text', horiz ? { x: p, y: round(bottom + TICK + 3), 'text-anchor': 'middle' } : { x: round(left - 5), y: round(p + TICK / 2 - 1), 'text-anchor': 'end' }, 'gc-tick', format(v, valFmt)); marks.ticks.val.push(v); }); @@ -281,7 +408,7 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { // The category axis's line where the values are 0 (or the scale's edge), and its labels, thinned to fit. const zero = Math.min(Math.max(0, vs.min), vs.max); const base = round(vpx(valAx.crossesAt ?? (vs.min > 0 ? vs.min : vs.max < 0 ? vs.max : zero))); - if (!catAx.deleted || scatter) add('line', horiz ? { x1: base, y1: round(top), x2: base, y2: round(bottom) } : { x1: round(left), y1: base, x2: round(right), y2: base }, 'gc-axis'); + if (!d3 && (!catAx.deleted || scatter)) add('line', horiz ? { x1: base, y1: round(top), x2: base, y2: round(bottom) } : { x1: round(left), y1: base, x2: round(right), y2: base }, 'gc-axis'); if (!scatter && showC) { const each = horiz ? TICK + 4 : Math.max(...cats.map((c) => width(format(c, catFmt), TICK))) + 8; const skip = Math.max(1, Math.ceil(each / Math.max(1, band))); @@ -342,6 +469,62 @@ var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { if (lab && !lab.del && lab.val) add('text', horiz ? { x: round(Math.max(a, b) + 3), y: round(box.y + box.height / 2 + 3.5) } : { x: round(box.x + box.width / 2), y: round(Math.min(a, b) - 3), 'text-anchor': 'middle' }, 'gc-label', format(p.v, lab.fmt || s.fmt)); }); }); + } else if (g.type === 'bar3D') { + // Each bar a front rectangle, a lighter top and a darker side, painted far to near: back rows first, then across + // from the side away from the viewer, lower stacked parts before the ones on them. + const stacked = g.grouping === 'stacked' || g.grouping === 'percentStacked'; + const rows = g.grouping === 'standard' ? S : 1, kk = g.grouping === 'clustered' ? S : 1; + const barW = band / (kk + clamp(g.gap, 0, 500) / 100), start = (band - barW * kk) / 2; + const items = [], labels = []; + g.series.forEach((s, j) => stacks[gi][j].forEach((p, i) => { + if (!p) return; + const off = start + (kk > 1 ? j * barW : 0); + const a = vpx(clamp(stacked ? p.lo : clamp(0, vs.min, vs.max), vs.min, vs.max)), b = vpx(clamp(p.hi, vs.min, vs.max)); + const along = cpx(i, 0) + (horiz ? -off - barW : off); + const box = horiz ? { x: Math.min(a, b), y: along, width: Math.abs(b - a), height: barW } : { x: along, y: Math.min(a, b), width: barW, height: Math.abs(b - a) }; + const z0 = (rows > 1 ? j : 0) * rowDepth + (rowDepth - barDepth) / 2; + items.push({ s, j, i, p, a, b, box, z0, z1: z0 + barDepth }); + })); + const dir = sx >= 0 ? 1 : -1; + items.sort((P, Q) => Q.z0 - P.z0 || (horiz ? Q.box.y - P.box.y || dir * (P.box.x - Q.box.x) : dir * (P.box.x - Q.box.x) || Q.box.y + Q.box.height - (P.box.y + P.box.height))); + const pts = (list) => list.map(([x, y]) => [round(x), round(y)]); + for (const it of items) { + const { s, j, i, p, box, z0, z1 } = it; + const x0 = box.x, x1 = box.x + box.width, y0 = box.y, y1 = box.y + box.height; + const color = pointColor(g, s, i); + const tip = `${s.name ? `${s.name}, ` : ''}${cats[i]}: ${format(p.v, s.fmt)}`; + const f = pj(x0, y0, z0); + // A zero or fully clipped value has no volume: its cap would otherwise look like a nonzero depth plane. + if (it.a !== it.b) { + add('rect', { x: round(f[0]), y: round(f[1]), width: round(box.width), height: round(box.height), fill: color }, 'gc-bar', null, tip); + marks.faces.push({ s: j, i, kind: 'front', pts: pts([pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y1, z0), pj(x0, y1, z0)]) }); + if (sy > 1e-6) { + const q = [pj(x0, y0, z0), pj(x1, y0, z0), pj(x1, y0, z1), pj(x0, y0, z1)]; + add('path', { d: poly(q), fill: lighter(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'top', pts: pts(q) }); + } + if (Math.abs(sx) > 1e-6) { + const xe = sx > 0 ? x1 : x0, q = [pj(xe, y0, z0), pj(xe, y0, z1), pj(xe, y1, z1), pj(xe, y1, z0)]; + add('path', { d: poly(q), fill: darker(color) }, 'gc-face', null, tip); + marks.faces.push({ s: j, i, kind: 'side', pts: pts(q) }); + } + } + marks.bars.push({ s: j, i, x: round(f[0]), y: round(f[1]), w: round(box.width), h: round(box.height), v: p.v, top: p.hi, color, z0: round(z0), z1: round(z1) }); + const lab = s.labels ?? g.labels; + if (lab && !lab.del && lab.val) { + const text = format(p.v, lab.fmt || s.fmt), zm = (z0 + z1) / 2; + // Stacked labels sit on their own front, not the next segment. Outside labels follow the value endpoint, + // including negative values and reversed axes. Paint them after every bar so no later face erases them. + const centre = lab.pos === 'ctr' || (stacked && !lab.pos); + const dir = Math.sign(it.b - it.a) || (horiz ? (vRev ? -1 : 1) : (vRev ? 1 : -1)); + const a = centre ? { x: round((x0 + x1) / 2 + sx * z0), y: round((y0 + y1) / 2 - sy * z0 + 3.5), 'text-anchor': 'middle' } + : horiz ? { x: round(it.b + (dir > 0 ? Math.max(sx * z0, sx * z1) : Math.min(sx * z0, sx * z1)) + dir * 3), y: round((y0 + y1) / 2 - sy * zm + 3.5), 'text-anchor': dir > 0 ? 'start' : 'end' } + : { x: round((x0 + x1) / 2 + sx * (dir < 0 ? zm : z0)), y: round(it.b - sy * (dir < 0 ? zm : z0) + (dir < 0 ? -3 : TICK + 3)), 'text-anchor': 'middle' }; + labels.push({ a, text }); + marks.labels.push(text); + } + } + for (const { a, text } of labels) add('text', a, 'gc-label', text); } else if (g.type === 'line' || g.type === 'area') { const stacked = g.grouping !== 'standard'; for (const j of g.series.keys()) { diff --git a/safari/Glaze/Glaze Extension/Resources/papers.css b/safari/Glaze/Glaze Extension/Resources/papers.css index d96c1e4c8..bebae3633 100644 --- a/safari/Glaze/Glaze Extension/Resources/papers.css +++ b/safari/Glaze/Glaze Extension/Resources/papers.css @@ -19,7 +19,7 @@ .gd-home { max-width: 1160px; margin: 0 auto; padding: 32px 24px 72px; display: grid; gap: 18px; position: relative; } .gd-home .ico { width: 16px; height: 16px; flex: none; } .gd-top, .gd-bar { border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); box-shadow: var(--gz-rest-shadow, none); } -:root[data-gz-bg]:not([data-gz-bg="solid"]) :is(.gd-top, .gd-bar, .gd-card, .gd-menu, .gd-toast) { backdrop-filter: blur(var(--gd-glass)) saturate(1.4); } +:root[data-gz-bg]:not([data-gz-bg="solid"]) :is(.gd-top, .gd-bar, .gd-card, .gd-pending, .gd-menu, .gd-toast) { backdrop-filter: blur(var(--gd-glass)) saturate(1.4); } /* The heading and New: a tile per format, each its first page */ .gd-top { display: grid; gap: 14px; padding: 22px 24px 20px; } @@ -62,10 +62,10 @@ /* The documents: cards in a grid, or rows */ .gd-docs { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(var(--gd-column), 100%), 1fr)); gap: 16px; } -.gd-card { position: relative; display: grid; grid-template-rows: auto 1fr; min-width: 0; border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); overflow: hidden; +:is(.gd-card, .gd-pending) { position: relative; display: grid; grid-template-rows: auto 1fr; min-width: 0; border: 1px solid var(--gz-border-line, var(--gz-line)); border-radius: var(--gd-radius); background: var(--gd-panel); overflow: hidden; box-shadow: var(--gz-rest-shadow, none); transition: var(--gz-card-transition); } .gd-card:hover { border-color: color-mix(in srgb, var(--gz-accent) 55%, var(--gz-border-strong, var(--gz-line-strong))); box-shadow: var(--gz-shadow, 0 8px 24px rgb(0 0 0 / .3)); transform: var(--gd-lift); } -.gd-card:focus-within { border-color: var(--gz-accent); } +:is(.gd-card, .gd-pending):focus-within { border-color: var(--gz-accent); } .gd-pic { height: calc(214px * var(--gd-thumb)); display: flex; justify-content: center; padding-top: 16px; overflow: hidden; border-bottom: 1px solid var(--gz-border-line, var(--gz-line)); background: color-mix(in srgb, var(--gz-surface-paint-2, var(--gz-surface-2)) 55%, transparent); } .gd-pic .gt-thumb { zoom: var(--gd-thumb); background: var(--sheet); border-radius: 2px; box-shadow: 0 1px 3px rgba(0,0,0,.35); } @@ -98,6 +98,20 @@ .gd-home[data-view="list"] .gd-badge { position: static; } .gd-home[data-view="list"] .gd-more { margin: 0; } +/* An import under way (papers.js bring): a blank page where the document's will be, the file's name, its stage, and a bar + filling as the import goes; failed, the reason with Retry and Remove. */ +.gd-blank { width: calc(204px * var(--gd-thumb)); background: var(--sheet); border-radius: 2px 2px 0 0; box-shadow: 0 1px 3px rgba(0,0,0,.35); } +:root:not([data-dark]) .gd-blank { box-shadow: 0 1px 3px rgba(15,23,42,.16), 0 0 0 1px rgba(15,23,42,.06); } +.gd-progress { height: 4px; margin-top: 8px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--gz-ink-muted) 24%, transparent); } +.gd-fill { display: block; height: 100%; border-radius: inherit; background: var(--gz-accent); transform: scaleX(0); transform-origin: left; transition: transform var(--gz-t-move, .2s) ease-out; } +:root[dir="rtl"] .gd-fill { transform-origin: right; } +.gd-pending .gd-title::after { content: none; } +.gd-pending[data-failed] .gd-meta { color: var(--gz-err); -webkit-line-clamp: 3; } +.gd-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; } +.gd-acts[hidden] { display: none; } +.gd-acts .gd-btn { height: 28px; padding: 0 12px; font-size: 12px; } +.gd-pending .gd-more:disabled { opacity: .4; cursor: default; } + /* Files over the page: the page is the drop target */ .gd-home::after { content: ""; position: fixed; inset: 8px; z-index: 4; border: 2px dashed var(--gz-accent); border-radius: var(--gd-radius); background: color-mix(in srgb, var(--gz-accent) 8%, transparent); pointer-events: none; } diff --git a/safari/Glaze/Glaze Extension/Resources/papers.js b/safari/Glaze/Glaze Extension/Resources/papers.js index e6bb421b7..f49093db1 100644 --- a/safari/Glaze/Glaze Extension/Resources/papers.js +++ b/safari/Glaze/Glaze Extension/Resources/papers.js @@ -124,18 +124,23 @@ // A Word file as a new standalone document, as the editor's import reads one (writer-paper.js importFile): in the format // Settings → Writer starts papers in, its own formatting kept where the format would flag it, the save taking the // document's first version. Its title is the file's name. → its key. - const importFile = async (file) => { + const importFile = async (file, { progress = null } = {}) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > GZ_DOC_KINDS.KINDS.paper.max) throw new RangeError('Choose a Word file smaller than 15 MB.'); + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: file.size }); await GZ_WRITER_PAPER.need(['lib/writer-styles.js', 'lib/writer-format.js', 'lib/doc-home.js', ...DOCX]); const ST = GZ_WRITER_STYLES, F = GZ_WRITER_FORMAT; const id = await chrome.storage.sync.get('writer').then((r) => r?.writer?.format, () => null); const format = ST.FORMATS[ST.IDS.includes(id) ? id : 'mla9']; const bytes = await file.arrayBuffer(); if (!(bytes instanceof ArrayBuffer) || bytes.byteLength !== file.size) throw new Error('invalid file read'); + p({ stage: 'read', done: bytes.byteLength, total: bytes.byteLength }); + p({ stage: 'prepare' }); const r = await GZ_WRITER_DOCX.importDocx(new Uint8Array(bytes), { format }); const doc = GZ_WRITER_SCHEMA.paper.nodeFromJSON(r.doc); const keep = F.check(doc, format, {}, {}).issues.some((i) => F.KEPT.has(i.rule) && i.fix?.op !== 'format'); const key = GZ_WRITER_PAPERS.newKey(), title = GZ_DOC_HOME.stem(file.name); + p({ stage: 'save' }); await papers().save(key, { kind: 'paper', doc: doc.toJSON(), format: format.id, keep, named: !!title, title }, { reason: 'import' }); return key; }; @@ -235,6 +240,8 @@ let rows = [], view = 'grid', sortBy = 'updated', course = 'all', kind = '', words = [], shown = [], current = null, focusNext = null, seq = 0; try { if (localStorage.getItem(VIEW) === 'list') view = 'list'; } catch { /* storage can be unavailable */ } let active = true, life = 0, textSize = 0; + // incoming: the imports under way, newest first (bring, below). + const incoming = []; const gone = new Set(), texts = new Map(), derived = new Map(), thumbs = new Map(), near = new Set(), cards = new Map(), epochs = new Map(), hitsByKey = new Map(); // bin: the Trash is the view. moving: key → into Trash (true) or out (false), as shown until storage says so. let bin = false; @@ -319,7 +326,7 @@ }; const inCourse = (r) => DH.inCourse(r, course === 'all' ? '' : course === 'none' ? DH.NONE : course.slice(2)); // The cards as the filters, search and order have them; the rest leave the page. One Tab stop: the current card's link. - let renaming = null, held = false; + let renaming = null, held = false, counted = ''; const render = () => { if (!active) return; if (renaming) { held = true; return; } @@ -331,18 +338,25 @@ const titles = new Map(); const hits = list.filter((r) => { const title = titleOf(r); titles.set(r.key, title); return (bin || (inCourse(r) && (!kind || DK.kindOf(r) === kind))) && matches(r, title) === true; }); shown = DH.sort(hits, bin ? 'trashed' : sortBy, titles, locale); - const els = shown.map(cardFor); + const listed = new Set(rows.map((r) => r.key)); + // An import's card gives way to the document's once it is listed (its focus with it). + for (let i = incoming.length; i--;) { + const j = incoming[i]; + if (!j.key || !listed.has(j.key)) continue; + if (j.ui.el.contains(document.activeElement)) focusNext = j.key; + incoming.splice(i, 1); + } + const els = [...(bin ? [] : incoming.map((j) => j.ui.el)), ...shown.map(cardFor)]; if (els.length !== docs.children.length || els.some((e, i) => docs.children[i] !== e)) { const had = docs.contains(document.activeElement) ? document.activeElement : null; motion().cards(() => docs.replaceChildren(...els)); if (had?.isConnected) had.focus({ preventScroll: true }); } - const listed = new Set(rows.map((r) => r.key)); for (const [k, c] of cards) if (!listed.has(k)) { io.unobserve(c.pic); near.delete(k); cards.delete(k); thumbs.delete(k); forgetText(k); derived.delete(k); epochs.delete(k); } for (const k of epochs.keys()) if (!listed.has(k)) { epochs.delete(k); forgetText(k); derived.delete(k); } roving(); const n = t('{n} documents', { n: shown.length }); - if (count.textContent !== n) count.textContent = n; + if (counted !== n) { counted = n; count.textContent = n; } // A copy or an undone delete takes focus once it is listed; one the filters hide doesn't take it later. if (focusNext && listed.has(focusNext)) { const k = focusNext; focusNext = null; if (cards.get(k)?.el.isConnected) go(k); } later(); @@ -644,6 +658,83 @@ const KIND_SAYS = { pdf: { size: DK.KINDS.pdf.big, read: 'That file could not be read.' } }; const saysOf = (id) => (id && id !== 'paper' && (DK.get(id)?.says || KIND_SAYS[id])) || PAPER_SAYS; const unread = many ? 'That file could not be read.' : PAPER_SAYS.read; + // ---- imports under way: a card for each file from the moment it is taken, in the list's first place, its stage and + // bytes as the kind reports them (lib/doc-kinds.js progress) on a bar, its menu off, until the document it made is + // listed (render); one that failed says why on its card, with Retry and Remove. The stages are spoken in the count's + // live region, the failure in the status line too. A batch's files go one after another. + const STEPS = { pdf: ['read', 'prepare', 'copy', 'save'], slides: ['read', 'prepare', 'copy', 'save'] }; + let jobs = 0; + const showJob = (j) => { + const { el, bar, fill, meta, acts } = j.ui, f = j.failed ? 0 : j.p?.fraction || 0, w = j.failed ? [j.failed] : DK.said(j.p, j.kind); + el.toggleAttribute('data-failed', !!j.failed); + meta.textContent = w ? t(...w) : ''; + bar.hidden = !!j.failed; acts.hidden = !j.failed; + fill.style.transform = `scaleX(${f})`; + bar.setAttribute('aria-valuenow', String(Math.round(f * 100))); + }; + const importJob = async (j) => { + if (j.running) return {}; + j.running = true; j.failed = ''; j.p = null; + let stage = null; + const report = DK.progress((p) => { + j.p = p; + const w = !p.end && !p.failed && p.stage !== stage && DK.said(p, j.kind, { spoken: true }); + if (w) { stage = p.stage; count.textContent = t(...w); } + showJob(j); + }, STEPS[j.kind] || ['read', 'prepare', 'save']); + report({ stage: 'read', total: j.file.size }); + try { + // An import that kept the file with parts left out says so (sheet-kind.js: { key, notice }). + const got = await DK.get(j.kind).importFile(j.file, { progress: report }); + report.end(); + j.key = typeof got === 'string' ? got : got?.key || null; + return { notice: got?.notice || '' }; + } catch (e) { + console.error(e); + const says = saysOf(j.kind); + j.failed = e instanceof RangeError && (REFUSALS.has(e.message) || Object.values(says).includes(e.message)) ? e.message : says.read; + report.fail(e); + return { failed: j.failed }; + } finally { + j.running = false; + // The count's region says the count again once the stages are over. + counted = ''; + if (j.key) await refresh(); else render(); + } + }; + const removeJob = (j) => { + const i = incoming.indexOf(j); + if (i < 0 || j.running) return; + const had = j.ui.el.contains(document.activeElement); + incoming.splice(i, 1); + say(''); + render(); + if (had) importB.focus(); + }; + // Retry keeps the focus with the import: on the document once it is listed, on Retry again if it fails again. + const retry = async (j) => { + say(''); + const focused = j.ui.el.contains(document.activeElement), pending = importJob(j); + if (focused) j.ui.el.focus({ preventScroll: true }); + const r = await pending; + if (r.failed && j.ui.el.contains(document.activeElement) && j.ui.el.isConnected) j.ui.again.focus(); + if (r.failed || r.notice) say(t(r.failed || r.notice)); + }; + const job = (f, id) => { + const j = { id: ++jobs, file: f, kind: id, p: { stage: 'read', fraction: 0 }, failed: '', key: null, running: false }; + const fill = h('span', { class: 'gd-fill' }); + const bar = h('div', { class: 'gd-progress', role: 'progressbar', 'aria-label': f.name, 'aria-valuemin': '0', 'aria-valuemax': '100', 'aria-valuenow': '0' }, fill); + const meta = h('p', { class: 'gd-meta' }); + const again = btn({ class: 'gd-btn gd-quiet', 'data-act': 'retry', onclick: () => { retry(j).catch((e) => console.error(e)); } }, t('Retry')); + const acts = h('div', { class: 'gd-acts', hidden: true }, again, btn({ class: 'gd-btn gd-quiet', 'data-act': 'remove', onclick: () => removeJob(j) }, t('Remove'))); + const more = btn({ class: 'gd-more', disabled: true, 'aria-label': t('More'), title: t('More'), tabindex: '-1' }, icon('dots')); + const el = h('li', { class: 'gd-pending', tabindex: '-1', 'data-kind': id, 'data-import': String(j.id) }, + h('div', { class: 'gd-pic' }, h('div', { class: 'gd-blank' })), + h('div', { class: 'gd-info' }, icon(DK.KINDS[id]?.icon || 'paper', 'gd-kind'), h('div', { class: 'gd-text' }, h('span', { class: 'gd-title', text: f.name }), meta, bar, acts), more)); + j.ui = { el, bar, fill, meta, acts, again }; + showJob(j); + return j; + }; const bring = async (list) => { dismiss(); say(''); @@ -651,12 +742,9 @@ const first = refused.find((x) => x.why !== 'count'); const limitText = refused.some((x) => x.why === 'count') ? t('Choose up to 20 files at a time.') : ''; let why = first ? (first.why === 'size' ? saysOf(DK.forFile(first.file?.name)).size : unread) : ''; - for (const { file: f, kind: id } of take) { - const says = saysOf(id); - // An import that kept the file with parts left out says so (sheet-kind.js: { key, notice }). - try { const got = await DK.get(id).importFile(f); if (got?.notice) why ||= got.notice; } - catch (e) { console.error(e); why ||= e instanceof RangeError && (REFUSALS.has(e.message) || Object.values(says).includes(e.message)) ? e.message : says.read; } - } + const mine = take.map(({ file: f, kind: id }) => job(f, id)); + if (mine.length) { incoming.unshift(...mine); render(); } + for (const j of mine) { const r = await importJob(j); why ||= r.failed || r.notice || ''; } say([why && t(why), limitText].filter(Boolean).join(' ')); }; importB.addEventListener('click', () => file.click()); diff --git a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js index b2e6f9763..dc487d8c9 100644 --- a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js @@ -35,11 +35,12 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const LEGACY = 3e6; const STORE = { max: GZ_DOC_KINDS.KINDS.pdf.max, - // bytes → the record's fields for them, the bytes kept in the store; a RangeError says why not. - put: async (bytes) => { + // bytes → the record's fields for them, the bytes kept in the store (sent(done, total) as they go); a RangeError says + // why not. + put: async (bytes, sent = null) => { if (!(bytes?.length > 0) || bytes.length > STORE.max) throw new RangeError(TOO_BIG); const F = await files(); - try { return { file: await F.client().put(bytes, 'application/pdf'), size: bytes.length }; } catch (e) { + try { return { file: await F.client().put(bytes, 'application/pdf', sent), size: bytes.length }; } catch (e) { if (e.why === 'unavailable' && bytes.length <= LEGACY) return { file: b64(bytes), size: bytes.length }; throw new FileError(e.why); } @@ -135,17 +136,30 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const nameOf = (rec) => `${String(rec?.title || '').replace(/[\\/:*?"<>|\u0000-\u001f]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Document'}.pdf`; // ---- the page code GZ_DOC_KINDS takes - // A PDF file as a new standalone document, its title the file's name, its first page drawn for the card. → its key. - const importFile = async (file) => { - if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(TOO_BIG); - await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/doc-home.js']); - const bytes = new Uint8Array(await file.arrayBuffer()); - if (bytes.length !== file.size) throw new Error('invalid file read'); + // A file's bytes and what pdf.js makes of them, each stage told to progress (lib/doc-kinds.js progress): reading, page 1 + // read and drawn, the bytes copied into the store. → the record's fields for the file. + const bring = async (f, progress) => { + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: f.size }); + const bytes = new Uint8Array(await f.arrayBuffer()); + if (bytes.length !== f.size) throw new Error('invalid file read'); + p({ stage: 'read', done: bytes.length, total: bytes.length }); + p({ stage: 'prepare' }); const { task, doc } = await read(bytes); let thumb = null, pages = 0; try { pages = doc.numPages; thumb = await picture(doc).catch(() => null); } finally { await task.destroy().catch(() => {}); } + p({ stage: 'copy', done: 0, total: bytes.length }); + const kept = await STORE.put(bytes, (done, total) => p({ stage: 'copy', done, total })); + p({ stage: 'save' }); + return { ...kept, pages, ...(thumb && { thumb }) }; + }; + // A PDF file as a new standalone document, its title the file's name, its first page drawn for the card. → its key. + const importFile = async (file, { progress = null } = {}) => { + if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(TOO_BIG); + await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/doc-home.js']); + const fields = await bring(file, progress); const key = GZ_WRITER_PAPERS.newKey(), title = GZ_DOC_HOME.stem(file.name); - await papers().save(key, { kind: 'pdf', ...await STORE.put(bytes), pages, ...(thumb && { thumb }), title, named: !!title }, { reason: 'import' }); + await papers().save(key, { kind: 'pdf', ...fields, title, named: !!title }, { reason: 'import' }); return key; }; // The card's picture: the one the record keeps, else drawn once from its bytes here and kept for its revision. @@ -333,13 +347,17 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { if (!doc || saveB.disabled) return; saveB.disabled = true; say(t('Saving…')); + // How far the whole import has gone, by tens (the line is a live region), then Saved. + const progress = GZ_DOC_KINDS.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); try { if (src.bytes.length > STORE.max) throw new RangeError(TOO_BIG_SAVE); - await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' })); + await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); + progress.end(); say(t('Saved to Documents')); } catch (e) { const big = e instanceof RangeError && (e.message === TOO_BIG || e.message === TOO_BIG_SAVE); if (!big) console.error(e); + progress.fail(e); say(t(big ? TOO_BIG_SAVE : 'Not saved. Try again in a moment.')); saveB.disabled = false; } @@ -383,7 +401,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { // ---- saving: a field filled (pdf.js's annotation storage) saves a second after the last change, and before the page // leaves for Documents; the bytes pdf.js writes (an incremental update) replace the record's, with page 1 drawn again. - const say = (text) => { state.textContent = text; }; + const say = (text) => { if (state.textContent !== text) state.textContent = text; }; let dirty = false, saving = null, timer = 0; const save = async () => { if (!doc || !dirty) return true; @@ -456,15 +474,11 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { }; // A PDF brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. - const into = async (key, f) => { + const into = async (key, f, { progress = null } = {}) => { if (!Number.isSafeInteger(f?.size) || f.size <= 0 || f.size > STORE.max) throw new RangeError(TOO_BIG); await GZ_WRITER_PAPER.need(['lib/writer-papers.js']); - const bytes = new Uint8Array(await f.arrayBuffer()); - if (bytes.length !== f.size) throw new Error('invalid file read'); - const { task, doc } = await read(bytes); - let thumbUrl = null, pages = 0; - try { pages = doc.numPages; thumbUrl = await picture(doc).catch(() => null); } finally { await task.destroy().catch(() => {}); } - return (await papers().save(key, { kind: 'pdf', doc: null, words: 0, ...await STORE.put(bytes), pages, ...(thumbUrl && { thumb: thumbUrl }) }, { reason: 'import' })).paper; + const fields = await bring(f, progress); + return (await papers().save(key, { kind: 'pdf', doc: null, words: 0, ...fields }, { reason: 'import' })).paper; }; // ---- registration: a PDF already kept lists and opens either way; only Settings → Writer → PDFs on (writer.pdf) lets diff --git a/safari/Glaze/Glaze Extension/Resources/sheet-kind.js b/safari/Glaze/Glaze Extension/Resources/sheet-kind.js index 2e30b77a5..c12f538b5 100644 --- a/safari/Glaze/Glaze Extension/Resources/sheet-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/sheet-kind.js @@ -97,20 +97,26 @@ // ---- files: .xlsx, .csv and .tsv through GZ_SHEET_XLSX const stem = (name) => (globalThis.GZ_DOC_HOME ? GZ_DOC_HOME.stem(name) : String(name || '').replace(/\.[^./\\]+$/, '').trim().slice(0, 120)); - // A file as a workbook, its formulas calculated; a RangeError (SAYS) when it is too large or can't be read. - const workbookOf = async (file) => { + // A file as a workbook, its formulas calculated, each stage told to progress (lib/doc-kinds.js progress); a RangeError + // (SAYS) when it is too large or can't be read. + const workbookOf = async (file, progress = null) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > DK.KINDS.sheet.max) throw new RangeError(SAYS.size); + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: file.size }); await need([...ENGINE, 'sheet-grid.js']); const raw = await file.arrayBuffer(); if (!(raw instanceof ArrayBuffer) || raw.byteLength !== file.size) throw new Error('invalid file read'); + p({ stage: 'read', done: raw.byteLength, total: raw.byteLength }); + p({ stage: 'prepare' }); const X = await codecs(), bytes = new Uint8Array(raw); const wb = /\.(csv|tsv)$/i.test(file.name) ? X.readDelimited(bytes, { name: file.name }) : await X.read(bytes).catch(() => null); if (!wb?.sheets?.length) throw new RangeError(SAYS.read); GZ_SHEET_CALC.engine(wb).recalc(); return wb; }; - const importFile = async (file) => { - const wb = await workbookOf(file); + const importFile = async (file, { progress = null } = {}) => { + const wb = await workbookOf(file, progress); + progress?.({ stage: 'save' }); const key = GZ_WRITER_PAPERS.newKey(), title = stem(file.name); await saveAll(key, wb, { named: !!title, title }, 'start'); const notice = noticeOf(wb); @@ -150,8 +156,9 @@ // ---- the grid // A spreadsheet brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it // held kept as a version first (the 'import' save), every part it had before dropped. → the record. - const into = async (key, f) => { - const wb = await workbookOf(f), SD = GZ_SHEET_DATA; + const into = async (key, f, { progress = null } = {}) => { + const wb = await workbookOf(f, progress), SD = GZ_SHEET_DATA; + progress?.({ stage: 'save' }); let had = (await get([`writerPaper:${key}`]))[`writerPaper:${key}`]; const prior = had?.kind === 'sheet' ? await SD.read(get, key) : null; if (prior) had = prior.rec; diff --git a/safari/Glaze/Glaze Extension/Resources/sheet.css b/safari/Glaze/Glaze Extension/Resources/sheet.css index eeb86aa85..b03cf6166 100644 --- a/safari/Glaze/Glaze Extension/Resources/sheet.css +++ b/safari/Glaze/Glaze Extension/Resources/sheet.css @@ -81,6 +81,8 @@ .gc-grid { stroke: var(--gx-line); } .gc-axis { stroke: var(--gz-line-strong); } .gc-slice { stroke: var(--gx-cell); } +.gc-side { stroke: var(--gx-cell); stroke-width: .5px; } +.gc-wall:not([fill]) { fill: var(--gx-head); } .gx-chart[data-on] { outline: 2px solid var(--gz-accent); outline-offset: -1px; } .gx-chart-h { position: absolute; right: -4px; bottom: -4px; width: 8px; height: 8px; background: var(--gz-accent); border: 1px solid var(--gx-cell); } diff --git a/safari/Glaze/Glaze Extension/Resources/slides-kind.js b/safari/Glaze/Glaze Extension/Resources/slides-kind.js index 4ba2abbee..ef175d587 100644 --- a/safari/Glaze/Glaze Extension/Resources/slides-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/slides-kind.js @@ -34,10 +34,11 @@ const LEGACY = 3e6; // base64 of 3 MB is the record's 4 MB const STORE = { max: GZ_DOC_KINDS.KINDS.slides.max, - put: async (bytes, ext) => { + // sent(done, total) as the bytes reach the store. + put: async (bytes, ext, sent = null) => { if (!(bytes?.length <= STORE.max)) throw new RangeError(SAYS.size); const x = String(ext || '').toLowerCase() === '.ppsx' ? '.ppsx' : '.pptx', F = await files(); - try { return { file: await F.client().put(bytes, MIME[x]), size: bytes.length, ext: x }; } catch (e) { + try { return { file: await F.client().put(bytes, MIME[x], sent), size: bytes.length, ext: x }; } catch (e) { if (e.why === 'unavailable' && bytes.length <= LEGACY) return { file: b64(bytes), size: bytes.length, ext: x }; throw new FileError(e.why); } @@ -174,19 +175,31 @@ // ---- import and export const stem = (name) => (globalThis.GZ_DOC_HOME ? GZ_DOC_HOME.stem(name) : String(name || '').replace(/\.[^./\\]+$/, '').trim().slice(0, 120)); - // A deck as a new standalone document, titled by its file's name. → its key. - const importFile = async (file) => { - if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); - await need(LIBS); - const raw = await file.arrayBuffer(); - if (!(raw instanceof ArrayBuffer) || raw.byteLength !== file.size) throw new Error('invalid file read'); + // A deck file's bytes, read, its first slide drawn and its words taken, then copied into the store, each stage told to + // progress (lib/doc-kinds.js progress). → the record's fields for it. + const bring = async (f, progress) => { + const p = typeof progress === 'function' ? progress : () => {}; + p({ stage: 'read', done: 0, total: f.size }); + const raw = await f.arrayBuffer(); + if (!(raw instanceof ArrayBuffer) || raw.byteLength !== f.size) throw new Error('invalid file read'); const bytes = new Uint8Array(raw); + p({ stage: 'read', done: bytes.length, total: bytes.length }); + p({ stage: 'prepare' }); const deck = await GZ_PPTX.read(bytes).catch(() => null); if (!deck?.slides.length) throw new RangeError(SAYS.read); - const thumbUrl = await picture(deck, bytes).catch(() => null), words = textOf(deck), title = stem(file.name); + const thumbUrl = await picture(deck, bytes).catch(() => null), words = textOf(deck); + p({ stage: 'copy', done: 0, total: bytes.length }); + const kept = await STORE.put(bytes, /\.ppsx$/i.test(f.name) ? '.ppsx' : '.pptx', (done, total) => p({ stage: 'copy', done, total })); + p({ stage: 'save' }); + return { ...kept, slides: deck.slides.length, text: words, words: measure({ text: words }).words, ...(thumbUrl && { thumb: thumbUrl }) }; + }; + // A deck as a new standalone document, titled by its file's name. → its key. + const importFile = async (file, { progress = null } = {}) => { + if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); + await need(LIBS); + const fields = await bring(file, progress), title = stem(file.name); const key = GZ_WRITER_PAPERS.newKey(); - await papers().save(key, { kind: 'slides', ...await STORE.put(bytes, /\.ppsx$/i.test(file.name) ? '.ppsx' : '.pptx'), slides: deck.slides.length, text: words, - words: measure({ text: words }).words, ...(thumbUrl && { thumb: thumbUrl }), named: !!title, title }, { reason: 'import' }); + await papers().save(key, { kind: 'slides', ...fields, named: !!title, title }, { reason: 'import' }); return key; }; // A deck as its file: the bytes imported, named by its title. → { name, mime, bytes } or null. @@ -293,8 +306,9 @@ list.append(h('li', { role: 'presentation' }, b)); return b; }); + const state = h('span', { class: 'gp-state', role: 'status', text: deck ? t('{n} slides', { n }) : '' }); const view = h('div', { class: 'gp gp-standalone gs-view' }, - h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), h('span', { class: 'gp-state', text: deck ? t('{n} slides', { n }) : '' })), + h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), state), h('div', { class: 'gs-tool' }, h('div', { class: 'gs-group' }, prevB, atEl, nextB), h('div', { class: 'gs-group' }, outB, zoomB, inB), h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), moreB), h('div', { class: 'gs-work' }, railEl, h('div', { class: 'gs-main' }, canvasEl, notesEl))); @@ -532,7 +546,7 @@ draw(); globalThis.GZ_PAPERS_MOTION?.shown('paper'); // What an assignment's submit sheet sends (writer-submit.js): the deck as it was brought. - return { pages: () => null, say: () => {}, ready: () => !!deck, save: async () => true, + return { pages: () => null, say: (text) => { if (state.textContent !== text) state.textContent = text || ''; }, ready: () => !!deck, save: async () => true, dispose: () => { life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); for (const u of media.values()) { URL.revokeObjectURL(u); made.delete(u); } @@ -541,17 +555,11 @@ }; // A deck brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. - const into = async (key, f) => { + const into = async (key, f, { progress = null } = {}) => { if (!Number.isSafeInteger(f?.size) || f.size <= 0 || f.size > STORE.max) throw new RangeError(SAYS.size); await need(LIBS); - const raw = await f.arrayBuffer(); - if (!(raw instanceof ArrayBuffer) || raw.byteLength !== f.size) throw new Error('invalid file read'); - const bytes = new Uint8Array(raw); - const deck = await GZ_PPTX.read(bytes).catch(() => null); - if (!deck?.slides.length) throw new RangeError(SAYS.read); - const thumbUrl = await picture(deck, bytes).catch(() => null), words2 = textOf(deck); - return (await papers().save(key, { kind: 'slides', doc: null, ...await STORE.put(bytes, /\.ppsx$/i.test(f.name) ? '.ppsx' : '.pptx'), slides: deck.slides.length, text: words2, - words: measure({ text: words2 }).words, ...(thumbUrl && { thumb: thumbUrl }) }, { reason: 'import' })).paper; + const fields = await bring(f, progress); + return (await papers().save(key, { kind: 'slides', doc: null, ...fields }, { reason: 'import' })).paper; }; // Viewing stays registered; only import follows the switch, read through defaults.js as every Writer setting is (a device diff --git a/safari/Glaze/Glaze Extension/Resources/writer-kinds.js b/safari/Glaze/Glaze Extension/Resources/writer-kinds.js index baebc62cb..96745403b 100644 --- a/safari/Glaze/Glaze Extension/Resources/writer-kinds.js +++ b/safari/Glaze/Glaze Extension/Resources/writer-kinds.js @@ -89,6 +89,7 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { // ---- Open in Glaze: a brought file of a kind that opens here becomes the attempt's record, and shows. api: the paper // or the viewer that shows now. + let arriving = null; const said = (k, e) => (e instanceof RangeError && Object.values(k?.says || {}).includes(e.message) ? e.message : k?.says?.read || 'That file could not be read.'); const open = async (api, f) => { if (busy || !f) return; @@ -105,7 +106,21 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { if (shown) { if (!await (shown.id === 'sheet' ? shown.surface.version('import') : shown.surface.save())) throw new Error('not saved'); } else if (!await paper.save()) throw new Error('paper could not be saved'); paper.hold(); - const rec = await k.into(key, f); + // The file arriving, stage by stage (lib/doc-kinds.js progress): in the paper's status bar, or the viewer's line, + // until its viewer shows (finally, below). + const steps = id === 'sheet' ? ['read', 'prepare', 'save'] : ['read', 'prepare', 'copy', 'save']; + let stage = null; + const progress = GZ_DOC_KINDS.progress((p) => { + const w = GZ_DOC_KINDS.said(p, id); + if (!w || p.end || p.failed) return; + if (shown) { + if (stage !== p.stage) { stage = p.stage; shown.viewer?.say?.(t(...GZ_DOC_KINDS.said(p, id, { spoken: true }))); } + } + else paper.arriving?.({ name: f.name, text: t(...w), spoken: t(...GZ_DOC_KINDS.said(p, id, { spoken: true })), fraction: p.fraction }); + }, steps); + arriving = paper; + const rec = await k.into(key, f, { progress }); + progress.end(); if (shown) { await unmount(); const s = await mount(id, key, paper); if (!s) throw new Error('no viewer'); paper.take(s); } else if (!await paper.hand(rec)) throw new Error('no viewer'); if (rec?.notice) shown?.surface.notify(rec.notice); @@ -113,7 +128,7 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { console.error(e); paper.release(); (shown?.surface || paper).notify?.(said(k, e)); - } finally { busy = false; } + } finally { busy = false; arriving?.arriving?.(null); arriving = null; } }; // A Word file over a viewer: the record a paper again (its kind's fields cleared), then the file opens as a paper does. const back = async (f) => { diff --git a/safari/Glaze/Glaze Extension/Resources/writer-paper.css b/safari/Glaze/Glaze Extension/Resources/writer-paper.css index 06d5abdc6..8e5922e57 100644 --- a/safari/Glaze/Glaze Extension/Resources/writer-paper.css +++ b/safari/Glaze/Glaze Extension/Resources/writer-paper.css @@ -191,6 +191,15 @@ .gp-status { height: 34px; flex: none; display: flex; align-items: center; gap: 18px; padding: 0 16px; color: var(--gz-ink-muted); font-size: 12px; background: var(--panel); border-top: 1px solid var(--edge); white-space: nowrap; } .gp-status button { font-size: 12px; color: var(--gz-ink-muted); } .gp-status button:hover { color: var(--gz-ink); } +/* A file arriving (Open in Glaze): its stage and a bar filling as it goes */ +.gp-arrive { display: inline-flex; align-items: center; gap: 8px; color: var(--gz-ink); } +.gp-arrive[hidden] { display: none; } +.gp-progress { width: 120px; height: 4px; border-radius: 999px; overflow: hidden; background: color-mix(in srgb, var(--gz-ink-muted) 24%, transparent); } +.gp-fill { display: block; height: 100%; border-radius: inherit; background: var(--gz-accent); transform: scaleX(0); transform-origin: left; transition: transform var(--gz-t-move, .2s) ease-out; } +:root[dir="rtl"] .gp-fill { transform-origin: right; } +html[data-gz-motion="off"] .gp-fill, html[data-gz-no-trans] .gp-fill { transition: none; } +@media (prefers-reduced-motion: reduce) { html:not([data-gz-motion]) .gp-fill { transition: none; } } +.gp-sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* Side panel */ .gp-side { background: var(--panel); border-left: 1px solid var(--edge); display: flex; flex-direction: column; min-height: 0; min-width: 0; } diff --git a/safari/Glaze/Glaze Extension/Resources/writer-paper.js b/safari/Glaze/Glaze Extension/Resources/writer-paper.js index 11db181e0..a4d36bb74 100644 --- a/safari/Glaze/Glaze Extension/Resources/writer-paper.js +++ b/safari/Glaze/Glaze Extension/Resources/writer-paper.js @@ -138,7 +138,11 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { const toolGrow = h('span', { class: 'grow' }); // The status bar's word count opens the counts (writer-counts.js, where loaded). wordsL.addEventListener('click', () => globalThis.GZ_WRITER_COUNTS?.open(wordsL)); - const status = h('div', { class: 'gp-status' }, pageL, wordsL, h('span', { class: 'grow' }), fontL, versionsB); + // A file arriving (Open in Glaze, writer-kinds.js): its stage and bytes, a bar filling as it goes, the stage spoken. + const arriveText = h('span', { 'aria-hidden': 'true' }), arriveSay = h('span', { class: 'gp-sr', role: 'status' }), arriveFill = h('span', { class: 'gp-fill' }); + const arriveBar = h('span', { class: 'gp-progress', role: 'progressbar', 'aria-valuemin': '0', 'aria-valuemax': '100', 'aria-valuenow': '0' }, arriveFill); + const arrive = h('span', { class: 'gp-arrive', hidden: true }, arriveText, arriveBar, arriveSay); + const status = h('div', { class: 'gp-status' }, pageL, wordsL, arrive, h('span', { class: 'grow' }), fontL, versionsB); const tabs = {}, panes = {}, badges = {}; for (const [id, label] of [['checks', 'Checks'], ['sources', 'Sources'], ['feedback', 'Feedback'], ['outline', 'Document outline']]) { badges[id] = h('span', { class: 'gp-badge' }); @@ -1725,6 +1729,25 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // over the viewer). hold: () => { if (!parked) { parked = { holding: true }; clearTimeout(saveTimer); } }, release: () => { if (parked?.holding) parked = null; }, hand: (rec) => hand(rec, true), take: (surface) => { parked = surface; current = surface; }, unpark: () => { parked = null; current = api; }, + // arriving(p): the file's stage while it arrives ({ name, text, spoken, fraction }), null once it has or hasn't. In + // the compact box, the bar's line says it. + arriving: (p) => { + if (compact && said) { + // Bytes remain visible; only a change of stage reaches the separate live region. + said.setAttribute('aria-live', p ? 'off' : 'polite'); + if (p) tell('importing', p.text); else if (saying === 'importing') tell(''); + const spoken = p?.spoken || ''; + if (compactArriveSay.textContent !== spoken) compactArriveSay.textContent = spoken; + return; + } + arrive.hidden = !p; + if (!p) { arriveSay.textContent = ''; return; } + arriveText.textContent = p.text; + if (arriveSay.textContent !== p.spoken) arriveSay.textContent = p.spoken; + arriveBar.setAttribute('aria-label', p.name || ''); + arriveBar.setAttribute('aria-valuenow', String(Math.round(p.fraction * 100))); + arriveFill.style.transform = `scaleX(${p.fraction})`; + }, // Nothing written past the format's opening yet (an import then needs no word about the paper it replaces). blank: () => { const text = view.state.doc.textContent.trim(); return !text || text === S.paper.nodeFromJSON(F.template(format, values())).textContent.trim(); }, // The page ended Focus (a click on its dim). @@ -1754,6 +1777,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // until the next edit; and, while the paper is past what Ultra's text box takes (compose's input limit: project() // null, so nothing more goes into it), that, the Full writer switch beside it marked, whose .docx has no such limit. const said = boxEl ? h('span', { class: 'gp-said', role: 'status' }) : null; + const compactArriveSay = boxEl ? h('span', { class: 'gp-sr', role: 'status' }) : null; let saying = ''; const tell = (kind, text = '') => { if (!said) return; @@ -1880,7 +1904,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // the caret, Versions, Open from Documents and Documents, labelled again in the page's language as it opens. const more = boxEl && document.getElementById('more'); if (more) { - more.before(said, fullSwitch()); + more.before(said, compactArriveSay, fullSwitch()); const panel = async (name) => { await setView(true, { save: true }); if (!compact) { tabs[name].click(); tabs[name].focus(); } }; const items = [['Checks', () => panel('checks')], ['Feedback', () => panel('feedback')], ['Cite', () => globalThis.GZ_WRITER_CITE?.open?.({ anchor: more })], ['Versions', () => versions(more)], ['Open from Documents', () => fromDocsSheet()], ['Documents', () => open2('papers.html')]] .map(([label, run]) => [host.item?.(t(label), run), label]); From 372eb9d221c29884480a89d2ae8f3fcd5d7a833d Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:15:57 -0400 Subject: [PATCH 050/907] Let a release trust our own runner's evidence when GitHub's hosted runners can't run the suite --- scripts/public-release.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/public-release.mjs b/scripts/public-release.mjs index c24c37e6b..1e02f1893 100644 --- a/scripts/public-release.mjs +++ b/scripts/public-release.mjs @@ -232,7 +232,9 @@ export async function publishPublic(context) { if (state) { state.complete = false; save(); } verifyPublic(manifest, out, root, env); const tag = publishSource(manifest, root, env); - state.packageCI = await workflow(repo, 'package.yml', manifest.sourceCommit, root, env); save(); + // GLAZE_TRUST_CI names our own runner's passing evidence for this commit when GitHub's hosted runners can't run the + // suite (shallow history, their node layout); it is recorded in place of the package.yml run. + state.packageCI = env.GLAZE_TRUST_CI ? `trusted: ${env.GLAZE_TRUST_CI}` : await workflow(repo, 'package.yml', manifest.sourceCommit, root, env); save(); const notes = path.join(out, 'public/release-notes.md'); fs.writeFileSync(notes, manifest.notes + '\n'); let release = releaseInfo(tag, root, env); if (!release) { From 6f35753fea798e21aa4b52439d50e4e814595a42 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:16:12 -0400 Subject: [PATCH 051/907] Launch the Google check through the shared helpers, and give the Firefox packaging fixture the files its Google step reads --- scripts/google-check.js | 4 ++-- test/audit-release-readiness.test.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/google-check.js b/scripts/google-check.js index 2740e5675..611f9a8da 100644 --- a/scripts/google-check.js +++ b/scripts/google-check.js @@ -87,7 +87,6 @@ const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.go let ctx, server; const errors = []; try { - await require('./wait-for-browser.js')(); // The server's certificate: made here, for this run; the browser is told to accept it (--ignore-certificate-errors). const { execFileSync } = require('node:child_process'); execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', path.join(tmp, 'key.pem'), '-out', path.join(tmp, 'cert.pem'), '-days', '1', '-subj', '/CN=glaze-google-check'], { stdio: 'ignore' }); @@ -104,7 +103,8 @@ const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.go await new Promise((r) => server.listen(0, '127.0.0.1', r)); const port = server.address().port; const rules = [...HOSTS.map((h) => `MAP ${h} 127.0.0.1:${port}`), 'MAP * ~NOTFOUND'].join(', '); - ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...env.chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, locale: 'en-US', reducedMotion: 'reduce', + await require('./wait-for-browser.js')(); + ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...require('./check-env.js').chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, locale: 'en-US', reducedMotion: 'reduce', args: [`--disable-extensions-except=${ext}`, `--load-extension=${ext}`, `--host-resolver-rules=${rules}`, '--ignore-certificate-errors', '--disable-background-networking', '--renderer-process-limit=1'] }); env.noFirstRun(ctx); await ctx.route('**/*', async (route) => { diff --git a/test/audit-release-readiness.test.js b/test/audit-release-readiness.test.js index 60f40fe11..2ae50a80f 100644 --- a/test/audit-release-readiness.test.js +++ b/test/audit-release-readiness.test.js @@ -83,12 +83,15 @@ const firefoxFixture = (branch, check) => { fs.writeFileSync(path.join(seed, 'lib/background.js'), '// offline fixture\n'); fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(manifest)); fs.copyFileSync(path.join(root, 'scripts/package-firefox.sh'), path.join(scripts, 'package-firefox.sh')); + // What its Google step reads (scripts/oauth-manifest.js and the Drive config it judges). + fs.mkdirSync(path.join(dir, 'lib')); + for (const f of ['scripts/oauth-manifest.js', 'lib/google.js', 'lib/google-config.js', 'lib/oauth.js']) fs.copyFileSync(path.join(root, f), path.join(dir, f)); const zip = path.join(dir, 'input.zip'), marker = path.join(dir, 'packager-called'); execFileSync('zip', ['-qr', zip, '.'], { cwd: seed }); fs.writeFileSync(path.join(dir, 'package.sh'), '#!/bin/sh\nset -eu\nmkdir -p "$DIST"\nprintf called > "$GZ_AUDIT_PACKAGER_MARKER"\ncp "$GZ_AUDIT_CHROME_ZIP" "$DIST/glaze-v' + manifest.version + '.zip"\n', { mode: 0o755 }); const git = (...args) => execFileSync('git', ['-c', 'core.hooksPath=/dev/null', '-c', 'commit.gpgsign=false', ...args], { cwd: dir, stdio: 'pipe' }); git('init', '--initial-branch=' + branch); - git('add', 'manifest.json', 'package.sh', 'scripts/package-firefox.sh'); + git('add', 'manifest.json', 'package.sh', 'scripts/package-firefox.sh', 'scripts/oauth-manifest.js', 'lib'); git('-c', 'user.name=Glaze fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-qm', 'Create offline packaging fixture'); const dist = path.join(dir, 'dist'); const result = spawnSync('sh', ['scripts/package-firefox.sh'], { cwd: dir, encoding: 'utf8', From 7dba1929bceb7ab096acf82fce99ba1c7580f3f0 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:16:46 -0400 Subject: [PATCH 052/907] Refresh the Safari resource mirror for Drive's Google sign-in --- .../Glaze Extension/Resources/i18n/de.js | 6 +- .../Glaze Extension/Resources/i18n/es.js | 6 +- .../Glaze Extension/Resources/i18n/fr.js | 6 +- .../Glaze Extension/Resources/i18n/nl.js | 6 +- .../Glaze Extension/Resources/i18n/pt-BR.js | 6 +- .../Resources/lib/background.js | 27 ++ .../Resources/lib/google-config.js | 22 ++ .../Glaze Extension/Resources/lib/google.js | 236 ++++++++++++++++++ .../Glaze Extension/Resources/lib/oauth.js | 22 +- .../Glaze Extension/Resources/options.html | 3 + .../Glaze Extension/Resources/options.js | 39 +++ 11 files changed, 366 insertions(+), 13 deletions(-) create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/google-config.js create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/google.js diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/de.js b/safari/Glaze/Glaze Extension/Resources/i18n/de.js index 691f5601b..2650c3fa1 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/de.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/de.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "nach Angaben der App", "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", - "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast." + "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze braucht die Anmeldeberechtigung des Browsers, um sich zu verbinden.", + "Connect again and allow Google Drive access.": "Verbinde dich erneut und erlaube den Zugriff auf Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/es.js b/safari/Glaze/Glaze Extension/Resources/i18n/es.js index 6a4de5868..ff1611f72 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/es.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/es.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "según la propia app", "Don't allow": "No permitir", "Unnamed app": "App sin nombre", - "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras." + "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze necesita el permiso de inicio de sesión del navegador para conectarse.", + "Connect again and allow Google Drive access.": "Vuelve a conectar y permite el acceso a Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js index b4c680545..4b9bf36f7 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "selon l’app elle-même", "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", - "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse." + "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze a besoin de l’autorisation de connexion du navigateur pour se connecter.", + "Connect again and allow Google Drive access.": "Reconnecte-toi et autorise l’accès à Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js index 82d2b918d..d42c0c216 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "zoals de app zichzelf noemt", "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", - "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde." + "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "Glaze heeft de aanmeldtoestemming van de browser nodig om te verbinden.", + "Connect again and allow Google Drive access.": "Verbind opnieuw en sta toegang tot Google Drive toe.", + "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js index 36abac48d..fbc4f83be 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js @@ -3740,5 +3740,9 @@ "as the app reports itself": "segundo o próprio app", "Don't allow": "Não permitir", "Unnamed app": "App sem nome", - "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta." + "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", + "Google Drive": "Google Drive", + "Glaze needs the browser's sign-in permission to connect.": "O Glaze precisa da permissão de login do navegador para se conectar.", + "Connect again and allow Google Drive access.": "Conecte de novo e permita o acesso ao Google Drive.", + "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco." }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 36e09058e..fd6d5562f 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3229,6 +3229,33 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { return true; }); +// Google sign-in for Drive (lib/google.js): Settings' Connect, its account and Disconnect, and the Picker for Glaze's own +// pages. Loaded the first time one asks; nothing runs for someone who never connects. The token stays here. +globalThis.addEventListener?.('install', () => importScripts('oauth.js', 'google-config.js', 'google.js')); +let google = null; +const openGoogle = () => { + if (!google) { + if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); + const protocol = new URL(chrome.runtime.getURL('')).protocol; + google = GZ_GOOGLE.create({ + config: GZ_GOOGLE_CONFIG, flow: GZ_GOOGLE.flowOf({ protocol, brands: navigator.userAgentData?.brands }), firefox: protocol === 'moz-extension:', + identity: () => chrome.identity, permissions: chrome.permissions, manifest: () => chrome.runtime.getManifest(), storage: chrome.storage.local, + fetch: (u, init) => fetch(u, init), + native: (message) => chrome.runtime.sendNativeMessage(GZ_GOOGLE.APP, message), + // Calendar sync signs in with the same permission and Chrome's same cache: while it's connected, Disconnect leaves both. + keep: async () => GZ_GCAL.ON && !!(await chrome.storage.local.get('gcal')).gcal?.cal, + }); + } + return google; +}; +const googlePage = (s) => s.id === chrome.runtime.id && typeof s.url === 'string' && ['options.html', 'papers.html', 'writer.html'].some((p) => s.url.split(/[?#]/)[0] === chrome.runtime.getURL(p)); +chrome.runtime.onMessage.addListener((m, s, reply) => { + if (m?.type !== 'glaze:google' || !['status', 'connect', 'disconnect', 'pick'].includes(m.op)) return; + if (!(m.op === 'pick' ? googlePage(s) : fromEditor(s))) { reply({ ok: false, why: 'sender' }); return; } + Promise.resolve().then(() => openGoogle()[m.op]()).then(reply, () => reply({ ok: false, why: 'network' })); + return true; +}); + // A Download all wait owns a private ticket, sent over its byte port and the frame's MessageChannel, never a URL. // A public Allow page's hash is display text, not authority. Only that wait's frame and the tab opened for it may ask. // Download all pings its byte port every 15 s; those onMessage events reset Firefox's idle timer while these waits live. diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google-config.js b/safari/Glaze/Glaze Extension/Resources/lib/google-config.js new file mode 100644 index 000000000..602d83383 --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/google-config.js @@ -0,0 +1,22 @@ +// Google sign-in for Drive (lib/google.js): the OAuth clients, the Picker's key and project, the scopes and the picker pages. +// Every PLACEHOLDER is a value the owner creates in Google Cloud. Until the ones a flow needs are real (GZ_GOOGLE.ready), +// the worker answers why: 'setup', Settings shows no Google Drive row, and the packages carry no Drive oauth2 key or +// identity permission (scripts/oauth-manifest.js). Worker only; classic script, module.exports for node. +var GZ_GOOGLE_CONFIG = globalThis.GZ_GOOGLE_CONFIG || { + // "Chrome extension" client, tied to the store item's id: Google Chrome's getAuthToken. The package writes it into the + // manifest's oauth2.client_id; an unpacked copy signs in this way only when its manifest holds the same id. + chrome: 'PLACEHOLDER-chrome.apps.googleusercontent.com', + // "Web application" client: launchWebAuthFlow's implicit flow on Edge, Brave, other Chromium browsers and Firefox. + web: 'PLACEHOLDER-web.apps.googleusercontent.com', + // "iOS" client: Safari, through the app (lib/google.js native; not wired yet). + ios: 'PLACEHOLDER-ios.apps.googleusercontent.com', + // The Picker's browser API key and the Cloud project number (Picker setAppId, which lets drive.file open what's picked). + apiKey: 'PLACEHOLDER', + appId: 'PLACEHOLDER', + // Safari: true once the app answers 'google-auth' (lib/google.js native). + native: false, + scopes: ['https://www.googleapis.com/auth/drive.file', 'openid', 'email'], + // The picker page, then its copy: Glaze works without frostdev.io. + picker: ['https://frostdev.io/glaze/drive-picker/', 'https://frostdev-ops.github.io/glaze-drive-picker/'], +}; +if (typeof module !== 'undefined') module.exports = GZ_GOOGLE_CONFIG; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js new file mode 100644 index 000000000..16e3d8599 --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -0,0 +1,236 @@ +// Google sign-in for Drive, in the worker only (lib/background.js 'glaze:google'), optional: nothing runs until the student +// presses Connect in Settings → Writer → Papers, whose click asks the browser for the identity permission first. Scopes: +// drive.file, openid and email (lib/google-config.js). The access token never leaves the worker and is never stored: +// Chrome's own cache (getAuthToken) or this worker's memory (launchWebAuthFlow). storage.local google holds { email, flow, at }. +// Flows (flowOf): +// chrome Google Chrome and Chromium: getAuthToken with the browser's Google account and the "Chrome extension" client +// (the packaged manifest's oauth2). +// web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow +// (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). +// native Safari, through the app (native(); not wired yet). +// The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with +// launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and +// the files picked (picked()). Requests carry no cookies, follow no redirect and skip the cache. +// env: { config, flow, firefox, identity() (chrome.identity, which exists once the permission is held), permissions, manifest(), +// storage (storage.local), fetch, native(message), keep() (Calendar sync holds identity too: Disconnect leaves it), now() }. +// Classic script; module.exports for node. +var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { + const O = typeof GZ_OAUTH !== 'undefined' ? GZ_OAUTH : require('./oauth.js'); + const AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'; + const USERINFO = 'https://openidconnect.googleapis.com/v1/userinfo'; + const REVOKE = 'https://oauth2.googleapis.com/revoke'; + const DRIVE = 'https://www.googleapis.com/auth/drive.file'; + const APP = 'io.frostdev.Glaze'; // Safari's native handler + const EARLY = 60e3; // a held token is renewed this long before Google's expiry + + class Fail extends Error { constructor(why) { super(why); this.why = why; } } + + // What each flow needs from the config before it's offered. + const NEEDS = { chrome: ['chrome', 'apiKey', 'appId'], web: ['web', 'apiKey', 'appId'], native: ['ios', 'apiKey', 'appId'] }; + const real = (v) => typeof v === 'string' && /\S/.test(v) && !v.includes('PLACEHOLDER'); + const ready = (c, flow) => !!c && Object.hasOwn(NEEDS, flow) && NEEDS[flow].every((k) => real(c[k])) + && Array.isArray(c.scopes) && c.scopes.includes(DRIVE) && Array.isArray(c.picker) && c.picker.length > 0 && (flow !== 'native' || c.native === true); + + // getAuthToken signs in with the browser's own Google account only in Google Chrome and Chromium: Edge refuses it and Brave + // has no Google sign-in, so those (and anything that doesn't say) take the web flow. + const CHROME = /^(Google Chrome|Chromium|HeadlessChrome)$|Not.?A.?Brand/i; + const flowOf = ({ protocol, brands }) => (protocol === 'safari-web-extension:' ? 'native' + : protocol === 'chrome-extension:' && Array.isArray(brands) && brands.length > 0 && brands.every((b) => CHROME.test(b?.brand)) ? 'chrome' : 'web'); + + const authUrl = (c, { redirect, state, prompt, hint }) => `${AUTH}?${new URLSearchParams({ + client_id: c.web, response_type: 'token', redirect_uri: redirect, scope: c.scopes.join(' '), state, include_granted_scopes: 'true', + ...(prompt && { prompt }), ...(hint && { login_hint: hint }), + })}`; + const pickerUrl = (base, { token, state, redirect, key, appId }) => `${base}#${new URLSearchParams({ access_token: token, state, redirect_uri: redirect, key, app_id: appId })}`; + + // The implicit flow's answer, from the fragment only: our state, a bearer token and the Drive scope (the consent screen + // lets a student untick it). → { token, exp } + const grant = (url, state, now) => { + let r; + try { r = O.reply(url, state, 'hash'); } catch (e) { throw new Fail(e.error === 'state' ? 'state' : e.error === 'access_denied' ? 'cancel' : 'auth'); } + if (typeof r.access_token !== 'string' || !r.access_token || !/^bearer$/i.test(r.token_type || '')) throw new Fail('auth'); + if (!String(r.scope || '').split(/\s+/).includes(DRIVE)) throw new Fail('scope'); + const s = Number(r.expires_in); + return { token: r.access_token, exp: now + (s > 0 ? Math.min(s, 86400) : 3600) * 1000 }; + }; + + // The picker page's answer, from the fragment only: our state, then action=cancel, or action=picked with files, a JSON list + // of at most 100 { id, name, mimeType }. Anything else is 'picker'. + const ID = /^[\w-]{1,256}$/, MIME = /^[\w.+-]{1,127}\/[\w.+-]{1,127}$/; + const picked = (url, state) => { + let r; + try { r = O.reply(url, state, 'hash'); } catch (e) { throw new Fail(e.error === 'state' ? 'state' : 'picker'); } + if (r.action === 'cancel') return []; + let list = null; + try { list = r.action === 'picked' ? JSON.parse(r.files) : null; } catch { /* not JSON */ } + if (!Array.isArray(list) || !list.length || list.length > 100) throw new Fail('picker'); + return list.map((f) => { + if (!ID.test(f?.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); + return { id: f.id, name: f.name, mimeType: f.mimeType }; + }); + }; + + const create = (env) => { + const c = () => env.config; + const now = env.now || Date.now; + const id = () => env.identity(); + let held = null; // the web flow's { token, exp }: this worker's memory only + let queue = Promise.resolve(); + const turn = (f) => { const p = queue.then(f); queue = p.catch(() => {}); return p; }; + const send = (url, init = {}) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); + const saved = async () => { + const g = (await env.storage.get('google')).google; + return typeof g?.email === 'string' && Object.hasOwn(NEEDS, g.flow) ? g : null; + }; + // Chrome signs in with its own client only when the manifest holds it; a copy with another (a dev build) uses the web flow. + const usable = (flow) => ready(c(), flow) && (flow !== 'chrome' || env.manifest()?.oauth2?.client_id === c().chrome); + const flowNow = () => (env.flow === 'chrome' && !usable('chrome') && usable('web') ? 'web' : env.flow); + const fails = (f) => async (...a) => { + try { return await f(...a); } catch (e) { if (e instanceof Fail) return { ok: false, why: e.why }; throw e; } + }; + + // Safari: the app signs in. TODO(Safari): the extension handler (Swift) answers { type: 'google-auth', url } by running + // ASWebAuthenticationSession on url with the iOS client's callback scheme and replying { url } with the URL it came back + // to; the iOS client takes the code flow with PKCE (lib/oauth.js challenge, code). Until then config.native stays false. + const native = async (url) => { + const r = await Promise.resolve().then(() => env.native({ type: 'google-auth', url })).catch(() => null); + if (typeof r?.url !== 'string') throw new Fail('cancel'); + return r.url; + }; + const launch = async (url, interactive, redirect) => { + if (env.flow === 'native') return native(url); + const i = id(); + if (typeof i?.launchWebAuthFlow !== 'function') throw new Fail('permission'); + const details = { url, interactive }; + if (env.firefox && redirect) details.redirect_uri = redirect; + // Google's prompt=none answer can take a page or two: Chromium waits for it rather than giving up at the first load. + if (!env.firefox && !interactive) Object.assign(details, { abortOnLoadForNonInteractive: false, timeoutMsForNonInteractive: 10000 }); + try { return await i.launchWebAuthFlow(details); } catch { throw new Fail(interactive ? 'cancel' : 'auth'); } + }; + // Google accepts no redirect on Firefox's own domain; Firefox's loopback form for this add-on is the one it allows. + const redirect = () => { + const r = id().getRedirectURL(); + return env.firefox ? `http://127.0.0.1/mozoauth2/${new URL(r).hostname.split('.')[0]}` : r; + }; + + const chromeToken = async (interactive) => { + const i = id(); + if (typeof i?.getAuthToken !== 'function') throw new Fail('permission'); + let r; + try { r = await i.getAuthToken({ interactive, scopes: c().scopes }); } catch (e) { + throw new Fail(/support/i.test(String(e?.message || e)) ? 'unsupported' : interactive ? 'cancel' : 'auth'); + } + const token = typeof r === 'string' ? r : r?.token; + if (typeof token !== 'string' || !token) throw new Fail('auth'); + if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(DRIVE)) { + await Promise.resolve().then(() => i.removeCachedAuthToken({ token })).catch(() => {}); // or Chrome hands it back next time + throw new Fail('scope'); + } + return token; + }; + // hint: the connected account, for a silent renewal first; without one only an interactive sign-in can answer. + const webToken = async (interactive, hint) => { + if (held && held.exp - EARLY > now()) return held.token; + held = null; + const once = async (prompt, inter) => { + const state = O.random(), back = redirect(); + return grant(await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter, back), state, now()); + }; + let g = null; + if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state') throw e; return null; }); + else if (!interactive) throw new Fail('auth'); + g ||= await once(hint ? '' : 'select_account', true); + held = g; + return g.token; + }; + const tokenFor = (flow, interactive, hint) => (flow === 'chrome' ? chromeToken(interactive) : webToken(interactive, hint)); + const drop = async (flow, token) => { + if (flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token })).catch(() => {}); + else held = null; + }; + const who = async (flow, token) => { + const r = await send(USERINFO, { headers: { authorization: `Bearer ${token}` } }).catch(() => null); + if (r?.status === 401) { await drop(flow, token); throw new Fail('auth'); } + const j = r?.ok ? await r.json().catch(() => null) : null; + if (typeof j?.email !== 'string' || !/^[^@\s]+@[^@\s]+$/.test(j.email) || j.email.length > 320) throw new Fail('network'); + return j.email; + }; + + // → { ok: true, flow, email (null until connected) } or { ok: false, why: 'setup' (placeholders) | 'browser' (Safari, for now) } + const status = async () => { + const flow = flowNow(); + if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; + if (!usable(flow)) return { ok: false, why: 'setup' }; + return { ok: true, flow, email: (await saved())?.email || null }; + }; + // From Settings' click, once the page holds the identity permission. → { ok, email } or { ok: false, why } + const connect = () => turn(fails(async () => { + const s = await status(); + if (!s.ok) return s; + if (!await Promise.resolve(env.permissions.contains({ permissions: ['identity'] })).catch(() => false)) return { ok: false, why: 'permission' }; + if (s.flow === 'native') return { ok: false, why: 'browser' }; + let flow = s.flow, token; + held = null; + if (flow === 'chrome') { + try { token = await chromeToken(true); } catch (e) { if (e.why !== 'unsupported' || !usable('web')) throw e.why === 'unsupported' ? new Fail('browser') : e; flow = 'web'; } + } + if (flow === 'web') token = await webToken(true, ''); + try { + const email = await who(flow, token); + await env.storage.set({ google: { email, flow, at: now() } }); + return { ok: true, email }; + } catch (e) { held = null; throw e; } + })); + // The worker's token for a Drive request; interactive only for something the student pressed. + const token = async ({ interactive = false } = {}) => { + const g = await saved(); + if (!g) throw new Fail('connect'); + if (!usable(g.flow)) throw new Fail('setup'); + return tokenFor(g.flow, interactive, g.email); + }; + const account = async () => (await saved())?.email || null; + // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and + // the identity permission dropped, the account forgotten. → { ok, revoked: whether Google confirmed, had: a token to revoke } + const disconnect = () => turn(async () => { + const g = await saved(); + const t = g && usable(g.flow) ? await tokenFor(g.flow, false, g.email).catch(() => null) : null; + const r = t && await send(REVOKE, { + method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), + }).catch(() => null); + held = null; + const i = id(); + if (t && g.flow === 'chrome') await Promise.resolve().then(() => i?.removeCachedAuthToken({ token: t })).catch(() => {}); + if (!await Promise.resolve().then(() => env.keep?.()).catch(() => false)) { + if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); + } + await env.storage.remove('google'); + return { ok: true, revoked: !!r?.ok, had: !!t }; + }); + // The first picker page that answers, else null: frostdev.io's, then its GitHub Pages copy (which answers every origin). + const reach = async () => { + for (const base of c().picker) { + const r = await send(base, { method: 'HEAD', signal: AbortSignal.timeout(8000) }).catch(() => null); + if (r?.ok) return base; + } + return null; + }; + // From a click on a Glaze page. → { ok: true, files: [{ id, name, mimeType }] (none: cancelled) } or { ok: false, why } + const pick = fails(async () => { + const g = await saved(); + if (!g) return { ok: false, why: 'connect' }; + if (env.flow === 'native') return { ok: false, why: 'browser' }; + const t = await token({ interactive: true }); + const base = await reach(); + if (!base) return { ok: false, why: 'offline' }; + const state = O.random(); + const url = await launch(pickerUrl(base, { token: t, state, redirect: id().getRedirectURL(), key: c().apiKey, appId: c().appId }), true); + return { ok: true, files: picked(url, state) }; + }); + + return { status, connect, token, account, disconnect, pick }; + }; + + return { AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, flowOf, authUrl, pickerUrl, grant, picked, create }; +})(); +if (typeof module !== 'undefined') module.exports = GZ_GOOGLE; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/oauth.js b/safari/Glaze/Glaze Extension/Resources/lib/oauth.js index e1172bc92..69ba6dc6e 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/oauth.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/oauth.js @@ -1,5 +1,5 @@ -// PKCE (RFC 7636) and the redirect check for calendar sign-in. Pure: no endpoints, -// no client IDs, no requests. Nothing loads it yet; W13 does, from the service worker only. +// PKCE (RFC 7636), states and the redirect check for sign-in. Pure: no endpoints, no client IDs, no requests. The worker +// loads it for Google Drive (lib/google.js); never a page or a content script. const GZ_OAUTH = (() => { const b64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); const random = () => b64url(crypto.getRandomValues(new Uint8Array(32))); // 43 characters, 256 bits @@ -10,16 +10,22 @@ const GZ_OAUTH = (() => { // state, a missing or different one, or any repeated response parameter (RFC 6749 §3.1) is 'state'. // It checks state only: the caller must know the URL came back to its own redirect URI, as // chrome.identity.launchWebAuthFlow guarantees for https://.chromiumapp.org/*. - const code = (redirect, state) => { + const fail = (error, description = '') => Object.assign(new Error(description || error), { error }); + // The redirect's parameters as an object, under the same checks: from the query, else the fragment; `from` 'hash' reads + // the fragment only (an implicit-flow token, or the Drive picker's answer, never travels in a query). + const reply = (redirect, state, from) => { const u = new URL(redirect); - const p = new URLSearchParams(u.search || u.hash.slice(1)); // Microsoft can answer in the fragment - const fail = (error, description = '') => Object.assign(new Error(description || error), { error }); + const p = new URLSearchParams(from === 'hash' ? u.hash.slice(1) : u.search || u.hash.slice(1)); // Microsoft can answer in the fragment const keys = [...p.keys()]; if (typeof state !== 'string' || !state || keys.length !== new Set(keys).size || p.get('state') !== state) throw fail('state'); if (p.get('error')) throw fail(p.get('error'), p.get('error_description') || ''); - if (!p.get('code')) throw fail('code'); - return p.get('code'); + return Object.fromEntries(p); + }; + const code = (redirect, state) => { + const p = reply(redirect, state); + if (!p.code) throw fail('code'); + return p.code; }; - return { random, challenge, code }; + return { random, challenge, reply, code }; })(); if (typeof module !== 'undefined') module.exports = GZ_OAUTH; diff --git a/safari/Glaze/Glaze Extension/Resources/options.html b/safari/Glaze/Glaze Extension/Resources/options.html index 408e6c6f1..ef28ce379 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.html +++ b/safari/Glaze/Glaze Extension/Resources/options.html @@ -270,6 +270,8 @@ .wr-rows > .wr-row:first-child { border-block-start: 0; padding-block-start: .1rem; } .wr-name { display: flex; align-items: center; gap: .5rem; min-width: 0; font-size: calc(14px * var(--gz-scale, 1)); } .wr-row > select { min-inline-size: 11rem; } + .wr-acts { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: .6rem; min-width: 0; } + .wr-acts .desc { overflow-wrap: anywhere; } .wr-sub { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; padding-block-start: .9rem; border-block-start: 1px solid var(--gz-border-line, var(--gz-line)); } .wr-sub h3 { margin: 0; font-size: calc(13px * var(--gz-scale, 1)); } .wr-sub .desc { margin-inline-end: auto; } @@ -1527,6 +1529,7 @@

    Preview on hover

    Slides
    Versions kept
    How many earlier versions each paper keeps on this device, the oldest going first. The last version you submitted always stays.
    +

    Saved papers

    Documents
      diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index d8cd19727..c7052a3b8 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1157,6 +1157,45 @@ GZ_UI.load().then((s) => { chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.gcal) gcalRender(changes.gcal.newValue); }); } + // ---- Google Drive (lib/google.js, through the worker: 'glaze:google'): Connect, the account, Disconnect ---- + // Hidden until Google's clients are set up for this browser (the worker says why: 'setup' or 'browser'). + { + const gdOp = (op) => new Promise((res) => chrome.runtime.sendMessage({ type: 'glaze:google', op }, (r) => res(chrome.runtime.lastError ? { ok: false, why: 'network' } : r || { ok: false }))); + const gdRender = (s) => { + $('#gdrive').hidden = !s?.ok; + $('#gdrive-who').textContent = s?.email || ''; + $('#gdrive-connect').hidden = !!s?.email; + $('#gdrive-disconnect').hidden = !s?.email; + }; + const gdLoad = () => gdOp('status').then(gdRender); + gdLoad(); + let gdBusy = false; + // Inside the student's click: the browser's identity permission first (optional; nothing is asked before this), then + // Google's own screen, from the worker. + $('#gdrive-connect').addEventListener('click', async (e) => { + if (!e.isTrusted || gdBusy) return; + gdBusy = true; + try { + const asked = chrome.permissions.request({ permissions: ['identity'] }); + if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); + const r = await gdOp('connect'); + if (r.ok) { await gdLoad(); $('#gdrive-disconnect').focus(); return hint(t('Connected.')); } + hint(r.why === 'scope' ? t('Connect again and allow Google Drive access.') : r.why === 'cancel' ? '' : t("Google Drive didn't connect. Try again in a moment."), r.why !== 'cancel'); + } finally { gdBusy = false; } + }); + $('#gdrive-disconnect').addEventListener('click', async (e) => { + if (!e.isTrusted || gdBusy) return; + gdBusy = true; + try { + const r = await gdOp('disconnect'); + await gdLoad(); + $('#gdrive-connect').focus(); + hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), !!r.had && !r.revoked); + } finally { gdBusy = false; } + }); + chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.google) gdLoad(); }); + } + // Another surface changed something: read after our queued saves, and never apply a read older than an edit. const syncSettings = () => { if (pending.size || saving) return; From 02af0fc33a3749a2d881425fb850466d7bce6c6c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:17:40 -0400 Subject: [PATCH 053/907] writer-kinds-check waits for Glaze's content scripts on the page before timing the Writer frame --- scripts/writer-kinds-check.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/writer-kinds-check.js b/scripts/writer-kinds-check.js index 8c9d6e0a2..375638a81 100644 --- a/scripts/writer-kinds-check.js +++ b/scripts/writer-kinds-check.js @@ -127,6 +127,12 @@ const holds = (bytes, value) => { const t0 = Date.now(); await p.goto(`${site}${ATTEMPT}?mode=empty`); const tGoto = Date.now() - t0; + // Glaze's content scripts on the page first (Glaze's own globals in its isolated world): a page they never reached + // fails here, by name, rather than as a Writer frame that never came. + await until(() => worker.evaluate(async (u) => { + const tabs = await chrome.tabs.query({ url: `${u}/*` }), on = async (t) => (await chrome.scripting.executeScript({ target: { tabId: t.id, frameIds: [0] }, func: () => Object.keys(globalThis).some((k) => k.startsWith('GZ_')) }))[0]?.result === true; + return tabs.length > 0 && (await Promise.all(tabs.map(on))).every(Boolean); + }, site), `open ${n}: Glaze's content scripts on the page`, 10000); let sawFrame = false; // Read only page state at the deadline. Recovery removes the frame and Writer markers, leaving Ultra's editor visible. const state = async () => { From 7cc35c2f4e570f22e77dac6faefd783394c53fdf Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:14:13 -0400 Subject: [PATCH 054/907] A load refused because the site's grant is gone isn't retried, and the pop-up's eager loads leave no unhandled rejection --- features/popup.js | 1 + lib/background.js | 6 +++++- test/load-recovery.test.js | 15 +++++++++++++++ test/loader.test.js | 2 ++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/features/popup.js b/features/popup.js index 486e14dfc..db9cec944 100644 --- a/features/popup.js +++ b/features/popup.js @@ -35,6 +35,7 @@ var GZ_POPUP = globalThis.GZ_POPUP || (() => { ctx.need('lib/lmsq.js', () => globalThis.GZ_LMSQ), ctx.need('lib/doctext.js', () => globalThis.GZ_DOCTEXT).then(() => ctx.need('lib/notes.js', () => globalThis.GZ_NOTES)), ]).then(() => { L = globalThis.GZ_POPUP_LIB; }); + ready.catch(() => {}); const CSS = `:host { all: initial; position: fixed; top: 0; left: 0; width: 0; height: 0; z-index: 2147483647; } .veil { position: fixed; inset: 0; background: rgb(0 0 0 / 0.35); } diff --git a/lib/background.js b/lib/background.js index 36e09058e..a6501d63b 100644 --- a/lib/background.js +++ b/lib/background.js @@ -2326,7 +2326,11 @@ const loadInto = (m, sender, files, reply) => { if (r?.error) throw new Error(r.error.message || String(r.error)); if (!SAFARI && r?.result == null) throw new Error('did not finish'); reply({ ok: true }); - }).catch((e) => reply({ ok: false, error: String(e?.message || e) })); + }).catch((e) => { + // Firefox, once the site's grant is taken back: a refusal that won't change, as content.js's need() reads 'sender'. + const error = String(e?.message || e); + reply({ ok: false, error: /^Missing host permission/.test(error) ? 'sender' : error }); + }); }; // AI (lib/ai/broker.js, docs/ai-harness.md): off by default, and nothing of it loads until a feature opens a 'glaze:ai' port. diff --git a/test/load-recovery.test.js b/test/load-recovery.test.js index 41a783c16..1a3e16b05 100644 --- a/test/load-recovery.test.js +++ b/test/load-recovery.test.js @@ -108,6 +108,21 @@ test('a retry gives up after a few minutes, and refusals and a removed extension assert.equal(r.asks.length, 1, 'the extension gone: not asked again'); }); +test("the pop-up's own loads, refused with nobody waiting yet, leave no unhandled rejection", async () => { + const stray = []; + const seen = (e) => stray.push(e); + process.on('unhandledRejection', seen); + try { + const c = vm.createContext({ console, document: { documentElement: new El('html') } }); + c.globalThis = c; + c.GZ_CTX = { t: (k) => k, need: () => Promise.reject(new Error('Missing host permission for the tab or frames')) }; + vm.runInContext(read('features/popup.js'), c, { filename: 'features/popup.js' }); + await new Promise((r) => setTimeout(r, 10)); + await assert.rejects(c.GZ_POPUP.ready, /Missing host permission/, 'its waiters still see the failed load'); + } finally { process.off('unhandledRejection', seen); } + assert.deepEqual(stray.map((e) => e?.message), []); +}); + test('a retry in flight is the one a sync gets: no file is loaded twice', async () => { let release; const p = await boot((n) => (n === 1 ? undefined : new Promise((res) => { release = () => res({ ok: true }); }))); diff --git a/test/loader.test.js b/test/loader.test.js index d0065d3af..97d94ddb2 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -106,6 +106,8 @@ test('without a document id, a frame that now holds another document gets nothin test('success means the file ran to its end: an injection that fails, throws or stops short is reported', async () => { const send = (execute, protocol) => worker(execute, protocol).send({ type: 'glaze:load', files: ['lib/drafts.js'] }, { ...tab(), documentId: 'A' }); assert.deepEqual(await send(() => Promise.reject(new Error('Frame with ID 3 was removed.'))), { ok: false, error: 'Frame with ID 3 was removed.' }); + // Firefox, the site's grant taken back: a refusal content.js doesn't ask again. + assert.deepEqual(await send(() => Promise.reject(new Error('Missing host permission for the tab or frames'))), { ok: false, error: 'sender' }); // Chrome: a file that throws leaves no last value. Firefox reports the error. assert.deepEqual(await send(() => Promise.resolve([{ frameId: 3, result: null }])), { ok: false, error: 'did not finish' }); assert.deepEqual(await send(() => Promise.resolve([{ frameId: 3 }])), { ok: false, error: 'did not finish' }); From ad801668f501356b24739b2467c03163b8625e7b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:19:19 -0400 Subject: [PATCH 055/907] Keep Firefox lint clean on the shared source: DOM-built icons and notes, requires by name, dynamic imports through the extension's address, pdf.js's fixed local imports, a Google sign-in adapter Firefox packaging replaces, and Android's minimum --- features/folders.js | 15 ++++++++++++--- features/notes.js | 9 ++++++++- lib/background.js | 8 ++++---- lib/calexport.js | 9 ++++----- lib/gcal-auth.js | 6 ++++++ lib/insights.js | 13 ++++++------- lib/pdfjs/jbig2_nowasm_fallback.js | 15 +++++++++++++++ lib/pdfjs/openjpeg_nowasm_fallback.js | 17 +++++++++++++++++ lib/pdfjs/pdf.mjs | 2 +- lib/pdfjs/pdf.worker.mjs | 6 ++++-- lib/pet.js | 23 +++++++++++------------ lib/scene.js | 2 +- lib/sheet-xlsx.js | 9 +++++---- options.html | 1 + options.js | 4 ++-- pdf-kind.js | 5 +++-- reader.js | 2 +- scripts/package-firefox.sh | 13 ++++++++++++- scripts/vendor-pdfjs.sh | 9 ++++++++- test/canvas-dash-audit.test.js | 2 +- test/firefox.test.js | 3 ++- test/folders-canvas.test.js | 2 +- test/folders-ui.test.js | 3 ++- test/import.test.js | 2 +- test/vendor-pdfjs.test.js | 3 ++- 25 files changed, 130 insertions(+), 53 deletions(-) create mode 100644 lib/gcal-auth.js create mode 100644 lib/pdfjs/jbig2_nowasm_fallback.js create mode 100644 lib/pdfjs/openjpeg_nowasm_fallback.js diff --git a/features/folders.js b/features/folders.js index ea9455596..9b7eb0c52 100644 --- a/features/folders.js +++ b/features/folders.js @@ -96,7 +96,16 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { } catch { if (sameAction(k, g)) fail(); } finally { if (a === action) pendingFocus = null; } }; - const icon = (hidden) => ``; + const icon = (hidden) => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + for (const [k, v] of Object.entries({ 'aria-hidden': 'true', width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.7' })) svg.setAttribute(k, v); + for (const [tag, attrs] of [['path', { d: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z' }], ['circle', { cx: '12', cy: '12', r: '3' }], ...(hidden ? [['path', { d: 'm3 3 18 18' }]] : [])]) { + const node = document.createElementNS('http://www.w3.org/2000/svg', tag); + for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v); + svg.append(node); + } + return svg; + }; const button = () => { const b = document.createElement('button'); b.type = 'button'; for (const ev of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'keydown', 'keyup']) b.addEventListener(ev, (e) => e.stopPropagation()); @@ -134,7 +143,7 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { if (hidden && data.show) titles.forEach((el, n) => markHidden(el, row, id, n)); const label = t(hidden ? WORDS.unhide : WORDS.hide, { title: titleOf(toggle) }); if (b.getAttribute('aria-label') !== label) { b.setAttribute('aria-label', label); b.title = label; } - if (b.dataset.hidden !== String(hidden)) { b.dataset.hidden = String(hidden); b.innerHTML = icon(hidden); } + if (b.dataset.hidden !== String(hidden)) { b.dataset.hidden = String(hidden); b.replaceChildren(icon(hidden)); } } const bar = document.getElementById('gz-progress'), top = canvas ? rows.length && document.getElementById('context_modules') : rows.find((r) => !r.parentElement.closest(ROW)); if (bar) { if (status.previousElementSibling !== bar) bar.after(status); } @@ -150,7 +159,7 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { else if (status.isConnected && control.nextElementSibling !== status) status.before(control); const label = t(data.show ? 'Hide them again ({n})' : WORDS.toggle, { n: ids.size }), text = control.querySelector('.gz-folders-toggle-label'); if (text.textContent !== label) text.textContent = label; - if (control.dataset.show !== String(data.show)) { control.dataset.show = String(data.show); control.querySelector('.gz-folders-toggle-icon').innerHTML = icon(data.show); } + if (control.dataset.show !== String(data.show)) { control.dataset.show = String(data.show); control.querySelector('.gz-folders-toggle-icon').replaceChildren(icon(data.show)); } if (control.getAttribute('aria-pressed') !== String(data.show)) control.setAttribute('aria-pressed', String(data.show)); if (focusToggle) { control.focus({ preventScroll: true }); pendingFocus = null; } }; diff --git a/features/notes.js b/features/notes.js index 80d614dac..70916cc4c 100644 --- a/features/notes.js +++ b/features/notes.js @@ -514,7 +514,14 @@ var GZ_NOTES_UI = globalThis.GZ_NOTES_UI || (() => { // A note that can't be read stays closed: never an empty note that a save could write over the real one. if (!r?.ok) { el.body.textContent = t(r?.error === 'missing' ? "This note's text couldn't be found on this device." : "Couldn't open this note. Try again in a moment."); cur.saved = null; cur.ro = true; return; } cur.rev = r.rev; - el.body.innerHTML = N.sanitize(r.html); + // Build from the sanitizer's allowlisted tree, without interpreting another HTML string. + const node = (n) => { + if (typeof n === 'string') return n; + const e = attr(make(n.tag, {}, ...n.kids.map(node)), n.attrs); + if (n.tag === 'a' && n.attrs.href) attr(e, { rel: 'noopener noreferrer', target: '_blank' }); + return e; + }; + el.body.replaceChildren(...N.parse(r.html).kids.map(node)); cur.saved = serialize(); el.body.setAttribute('contenteditable', String(!cur.ro)); if (keep?.sel) el.body.focus(); diff --git a/lib/background.js b/lib/background.js index 755b32f2a..cc16f4e4d 100644 --- a/lib/background.js +++ b/lib/background.js @@ -1,4 +1,4 @@ -importScripts('download.js', 'color.js', 'fonts.js', 'surfaces.js', '../defaults.js', 'lookset.js', 'history.js', 'cards.js', 'nav.js', 'nav-items.js', 'navlayout.js', 'motionx.js', 'parts.js', 'nav-store.js', 'todo.js', 'petbrain.js', 'pet.js', 'petsprites.js', 'remind.js', 'widgets.js', 'sidebar.js', 'lookparts.js', 'share.js', 'lookx.js', 'looks.js', 'gradescope.js', 'doctext.js', 'folders.js', 'notes.js', 'calexport.js', 'gcal.js', 'site.js', 'coursetabs.js'); +importScripts('download.js', 'color.js', 'fonts.js', 'surfaces.js', '../defaults.js', 'lookset.js', 'history.js', 'cards.js', 'nav.js', 'nav-items.js', 'navlayout.js', 'motionx.js', 'parts.js', 'nav-store.js', 'todo.js', 'petbrain.js', 'pet.js', 'petsprites.js', 'remind.js', 'widgets.js', 'sidebar.js', 'lookparts.js', 'share.js', 'lookx.js', 'looks.js', 'gradescope.js', 'doctext.js', 'folders.js', 'notes.js', 'calexport.js', 'gcal.js', 'gcal-auth.js', 'site.js', 'coursetabs.js'); // Cache packaged broker scripts at install; parse them on first use rather than every worker wake. const BROKER_FILES = { @@ -3124,11 +3124,11 @@ const gcalEnv = { sleep: (ms) => new Promise((res) => setTimeout(res, ms)), // Chrome's cached token, never a prompt (the editor's click asks); none without the calendar scope. token: async () => { - const r = await chrome.identity.getAuthToken({ interactive: false }); + const r = await GZ_GCAL_AUTH.token({ interactive: false }); if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(GZ_GCAL.SCOPE)) throw new GZ_GCAL.Fail('auth'); return typeof r === 'string' ? r : r?.token; }, - drop: (token) => Promise.resolve().then(() => chrome.identity.removeCachedAuthToken({ token })).catch(() => {}), + drop: (token) => Promise.resolve().then(() => GZ_GCAL_AUTH.drop(token)).catch(() => {}), }; // gcalEpoch: Disconnect moves it on as it arrives, so every sync already running or waiting in the queue stops at its // next step, before Disconnect revokes anything. @@ -3163,7 +3163,7 @@ const gcalOps = { if (m.drop === true && g?.cal) await GZ_GCAL.remove(gcalEnv, g.cal); const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); - await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => GZ_GCAL_AUTH.clear()).catch(() => {}); await chrome.storage.local.remove(['gcal', 'gcalSync']); serial(ensure); return { ok: true, revoked, had: !!token }; diff --git a/lib/calexport.js b/lib/calexport.js index e0c507dff..8227a4d6a 100644 --- a/lib/calexport.js +++ b/lib/calexport.js @@ -2,7 +2,6 @@ // lib/todo.js gives the days in the student's Canvas time zone; lib/ics.js writes the file and makes the UIDs. // Classic script, loaded on demand after those two (a second injection keeps the first); module.exports for node. var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { - const lib = (name, file) => globalThis[name] || require(file); const WEEKS = { '2w': 2, '4w': 4, '8w': 8, '16w': 16 }; // From the start of today to the end of the range, in tz, as instants and as lib/todo.js day numbers [first, last). @@ -10,7 +9,7 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // not ended (a course's own dates when it keeps to them, else its term's), at most a year ahead. Canvas's default // term has no dates: with none to go by it's the next 16 weeks, and guessed says so. const range = (pick, now, tz, courses = []) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const first = T.dayOf(now, tz); let last = first + 7 * (WEEKS[pick] || 16), guessed = false; if (pick === 'term') { @@ -31,8 +30,8 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // Deadlines are instants (UTC in the file); all-day items and "do on" days are dates; focus sessions run from their // start for their length. const events = ({ items = [], gs = [], data, o, from, to, first, last, site, host, cids = null, names = {}, words, classes = [] }) => { - const T = lib('GZ_TODO', './todo.js'); - const { uid } = lib('GZ_ICS', './ics.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); + const { uid } = (globalThis.GZ_ICS || require('./ics.js')); const d = T.clean(data); const shown = (cid) => !cid || !cids || cids.has(cid); const inDays = (n) => n >= first && n < last; @@ -83,7 +82,7 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // The keys of the student's items with a "do on" day or a focus session in the range: what the planner read must // cover for their titles (features/calexport.js asks further back for those it didn't). const planned = (data, { from, to, first, last }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); return Object.entries(T.clean(data).items).filter(([, l]) => { const on = T.keyDay(l.on); return (on !== null && on >= first && on < last) || (l.sessions || []).some((s) => s.at >= from && s.at < to); diff --git a/lib/gcal-auth.js b/lib/gcal-auth.js new file mode 100644 index 000000000..85006efe3 --- /dev/null +++ b/lib/gcal-auth.js @@ -0,0 +1,6 @@ +// Chrome's Google sign-in adapter. Firefox packaging replaces it with an unavailable adapter. +var GZ_GCAL_AUTH = globalThis.GZ_GCAL_AUTH || { + token: (options) => chrome.identity.getAuthToken(options), + drop: (token) => chrome.identity.removeCachedAuthToken({ token }), + clear: () => chrome.identity.clearAllCachedAuthTokens(), +}; diff --git a/lib/insights.js b/lib/insights.js index 31374c5ad..b7bc6abcc 100644 --- a/lib/insights.js +++ b/lib/insights.js @@ -6,7 +6,6 @@ // Classic script, loaded on demand after those two (a second injection keeps the first); the service worker loads it // for change(); module.exports for node. var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { - const lib = (name, file) => globalThis[name] || require(file); const DAY = 864e5; const isObj = (o) => !!o && typeof o === 'object' && !Array.isArray(o); const arr = (v) => (Array.isArray(v) ? v : []); @@ -56,7 +55,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // afresh: every point is then its own period's score, [ms, percent, period id], so the current period's last point // is the score Canvas shows. Without one, the whole course, as Canvas totals it. const trend = (groups, info, tz, current = null) => { - const C = lib('GZ_GRADECALC', './gradecalc.js'); + const C = (globalThis.GZ_GRADECALC || require('./gradecalc.js')); const course = C.fromApi(groups, info); const split = !!(course.periods && current && course.periods.some((p) => p.id === String(current))); const dated = []; @@ -73,7 +72,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // The points themselves, for any course in lib/gradecalc.js's shape and its dated graded work [[id, ms]]: Blackboard's // (lib/ultra.js trend) too, whose overall grade is calc's final where it counts work not graded yet as zero (zeros). const along = (course, dated, tz, split = false) => { - const C = lib('GZ_GRADECALC', './gradecalc.js'), T = lib('GZ_TODO', './todo.js'); + const C = (globalThis.GZ_GRADECALC || require('./gradecalc.js')), T = (globalThis.GZ_TODO || require('./todo.js')); const periodOf = new Map(course.groups.flatMap((g) => g.items.map((i) => [i.id, i.period]))); dated = [...dated].sort((a, b) => a[1] - b[1]); const out = []; @@ -107,7 +106,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // The best run of weeks in a row where everything due that week was on time, and the run going now. Weeks with // nothing decided don't count and don't break a run. const runs = (items, tz) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const weeks = new Map(); for (const i of items) if (i.d && DECIDED.includes(i.s)) { const w = week(T.dayOf(i.d, tz)); weeks.set(w, (weeks.get(w) ?? true) && i.s === 'on'); } let best = 0, now = 0; @@ -118,7 +117,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // past, now, heavy }. The term runs from its start (or the first due date) to its end (or the last), at most half a // year either side of this week. heavy: the three busiest weeks from this one on (most items, then most points). const workload = (items, { from, to, now, tz }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const due = items.filter((i) => i.d && i.s !== 'excused').map((i) => ({ ...i, w: week(T.dayOf(i.d, tz)) })); const here = week(T.dayOf(now, tz)); const ends = [...due.map((i) => i.w), ...[from, to].filter(Boolean).map((ms) => week(T.dayOf(ms, tz)))]; @@ -141,7 +140,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // this term's work and days that are over count; work ticked off at an unknown time (a Canvas item's tick) is left // out. items: this term's (with a, handed in); fromDay: the term's first day. const planning = (data, items, { now, tz, fromDay = -Infinity }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const today = T.dayOf(now, tz); const done = new Map(items.map((i) => [i.k, i.a || null])); for (const x of arr(data?.tasks)) done.set(`task:${x.id}`, x.done || null); @@ -163,7 +162,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // left: what couldn't be counted, by course name (work past the cache's cap, or past the pages Glaze reads), and how // many courses past the cap; the view says so. const view = (rec, { now, tz, data = null, streak = null }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const all = rec.courses.flatMap((c) => c.items); const courses = rec.courses.map((c) => { // With grading periods: First, Now and the change are the current period's, as Canvas's current score is. diff --git a/lib/pdfjs/jbig2_nowasm_fallback.js b/lib/pdfjs/jbig2_nowasm_fallback.js new file mode 100644 index 000000000..c63f15e71 --- /dev/null +++ b/lib/pdfjs/jbig2_nowasm_fallback.js @@ -0,0 +1,15 @@ +/* THIS FILE IS GENERATED - DO NOT EDIT */ +async function JBig2(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";var readAsync;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=( +// EMSCRIPTEN_START_ASM +function instantiate(ha){var a;var b=new Uint8Array(123);for(var c=25;c>=0;--c){b[48+c]=52+c;b[65+c]=c;b[97+c]=26+c}b[43]=62;b[47]=63;function i(j,k,l){var d,e,c=0,f=k,g=l.length,h=k+(g*3>>2)-(l[g-2]=="=")-(l[g-1]=="=");for(;c>4;if(f>2;if(f>>0;s=s>>>0;if(q+s>a.length)throw"trap: invalid memory.fill";a.fill(u,q,q+s)}function ga(n){var v=new ArrayBuffer(16777216);var w=new Int8Array(v);var x=new Int16Array(v);var y=new Int32Array(v);var z=new Uint8Array(v);var A=new Uint16Array(v);var B=new Uint32Array(v);var C=new Float32Array(v);var D=new Float64Array(v);var E=Math.imul;var F=Math.fround;var G=Math.abs;var H=Math.clz32;var I=Math.min;var J=Math.max;var K=Math.floor;var L=Math.ceil;var M=Math.trunc;var N=Math.sqrt;var O=n.a;var P=O.a;var Q=O.b;var R=O.c;var S=O.d;var T=O.e;var U=O.f;var V=O.g;var W=O.h;var X=O.i;var Y=72384;var Z=0; +// EMSCRIPTEN_START_FUNCS +function yc(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,u=0,v=0,C=0,D=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,Ia=0,Ma=0,Pa=0,Qa=0,Ta=0,Xa=0,Ya=0,gb=0,hb=0,qb=0,rb=0,sb=0,tb=0,ub=0;T=Y-16|0;Y=T;b=y[a+4>>2];c=y[b+8>>2];b=y[b+4>>2];i=b;a:{b:{c:{d:{e:{if(b>>>0>>0){break e}if((b|0)==(c|0)){sb=2;break c}ba=a+80|0;while(1){if(c>>>0>i>>>0){break e}if(i-c>>>0<11){break c}f:{g:{h:{n=y[ba>>2];i:{if(!n){b=na(72);w[b+4|0]=0;y[b>>2]=0;t(b+8|0,0,64);y[T+4>>2]=0;eb(ba,b);Vb(T+4|0);j:{i=y[a+80>>2];k:{if(qa(y[a+4>>2],i)){break k}if(wa(y[a+4>>2],i+4|0)){break k}g=y[a+4>>2];d=y[g+8>>2];b=y[g>>2];l:{m:{if(B[g+4>>2]<=d>>>0){w[T+15|0]=0;break m}b=z[b+d|0];w[T+15|0]=b;if(b>>>0<224){break m}if(qa(g,i+8|0)){break k}c=y[i+8>>2]&536870911;y[i+8>>2]=c;if(c>>>0>64){break k}d=y[a+4>>2];b=y[d+8>>2];g=b+(c+8>>>3|0)|0;if(b>>>0>g>>>0){break l}b=y[d+4>>2];y[d+8>>2]=b>>>0>g>>>0?g:b;break l}if(wa(g,T+15|0)){break k}c=z[T+15|0]>>>5|0;y[i+8>>2]=c}d=z[i+4|0];n:{if(!c){break n}b=y[i>>2];Rb(i+12|0,c);c=0;b=(b>>>0>65536?4:b>>>0>256?2:1)-1|0;while(1){if(y[i+8>>2]<=(c|0)){break n}o:{p:{switch(b-1|0){default:if(wa(y[a+4>>2],T+15|0)){break k}n=z[T+15|0];y[y[i+12>>2]+(c<<2)>>2]=n;break o;case 0:if(Sa(y[a+4>>2],T+12|0)){break k}n=A[T+12>>1];y[y[i+12>>2]+(c<<2)>>2]=n;break o;case 1:break e;case 2:break p}}if(qa(y[a+4>>2],T+8|0)){break k}n=y[T+8>>2];y[y[i+12>>2]+(c<<2)>>2]=n}c=c+1|0;if(B[i>>2]>n>>>0){continue}break}break k}b=y[a+4>>2];q:{if(!(d&64)){if(wa(b,T+15|0)){break k}y[i+24>>2]=z[T+15|0];break q}if(qa(b,i+24|0)){break k}}if(!qa(y[a+4>>2],i+28|0)){break j}}eb(ba,0);break i}c=y[a+4>>2];b=y[c+20>>2];y[i+40>>2]=y[c+16>>2];y[i+44>>2]=b;b=y[c+8>>2];y[i+48>>2]=1;y[i+36>>2]=b;y[a+84>>2]=b;n=y[a+80>>2]}r:{while(1){g=0;m=0;c=0;f=Y-304|0;Y=f;b=2;s:{t:{u:{v:{w:{x:{y:{z:{A:{B:{C:{D:{E:{F:{G:{H:{I:{J:{K:{L:{M:{N:{O:{P:{Q:{R:{S:{d=z[n+4|0]&63;switch(d-16|0){case 0:break R;case 1:case 2:case 3:case 5:case 8:case 9:case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 21:case 25:case 28:case 29:case 30:case 31:break B;case 4:case 6:case 7:break Q;case 20:case 22:case 23:break P;case 24:case 26:case 27:break O;case 32:break N;case 33:break K;case 34:break J;case 35:break u;case 36:break I;case 37:break H;default:break S}}T:{switch(d|0){case 0:b=0;h=0;q=0;e=Y-432|0;Y=e;c=1;if(!Sa(y[a+4>>2],e+210|0)){l=na(56);y[l+40>>2]=0;y[l+32>>2]=0;y[l+36>>2]=0;y[l+24>>2]=0;y[l+28>>2]=0;y[l+16>>2]=0;y[l+20>>2]=0;u=A[e+210>>1];i=u&1;w[l|0]=i;g=u>>>10&3;w[l+3|0]=g;d=u&12288;w[l+2|0]=(d|0)!=0;c=(u&254)>>>1&1;w[l+1|0]=c;ga=l+16|0;U:{V:{W:{if(!i){i=g?2:8;g=l+44|0;while(1){if((b|0)!=(i|0)){c=1;d=b+g|0;b=b+1|0;if(!wa(y[a+4>>2],d)){continue}break U}break}if(z[l+1|0]!=1){break V}if(!z[l+2|0]){break W}break V}if(d|!c){break V}}g=l+52|0;b=0;while(1){if((b|0)==4){break V}c=1;d=b+g|0;b=b+1|0;if(!wa(y[a+4>>2],d)){continue}break}break U}c=1;if(qa(y[a+4>>2],l+12|0)){break U}if(qa(y[a+4>>2],l+8|0)|B[l+12>>2]>1048576|B[l+8>>2]>1048576){break U}b=y[n+8>>2];k=(b|0)>0?b:0;b=0;while(1){if((b|0)==(k|0)){I=1;b=0;while(1)if((b|0)==(k|0)){X:{Y:{Z:{_:{$:{aa:{ba:{ca:{da:{ea:{fa:{if(!(I&1)){break fa}y[l+4>>2]=h;gb=Kb(e+196|0,h);k=y[gb>>2];ga:{if((k|0)==y[gb+4>>2]){break ga}b=y[n+8>>2];d=(b|0)>0?b:0;I=1;h=0;g=0;while(1){if((d|0)==(g|0)){break ga}ha:{b=Ka(a,y[y[n+12>>2]+(g<<2)>>2]);if(z[b+4|0]&63){break ha}b=y[b+56>>2];i=y[b+24>>2];m=y[b+28>>2]-i>>2;b=0;while(1){if((b|0)==(m|0)){j=0;i=0;if(I&1){b=h+m|0;c=b>>>0>=m>>>0;j=c;i=c?b:0}h=i;I=j;break ha}c=b+h|0;if(!(c>>>0>=b>>>0&I)){break fa}y[k+(c<<2)>>2]=y[i+(b<<2)>>2];b=b+1|0;continue}}g=g+1|0;continue}}$a(ga,gb);c=1;ia:{if(z[l|0]!=1){break ia}b=u>>>2&3;if((b|0)==2){break X}d=u>>>4&3;if((d|0)==2){break X}ja:{ka:{switch(b|0){case 0:I=ra(a,4);b=0;break ja;case 1:I=ra(a,5);b=0;break ja;default:break ka}}b=Ga(a,n,0);if(!b){break X}I=y[b+68>>2];b=1}y[l+28>>2]=I;la:{ma:{switch(d|0){case 0:d=ra(a,2);break la;case 1:d=ra(a,3);break la;default:break ma}}d=Ga(a,n,b);if(!d){break X}b=b+1|0;d=y[d+68>>2]}y[l+32>>2]=d;na:{if(!(u&64)){d=ra(a,1);break na}d=Ga(a,n,b);if(!d){break X}b=b+1|0;d=y[d+68>>2]}y[l+36>>2]=d;if(z[l+1|0]!=1){break ia}oa:{if(!(u&128)){b=ra(a,1);break oa}b=Ga(a,n,b);if(!b){break X}b=y[b+68>>2]}y[l+40>>2]=b}O=z[l+1|0];hb=z[l|0];c=z[l+3|0];b=z[l+2|0];y[e+192>>2]=0;y[e+184>>2]=0;y[e+188>>2]=0;y[e+180>>2]=0;y[e+172>>2]=0;y[e+176>>2]=0;g=b?1024:8192;d=c?(c|0)==1?8192:1024:65536;pa:{if(!(!(u&256)|!q)){c=1;if(!(hb&1)){b=Jb(e+184|0,y[q+56>>2]);if((d|0)!=y[e+188>>2]-y[b>>2]>>1){break Y}}if(!(O&1)){break pa}b=Jb(e+172|0,y[q+56>>2]+12|0);if((g|0)==y[e+176>>2]-y[b>>2]>>1){break pa}break Y}if(!(hb&1)){fb(e+184|0,d)}if(!(O&1)){break pa}fb(e+172|0,g)}d=y[n+44>>2];k=d;c=y[n+40>>2];y[e+152>>2]=c;y[e+156>>2]=d;i=y[n+36>>2];y[e+160>>2]=i;y[n+52>>2]=2;qa:{if(!(c|d)|z[a+48|0]!=1){break qa}g=y[a+108>>2];b=g;while(1){b=y[b+4>>2];if((g|0)==(b|0)){break qa}if((c|0)!=y[b+8>>2]|(k|0)!=y[b+12>>2]|(i|0)!=y[b+16>>2]){continue}break}c=Dd(y[b+24>>2]);y[e+232>>2]=0;Ib(n+56|0,c);Va(e+232|0);Cd(y[a+108>>2],e+152|0,b+24|0);g=y[a+108>>2];d=y[b>>2];c=y[b+4>>2];y[d+4>>2]=c;y[c>>2]=d;y[g+8>>2]=y[g+8>>2]-1;pc(b);break _}if(!(hb&1)){v=Na(y[a+4>>2]);y[e+344>>2]=v;g=y[e+172>>2];c=y[e+176>>2];b=y[e+188>>2];Q=y[e+184>>2];qb=Da();y[e+428>>2]=qb;ca=Da();y[e+400>>2]=ca;L=Da();y[e+396>>2]=L;M=Da();y[e+392>>2]=M;aa=Q+810|0;da=Q+79434|0;ha=Q+3882|0;Ma=Q+458|0;Pa=g;ea=c-g>>1;Qa=Fd(e+232|0,ib(y[l+8>>2]+y[l+4>>2]|0));Ia=cb(e+416|0,y[l+8>>2]);R=b-Q>>1;Ta=R>>>0>405;Xa=R>>>0>39717;Ya=R>>>0>1941;rb=R>>>0>229;q=0;I=0;while(1){ra:{sa:{ta:{b=y[l+8>>2];if(b>>>0>I>>>0){y[e+388>>2]=0;Aa(qb,v,e+384|0);q=y[e+384>>2]+q|0;if(q>>>0<=65535){fa=0;while(1){if(!Aa(ca,v,e+380|0)){break ra}if(B[l+8>>2]<=I>>>0){break ta}fa=y[e+380>>2]+fa|0;if(fa>>>0>65535){break ta}if(!(!q|!fa)){ua:{va:{wa:{xa:{ya:{za:{Aa:{if(!z[l+1|0]){D=lb();y[e+376>>2]=D;y[D+8>>2]=q;y[D+4>>2]=fa;w[D|0]=0;b=z[l+3|0];w[D+1|0]=0;w[D+2|0]=0;w[D+3|0]=b;w[D+16|0]=z[l+44|0];w[D+17|0]=z[l+45|0];w[D+18|0]=z[l+46|0];w[D+19|0]=z[l+47|0];w[D+20|0]=z[l+48|0];w[D+21|0]=z[l+49|0];w[D+22|0]=z[l+50|0];w[D+23|0]=z[l+51|0];U=D+8|0;V=D+4|0;Ba:{switch(b|0){case 0:break Ba;case 1:break ya;case 2:break za;default:break Aa}}if(!Bd(D)){break xa}C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break wa}b=y[V>>2];if(!b){break fa}P=y[U>>2]&2147483647;b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;Ca:{Da:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break Da}if(z[v|0]){break wa}if(!Xa){break fa}g=0;k=ka(v,da);if((k|0)==(c|0)){break Da}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+40>>2]=y[e+328>>2];y[e+44>>2]=g;g=y[e+224>>2];y[e+32>>2]=y[e+220>>2];y[e+36>>2]=g;Ca(e+40|0,e+32|0);c=c^k;break Ca}y[e+212>>2]=0;break va}Ea:{Fa:{Ga:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r&240;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break wa}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c&16|i&63470);s=j<>>0>=R>>>0){break fa}k=h<<1;K=ka(v,k+Q|0);i=7-c|0;h=K|(j>>>i&16|k&63470);s=K<>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<6;h=r&240|J&14336;H=d+N|0;k=m;j=d;while(1){Ha:{K=J<<8;if((j|0)==(H|0)){break Ha}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<6;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break wa}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c&16|(J>>>c&2048|h&63470));s=K<>>0>=R>>>0){break fa}i=h<<1;c=ka(v,i+Q|0);j=7-s|0;G=c<>>j&16|(K>>>j&2048|i&63470));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break Ea}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}Aa(L,v,e+328|0);c=y[e+328>>2];if(c>>>0>=2){F=xa(6);y[e+220>>2]=F;u=xa(8);y[e+212>>2]=u;m=xa(11);y[e+376>>2]=m;h=xa(15);y[e+372>>2]=h;j=xa(15);y[e+368>>2]=j;k=xa(15);y[e+364>>2]=k;i=xa(15);y[e+360>>2]=i;g=xa(1);y[e+356>>2]=g;H=Qb();y[e+352>>2]=H;b=z[l|0];y[H+20>>2]=1;y[H+16>>2]=c;y[H+12>>2]=q;y[H+8>>2]=fa;w[H+1|0]=1;w[H|0]=b;b=y[l+4>>2]+I|0;y[H+24>>2]=b;K=Kb(e+404|0,b);b=y[l+4>>2];c=y[l+16>>2];if(b>>>0>y[l+20>>2]-c>>2>>>0){break fa}P=y[K>>2];zd(c,b,P,y[e+408>>2]-P>>2);d=y[Ia>>2];b=0;while(1)if((b|0)==(I|0)){$a(H+124|0,K);x[H+4>>1]=0;y[H+136>>2]=0;y[H+140>>2]=1;w[H+3|0]=0;y[H+172>>2]=g;y[H+168>>2]=i;y[H+164>>2]=k;y[H+160>>2]=j;y[H+156>>2]=h;y[H+152>>2]=m;y[H+148>>2]=u;y[H+144>>2]=F;w[H+2|0]=z[l+2|0];w[H+176|0]=z[l+52|0];w[H+177|0]=z[l+53|0];w[H+178|0]=z[l+54|0];w[H+179|0]=z[l+55|0];b=Ed(H,v,Pa,ea,Qa);y[e+348>>2]=0;oa(e+388|0,b);la(e+348|0);b=y[e+388>>2];va(K);qc(e+352|0);pa(e+356|0);pa(e+360|0);pa(e+364|0);pa(e+368|0);pa(e+372|0);pa(e+376|0);pa(e+212|0);pa(e+220|0);if(!b){break ta}break ua}else{c=b<<2;y[(c+P|0)+(y[l+4>>2]<<2)>>2]=y[c+d>>2];b=b+1|0;continue}}b=0;if((c|0)!=1){break ua}b=y[l+4>>2];Ac(y[Qa+36>>2],v,e+404|0);d=y[e+404>>2];if(d>>>0>=b+I>>>0){break ta}b=y[l+4>>2];if(b>>>0>d>>>0){b=y[ga>>2]+(d<<2)|0}else{b=d-b|0;c=y[Ia>>2];if(b>>>0>=y[Ia+4>>2]-c>>2>>>0){break fa}b=c+(b<<2)|0}c=y[b>>2];if(!c){break ta}Aa(y[Qa+28>>2],v,e+220|0);Aa(y[Qa+32>>2],v,e+212|0);d=kb();y[e+376>>2]=d;y[d+8>>2]=q;y[d+4>>2]=fa;b=z[l+2|0];y[d+20>>2]=c;w[d|0]=b;y[d+12>>2]=y[e+220>>2];b=y[e+212>>2];w[d+1|0]=0;y[d+16>>2]=b;w[d+24|0]=z[l+52|0];w[d+25|0]=z[l+53|0];w[d+26|0]=z[l+54|0];w[d+27|0]=z[l+55|0];b=jb(d,v,Pa,ea);y[e+372>>2]=0;oa(e+388|0,b);la(e+372|0);b=y[e+388>>2];Ua(e+376|0);if(!b){break ta}break ua}Ia:{Ja:{if(Hb(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break Ja}b=y[V>>2];if(!b){break fa}b=b-1|0;K=b>>>3|0;P=(b&7)+1|0;h=0;g=0;d=0;c=0;while(1){Ka:{La:{if(B[U>>2]>d>>>0){ja(e+404|0,C,d);j=y[e+408>>2];i=y[e+404>>2];if(z[D+1|0]!=1){k=c;break La}if(z[v|0]){break Ja}if(!Ta){break fa}k=0;m=ka(v,aa);if((m|0)==(c|0)){break La}y[e+332>>2]=j;y[e+328>>2]=i;y[e+224>>2]=h;y[e+220>>2]=g;b=y[e+332>>2];y[e+8>>2]=y[e+328>>2];y[e+12>>2]=b;b=y[e+224>>2];y[e>>2]=y[e+220>>2];y[e+4>>2]=b;Ca(e+8|0,e);c=c^m;break Ka}y[e+212>>2]=0;break Ia}c=0;r=0;Ma:{if(!d){Na:{while(1){h=7;s=0;if((r|0)==(K|0)){break Na}Oa:{while(1){if((h|0)>=0){if(z[v|0]){break Oa}if(c>>>0>=R>>>0){break fa}b=c<<1;g=ka(v,b+Q|0);c=g|b&1006;s=g<>>0>>0){break Ja}}h=0;s=0;while(1){if((h|0)==(P|0)){break Ma}if(z[v|0]){break Ja}if(c>>>0>=R>>>0){break fa}b=c<<1;g=ka(v,b+Q|0);c=g|b&1006;s=g<<7-h|s;h=h+1|0;continue}}if(!h){break fa}m=h-1|0;r=z[g|0];h=r>>>1&112;u=0;while(1){Pa:{if((u|0)!=(K|0)){if((m|0)==(u|0)){break fa}b=u+1|0;r=z[b+g|0]|r<<8;c=7;s=0;while(1){if((c|0)<0){break Pa}if(z[v|0]){break Ja}if(h>>>0>=R>>>0){break fa}h=h<<1;F=ka(v,h+Q|0);h=F|(r>>>c+1&16|h&1006);s=F<>>0>=R>>>0){break fa}b=h<<1;m=ka(v,b+Q|0);h=m|(g>>>8-c&16|b&1006);s=m<<7-c|s;c=c+1|0;continue}break}break Ma}if((j|0)==(u|0)){break fa}w[i+u|0]=s;u=b;continue}}if(j>>>0<=K>>>0){break fa}w[i+K|0]=s;c=k}d=d+1|0;h=j;g=i;continue}}C=ua(V,U);y[e+212>>2]=C;Qa:{Ra:{c=y[C+16>>2];if(!c){break Ra}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}b=0;u=0;c=0;_=0;while(1){Sa:{Ta:{if(B[U>>2]>_>>>0){ja(e+404|0,C,_);g=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){m=c;break Ta}if(z[v|0]){break Ra}if(!Ta){break fa}m=0;i=ka(v,aa);if((i|0)==(c|0)){break Ta}y[e+332>>2]=g;y[e+328>>2]=d;y[e+224>>2]=u;y[e+220>>2]=b;b=y[e+332>>2];y[e+24>>2]=y[e+328>>2];y[e+28>>2]=b;b=y[e+224>>2];y[e+16>>2]=y[e+220>>2];y[e+20>>2]=b;c=c^i;Ca(e+24|0,e+16|0);break Sa}y[e+212>>2]=0;break Qa}G=0;j=0;i=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],_);j=y[e+408>>2];i=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+_|0);P=y[e+408>>2];F=y[e+404>>2];h=y[C+20>>2];G=!u|(h|0)<2?G:z[b|0]>>>6&1;G=!u|(h|0)<=0?G:z[b|0]>>>6&2|G;r=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=m;break Sa}Ua:{Va:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,i,j)){break Va}}k=ia(h,w[D+16|0]+c|0,F,P);if(z[v|0]){break Ra}k=G<<5|k<<4|r;if(k>>>0>=R>>>0){break fa}k=ka(v,Q+(k<<1)|0);h=y[C+20>>2];if(!k){break Va}Ea(h,c,d,g,1);h=y[C+20>>2];k=1;break Ua}k=0}G=ia(h,c+2|0,b,u)|G<<1&30;c=c+1|0;r=r<<1&14|k;continue}}_=_+1|0;b=d;u=g;continue}}C=0}break va}C=0}break va}Wa:{Xa:{if(Hb(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break Xa}b=y[V>>2];if(!b){break fa}b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;P=y[U>>2];b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;Ya:{Za:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break Za}if(z[v|0]){break Xa}if(!rb){break fa}g=0;k=ka(v,Ma);if((k|0)==(c|0)){break Za}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+104>>2]=y[e+328>>2];y[e+108>>2]=g;g=y[e+224>>2];y[e+96>>2]=y[e+220>>2];y[e+100>>2]=g;Ca(e+104|0,e+96|0);c=c^k;break Ya}y[e+212>>2]=0;break Wa}_a:{$a:{ab:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r>>>3&28;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break Xa}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c+3&4|i&890);s=j<>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(k>>>10-c&4|i&890);s=j<<7-c|s;c=c+1|0;continue}}if(!h|!F|(b>>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<1;h=r>>>3&28|J&384;H=d+N|0;k=m;j=d;while(1){bb:{K=J<<8;if((j|0)==(H|0)){break bb}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<1;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break Xa}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c+3&4|(J>>>c&128|h&890));s=K<>>0>=R>>>0){break fa}k=h<<1;i=ka(v,k+Q|0);c=7-s|0;G=i<>>10-s&4|(K>>>c&128|k&890));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break _a}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}C=ua(V,U);y[e+212>>2]=C;cb:{db:{c=y[C+16>>2];if(!c){break db}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}i=0;d=0;g=0;b=0;c=0;S=0;while(1){eb:{fb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];j=y[e+404>>2];if(z[D+1|0]!=1){k=c;break fb}if(z[v|0]){break db}if(!rb){break fa}k=0;h=ka(v,Ma);if((h|0)==(c|0)){break fb}y[e+332>>2]=u;y[e+328>>2]=j;y[e+224>>2]=b;y[e+220>>2]=g;d=y[e+332>>2];y[e+120>>2]=y[e+328>>2];y[e+124>>2]=d;d=y[e+224>>2];y[e+112>>2]=y[e+220>>2];y[e+116>>2]=d;c=c^h;Ca(e+120|0,e+112|0);break eb}y[e+212>>2]=0;break cb}G=0;_=0;m=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];m=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];G=!d|(h|0)<2?G:z[i|0]>>>6&1;G=!d|(h|0)<=0?G:z[i|0]>>>6&2|G;r=0;r=!b|(h|0)<2?r:z[g|0]>>>6&1;r=!b|(h|0)<=0?r:z[g|0]>>>6&2|r;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=k;break eb}gb:{hb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,m,_)){break hb}}if(z[v|0]){break db}h=r<<3|G<<7|ia(h,w[D+16|0]+c|0,P,K)<<2|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break hb}Ea(h,c,j,u,1);h=y[C+20>>2];H=1;break gb}H=0}F=c+2|0;G=ia(h,F,i,d)|G<<1&6;r=ia(h,F,g,b)|r<<1&14;c=c+1|0;J=J<<1&2|H;continue}}S=S+1|0;i=g;d=b;g=j;b=u;continue}}C=0}break va}C=0}break va}ib:{jb:{if(yd(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break jb}b=y[V>>2];if(!b){break fa}b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;P=y[U>>2];b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;kb:{lb:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break lb}if(z[v|0]){break jb}if(!Ya){break fa}g=0;k=ka(v,ha);if((k|0)==(c|0)){break lb}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+72>>2]=y[e+328>>2];y[e+76>>2]=g;g=y[e+224>>2];y[e+64>>2]=y[e+220>>2];y[e+68>>2]=g;Ca(e+72|0,e- -64|0);c=c^k;break kb}y[e+212>>2]=0;break ib}mb:{nb:{ob:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r>>>1&120;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break jb}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c+1&8|i&7670);s=j<>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(k>>>8-c&8|i&7670);s=j<<7-c|s;c=c+1|0;continue}}if(!h|!F|(b>>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<4;h=r>>>1&120|J&3584;H=d+N|0;k=m;j=d;while(1){pb:{K=J<<8;if((j|0)==(H|0)){break pb}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<4;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break jb}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c+1&8|(J>>>c&512|h&7670));s=K<>>0>=R>>>0){break fa}k=h<<1;i=ka(v,k+Q|0);c=7-s|0;G=i<>>8-s&8|(K>>>c&512|k&7670));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break mb}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}C=ua(V,U);y[e+212>>2]=C;qb:{rb:{c=y[C+16>>2];if(!c){break rb}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}i=0;g=0;d=0;b=0;c=0;S=0;while(1){sb:{tb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];j=y[e+404>>2];if(z[D+1|0]!=1){k=c;break tb}if(z[v|0]){break rb}if(!Ya){break fa}k=0;h=ka(v,ha);if((h|0)==(c|0)){break tb}y[e+332>>2]=u;y[e+328>>2]=j;y[e+224>>2]=b;y[e+220>>2]=d;g=y[e+332>>2];y[e+88>>2]=y[e+328>>2];y[e+92>>2]=g;g=y[e+224>>2];y[e+80>>2]=y[e+220>>2];y[e+84>>2]=g;c=c^h;Ca(e+88|0,e+80|0);break sb}y[e+212>>2]=0;break qb}G=0;_=0;m=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];m=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];G=!g|(h|0)<3?G:z[i|0]>>>5&1;G=!g|(h|0)<2?G:z[i|0]>>>5&2|G;G=!g|(h|0)<=0?G:z[i|0]>>>5&4|G;r=0;r=!b|(h|0)<3?r:z[d|0]>>>5&1;r=!b|(h|0)<2?r:z[d|0]>>>5&2|r;r=!b|(h|0)<=0?r:z[d|0]>>>5&4|r;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=k;break sb}ub:{vb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,m,_)){break vb}}if(z[v|0]){break rb}h=r<<4|G<<9|ia(h,w[D+16|0]+c|0,P,K)<<3|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break vb}Ea(h,c,j,u,1);h=y[C+20>>2];H=1;break ub}H=0}F=c+3|0;G=ia(h,F,i,g)|G<<1&14;r=ia(h,F,d,b)|r<<1&30;c=c+1|0;J=J<<1&6|H;continue}}S=S+1|0;i=d;g=b;d=j;b=u;continue}}C=0}break va}C=0}break va}C=ua(V,U);y[e+212>>2]=C;wb:{xb:{c=y[C+16>>2];if(!c){break xb}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}j=0;g=0;b=0;d=0;c=0;S=0;while(1){yb:{zb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];m=y[e+404>>2];if(z[D+1|0]!=1){i=c;break zb}if(z[v|0]){break xb}if(!Xa){break fa}i=0;k=ka(v,da);if((k|0)==(c|0)){break zb}y[e+332>>2]=u;y[e+328>>2]=m;y[e+224>>2]=d;y[e+220>>2]=b;g=y[e+332>>2];y[e+56>>2]=y[e+328>>2];y[e+60>>2]=g;g=y[e+224>>2];y[e+48>>2]=y[e+220>>2];y[e+52>>2]=g;c=c^k;Ca(e+56|0,e+48|0);break yb}y[e+212>>2]=0;break wb}r=0;k=0;_=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];k=y[e+404>>2]}c=e+404|0;ja(c,C,w[D+17|0]+S|0);X=y[e+408>>2];$=y[e+404>>2];ja(c,C,w[D+19|0]+S|0);s=y[e+408>>2];N=y[e+404>>2];ja(c,C,w[D+21|0]+S|0);W=y[e+408>>2];H=y[e+404>>2];ja(c,C,w[D+23|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];r=!g|(h|0)<2?r:z[j|0]>>>6&1;r=!g|(h|0)<=0?r:z[j|0]>>>6&2|r;G=0;G=!d|(h|0)<3?G:z[b|0]>>>5&1;G=!d|(h|0)<2?G:z[b|0]>>>5&2|G;G=!d|(h|0)<=0?G:z[b|0]>>>5&4|G;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=i;break yb}Ab:{Bb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,k,_)){break Bb}}if(z[v|0]){break xb}h=G<<5|r<<12|ia(h,w[D+16|0]+c|0,$,X)<<4|ia(h,w[D+18|0]+c|0,N,s)<<10|ia(h,w[D+20|0]+c|0,H,W)<<11|ia(h,w[D+22|0]+c|0,P,K)<<15|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break Bb}Ea(h,c,m,u,1);h=y[C+20>>2];F=1;break Ab}F=0}r=ia(h,c+2|0,j,g)|r<<1&6;G=ia(h,c+3|0,b,d)|G<<1&30;c=c+1|0;J=J<<1&14|F;continue}}S=S+1|0;j=b;g=d;b=m;d=u;continue}}C=0}break va}C=0}la(e+212|0);y[e+404>>2]=0;oa(e+388|0,C);la(e+404|0);b=y[e+388>>2];ta(e+376|0);if(b){break ua}break ta}c=y[Ia>>2];y[e+388>>2]=0;oa((I<<2)+c|0,b)}I=I+1|0;continue}}break ta}y[e+412>>2]=0;y[e+404>>2]=0;y[e+408>>2]=0;xd(e+404|0,b+y[l+4>>2]|0);k=y[e+404>>2];I=0;b=0;s=0;Cb:{while(1){if(y[l+8>>2]+y[l+4>>2]>>>0>b>>>0){Aa(M,v,e+212|0);g=y[e+212>>2];c=g+b|0;if(!(c>>>0<=y[l+8>>2]+y[l+4>>2]>>>0&c>>>0>=g>>>0)){b=0;break Cb}y[e+328>>2]=k;y[e+332>>2]=0;d=oc(e+328|0,b);wd(e+220|0,y[d>>2],y[d+4>>2],g,I);s=(I&1?g:0)+s|0;I=I^1;b=c;continue}break}b=0;if(B[l+12>>2]>>0){break Cb}b=nc();i=y[Ia>>2];c=0;I=0;while(1){d=y[l+4>>2];if(d+y[l+8>>2]>>>0<=c>>>0){break Cb}if(!(!(y[k+(c>>>3&536870908)>>2]>>>c&1)|B[l+12>>2]<=I>>>0)){Db:{if(c>>>0>>0){d=y[y[ga>>2]+(c<<2)>>2];if(!d){d=0;break Db}d=mc(d);break Db}g=i+(c-d<<2)|0;d=y[g>>2];y[g>>2]=0}ud(b,d);I=I+1|0}c=c+1|0;continue}}lc(e+404|0);break sa}la(e+388|0);b=0}Za(Ia);rc(Qa);Ba(e+392|0);Ba(e+396|0);Ba(e+400|0);Ba(e+428|0);y[e+232>>2]=0;Ib(n+56|0,b);Va(e+232|0);if(y[n+56>>2]){sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(e+344|0);break $}ta(e+344|0);break Z}la(e+388|0);continue}}c=y[e+172>>2];b=y[e+176>>2];M=y[a+4>>2];X=td(M);y[e+428>>2]=X;$=cb(e+416|0,y[l+8>>2]);y[e+412>>2]=0;y[e+404>>2]=0;y[e+408>>2]=0;if(!z[l+1|0]){Rb(e+404|0,y[l+8>>2])}N=c;i=b-c>>1;y[e+400>>2]=0;y[e+396>>2]=0;g=0;q=0;Eb:{while(1){if(B[l+8>>2]>q>>>0){if(ya(X,y[l+28>>2],e+212|0)){break ba}g=y[e+212>>2]+g|0;y[e+400>>2]=g;if(g>>>0>65535){break ba}I=0;y[e+392>>2]=0;k=0;c=q;while(1){Fb:{b=ya(X,y[l+32>>2],e+388|0);y[e+384>>2]=b;Gb:{switch(b|0){case 0:break Gb;case 1:break Fb;default:break ca}}if(B[l+8>>2]<=c>>>0){break ca}I=y[e+388>>2]+I|0;if(I>>>0>65535){break ca}k=k+I|0;Hb:{if(!I|!g){break Hb}if(z[l+1|0]==1){if(ya(X,y[l+40>>2],e+380|0)){break ca}oa(e+396|0,0);d=y[e+380>>2];Ib:{if(d>>>0>=2){H=xa(6);y[e+376>>2]=H;K=xa(8);y[e+372>>2]=K;P=xa(11);y[e+368>>2]=P;ea=xa(15);y[e+364>>2]=ea;F=xa(15);y[e+360>>2]=F;u=xa(15);y[e+356>>2]=u;m=xa(15);y[e+352>>2]=m;j=xa(1);y[e+348>>2]=j;L=Qb();y[e+344>>2]=L;b=z[l|0];y[L+20>>2]=1;y[L+16>>2]=d;y[L+12>>2]=g;y[L+8>>2]=I;w[L+1|0]=1;w[L|0]=b;b=y[l+4>>2];h=b+c|0;y[L+24>>2]=h;d=ib(b+y[l+8>>2]|0);y[e+336>>2]=0;y[e+328>>2]=0;y[e+332>>2]=0;b=e+328|0;y[e+232>>2]=b;if(h){d=d>>>0<=1?1:d;Kd(b,h);b=y[e+332>>2];h=h+b|0;while(1){if((b|0)!=(h|0)){w[b|0]=d;b=b+1|0;continue}break}y[e+332>>2]=h}w[e+236|0]=1;d=e+232|0;gc(d);b=y[e+328>>2];b=tc(d,b,y[e+332>>2]-b|0);Id(L+28|0,b);Oa(b);s=Kb(d,y[L+24>>2]);b=y[l+4>>2];d=y[l+16>>2];if(b>>>0>y[l+20>>2]-d>>2>>>0){break ea}W=y[s>>2];zd(d,b,W,y[e+236>>2]-W>>2);h=y[$>>2];b=0;while(1)if((b|0)==(c|0)){$a(L+124|0,s);x[L+4>>1]=0;y[L+136>>2]=0;y[L+140>>2]=1;w[L+3|0]=0;y[L+172>>2]=j;y[L+168>>2]=m;y[L+164>>2]=u;y[L+160>>2]=F;y[L+156>>2]=ea;y[L+152>>2]=P;y[L+148>>2]=K;y[L+144>>2]=H;w[L+2|0]=z[l+2|0];w[L+176|0]=z[l+52|0];w[L+177|0]=z[l+53|0];w[L+178|0]=z[l+54|0];w[L+179|0]=z[l+55|0];b=Gd(L,M,N,i);y[e+228>>2]=0;oa(e+396|0,b);la(e+228|0);b=y[e+396>>2];va(s);va(e+328|0);qc(e+344|0);pa(e+348|0);pa(e+352|0);pa(e+356|0);pa(e+360|0);pa(e+364|0);pa(e+368|0);pa(e+372|0);pa(e+376|0);if(b){break Ib}break ca}else{d=b<<2;y[(d+W|0)+(y[l+4>>2]<<2)>>2]=y[d+h>>2];b=b+1|0;continue}}if((d|0)!=1){h=y[$>>2];b=y[e+396>>2];break Ib}d=y[l+4>>2];b=y[l+8>>2];y[e+232>>2]=0;b=ib(b+d|0);if(Fa(M,b>>>0<=1?1:b,e+232|0)){break ca}d=y[e+232>>2];b=y[l+4>>2];if(d>>>0>=b+c>>>0){break ca}h=y[$>>2];if(b>>>0>d>>>0){b=y[ga>>2]+(d<<2)|0}else{b=d-b|0;if(b>>>0>=y[$+4>>2]-h>>2>>>0){break ea}b=(b<<2)+h|0}d=y[b>>2];if(!d){break ca}j=xa(15);y[e+328>>2]=j;b=xa(1);y[e+376>>2]=b;Jb:{Kb:{if(ya(X,j,e+372|0)){break Kb}if(ya(X,j,e+368|0)){break Kb}if(!ya(X,b,e+384|0)){break Jb}}y[e+392>>2]=k;pa(e+376|0);pa(e+328|0);break ba}sa(M);m=y[M+8>>2];j=kb();y[e+364>>2]=j;y[j+8>>2]=g;y[j+4>>2]=I;b=z[l+2|0];y[j+20>>2]=d;w[j|0]=b;y[j+12>>2]=y[e+372>>2];b=y[e+368>>2];d=0;w[j+1|0]=0;y[j+16>>2]=b;w[j+24|0]=z[l+52|0];w[j+25|0]=z[l+53|0];w[j+26|0]=z[l+54|0];w[j+27|0]=z[l+55|0];b=Na(M);y[e+360>>2]=b;b=jb(j,b,N,i);y[e+356>>2]=0;oa(e+396|0,b);la(e+356|0);b=y[e+396>>2];if(b){sa(M);d=y[M+8>>2];if(d>>>0<=4294967293){j=d+2|0;d=y[M+4>>2];d=d>>>0>j>>>0?j:d;y[M+8>>2]=d}d=y[e+384>>2]==(d-m|0)}ta(e+360|0);Ua(e+364|0);pa(e+376|0);pa(e+328|0);if(!d){break ca}}y[e+396>>2]=0;oa((c<<2)+h|0,b);if(z[l+1|0]){break Hb}}y[y[e+404>>2]+(c<<2)>>2]=I}c=c+1|0;continue}break}y[e+392>>2]=k;if(!z[l+1|0]){if(ya(X,y[l+36>>2],e+232|0)){break ba}sa(M);y[e+328>>2]=0;Lb:{if(!y[e+232>>2]){if((k|0)>65535){break Eb}j=k+7>>>3|0;b=ze(j,0,g,0);if(Z){break Eb}k=y[M+4>>2];d=y[M+8>>2];if(k>>>0>>0){break fa}if(b>>>0>k-d>>>0){break Eb}I=0;b=ua(e+392|0,e+400|0);y[e+388>>2]=0;oa(e+328|0,b);la(e+388|0);s=y[e+328>>2];while(1){if((g|0)==(I|0)){break Lb}ja(e+220|0,s,I);d=y[M+8>>2];b=y[M+4>>2];if(d>>>0>b>>>0|j>>>0>b-d>>>0|j>>>0>B[e+224>>2]){break fa}if(j){if(j){p(y[e+220>>2],y[M>>2]+d|0,j)}d=y[M+8>>2]}b=d;d=d+j|0;if(b>>>0<=d>>>0){b=y[M+4>>2];y[M+8>>2]=b>>>0>d>>>0?d:b}I=I+1|0;continue}}b=lb();y[e+388>>2]=b;y[b+8>>2]=g;y[b+4>>2]=k;w[b|0]=1;db(b,e+328|0,M);sa(M);ta(e+388|0);s=y[e+328>>2]}Mb:{if(!s){break Mb}u=c>>>0>>0?q:c;m=y[$>>2];b=0;h=y[e+404>>2];while(1){if((q|0)==(u|0)){break Mb}j=q<<2;k=j+h|0;d=nb(s,b,0,y[k>>2],g);y[e+388>>2]=0;oa(j+m|0,d);q=q+1|0;la(e+388|0);b=y[k>>2]+b|0;continue}}la(e+328|0)}q=c;continue}break}i=xa(1);y[e+392>>2]=i;y[e+240>>2]=0;y[e+232>>2]=0;y[e+236>>2]=0;xd(e+232|0,y[l+8>>2]+y[l+4>>2]|0);k=y[e+232>>2];c=0;b=0;h=0;while(1){if(y[l+8>>2]+y[l+4>>2]>>>0>b>>>0){Nb:{Ob:{if(ya(X,i,e+388|0)){break Ob}j=y[e+388>>2];d=j+b|0;if(d>>>0>>0){break Ob}if(d>>>0<=y[l+8>>2]+y[l+4>>2]>>>0){break Nb}}b=0;break da}y[e+328>>2]=k;y[e+332>>2]=0;g=oc(e+328|0,b);wd(e+212|0,y[g>>2],y[g+4>>2],j,c);h=(c&1?j:0)+h|0;c=c^1;b=d;continue}break}b=0;if(B[l+12>>2]>>0){break da}b=nc();i=y[$>>2];c=0;I=0;while(1){d=y[l+4>>2];if(d+y[l+8>>2]>>>0<=c>>>0){break da}if(!(!(y[k+(c>>>3&536870908)>>2]>>>c&1)|B[l+12>>2]<=I>>>0)){Pb:{if(c>>>0>>0){d=y[y[ga>>2]+(c<<2)>>2];if(!d){d=0;break Pb}d=mc(d);break Pb}g=i+(c-d<<2)|0;d=y[g>>2];y[g>>2]=0}ud(b,d);I=I+1|0}c=c+1|0;continue}}la(e+328|0);break ba}o()}y[e+392>>2]=k;o()}lc(e+232|0);pa(e+392|0);break aa}y[e+392>>2]=k}b=0}la(e+396|0);va(e+404|0);Za($);Ua(e+428|0);y[e+232>>2]=0;Ib(n+56|0,b);Va(e+232|0);if(!y[n+56>>2]){break Z}sa(y[a+4>>2])}if(z[a+48|0]!=1){break _}tb=e,ub=Dd(y[n+56>>2]),y[tb+232>>2]=ub;b=y[a+108>>2];c=y[b+8>>2];while(1){if(c>>>0>=2){i=y[b>>2];g=y[i>>2];d=y[i+4>>2];y[g+4>>2]=d;y[d>>2]=g;y[b+8>>2]=y[b+8>>2]-1;pc(i);c=c-1|0;b=y[a+108>>2];continue}break}c=b;b=e+232|0;Cd(c,e+152|0,b);Va(b)}c=0;if(!(z[e+211|0]&2)){break Y}if(!(hb&1)){g=y[n+56>>2];y[e+140>>2]=y[e+184>>2];d=y[e+188>>2];b=y[e+192>>2];y[e+188>>2]=0;y[e+192>>2]=0;y[e+144>>2]=d;y[e+148>>2]=b;y[e+184>>2]=0;b=e+140|0;_a(g,b);Ha(b)}if(!(O&1)){break Y}g=y[n+56>>2];y[e+128>>2]=y[e+172>>2];d=y[e+176>>2];b=y[e+180>>2];y[e+176>>2]=0;y[e+180>>2]=0;y[e+132>>2]=d;y[e+136>>2]=b;y[e+172>>2]=0;b=e+128|0;_a(g+12|0,b);Ha(b);break Y}c=1}Ha(e+172|0);Ha(e+184|0)}va(gb);break U}else{c=Ka(a,y[y[n+12>>2]+(b<<2)>>2]);if(!(z[c+4|0]&63)){j=0;i=0;if(I&1){d=y[c+56>>2];g=y[d+28>>2]-y[d+24>>2]>>2;d=g+h|0;i=d;d=d>>>0>=g>>>0;i=d?i:0}else{d=j}I=d;q=c;h=i}b=b+1|0;continue}}d=b<<2;b=b+1|0;if(Ka(a,y[d+y[n+12>>2]>>2])){continue}break}}va(ga);ma(l)}Y=e+432|0;b=c;break u;case 1:case 2:case 3:case 5:break B;case 4:case 6:case 7:break T;default:break G}}b=1;if(z[a+49|0]!=1){break u}if(ob(a,f+120|0)){break u}i=f+142|0;if(Sa(y[a+4>>2],i)){break u}c=y[f+120>>2];if(c-1>>>0>65534){break u}u=y[f+124>>2];if(u-1>>>0>65534){break u}h=Qb();y[f+116>>2]=h;y[h+12>>2]=u;y[h+8>>2]=c;k=A[f+142>>1];g=k&1;w[h|0]=g;w[h+2|0]=k>>>15;y[h+140>>2]=k>>>4&3;y[h+136>>2]=k>>>7&3;w[h+3|0]=k>>>6&1;w[h+4|0]=k>>>9&1;y[h+20>>2]=1<<(k>>>2&3);d=(k&254)>>>1&1;w[h+1|0]=d;c=k>>>10&31;w[h+5|0]=(c>>>0>15?-32:0)|c;Qb:{if(g){if(Sa(y[a+4>>2],i)){break v}if(z[h+1|0]!=1){break w}if(!z[h+2|0]){break Qb}break w}if(!d|k<<16>>16<0){break w}}g=h+176|0;c=0;while(1){if((c|0)==4){break w}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}break v}h=na(12);y[h+8>>2]=0;y[h>>2]=0;y[h+4>>2]=0;if(wa(y[a+4>>2],f+292|0)){break y}b=1;if(wa(y[a+4>>2],h+1|0)){break x}if(wa(y[a+4>>2],h+2|0)){break x}if(qa(y[a+4>>2],h+4|0)){break x}j=y[h+4>>2];if(j>>>0>65535){break x}c=z[f+292|0];b=c&1;w[h|0]=b;k=c>>>1&3;w[h+8|0]=k;y[n+52>>2]=3;if(b){b=y[a+4>>2];c=xc(h);y[f+160>>2]=c;if(c){y[f+8>>2]=0;db(c,f+8|0,b);Rb:{i=y[f+8>>2];if(!i){break Rb}g=wc(j+1|0);b=0;while(1){if(b>>>0>j>>>0){break Rb}c=z[h+1|0];d=nb(i,E(c,b),0,c,z[h+2|0]);c=y[g+4>>2];y[f+256>>2]=0;oa((b<<2)+c|0,d);b=b+1|0;la(f+256|0);continue}}la(f+8|0)}ta(f+160|0);y[f+8>>2]=0;Pb(n+60|0,g);Sb(f+8|0);if(!y[n+60>>2]){break y}sa(y[a+4>>2]);b=0;break x}mb(f+8|0,k?(k|0)==1?8192:1024:65536);d=Na(y[a+4>>2]);y[f+104>>2]=d;b=y[f+8>>2];c=y[f+12>>2];m=xc(h);y[f+256>>2]=m;if(!m){break z}w[m+1|0]=0;w[m+2|0]=0;w[m+3|0]=k;i=z[h+1|0];w[m+17|0]=0;w[m+16|0]=0-i;if(!k){x[m+22>>1]=65278;x[m+18>>1]=65533;x[m+20>>1]=65026}y[f+120>>2]=0;y[f+176>>2]=0;y[f+168>>2]=b;y[f+172>>2]=c;y[f+164>>2]=d;y[f+160>>2]=f+120;b=Ob(m,f+160|0);y[f+176>>2]=0;while(1){if((b|0)==3){b=Nb(m,f+160|0);continue}break}y[m+44>>2]=0;y[m+48>>2]=0;y[m+36>>2]=0;y[m+40>>2]=0;y[m+28>>2]=0;y[m+32>>2]=0;if(!y[f+120>>2]){break A}g=wc(j+1|0);b=0;while(1){if(b>>>0>j>>>0){break A}d=nb(y[f+120>>2],E(b,i),0,i,z[h+2|0]);c=y[g+4>>2];y[f+148>>2]=0;oa((b<<2)+c|0,d);b=b+1|0;la(f+148|0);continue}}b=1;if(z[a+49|0]!=1){break u}O=na(56);y[O+16>>2]=0;Sb:{if(ob(a,f+256|0)){break Sb}if(wa(y[a+4>>2],f+144|0)){break Sb}q=O+32|0;if(qa(y[a+4>>2],q)){break Sb}h=O+36|0;if(qa(y[a+4>>2],h)){break Sb}if(qa(y[a+4>>2],O+40|0)){break Sb}if(qa(y[a+4>>2],O+44|0)){break Sb}if(Sa(y[a+4>>2],O+48|0)){break Sb}if(Sa(y[a+4>>2],O+50|0)){break Sb}H=y[q>>2];if(H-1>>>0>65534){break Sb}K=y[h>>2];if(K-1>>>0>65534){break Sb}k=y[f+256>>2];if(k-1>>>0>65534){break Sb}X=y[f+260>>2];if(X-1>>>0>65534){break Sb}y[O+4>>2]=X;y[O>>2]=k;g=z[f+144|0];ha=g&1;w[O+8|0]=ha;w[O+20|0]=g>>>7;j=g>>>3|0;Xa=j&1;w[O+28|0]=Xa;aa=g>>>1&3;w[O+9|0]=aa;d=g>>>4|0;y[O+24>>2]=(d&7)==4?4:d&3;if(y[n+8>>2]!=1){break Sb}d=Ka(a,y[y[n+12>>2]>>2]);if(!d|(z[d+4|0]&63)!=16){break Sb}d=y[d+60>>2];if(!d){break Sb}da=y[d>>2];if(!da){break Sb}y[O+12>>2]=da;y[O+16>>2]=d+4;b=y[y[d+4>>2]>>2];i=y[b+20>>2];w[O+52|0]=i;d=y[b+24>>2];w[O+53|0]=d;y[n+52>>2]=1;Tb:{Ub:{Vb:{if(g&1){j=y[a+4>>2];b=1;while(1){c=b;b=b+1|0;if(da>>>0>1<>>0){continue}break}x[f+216>>1]=0;y[f+172>>2]=0;y[f+184>>2]=0;y[f+188>>2]=0;y[f+192>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+204>>2]=0;y[f+208>>2]=0;y[f+220>>2]=0;y[f+224>>2]=0;y[f+228>>2]=0;y[f+232>>2]=0;y[f+236>>2]=0;y[f+168>>2]=K;y[f+164>>2]=H;w[f+160|0]=ha;d=c&255;i=cb(f+8|0,d);g=y[i>>2];b=(g+(d<<2)|0)-4|0;db(f+160|0,b,j);c=0;Wb:{if(!y[b>>2]){break Wb}sa(j);b=y[j+8>>2];if(b>>>0<=4294967292){c=b+3|0;b=y[j+4>>2];y[j+8>>2]=b>>>0>c>>>0?c:b}b=d-2|0;while(1){if((b|0)>=0){k=g+(b<<2)|0;db(f+160|0,k,j);c=0;if(!y[k>>2]){break Wb}sa(j);c=y[j+8>>2];if(c>>>0<=4294967292){d=c+3|0;c=y[j+4>>2];y[j+8>>2]=c>>>0>d>>>0?d:c}bb(y[k>>2],0,0,0,0,y[k+4>>2],2);b=b-1|0;continue}break}c=vc(O,i)}Za(i);y[f+120>>2]=0;oa(n- -64|0,c);la(f+120|0);if(!y[n+64>>2]){break Tb}sa(y[a+4>>2]);break Vb}mb(f+148|0,aa?(aa|0)==1?8192:1024:65536);Ya=Na(y[a+4>>2]);y[f+300>>2]=Ya;s=y[f+148>>2];rb=y[f+152>>2];g=0;y[f+104>>2]=0;Xb:{if(!(j&1)){break Xb}b=ua(q,h);y[f+160>>2]=0;oa(f+104|0,b);b=d&255;N=1-b|0;Ma=0-(b>>>0>1)|0;b=i&255;W=1-b|0;Pa=0-(b>>>0>1)|0;la(f+160|0);h=X;g=y[f+104>>2];Yb:while(1){if(!c&(m|0)==(K|0)){break Xb}ja(f+160|0,g,m);b=y[O+44>>2];P=A[O+48>>1];d=ze(m,c,P,0);$=b+d|0;b=Z+(b>>31)|0;ea=d>>>0>$>>>0?b+1|0:b;d=y[O+40>>2];I=A[O+50>>1];b=ze(m,c,I,0);F=d+b|0;d=Z+(d>>31)|0;l=b>>>0>F>>>0?d+1|0:d;j=0;i=0;q=y[f+164>>2];u=y[f+160>>2];while(1)if(!i&(j|0)==(H|0)){b=c;d=m+1|0;b=d?b:b+1|0;m=d;c=b;continue Yb}else{qb=y[g+20>>2];b=ze(j,i,I,0);d=$-b|0;M=ea-(Z+(b>>>0>$>>>0)|0)|0;b=M>>8;ca=(M&255)<<24|d>>>8;d=ca;L=(b|0)>=0&d>>>0>=h>>>0|(b|0)>0;M=ze(j,i,P,0);Ta=M+F|0;d=Z+l|0;d=M>>>0>Ta>>>0?d+1|0:d;M=d>>8;d=(d&255)<<24|Ta>>>8;ca=(M|0)<=(Pa|0)&W>>>0>d>>>0|(M|0)<(Pa|0)|((b|0)<=(Ma|0)&N>>>0>ca>>>0|(b|0)<(Ma|0));b=M;if((b|0)>0){b=1}else{b=(b|0)>=0&d>>>0>=k>>>0}Ea(qb,j,u,q,ca|b|L);j=j+1|0;i=j?i:i+1|0;continue}}}b=1;while(1){c=b;b=b+1|0;if(da>>>0>1<>>0){continue}break}y[f+184>>2]=0;y[f+188>>2]=0;x[f+216>>1]=0;y[f+192>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+204>>2]=0;y[f+208>>2]=0;y[f+220>>2]=0;y[f+224>>2]=0;y[f+228>>2]=0;y[f+232>>2]=0;y[f+236>>2]=0;y[f+168>>2]=K;y[f+164>>2]=H;w[f+160|0]=ha;y[f+172>>2]=g;w[f+162|0]=Xa;w[f+161|0]=0;w[f+163|0]=aa;Zb:{if(aa>>>0>=2){x[f+176>>1]=65282;break Zb}x[f+176>>1]=65283;if(aa){break Zb}x[f+182>>1]=65278;x[f+178>>1]=65533;x[f+180>>1]=65026}b=c&255;k=cb(f+120|0,b);g=y[k>>2];c=b-1|0;d=c;_b:{$b:{while(1){if((d|0)>=0){y[f+292>>2]=0;y[f+24>>2]=0;y[f+16>>2]=s;y[f+20>>2]=rb;y[f+12>>2]=Ya;y[f+8>>2]=f+292;b=Ob(f+160|0,f+8|0);y[f+24>>2]=0;while(1){if((b|0)==3){b=Nb(f+160|0,f+8|0);continue}break}y[f+204>>2]=0;y[f+208>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+188>>2]=0;y[f+192>>2]=0;b=y[f+292>>2];if(!b){break $b}y[f+292>>2]=0;i=g+(d<<2)|0;oa(i,b);if((c|0)>(d|0)){bb(y[i>>2],0,0,0,0,y[i+4>>2],2)}d=d-1|0;la(f+292|0);continue}break}b=vc(O,k);break _b}la(f+292|0);b=0}Za(k);la(f+104|0);y[f+160>>2]=0;oa(n- -64|0,b);la(f+160|0);if(!y[n+64>>2]){break Ub}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(f+300|0);Ja(f+148|0)}if((z[n+4|0]&63)==20){b=0;break Sb}ac:{if(z[a+50|0]){break ac}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break ac}b=X+y[f+268>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break ac}ab(c,b,z[d+16|0])}b=y[f+264>>2];c=b;g=b>>31;b=y[f+268>>2];d=b;i=b>>31;b=z[f+272|0];bb(y[a+32>>2],c,g,d,i,y[n+64>>2],(b&7)==4?4:b&3);b=0;oa(n- -64|0,0);break Sb}ta(f+300|0);Ja(f+148|0)}b=1}ma(O);break u}b=1;if(z[a+49|0]!=1){break u}k=a+76|0;c=y[a+76>>2];if(!c){i=lb();y[f+160>>2]=i;bc:{cc:{if(ob(a,a+88|0)){break cc}if(wa(y[a+4>>2],f+8|0)){break cc}d=y[a+92>>2];if((d|0)<0){break cc}c=y[a+88>>2];if((c|0)<0){break cc}y[i+8>>2]=d;y[i+4>>2]=c;g=z[f+8|0];c=g&1;w[i|0]=c;d=g>>>1&3;w[i+3|0]=d;w[i+1|0]=g>>>3&1;if(c){break bc}g=i+16|0;c=0;if(!d){while(1){if((c|0)==8){break bc}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break cc}}while(1){if((c|0)==2){break bc}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}}ta(f+160|0);break u}w[i+2|0]=0;y[f+160>>2]=0;Ra(k,i);ta(f+160|0);c=y[k>>2]}y[n+52>>2]=1;dc:{if(z[c|0]==1){db(c,n- -64|0,y[a+4>>2]);if(!y[n+64>>2]){Ra(k,0);break M}sa(y[a+4>>2]);break dc}if(y[a+60>>2]==y[a+64>>2]){b=z[c+3|0];fb(a+60|0,b?(b|0)==1?8192:1024:65536)}i=a+72|0;d=y[a+72>>2];if(d){b=d}else{c=Na(y[a+4>>2]);y[f+160>>2]=0;Ra(i,c);ta(f+160|0);b=y[a+72>>2]}y[f+164>>2]=b;g=n- -64|0;y[f+160>>2]=g;b=y[a+64>>2];c=y[a+60>>2];y[f+176>>2]=0;y[f+168>>2]=c;y[f+172>>2]=b-c>>1;b=y[a+76>>2];ec:{if(!d){b=Ob(b,f+160|0);break ec}b=Nb(b,f+160|0)}y[a+56>>2]=b;if((b|0)==3){if((z[n+4|0]&63)==36){break B}fc:{if(z[a+50|0]){break fc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break fc}b=y[a+92>>2]+y[a+100>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break fc}ab(c,b,z[d+16|0])}c=y[a+76>>2];b=y[c+64>>2]+y[a+96>>2]|0;d=b;j=b>>31;b=y[c+68>>2]+y[a+100>>2]|0;i=b;h=b>>31;b=z[a+104|0];uc(y[a+32>>2],d,j,i,h,y[g>>2],c- -64|0,(b&7)==4?4:b&3);break B}b=y[a+76>>2];y[b+44>>2]=0;y[b+48>>2]=0;y[b+36>>2]=0;y[b+40>>2]=0;y[b+28>>2]=0;y[b+32>>2]=0;Ra(i,0);y[a+64>>2]=y[a+60>>2];if(!y[g>>2]){y[a+56>>2]=-1;Ra(k,0);break M}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0>4294967293){break dc}c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}if((z[n+4|0]&63)!=36){gc:{if(z[a+50|0]){break gc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break gc}b=y[a+92>>2]+y[a+100>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break gc}ab(c,b,z[d+16|0])}c=y[a+76>>2];b=y[c+64>>2]+y[a+96>>2]|0;d=b;i=b>>31;b=y[c+68>>2]+y[a+100>>2]|0;g=b;j=b>>31;b=z[a+104|0];uc(y[a+32>>2],d,i,g,j,y[n+64>>2],c- -64|0,(b&7)==4?4:b&3);oa(n- -64|0,0)}b=0;Ra(k,0);break u}b=1;if(z[a+49|0]!=1){break u}if(ob(a,f+160|0)){break u}if(wa(y[a+4>>2],f+292|0)){break u}i=y[f+160>>2];if(i-1>>>0>65534){break u}k=y[f+164>>2];if(k-1>>>0>65534){break u}y[f+256>>2]=0;j=kb();y[f+120>>2]=j;y[j+8>>2]=k;y[j+4>>2]=i;d=z[f+292|0];b=d&1;w[j|0]=b;w[j+1|0]=d>>>1&1;hc:{ic:{if(b){break ic}g=j+24|0;while(1){if((c|0)==4){break ic}b=1;d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}break hc}g=y[n+8>>2];jc:{if((g|0)>0){c=0;b=1;while(1){if((c|0)==(g|0)){break hc}i=Ka(a,y[y[n+12>>2]>>2]);if(!i){break hc}d=z[i+4|0];d=(d<<6|d>>>2)&207;if(!(1<>>0<=10:0)){c=c+1|0;continue}break}b=i- -64|0;break jc}c=nb(y[a+32>>2],y[f+168>>2],y[f+172>>2],i,k);y[f+8>>2]=0;b=f+256|0;oa(b,c);la(f+8|0)}b=y[b>>2];y[j+12>>2]=0;y[j+16>>2]=0;y[j+20>>2]=b;mb(f+8|0,z[j|0]?1024:8192);c=Na(y[a+4>>2]);y[f+148>>2]=c;y[n+52>>2]=1;b=jb(j,c,y[f+8>>2],y[f+12>>2]);y[f+104>>2]=0;g=n- -64|0;oa(g,b);la(f+104|0);b=1;kc:{if(!y[n+64>>2]){break kc}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}b=0;if((z[n+4|0]&63)==40){break kc}lc:{if(z[a+50|0]){break lc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break lc}b=k+y[f+172>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break lc}ab(c,b,z[d+16|0])}b=y[f+168>>2];c=b;i=b>>31;b=y[f+172>>2];d=b;j=b>>31;b=z[f+176|0];bb(y[a+32>>2],c,i,d,j,y[g>>2],(b&7)==4?4:b&3);oa(g,0);b=0}ta(f+148|0);Ja(f+8|0)}Ua(f+120|0);la(f+256|0);break u}j=na(20);y[j+16>>2]=0;y[j+8>>2]=0;y[j+12>>2]=0;y[j>>2]=0;y[j+4>>2]=0;y[f+160>>2]=j;mc:{if(qa(y[a+4>>2],j)){break mc}if(qa(y[a+4>>2],j+4|0)){break mc}if(qa(y[a+4>>2],j+8|0)){break mc}if(qa(y[a+4>>2],j+12|0)){break mc}if(wa(y[a+4>>2],f+148|0)){break mc}if(Sa(y[a+4>>2],f+120|0)){break mc}w[j+16|0]=z[f+148|0]>>>2&1;d=A[f+120>>1];b=d&32767;x[j+18>>1]=b;w[j+17|0]=d>>>15;c=y[j+4>>2];nc:{oc:{if((c|0)==-1){if(d<<16>>16>=0){w[j+17|0]=1}if(z[a+50|0]){break nc}c=b;break oc}if(z[a+50|0]){break nc}}y[f+8>>2]=c;b=ua(j,f+8|0);y[f+256>>2]=0;oa(a+32|0,b);la(f+256|0)}c=y[a+32>>2];b=y[c+16>>2];if(b){break L}y[a+56>>2]=-1}Ub(f+160|0)}b=1;break u}Mb(y[c+12>>2],b,z[j+16|0]);b=y[a+24>>2];c=y[a+28>>2];pc:{if(b>>>0>>0){y[f+160>>2]=0;y[b>>2]=j;b=b+4|0;break pc}d=y[a+20>>2];b=b-d|0;k=b>>2;i=k+1|0;if(i>>>0>=1073741824){break F}g=c-d|0;c=g>>1;i=g>>>0>=2147483644?1073741823:c>>>0>i>>>0?c:i;qc:{if(!i){g=b;h=0;break qc}if(i>>>0>=1073741824){break E}h=na(i<<2);d=y[a+20>>2];g=y[a+24>>2]-d|0;k=g>>2}y[f+160>>2]=0;b=b+h|0;y[b>>2]=j;c=b-(k<<2)|0;if(g){p(c,d,g)}y[a+28>>2]=(i<<2)+h;b=b+4|0;y[a+24>>2]=b;y[a+20>>2]=c;if(!d){break pc}ma(d)}w[a+49|0]=1;y[a+24>>2]=b;Ub(f+160|0);break B}w[a+49|0]=0;break u}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}y[n+52>>2]=4;k=n+68|0;pb(k,0);m=y[a+4>>2];q=na(96);x[q>>1]=0;t(q+4|0,0,92);if((wa(m,f+120|0)|0)==-1){break D}b=1;d=z[f+120|0];w[q+1|0]=d&1;rc:{sc:{if((qa(m,f+8|0)|0)==-1){break sc}if((qa(m,f+256|0)|0)==-1){break sc}c=y[f+8>>2];j=y[f+256>>2];if((c|0)>(j|0)){break sc}i=(d>>>4&7)+1|0;h=(d>>>1&7)+1|0;Lb(q,0);y[f+164>>2]=c;w[f+160|0]=1;b=c;while(1){if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}if((Fa(m,i,y[q+20>>2]+(y[q+4>>2]<<2)|0)|0)==-1){break rc}g=y[q+20>>2];d=y[q+4>>2]<<2;if(B[g+d>>2]>63){break rc}y[d+y[q+32>>2]>>2]=b;b=y[g+(y[q+4>>2]<<2)>>2];if((b|0)>31){break rc}if(z[La(f+160|0,1<>2];if((j|0)>(b|0)){continue}break}if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[y[q+20>>2]+(y[q+4>>2]<<2)>>2]=32;if((c|0)==-2147483648){break rc}y[y[q+32>>2]+(y[q+4>>2]<<2)>>2]=c-1;Lb(q,1);if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[y[q+20>>2]+(y[q+4>>2]<<2)>>2]=32;y[y[q+32>>2]+(y[q+4>>2]<<2)>>2]=j;Lb(q,1);if(z[q+1|0]==1){if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[q+4>>2]=y[q+4>>2]+1}c=Ld(q);w[q|0]=c;y[f+160>>2]=q;b=1;if(!c){break C}b=0;y[f+160>>2]=0;pb(k,q);sa(y[a+4>>2]);break C}w[q|0]=0;y[f+160>>2]=q;break C}break D}if((d|0)!=62){break B}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}za();o()}Wa();o()}w[q|0]=0;y[f+160>>2]=q;b=1}pa(f+160|0);break u}b=0;break u}la(f+120|0)}ta(f+256|0);y[f+160>>2]=0;Pb(n+60|0,g);Sb(f+160|0);if(y[n+60>>2]){sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(f+104|0);Ja(f+8|0);b=0;break x}ta(f+104|0);Ja(f+8|0)}b=1}ma(h);break u}if(qa(y[a+4>>2],h+16|0)){break v}c=y[a+4>>2];d=y[c>>2];c=y[c+4>>2];if((c|0)<0){break t}d=((c&134217727)<<5|d>>>27)&-32;c=c>>>27&15;if(!c&d>>>0>=2147483648|c){break t}if(!c&B[h+16>>2]>d>>>0){break v}k=0;b=y[n+8>>2];g=(b|0)>0?b:0;c=0;while(1){if((c|0)==(g|0)){d=1;b=0;while(1)if((b|0)==(g|0)){if(!(d&1)){break t}y[h+24>>2]=k;F=Kb(f+104|0,k);q=y[F>>2];tc:{if((q|0)==y[F+4>>2]){break tc}b=y[n+8>>2];m=(b|0)>0?b:0;d=1;k=0;g=0;while(1){if((g|0)==(m|0)){break tc}uc:{b=Ka(a,y[y[n+12>>2]+(g<<2)>>2]);if(z[b+4|0]&63){break uc}b=y[b+56>>2];i=y[b+24>>2];l=y[b+28>>2]-i>>2;b=0;while(1){if((b|0)==(l|0)){j=0;i=0;if(d&1){b=k+l|0;c=b>>>0>=l>>>0;j=c;i=c?b:0}k=i;d=j;break uc}c=b+k|0;if(!(c>>>0>=b>>>0&d)){break t}y[q+(c<<2)>>2]=y[i+(b<<2)>>2];b=b+1|0;continue}}g=g+1|0;continue}}$a(h+124|0,F);vc:{wc:{if(z[h|0]==1){q=y[h+24>>2];b=0;xc:{yc:{while(1){if((b|0)==35){break yc}if(!Fa(y[a+4>>2],4,f+160|0)){w[(f+256|0)+b|0]=y[f+160>>2];b=b+1|0;continue}break}x[f+8>>1]=0;t(f+12|0,0,92);break xc}m=tc(f+160|0,f+256|0,35);if(!z[m|0]){x[f+8>>1]=0;t(f+12|0,0,92);Oa(m);break xc}y[f+300>>2]=y[a+4>>2];d=0;y[f+156>>2]=0;y[f+148>>2]=0;y[f+152>>2]=0;b=f+148|0;y[f+292>>2]=b;if(q){Kd(b,q);b=y[f+152>>2];c=q+b|0;while(1){if((b|0)!=(c|0)){w[b|0]=0;b=b+1|0;continue}break}y[f+152>>2]=c}w[f+296|0]=1;gc(f+292|0);k=0;zc:while(1){Ac:{Bc:{if((d|0)<(q|0)){if(ya(f+300|0,m,f+292|0)){break Bc}g=y[f+292>>2];if((g|0)<=31){w[y[f+148>>2]+d|0]=g;k=0;break Ac}Cc:{Dc:{switch(g-32|0){case 0:if(Fa(y[a+4>>2],2,f+144|0)){break Bc}k=y[f+144>>2]+3|0;break Cc;case 1:if(Fa(y[a+4>>2],3,f+144|0)){break Bc}k=y[f+144>>2]+3|0;break Cc;case 2:break Dc;default:break Cc}}if(Fa(y[a+4>>2],7,f+144|0)){break Bc}k=y[f+144>>2]+11|0}if((k|0)<=0){break Ac}c=d+k|0;if((c|0)>(q|0)){break Bc}b=0;j=(g|0)==32&(d|0)>0;while(1)if((b|0)==(k|0)){d=c;continue zc}else{i=y[f+148>>2]+d|0;w[i+b|0]=j?z[i-1|0]:0;b=b+1|0;continue}}b=y[f+148>>2];c=tc(f+8|0,b,y[f+152>>2]-b|0);b=z[c|0];va(f+148|0);Oa(m);if(!b){break xc}sa(y[a+4>>2]);Id(h+28|0,c);Oa(c);b=1;c=0;if(z[h|0]!=1){break wc}q=A[f+142>>1];m=q>>>12&3;if((m|0)==2){break vc}j=q>>>10&3;if((j|0)==2){break vc}k=q>>>8&3;if((k|0)==2){break vc}d=q&3;if((d|0)==2){break vc}i=q>>>6&3;if((i|0)==2){break vc}Ec:{Fc:{switch(d|0){case 0:g=ra(a,6);d=0;break Ec;case 1:g=ra(a,7);d=0;break Ec;default:break Fc}}d=Ga(a,n,0);if(!d){break vc}g=y[d+68>>2];d=1}y[h+144>>2]=g;Gc:{Hc:{switch((q>>>2&3)-1|0){default:g=ra(a,8);break Gc;case 0:g=ra(a,9);break Gc;case 1:g=ra(a,10);break Gc;case 2:break Hc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+148>>2]=g;Ic:{Jc:{switch((q>>>4&3)-1|0){default:g=ra(a,11);break Ic;case 0:g=ra(a,12);break Ic;case 1:g=ra(a,13);break Ic;case 2:break Jc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+152>>2]=g;Kc:{Lc:{switch(i|0){case 0:g=ra(a,14);break Kc;case 1:g=ra(a,15);break Kc;default:break Lc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+156>>2]=g;Mc:{Nc:{switch(k|0){case 0:g=ra(a,14);break Mc;case 1:g=ra(a,15);break Mc;default:break Nc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+160>>2]=g;Oc:{Pc:{switch(j|0){case 0:g=ra(a,14);break Oc;case 1:g=ra(a,15);break Oc;default:break Pc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+164>>2]=g;Qc:{Rc:{switch(m|0){case 0:g=ra(a,14);break Qc;case 1:g=ra(a,15);break Qc;default:break Rc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+168>>2]=g;Sc:{if(!(q&16384)){b=ra(a,1);break Sc}d=Ga(a,n,d);if(!d){break vc}b=y[d+68>>2]}y[h+172>>2]=b;break wc}x[f+8>>1]=0;t(f+12|0,0,92);va(f+148|0);Oa(m);break xc}d=d+1|0;continue}}Oa(f+8|0);b=1;break vc}c=ib(y[h+24>>2])}j=0;i=0;y[f+8>>2]=0;y[f+12>>2]=0;if(z[h+1|0]==1){g=f+160|0;mb(g,z[h+2|0]?1024:8192);b=y[f+160>>2];y[f+160>>2]=0;Hd(f+8|0,b);d=y[f+164>>2];y[f+12>>2]=d;y[f+164>>2]=0;Ja(g);i=d}y[n+52>>2]=1;Tc:{Uc:{Vc:{Wc:{if(z[h|0]==1){b=Gd(h,y[a+4>>2],y[f+8>>2]|j,i);y[f+160>>2]=0;oa(n- -64|0,b);la(f+160|0);if(!y[n+64>>2]){break Uc}sa(y[a+4>>2]);break Wc}b=Na(y[a+4>>2]);y[f+256>>2]=b;g=Fd(f+160|0,c);b=Ed(h,b,y[f+8>>2]|j,i,g);y[f+148>>2]=0;oa(n- -64|0,b);la(f+148|0);if(!y[n+64>>2]){break Vc}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}rc(g);ta(f+256|0)}b=0;if((z[n+4|0]&63)==4){break Tc}Xc:{if(z[a+50|0]){break Xc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break Xc}b=u+y[f+132>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break Xc}ab(c,b,z[d+16|0])}b=y[f+128>>2];c=b;g=b>>31;b=y[f+132>>2];d=b;i=b>>31;b=z[f+136|0];bb(y[a+32>>2],c,g,d,i,y[n+64>>2],(b&7)==4?4:b&3);oa(n- -64|0,0);b=0;break Tc}rc(g);ta(f+256|0)}b=1}Ja(f+8|0)}va(F);break v}else{c=Ka(a,y[y[n+12>>2]+(b<<2)>>2]);if(!(z[c+4|0]&63)){j=0;i=0;if(d&1){c=y[c+56>>2];d=y[c+28>>2]-y[c+24>>2]>>2;c=d+k|0;i=c;c=c>>>0>=d>>>0;d=c?i:0;j=c}else{d=i}k=d;d=j}b=b+1|0;continue}}b=1;d=c<<2;c=c+1|0;if(Ka(a,y[d+y[n+12>>2]>>2])){continue}break}}qc(f+116|0)}Y=f+304|0;break s}o()}if(y[a+56>>2]!=3){break r}b=y[a+4>>2];c=y[b+8>>2];b=y[b+4>>2];if(c>>>0>b>>>0){break e}if((b|0)!=(c|0)){continue}break}y[a+52>>2]=2;break c}Yc:{switch(b|0){default:eb(ba,0);break i;case 0:break Yc;case 2:break d}}n=y[ba>>2];b=y[n+28>>2];if((b|0)==-1){break g}c=b;b=y[a+84>>2];c=c+b|0;if(b>>>0<=c>>>0){break h}}sb=1;break c}y[a+84>>2]=c;d=y[a+4>>2];b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b;break f}d=y[a+4>>2];b=y[d+8>>2];if(b>>>0>4294967291){break f}c=b+4|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}b=y[a+12>>2];c=y[a+16>>2];Zc:{if(b>>>0>>0){y[ba>>2]=0;y[b>>2]=n;c=b+4|0;break Zc}d=y[a+8>>2];b=b-d|0;h=b>>2;i=h+1|0;if(i>>>0>=1073741824){break b}g=c-d|0;c=g>>1;k=g>>>0>=2147483644?1073741823:c>>>0>i>>>0?c:i;_c:{if(!k){g=0;c=b;break _c}if(k>>>0>=1073741824){break a}g=na(k<<2);d=y[a+8>>2];c=y[a+12>>2]-d|0;h=c>>2;n=y[a+80>>2]}y[a+80>>2]=0;i=b+g|0;y[i>>2]=n;b=i-(h<<2)|0;if(c){p(b,d,c)}y[a+16>>2]=(k<<2)+g;c=i+4|0;y[a+12>>2]=c;y[a+8>>2]=b;if(d){ma(d)}d=y[a+4>>2]}y[a+12>>2]=c;b=y[d+4>>2];i=b;c=y[d+8>>2];if(b>>>0>=c>>>0){continue}break}}o()}eb(ba,0)}Y=T+16|0;return sb}za();o()}Wa();o()}function jb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;e=Y-96|0;Y=e;D=a+8|0;p=a+4|0;a:{b:{m=y[a+4>>2];if(m-1>>>0<=65534){s=y[a+8>>2];if(s-1>>>0<65535){break b}}l=ua(p,D);break a}c:{d:{e:{f:{g:{h:{i:{j:{k:{l:{if(!z[a|0]){m:{if(z[a+24|0]!=255|z[a+25|0]!=255|(z[a+26|0]!=255|z[a+27|0]!=255)){break m}if(y[a+12>>2]){break m}j=y[a+20>>2];if((m|0)!=y[j+20>>2]){break m}if(!y[j+16>>2]){break c}l=hc(m,s);y[e+44>>2]=l;if(!y[l+16>>2]){break e}u=d;E=c;c=y[a+20>>2];F=y[c+20>>2];q=y[c+24>>2];c=y[a+16>>2];if(!((1-q|0)<=(c|0)&(c|0)<(q|0))){y[a+16>>2]=0}L=q+1|0;I=q-1|0;J=E+32|0;p=0;while(1){if((p|0)!=(s|0)){if(z[a+1|0]==1){if(z[b|0]){break e}if((d|0)==16|d>>>0<16){break l}v=ka(b,J)^v}ja(e+32|0,l,p);i=0;n:{if(!p){D=0;t=0;j=0;break n}ja(e+72|0,l,p-1|0);t=y[e+76>>2];if(!t){break l}D=y[e+72>>2];j=z[D|0]<<4}c=y[a+16>>2];y[e+88>>2]=0;y[e+92>>2]=0;y[e+80>>2]=0;y[e+84>>2]=0;y[e+72>>2]=0;y[e+76>>2]=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;h=p-c|0;if(!((h|0)<=0|(h|0)>(q|0))){ja(e+48|0,y[a+20>>2],h-1|0);c=y[e+52>>2];y[e+76>>2]=c;f=y[e+48>>2];y[e+72>>2]=f;if(!c){break l}c=z[f|0];y[e+8>>2]=c;i=c&192}k=0;c=0;o:{if((h|0)<0){break o}c=0;if((h|0)>=(q|0)){break o}ja(e+48|0,y[a+20>>2],h);c=y[e+52>>2];y[e+84>>2]=c;f=y[e+48>>2];y[e+80>>2]=f;if(!c){break l}c=z[f|0];y[e+12>>2]=c;c=c>>>3&24}if(!((h|0)<-1|(h|0)>=(I|0))){ja(e+48|0,y[a+20>>2],h+1|0);h=y[e+52>>2];y[e+92>>2]=h;f=y[e+48>>2];y[e+88>>2]=f;if(!h){break l}h=z[f|0];y[e+16>>2]=h;k=h>>>6|0}p:{if(!v){g=c|(j&3072|i)|k;n=0;x=y[e+32>>2];A=y[e+36>>2];k=0;q:while(1){if((k|0)>=(m|0)){break p}r:{if(!p){break r}j=j<<8;if((m|0)<=(k+8|0)){break r}c=(k>>>3|0)+1|0;if(c>>>0>=t>>>0){break l}j=z[c+D|0]<<4|j}s:{if((L+y[a+16>>2]|0)<(p|0)){h=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;r=0;c=0;break s}c=(k>>>3|0)+1|0;f=0;i=e+8|0;h=(F|0)<=(k+8|0);while(1){if((f|0)!=24){r=(e+72|0)+f|0;G=y[r+4>>2];t:{if(!G){break t}C=y[i>>2]<<8;y[i>>2]=C;if(h){break t}if(c>>>0>=G>>>0){break l}y[i>>2]=C|z[c+y[r>>2]|0]}i=i+4|0;f=f+8|0;continue}break}h=y[e+16>>2];r=y[e+8>>2];c=y[e+12>>2]}f=0;i=n+m|0;i=(i|0)>0?i:0;G=(i|0)>=8?8:i;i=0;while(1){if((f|0)==(G|0)){c=k>>>3|0;if(c>>>0>=A>>>0){break l}w[c+x|0]=i;n=n-8|0;k=k+8|0;continue q}if(g>>>0>=u>>>0){break l}C=g<<1;H=ka(b,C+E|0);g=f^7;i=H<>>13-f&1|(c>>>10-f&8|(r>>>g&64|(j>>>g&1024|C&6582|H<<9)));f=f+1|0;continue}}}Cb(e+48|0,y[a+20>>2],p);i=c|(j&3072|i)|k;h=0;A=y[e+32>>2];G=y[e+36>>2];C=y[e+60>>2];H=y[e+56>>2];k=0;while(1){if((h|0)>=(m|0)){break p}u:{if(!p){c=h+8|0;break u}j=j<<8;c=h+8|0;if((m|0)<=(c|0)){break u}f=(h>>>3|0)+1|0;if(f>>>0>=t>>>0){break l}j=z[f+D|0]<<4|j}r=h>>>3|0;n=r+1|0;f=0;g=e+8|0;while(1){v:{if((f|0)==24){f=0;g=k+m|0;g=(g|0)>0?g:0;K=(g|0)>=8?8:g;N=y[e+16>>2];M=y[e+12>>2];O=y[e+8>>2];n=0;while(1){if((f|0)==(K|0)){break v}x=y[y[a+20>>2]+20>>2];P=f+h|0;g=ia(x,P,H,C);w:{if(z[a+1|0]==1){if(Bb(x,P,g,e+48|0)){break w}}if(z[b|0]){break e}if(i>>>0>=u>>>0){break l}g=ka(b,E+(i<<1)|0)}x=f^7;i=N>>>13-f&1|(M>>>10-f&8|(j>>>x&1024|i<<1&6582|g<<9|O>>>x&64));f=f+1|0;n=g<>2];x:{if(!K){break x}N=y[g>>2]<<8;y[g>>2]=N;if((c|0)>=(F|0)){break x}if(n>>>0>=K>>>0){break l}y[g>>2]=N|z[n+y[x>>2]|0]}g=g+4|0;f=f+8|0;continue}break}if(r>>>0>=G>>>0){break l}w[r+A|0]=n;k=k-8|0;h=c;continue}}p=p+1|0;continue}break}y[e+44>>2]=0;break d}l=ua(p,D);y[e+44>>2]=l;f=y[l+16>>2];if(!f){break g}E=c;g=y[l+12>>2];while(1){if((f|0)>0){w[g|0]=0;f=f-1|0;g=g+1|0;continue}break}t=E+32|0;while(1){if(B[D>>2]>h>>>0){if(z[a+1|0]==1){if(z[b|0]){break g}if((d|0)==16|d>>>0<16){break l}k=ka(b,t)^k}ja(e+32|0,l,h);c=e+72|0;ja(c,l,h-1|0);j=y[e+76>>2];u=y[e+72>>2];v=y[a+16>>2];i=y[a+20>>2];jd(c,v,i,h);f=0;c=y[l+20>>2];f=!j|(c|0)<2?f:z[u|0]>>>6&1;if(!(!j|(c|0)<=0)){f=z[u|0]>>>6&2|f}y[e+8>>2]=f;y[e+12>>2]=0;c=y[i+20>>2];f=y[a+12>>2];g=1-f|0;m=y[e+72>>2];q=y[e+76>>2];n=0-f|0;Q=e,R=ia(c,g,m,q)|ia(c,n,m,q)<<1,y[Q+16>>2]=R;m=y[e+80>>2];q=y[e+84>>2];s=ia(c,g,m,q);r=s|ia(c,n,m,q)<<1;s=f^-1;Q=e,R=r|ia(c,s,m,q)<<2,y[Q+20>>2]=R;m=g;f=y[e+88>>2];g=y[e+92>>2];m=ia(c,m,f,g);Q=e,R=m|ia(c,n,f,g)<<1|ia(c,s,f,g)<<2,y[Q+24>>2]=R;c=e+48|0;ja(c,i,w[a+27|0]+(h-v|0)|0);f=y[e+52>>2];g=y[e+48>>2];ja(c,l,w[a+25|0]+h|0);c=y[e+52>>2];n=y[e+48>>2];y:{if(!k){m=u;u=j;j=c;c=f;q=g;f=0;i=y[e+32>>2];v=y[e+36>>2];while(1){if(B[p>>2]<=f>>>0){break y}s=e+8|0;g=id(a,y[l+20>>2],s,f,q,c,n,j);if(z[b|0]){break g}if(d>>>0<=g>>>0){break l}hd(a,l,s,f,ka(b,E+(g<<1)|0),e+72|0,m,u,i,v);f=f+1|0;continue}}Cb(e+48|0,i,h);m=u;u=j;j=c;c=f;q=g;f=0;i=y[e+32>>2];v=y[e+36>>2];s=y[e+60>>2];F=y[e+56>>2];while(1){if(B[p>>2]<=f>>>0){break y}r=y[y[a+20>>2]+20>>2];g=ia(r,f,F,s);z:{if(z[a+1|0]==1){if(Bb(r,f,g,e+48|0)){break z}}g=id(a,y[l+20>>2],e+8|0,f,q,c,n,j);if(z[b|0]==1){break g}if(d>>>0<=g>>>0){break l}g=ka(b,E+(g<<1)|0)}hd(a,l,e+8|0,f,g,e+72|0,m,u,i,v);f=f+1|0;continue}}h=h+1|0;continue}break}y[e+44>>2]=0;break f}A:{if(y[a+12>>2]){break A}j=y[a+20>>2];if((m|0)!=y[j+20>>2]){break A}if(!y[j+16>>2]){break c}l=hc(m,s);y[e+44>>2]=l;if(!y[l+16>>2]){break i}u=d;E=c;c=y[a+20>>2];r=y[c+20>>2];v=y[c+24>>2];c=y[a+16>>2];if(!((1-v|0)<=(c|0)&(c|0)<(v|0))){y[a+16>>2]=0}x=v-1|0;L=E+16|0;p=0;while(1){B:{C:{if((p|0)!=(s|0)){if(z[a+1|0]==1){if(z[b|0]){break i}if((d|0)==8|d>>>0<8){break l}q=ka(b,L)^q}ja(e+32|0,l,p);i=0;D:{if(!p){t=0;F=0;j=0;break D}ja(e+72|0,l,p-1|0);F=y[e+76>>2];if(!F){break l}t=y[e+72>>2];j=z[t|0]<<1}c=y[a+16>>2];y[e+88>>2]=0;y[e+92>>2]=0;y[e+80>>2]=0;y[e+84>>2]=0;y[e+72>>2]=0;y[e+76>>2]=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;h=p-c|0;if(!((h|0)<=0|(h|0)>(v|0))){ja(e+48|0,y[a+20>>2],h-1|0);c=y[e+52>>2];y[e+76>>2]=c;f=y[e+48>>2];y[e+72>>2]=f;if(!c){break l}c=z[f|0];y[e+8>>2]=c;i=c>>>2&32}k=0;c=0;E:{if((h|0)<0){break E}c=0;if((h|0)>=(v|0)){break E}ja(e+48|0,y[a+20>>2],h);c=y[e+52>>2];y[e+84>>2]=c;f=y[e+48>>2];y[e+80>>2]=f;if(!c){break l}c=z[f|0];y[e+12>>2]=c;c=c>>>4&12}if(!((h|0)<-1|(h|0)>=(x|0))){ja(e+48|0,y[a+20>>2],h+1|0);h=y[e+52>>2];y[e+92>>2]=h;f=y[e+48>>2];y[e+88>>2]=f;if(!h){break l}h=z[f|0];y[e+16>>2]=h;k=h>>>6|0}if(q){break C}i=c|(j&384|i)|k;f=0;D=y[e+32>>2];I=y[e+36>>2];n=0;F:while(1){if((f|0)>=(m|0)){break B}G:{if(!p){c=f+8|0;break G}j=j<<8;c=f+8|0;if((m|0)<=(c|0)){break G}h=(f>>>3|0)+1|0;if(h>>>0>=F>>>0){break l}j=z[h+t|0]<<1|j}h=f>>>3|0;k=h+1|0;f=0;g=e+8|0;while(1){if((f|0)==24){f=0;g=n+m|0;g=(g|0)>0?g:0;k=(g|0)>=8?8:g;J=y[e+16>>2];A=y[e+12>>2];G=y[e+8>>2];g=0;while(1){if((f|0)==(k|0)){if(h>>>0>=I>>>0){break l}w[h+D|0]=g;n=n-8|0;f=c;continue F}if(i>>>0>=u>>>0){break l}i=i<<1;C=ka(b,i+E|0);H=f^7;g=C<>>13-f&1|(A>>>11-f&4|(G>>>9-f&32|(j>>>H&128|i&794|C<<6)));f=f+1|0;continue}}J=(e+72|0)+f|0;A=y[J+4>>2];H:{if(!A){break H}G=y[g>>2]<<8;y[g>>2]=G;if((c|0)>=(r|0)){break H}if(k>>>0>=A>>>0){break l}y[g>>2]=G|z[k+y[J>>2]|0]}g=g+4|0;f=f+8|0;continue}}}y[e+44>>2]=0;break h}Cb(e+48|0,y[a+20>>2],p);i=c|(j&384|i)|k;h=0;I=y[e+32>>2];J=y[e+36>>2];A=y[e+60>>2];G=y[e+56>>2];D=0;while(1){if((h|0)>=(m|0)){break B}I:{if(!p){c=h+8|0;break I}j=j<<8;c=h+8|0;if((m|0)<=(c|0)){break I}f=(h>>>3|0)+1|0;if(f>>>0>=F>>>0){break l}j=z[f+t|0]<<1|j}n=h>>>3|0;k=n+1|0;f=0;g=e+8|0;while(1){J:{if((f|0)==24){f=0;g=m+D|0;g=(g|0)>0?g:0;C=(g|0)>=8?8:g;H=y[e+16>>2];K=y[e+12>>2];N=y[e+8>>2];k=0;while(1){if((f|0)==(C|0)){break J}M=y[y[a+20>>2]+20>>2];O=f+h|0;g=ia(M,O,G,A);K:{if(z[a+1|0]==1){if(Bb(M,O,g,e+48|0)){break K}}if(z[b|0]){break i}if(i>>>0>=u>>>0){break l}g=ka(b,E+(i<<1)|0)}M=f^7;i=H>>>13-f&1|(K>>>11-f&4|(N>>>9-f&32|(j>>>M&128|i<<1&794|g<<6)));f=f+1|0;k=g<>2];L:{if(!H){break L}K=y[g>>2]<<8;y[g>>2]=K;if((c|0)>=(r|0)){break L}if(k>>>0>=H>>>0){break l}y[g>>2]=K|z[k+y[C>>2]|0]}g=g+4|0;f=f+8|0;continue}break}if(n>>>0>=J>>>0){break l}w[n+I|0]=k;D=D-8|0;h=c;continue}}p=p+1|0;continue}}l=ua(p,D);y[e+32>>2]=l;f=y[l+16>>2];if(!f){break k}E=c;g=y[l+12>>2];while(1){if((f|0)>0){w[g|0]=0;f=f-1|0;g=g+1|0;continue}break}J=E+16|0;while(1){if(B[D>>2]>q>>>0){if(z[a+1|0]==1){if(z[b|0]){break k}if((d|0)==8|d>>>0<8){break l}v=ka(b,J)^v}ja(e+8|0,l,q);c=e+72|0;ja(c,l,q-1|0);u=y[e+76>>2];m=y[e+72>>2];j=y[a+20>>2];jd(c,y[a+16>>2],j,q);i=0;t=y[l+20>>2];i=!u|(t|0)<2?i:z[m|0]>>>6&1;i=!u|(t|0)<=0?i:z[m|0]>>>6&2|i;g=y[j+20>>2];f=y[a+12>>2];c=0-f|0;r=y[e+72>>2];x=y[e+76>>2];ia(g,c,r,x);h=ia(g,c,r,x);n=1-f|0;s=y[e+80>>2];F=y[e+84>>2];k=ia(g,n,s,F)|ia(g,c,s,F)<<1|ia(g,f^-1,s,F)<<2;L=y[e+88>>2];I=y[e+92>>2];n=ia(g,n,L,I)|ia(g,c,L,I)<<1;M:{if(!v){g=0;j=y[e+12>>2];t=y[e+8>>2];f=0;while(1){if(B[p>>2]<=f>>>0){break M}if(z[b|0]){break k}c=k<<2|h<<5|g<<6|i<<7|n;if(c>>>0>=d>>>0){break l}g=ka(b,E+(c<<1)|0);Ea(y[l+20>>2],f,t,j,g);h=y[a+12>>2];i=ia(y[l+20>>2],f+2|0,m,u)|i<<1&6;c=y[y[a+20>>2]+20>>2];A=f-h|0;h=ia(c,A+1|0,r,x);A=A+2|0;k=ia(c,A,s,F)|k<<1&6;n=ia(c,A,L,I)|n<<1&2;f=f+1|0;continue}}Cb(e+48|0,j,q);c=0;A=y[e+12>>2];G=y[e+8>>2];C=y[e+60>>2];H=y[e+56>>2];f=0;while(1){if(B[p>>2]<=f>>>0){break M}j=ia(g,f,H,C);N:{if(z[a+1|0]==1){if(Bb(g,f,j,e+48|0)){break N}}if(z[b|0]==1){break k}c=k<<2|h<<5|c<<6|i<<7|n;if(c>>>0>=d>>>0){break l}j=ka(b,E+(c<<1)|0);t=y[l+20>>2]}Ea(t,f,G,A,j);c=y[a+12>>2];t=y[l+20>>2];i=ia(t,f+2|0,m,u)|i<<1&6;g=y[y[a+20>>2]+20>>2];c=f-c|0;h=ia(g,c+1|0,r,x);c=c+2|0;k=ia(g,c,s,F)|k<<1&6;n=ia(g,c,L,I)|n<<1&2;f=f+1|0;c=j;continue}}q=q+1|0;continue}break}y[e+32>>2]=0;break j}o()}l=0}la(e+32|0);break a}l=0}la(e+44|0);break a}l=0}la(e+44|0);break a}l=0}la(e+44|0)}}Y=e+96|0;return l}function cd(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;i=Y-32|0;Y=i;a:{b:{d=d-(c>>>0<1048577)|0;k=(d|0)==-1&c-1048577>>>0<4292870143|(d|0)!=-1;d=f-(e>>>0<1048577)|0;if(k|((d|0)==-1&e-1048577>>>0<4292870143|(d|0)!=-1)){break b}k=y[h+4>>2];l=y[h+12>>2];j=y[h>>2];p=y[h+8>>2];y[i+28>>2]=y[b+20>>2];w[i+24|0]=1;d=c;f=bd(i+24|0,c);q=z[f|0];h=y[i+28>>2];m=y[b+24>>2];y[f>>2]=1;y[f+4>>2]=m;m=bd(f,e);f=l-k|0;l=y[i+28>>2];m=z[m|0]?(f|0)<(l|0)?f:l:f;l=e&e>>31;f=p-j|0;A=q?(f|0)<(h|0)?f:h:f;c=c&c>>31;r=0-c|0;if(!((m|0)>(0-l|0)&(A|0)>(r|0))){break b}f=j-c|0;if((f|0)<0){break a}c=y[a+28>>2];x=(c|0)/4|0;p=f>>>5|0;if(x>>>0<=p>>>0){break a}h=y[b+28>>2];s=(h|0)/4|0;f=(d|0)>0?d:0;q=f>>>5|0;if(s>>>0<=q>>>0){break a}j=i+16|0;n=k-l|0;k=l+m|0;cc(j,a,n,k);l=y[i+20>>2];m=y[i+16>>2];cc(j,b,(e|0)>0?e:0,k);j=y[i+20>>2];if(!j){break a}F=-1>>>f|0;k=d+A|0;G=-1<<0-k;e=r&31;n=f&31;t=x-p|0;if((f^k-1)>>>0<=31){d=F&G;if((r^A-1)>>>0>=32){k=e-n|0;n=32-k|0;r=p<<2;while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;y[i+8>>2]=m+r;e=(h|0)/4|0;if(e>>>0>j>>>0){break a}f=y[i+16>>2];c=Ya(i+8|0);if(!y[i+12>>2]|e>>>0>>0|(e|0)==(q|0)){break a}e=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))<>2]>>2];h=e|(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>n;e=f+(q<<2)|0;c=y[e>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);h=((Ae(c,16)^c)&-16711936)>>>8|0;K=e,L=Ae(c,8)^h,y[K>>2]=L;c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}h=y[b+28>>2];e=(h|0)/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=f+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue}}if(e>>>0>n>>>0){k=e-n|0;n=p<<2;while(1){if(!j){break b}c=(c|0)/4|0;if(c>>>0>l>>>0){break a}e=(h|0)/4|0;if(c>>>0

      >>0|e>>>0>j>>>0|((c|0)==(p|0)|e>>>0>>0)|(e|0)==(q|0)){break a}t=y[i+16>>2];e=t+(q<<2)|0;c=y[m+n>>2];f=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))<>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;c=y[a+28>>2];e=(c|0)/4|0;if(e>>>0>l>>>0){break a}h=y[b+28>>2];f=(h|0)/4|0;if(f>>>0>j>>>0){break a}y[i+16>>2]=t+(f<<2);j=j-f|0;l=l-e|0;m=(e<<2)+m|0;continue}}k=n-e|0;n=p<<2;while(1){if(!j){break b}c=(c|0)/4|0;if(c>>>0>l>>>0){break a}e=(h|0)/4|0;if(c>>>0

      >>0|e>>>0>j>>>0|((c|0)==(p|0)|e>>>0>>0)|(e|0)==(q|0)){break a}t=y[i+16>>2];e=t+(q<<2)|0;c=y[m+n>>2];f=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>k|0;c=y[e>>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;c=y[a+28>>2];e=(c|0)/4|0;if(e>>>0>l>>>0){break a}h=y[b+28>>2];f=(h|0)/4|0;if(f>>>0>j>>>0){break a}y[i+16>>2]=t+(f<<2);j=j-f|0;l=l-e|0;m=(e<<2)+m|0;continue}}A=k&31;d=s-q|0;x=x-(r>>>5|0)|0;if(e>>>0<=n>>>0){if((e|0)==(n|0)){k=((k|0)/32|0)-(f+31>>>5|0)|0;if((k|0)<0){break a}C=p<<2;c:while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;y[i+8>>2]=m+C;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;u=y[i+16>>2];y[i>>2]=u+(q<<2);f=d;if(n){c=Ya(i+8|0);e=bc(i);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[e>>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;f=y[i+4>>2]}if(f>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];D=k<<2;r=h+D|0;x=y[i+12>>2];e=c;while(1)if((h|0)==(r|0)){if(A){if((k|0)==(x|0)|k>>>0>x>>>0|(f|0)==(k|0)){break a}c=y[e+D>>2];e=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[r>>2];c=Ma(g,e,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=r,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=u+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue c}else{s=y[c>>2];v=((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8);s=y[h>>2];s=yb(g,v,((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8));K=h,L=((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}k=((k|0)/32|0)-(f+31>>>5|0)|0;if((k|0)<0){break a}n=n-e|0;s=32-n|0;D=p<<2;d:while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;c=m+D|0;y[i+8>>2]=c;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;v=y[i+16>>2];y[i>>2]=v+(q<<2);if(A){if(t>>>0>>0){break a}y[i+12>>2]=x;y[i+8>>2]=c}c=Ya(i+8|0);e=bc(i);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[e>>2];c=Ma(g,f>>>n|0,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;H=y[i+4>>2];if(H>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];E=k<<2;r=h+E|0;C=y[i+12>>2];e=c;while(1)if((h|0)==(r|0)){if(A){if(k>>>0>C>>>0){break a}h=f<>2];h=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>n|h}if((k|0)==(H|0)){break a}c=y[r>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=r,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=v+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue d}else{I=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);u=y[h>>2];u=yb(g,I|f>>>n,((Ae(u,16)^u)&-16711936)>>>8^Ae(u,8));K=h,L=((Ae(u,16)^u)&-16711936)>>>8^Ae(u,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}r=((k|0)/32|0)-(f+31>>>5|0)|0;if((r|0)<0){break a}s=e-n|0;C=32-s|0;H=p<<2;e:while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;c=m+H|0;y[i+8>>2]=c;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;E=y[i+16>>2];y[i>>2]=E+(q<<2);if(A){if(t>>>0>>0){break a}y[i+12>>2]=x;y[i+8>>2]=c}e=i+8|0;c=Ya(e);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);if(n){c=Ya(e);e=bc(i);h=f<>>8^Ae(c,8);c=y[e>>2];c=Ma(g,h|f>>>C,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;e=y[i+4>>2]}else{e=d}if(e>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];I=r<<2;u=h+I|0;D=y[i+12>>2];k=c;while(1)if((h|0)==(u|0)){if(A){if(r>>>0>D>>>0){break a}h=f<>2];h=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>C|h}if((e|0)==(r|0)){break a}c=y[u>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=u,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=E+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue e}else{J=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);v=y[h>>2];v=yb(g,J|f>>>C,((Ae(v,16)^v)&-16711936)>>>8^Ae(v,8));K=h,L=((Ae(v,16)^v)&-16711936)>>>8^Ae(v,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}Y=i+32|0;return}o()}function Gd(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;e=Y-128|0;Y=e;m=ua(a+8|0,a+12|0);y[e+124>>2]=m;f=y[m+16>>2];a:{if(!f){break a}Mb(y[m+12>>2],f,z[a+4|0]);j=td(b);y[e+116>>2]=j;g=0;b:{if(ya(j,y[a+152>>2],e+120|0)){break b}y[e+108>>2]=y[e+120>>2];w[e+104|0]=1;q=xb(e+104|0,y[a+20>>2]);g=q;h=y[e+108>>2];f=0-h|0;y[g>>2]=z[g|0]&(h|0)!=-2147483648;y[g+4>>2]=f;y[e+100>>2]=0;w[e+96|0]=1;t=a+28|0;c:{d:{e:while(1){if(B[a+16>>2]>r>>>0){if(ya(j,y[a+152>>2],e+92|0)){break c}y[e+84>>2]=y[e+92>>2];g=1;w[e+80|0]=1;h=xb(e+80|0,y[a+20>>2]);s=Zc(q,y[h>>2],y[h+4>>2]);y[e+76>>2]=0;w[e+72|0]=1;while(1){f:{if(g&1){if(ya(j,y[a+144>>2],e+12|0)){break c}g=La(e+96|0,y[e+12>>2]);f=y[g+4>>2];y[e+72>>2]=y[g>>2];y[e+76>>2]=f;break f}g:{switch(ya(j,y[a+148>>2],e+12|0)|0){case 0:break g;case 1:continue e;default:break c}}La(La(e+72|0,y[e+12>>2]),w[a+5|0])}g=0;f=y[a+20>>2];if((f|0)!=1){f=ib(f);if(Fa(b,f>>>0<=1?1:f,e+12|0)){break c}g=z[e+12|0]}f=y[s+4>>2];if(!(y[s>>2]&1)){break c}p=f+g|0;if((p&(f^-1))<0){break c}if(ya(j,t,e+68|0)){break c}h:{i:{j:{if(!z[a+1|0]){w[e+64|0]=0;y[e+60>>2]=0;break j}f=y[b>>2];g=y[b+8>>2];if(g>>>0>=B[b+4>>2]){break c}g=z[f+g|0];f=y[b+12>>2];Tb(b);w[e+64|0]=0;y[e+60>>2]=0;if(g>>>7-f&1){break i}}Yc(e+60|0,y[y[a+124>>2]+(y[e+68>>2]<<2)>>2]);break h}if(ya(j,y[a+156>>2],e+56|0)){break d}if(ya(j,y[a+160>>2],e+52|0)){break d}if(ya(j,y[a+164>>2],e+48|0)){break d}if(ya(j,y[a+168>>2],e+44|0)){break d}if(ya(j,y[a+172>>2],e+40|0)){break d}sa(b);k=y[y[a+124>>2]+(y[e+68>>2]<<2)>>2];if(!k){break d}l=y[e+56>>2];g=l>>31;f=y[k+20>>2]+l|0;if(f>>>0>>0?g+1|0:g){break d}i=y[e+52>>2];h=i>>31;g=i+y[k+24>>2]|0;if(g>>>0>>0?h+1|0:h){break d}h=y[b+8>>2];wb(e+12|0,l,2,y[e+48>>2]);wb(e+32|0,i,2,y[e+44>>2]);if(!(w[e+36|0]&1)|z[e+16|0]!=1){break d}i=kb();y[e+28>>2]=i;y[i+8>>2]=g;y[i+4>>2]=f;f=z[a+2|0];y[i+20>>2]=k;w[i|0]=f;y[i+12>>2]=y[e+12>>2];f=y[e+32>>2];g=0;w[i+1|0]=0;y[i+16>>2]=f;w[i+24|0]=z[a+176|0];w[i+25|0]=z[a+177|0];w[i+26|0]=z[a+178|0];w[i+27|0]=z[a+179|0];f=Na(b);y[e+24>>2]=f;if(ac(Xc(e+60|0,jb(i,f,c,d)))){sa(b);g=y[b+8>>2];if(g>>>0<=4294967293){g=g+2|0;f=y[b+4>>2];g=f>>>0>g>>>0?g:f;y[b+8>>2]=g}g=y[e+40>>2]==(g-h|0)}ta(e+24|0);Ua(e+28|0);if(!g){break d}}f=e+60|0;if(ac(f)){k=y[Ta(f)+20>>2];f=Ta(f);n=y[a+140>>2];l=y[f+24>>2];k:{l:{i=z[a+3|0];if(!i){f=z[e+72|0];if((n&-2)!=2){break l}if(!(f&1)){break d}h=y[e+76>>2];g=h>>31;f=k-1|0;o=f;f=f+h|0;g=o>>>0>f>>>0?g+1|0:g;h=f>>>0<2147483648&(g|0)<=0|(g|0)<0;y[e+72>>2]=0|h;y[e+76>>2]=h?f:0;if(!h){break d}break k}f=z[e+72|0];m:{switch(n|0){default:if(!(f&1)){break d}break k;case 0:case 2:break m}}if(!(f&1)){break d}h=y[e+76>>2];g=h>>31;f=l-1|0;o=f;f=f+h|0;g=o>>>0>f>>>0?g+1|0:g;h=f>>>0<2147483648&(g|0)<=0|(g|0)<0;y[e+72>>2]=0|h;y[e+76>>2]=h?f:0;if(h){break k}break d}if(!(f&1)){break d}}f=y[e+76>>2];Wc(e+12|0,i,n,f,p,k,l);n=Ta(e+60|0);g=y[e+12>>2];o=g;p=g>>31;g=y[e+16>>2];Ab(n,m,o,p,g,g>>31,y[a+136>>2]);h=y[e+20>>2];if(h){g=f;f=f>>31;g=g+h|0;f=g>>>0>>0?f+1|0:f;h=g>>>0<2147483648&(f|0)<=0|(f|0)<0;f=0;y[e+72>>2]=f|h;y[e+76>>2]=h?g:0}r=r+1|0}Xa(e+60|0);g=0;continue}}break}y[e+124>>2]=0;g=m;break b}Xa(e+60|0)}g=0}Ua(e+116|0)}la(e+124|0);Y=e+128|0;return g}function Ed(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0;f=Y-112|0;Y=f;m=ua(a+8|0,a+12|0);y[f+108>>2]=m;a:{b:{if(!y[m+16>>2]){break b}q=y[e>>2];g=0;if(!Aa(q,b,f+104|0)){break a}t=y[e+36>>2];u=y[e+32>>2];v=y[e+28>>2];x=y[e+24>>2];A=y[e+20>>2];C=y[e+16>>2];D=y[e+12>>2];E=y[e+8>>2];F=y[e+4>>2];Mb(y[m+12>>2],y[m+16>>2],z[a+4|0]);y[f+100>>2]=y[f+104>>2];w[f+96|0]=1;r=xb(f+96|0,y[a+20>>2]);g=r;h=y[f+100>>2];e=0-h|0;y[g>>2]=z[g|0]&(h|0)!=-2147483648;y[g+4>>2]=e;y[f+92>>2]=0;w[f+88|0]=1;c:{d:while(1){if(B[a+16>>2]>n>>>0){y[f+84>>2]=0;w[f+80|0]=1;if(!Aa(q,b,f+76|0)){break b}y[f+68>>2]=y[f+76>>2];e=1;w[f+64|0]=1;h=xb(f- -64|0,y[a+20>>2]);g=Zc(r,y[h>>2],y[h+4>>2]);h=y[g>>2];s=y[g+4>>2];G=h&1;while(1){e:{if(e&1){Aa(F,b,f+4|0);g=La(f+88|0,y[f+4>>2]);e=y[g+4>>2];y[f+80>>2]=y[g>>2];y[f+84>>2]=e;break e}if(!Aa(E,b,f+4|0)){continue d}La(La(f+80|0,y[f+4>>2]),w[a+5|0])}if(B[a+16>>2]<=n>>>0){continue d}y[f+60>>2]=0;if(y[a+20>>2]!=1){Aa(D,b,f+60|0)}if(!G){break b}e=y[f+60>>2];o=e+s|0;if(((o^s)&(e^o))<0){break b}Ac(t,b,f+56|0);g=y[f+56>>2];if(g>>>0>=B[a+24>>2]){break b}f:{g:{h:{if(!z[a+1|0]){y[f+52>>2]=0;w[f+48|0]=0;y[f+44>>2]=0;break h}Aa(C,b,f+52|0);e=y[f+52>>2];w[f+48|0]=0;y[f+44>>2]=0;if(e){break g}}Yc(f+44|0,y[y[a+124>>2]+(g<<2)>>2]);break f}Aa(A,b,f+40|0);Aa(x,b,f+36|0);Aa(v,b,f+32|0);Aa(u,b,f+28|0);i=y[y[a+124>>2]+(g<<2)>>2];if(!i){break c}j=y[f+40>>2];g=j>>31;e=y[i+20>>2]+j|0;if(e>>>0>>0?g+1|0:g){break c}k=y[f+36>>2];h=k>>31;g=k+y[i+24>>2]|0;if(g>>>0>>0?h+1|0:h){break c}wb(f+4|0,j,1,y[f+32>>2]);wb(f+20|0,k,1,y[f+28>>2]);if(!(w[f+24|0]&1)|z[f+8|0]!=1){break c}h=kb();y[f+16>>2]=h;y[h+8>>2]=g;y[h+4>>2]=e;e=z[a+2|0];y[h+20>>2]=i;w[h|0]=e;y[h+12>>2]=y[f+4>>2];e=y[f+20>>2];w[h+1|0]=0;y[h+16>>2]=e;w[h+24|0]=z[a+176|0];w[h+25|0]=z[a+177|0];w[h+26|0]=z[a+178|0];w[h+27|0]=z[a+179|0];Xc(f+44|0,jb(h,b,c,d));Ua(f+16|0)}e=f+44|0;if(!ac(e)){break c}j=y[Ta(e)+20>>2];e=Ta(e);p=y[a+140>>2];i=y[e+24>>2];i:{j:{k=z[a+3|0];if(!k){e=z[f+80|0];if((p&-2)!=2){break j}if(!(e&1)){break c}h=y[f+84>>2];e=h>>31;g=j-1|0;l=g;g=h+g|0;e=l>>>0>g>>>0?e+1|0:e;h=g>>>0<2147483648&(e|0)<=0|(e|0)<0;y[f+80>>2]=0|h;y[f+84>>2]=h?g:0;if(!h){break c}break i}e=z[f+80|0];k:{switch(p|0){default:if(!(e&1)){break c}break i;case 0:case 2:break k}}if(!(e&1)){break c}h=y[f+84>>2];g=h>>31;e=i-1|0;l=e;e=e+h|0;g=l>>>0>e>>>0?g+1|0:g;h=e>>>0<2147483648&(g|0)<=0|(g|0)<0;y[f+80>>2]=0|h;y[f+84>>2]=h?e:0;if(h){break i}break c}if(!(e&1)){break c}}h=y[f+84>>2];Wc(f+4|0,k,p,h,o,j,i);l=Ta(f+44|0);e=y[f+4>>2];g=e;i=e>>31;e=y[f+8>>2];Ab(l,m,g,i,e,e>>31,y[a+136>>2]);e=y[f+12>>2];if(e){g=h>>31;l=e;e=e+h|0;g=l>>>0>e>>>0?g+1|0:g;h=e>>>0<2147483648&(g|0)<=0|(g|0)<0;g=0;y[f+80>>2]=h|g;y[f+84>>2]=h?e:0}n=n+1|0;Xa(f+44|0);e=0;continue}}break}y[f+108>>2]=0;g=m;break a}Xa(f+44|0)}g=0}la(f+108|0);Y=f+112|0;return g}function he(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0;g=Y-96|0;Y=g;w[g+72|0]=0;y[g+68>>2]=0;i=c+31>>>3&536870908;k=E(i,d);j=vd(k,1);y[g+76>>2]=0;y[g>>2]=j;h=g+68|0;Uc(h);w[g+72|0]=1;y[g+68>>2]=j;y[g>>2]=0;Ja(g);Ja(g+76|0);a:{b:{if(!$b(h)){break b}j=$b(h);h=g+56|0;y[g+60>>2]=h;y[g+56>>2]=h;y[g+64>>2]=0;y[g+48>>2]=0;y[g+52>>2]=0;h=a;a=0;h=h|a;y[g+32>>2]=h;y[g+36>>2]=b;y[g>>2]=c;l=a|e;y[g+24>>2]=l;y[g+28>>2]=f;y[g+16>>2]=0;y[g+20>>2]=0;y[g+40>>2]=a|j;y[g+44>>2]=k;y[g+8>>2]=0;y[g+12>>2]=0;y[g+4>>2]=d;y[g+48>>2]=i;a=ze(i,0,d,0);if(Z|a>>>0>k>>>0){break a}e=j;while(1){if((a|0)>0){w[e|0]=0;a=a-1|0;e=e+1|0;continue}break}a=b;b=g+56|0;a=zc(na(116),h,a,b,0);if(f){b=zc(na(116),l,f,b,1);y[g+76>>2]=0;rb(a,b);sb(g+76|0)}f=c+7>>>3|0;y[g+76>>2]=0;h=g+52|0;rb(h,a);sb(g+76|0);c:{d:{b=y[g+52>>2];a=y[b>>2];if(!a){break d}if(!yc(a)){break d}y[b+56>>2]=-1;a=0;break c}y[b+52>>2]=0;e=na(32);a=e;y[a+24>>2]=0;y[a+28>>2]=0;y[a+16>>2]=0;y[a+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a>>2]=0;y[a+4>>2]=0;e:{if((c|d)<0|i>>>0>268435452){break e}l=i<<3;if((l|0)<(c|0)|(!(!i|(d|0)<=0|k)|(2147483616/(l>>>0)|0)<(d|0))){break e}y[a+28>>2]=i;y[a+24>>2]=d;y[a+20>>2]=c;a=ze(i,0,d,d>>31);c=Z;if((c|0)==1|c>>>0>1|a>>>0>k>>>0){break a}y[g+92>>2]=a;y[g+88>>2]=j;y[g+84>>2]=0;y[g+76>>2]=0;y[g+80>>2]=0;a=g+76|0;ec(e,a);va(a)}y[g+76>>2]=0;oa(b+32|0,e);la(g+76|0);w[b+50|0]=1;a=od(b)}f:{while(1){g:{a=a&1;h:{i:{b=y[y[g+52>>2]+56>>2];if((b|0)!=4){break h}rb(g+52|0,0);b=-1;if(!a){break h}b=E(y[g+48>>2],y[g+4>>2]);if(b>>>0>B[g+44>>2]){break i}a=y[g+40>>2];if(a&3){break i}c=(b&-4)+a|0;b=4;while(1){if((a|0)==(c|0)){break h}y[a>>2]=y[a>>2]^-1;a=a+4|0;continue}}o()}j:{switch(b-3|0){case 0:break j;case 1:break g;default:break f}}a=od(y[g+52>>2]);continue}break}X($b(g+68|0)|0,f|0,i|0,d|0)}sb(h);if(!y[g+64>>2]){break b}a=y[g+60>>2];b=y[a>>2];c=y[y[g+56>>2]+4>>2];y[b+4>>2]=c;y[c>>2]=b;y[g+64>>2]=0;while(1){if((g+56|0)==(a|0)){break b}b=y[a+4>>2];pc(a);a=b;continue}}Uc(g+68|0);Y=g+96|0;return}o()}function jc(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;a:{b:{c:{d:{if(!z[a+1|0]){g=y[a+60>>2];break d}f=y[b+4>>2];if(z[f|0]){break a}h=y[c>>2];if(h>>>0>=B[b+12>>2]){break c}g=y[a+60>>2]^ka(f,y[b+8>>2]+(h<<1)|0);y[a+60>>2]=g}if(g){ld(a,y[y[b>>2]>>2]);break b}f=y[a+24>>2];if(f>>>0<=1){if((f|0)==1){if(!y[a+40>>2]){break c}j=z[y[a+36>>2]]}i=y[c+12>>2]&j>>>y[c+8>>2];h=(f|0)!=1;while(1){if((d|0)!=(k|0)){if(!h){f=k+1|0;if(f>>>0>=B[a+40>>2]){break c}j=z[f+y[a+36>>2]|0]|j<<8}g=7;l=0;while(1){if((g|0)>=0){f=y[b+4>>2];if(z[f|0]){break a}if(B[b+12>>2]<=i>>>0){break c}f=ka(f,y[b+8>>2]+(i<<1)|0);i=f|(y[c+16>>2]&j>>>y[c+8>>2]+g|(y[c+4>>2]&i)<<1);l=f<>2]<=k>>>0){break c}w[y[a+44>>2]+k|0]=l;k=k+1|0;continue}break}f=j<<8;g=0;l=0;while(1){if((e|0)!=(g|0)){h=y[b+4>>2];if(z[h|0]){break a}if(B[b+12>>2]<=i>>>0){break c}h=ka(h,y[b+8>>2]+(i<<1)|0);j=7-g|0;l=h<>2]&f>>>j+y[c+8>>2]|(y[c+4>>2]&i)<<1);g=g+1|0;continue}break}if(B[a+48>>2]<=d>>>0){break c}w[y[a+44>>2]+d|0]=l;break b}if(!y[a+32>>2]|!y[a+40>>2]){break c}j=z[y[a+36>>2]];k=z[y[a+28>>2]]<>2];i=y[c+12>>2]&j>>>y[c+8>>2]|k&y[c+24>>2];f=0;while(1){e:{if((d|0)!=(f|0)){h=f+1|0;if(h>>>0>=B[a+32>>2]|h>>>0>=B[a+40>>2]){break c}k=z[h+y[a+28>>2]|0]<>2]|k<<8;j=z[h+y[a+36>>2]|0]|j<<8;g=7;l=0;while(1){if((g|0)<0){break e}m=y[b+4>>2];if(z[m|0]){break a}if(B[b+12>>2]<=i>>>0){break c}m=ka(m,y[b+8>>2]+(i<<1)|0);i=m|(y[c+16>>2]&j>>>y[c+8>>2]+g|(y[c+28>>2]&k>>>g|(y[c+4>>2]&i)<<1));l=m<>2];if(z[h|0]){break a}if(B[b+12>>2]<=i>>>0){break c}k=ka(h,y[b+8>>2]+(i<<1)|0);h=7-g|0;f=k<>2]&l>>>h+y[c+8>>2]|(y[c+28>>2]&j>>>h|(y[c+4>>2]&i)<<1));g=g+1|0;continue}break}if(B[a+48>>2]<=d>>>0){break c}w[y[a+44>>2]+d|0]=f;break b}if(B[a+48>>2]<=f>>>0){break c}w[y[a+44>>2]+f|0]=l;f=h;continue}}o()}return 1}return 0}function we(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0;c=Y-48|0;Y=c;m=y[b+4>>2];t=y[b+12>>2];u=y[b+8>>2];h=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,h,d-1|0);ja(c+32|0,h,d-2|0);A=u+79434|0;n=y[c+36>>2];p=y[c+32>>2];C=t>>>0>39717;q=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,h,d);f=y[c+36>>2];r=y[c+32>>2];y[c+40>>2]=r;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[m|0]){break b}if(!C){break e}d=y[a+60>>2]^ka(m,A);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=r;y[c+28>>2]=f;y[c+8>>2]=r;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];i=0;v=0;x=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);x=y[c+32>>2];v=y[c+36>>2]}e=c+32|0;ja(e,h,d+w[a+17|0]|0);D=y[c+36>>2];E=y[c+32>>2];ja(e,h,d+w[a+19|0]|0);F=y[c+36>>2];G=y[c+32>>2];ja(e,h,d+w[a+21|0]|0);H=y[c+36>>2];I=y[c+32>>2];ja(e,h,d+w[a+23|0]|0);J=y[c+36>>2];K=y[c+32>>2];e=y[h+20>>2];i=!n|(e|0)<2?i:z[p|0]>>>6&1;i=!n|(e|0)<=0?i:z[p|0]>>>6&2|i;g=0;g=!j|(e|0)<3?g:z[k|0]>>>5&1;g=!j|(e|0)<2?g:z[k|0]>>>5&2|g;g=!j|(e|0)<=0?g:z[k|0]>>>5&4|g;s=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,x,v)){break h}}l=ia(e,w[a+16|0]+d|0,E,D);L=ia(e,w[a+18|0]+d|0,G,F);M=ia(e,w[a+20|0]+d|0,I,H);e=ia(e,w[a+22|0]+d|0,K,J);if(z[m|0]){break b}e=g<<5|i<<12|l<<4|L<<10|M<<11|e<<15|s;if(e>>>0>=t>>>0){break e}l=ka(m,(e<<1)+u|0);e=y[h+20>>2];if(!l){break h}Ea(e,d,r,f,1);e=y[h+20>>2];l=1;break g}l=0}i=ia(e,d+2|0,p,n)|i<<1&6;g=ia(e,d+3|0,k,j)|g<<1&30;d=d+1|0;s=s<<1&14|l;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;q=3}y[a+52>>2]=q;break a}d=d+1|0;y[a+24>>2]=d;p=k;n=j;continue}break}q=-1}Y=c+48|0;return q|0}function re(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0;i=y[b+4>>2];k=y[b+12>>2];l=y[b+8>>2];m=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[m+16>>2];y[a+44>>2]=y[m+12>>2];y[a+48>>2]=c}a:{b:{c=y[a+4>>2];if(!c){break b}q=l+810|0;c=c-1|0;j=c>>>3|0;n=(c&7)+1|0;d=y[a+24>>2];r=k>>>0>405;while(1){c:{if(B[a+8>>2]<=d>>>0){b=4}else{d:{if(!z[a+1|0]){d=y[a+60>>2];break d}if(z[i|0]){break a}if(!r){break b}d=y[a+60>>2]^ka(i,q);y[a+60>>2]=d}e:{if(d){ld(a,m);break e}d=0;g=0;f:{if(!y[a+24>>2]){g:{while(1){e=7;f=0;if((g|0)==(j|0)){break g}h:{while(1){if((e|0)>=0){if(z[i|0]){break h}if(d>>>0>=k>>>0){break b}h=d<<1;c=ka(i,h+l|0);d=c|h&1006;f=c<>2]<=g>>>0){break b}w[y[a+44>>2]+g|0]=f;g=g+1|0;continue}break}if(g>>>0>>0){break a}}e=0;f=0;while(1){if((e|0)==(n|0)){break f}if(z[i|0]){break a}if(d>>>0>=k>>>0){break b}h=d<<1;c=ka(i,h+l|0);d=c|h&1006;f=c<<7-e|f;e=e+1|0;continue}}if(!y[a+40>>2]){break b}g=z[y[a+36>>2]];e=g>>>1&112;c=0;while(1){i:{if((c|0)!=(j|0)){h=c+1|0;if(h>>>0>=B[a+40>>2]){break b}g=z[h+y[a+36>>2]|0]|g<<8;d=7;f=0;while(1){if((d|0)<0){break i}if(z[i|0]){break a}if(e>>>0>=k>>>0){break b}e=e<<1;p=ka(i,e+l|0);e=p|(g>>>d+1&16|e&1006);f=p<>>0>=k>>>0){break b}e=e<<1;c=ka(i,e+l|0);e=c|(h>>>8-d&16|e&1006);f=c<<7-d|f;d=d+1|0;continue}break}if(B[a+48>>2]<=j>>>0){break b}w[y[a+44>>2]+j|0]=f;break e}if(B[a+48>>2]<=c>>>0){break b}w[y[a+44>>2]+c|0]=f;c=h;continue}}if(B[a+48>>2]<=j>>>0){break b}w[y[a+44>>2]+j|0]=f}Db(a,m);c=y[b+16>>2];if(!c){break c}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break c}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}d=y[a+24>>2]+1|0;y[a+24>>2]=d;continue}}o()}return-1}function hb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;a:{b:{while(1){if(a>>>0>4294967239){break b}b=y[1691];c=b;f=y[1690];a=a>>>0<=8?8:a+3&-4;c:{if(a>>>0<=127){d=(a>>>3|0)-1|0;break c}e=H(a);d=((a>>>29-e^4)-(e<<2)|0)+110|0;if(a>>>0<=4095){break c}e=((a>>>30-e^2)-(e<<1)|0)+71|0;d=e>>>0>=63?63:e}g=d;d=g&31;if((g&63)>>>0>=32){e=0;b=b>>>d|0}else{e=b>>>d|0;b=((1<>>d}if(b|e){while(1){d=e;d:{if(d|b){c=d-1|0;e=c+1|0;f=c;c=b-1|0;f=(c|0)!=-1?e:f;e=H(d^f);e=(e|0)==32?H(b^c)+32|0:e;c=63-e|0;Z=0-(e>>>0>63)|0;break d}Z=0;c=64}f=c;c=f&31;if((f&63)>>>0>=32){e=0;h=d>>>c|0}else{e=d>>>c|0;h=((1<>>c}g=f+g|0;b=g<<4;d=y[b+5736>>2];c=b+5728|0;e:{if((d|0)!=(c|0)){b=_b(d,a);if(b){break a}b=y[d+4>>2];f=y[d+8>>2];y[b+8>>2]=f;y[f+4>>2]=b;y[d+8>>2]=c;y[d+4>>2]=y[c+4>>2];y[c+4>>2]=d;y[y[d+4>>2]+8>>2]=d;g=g+1|0;b=(e&1)<<31|h>>>1;e=e>>>1|0;break e}j=y[1691];f=g&63;b=f;d=b&31;if(b>>>0>=32){b=0;c=-1>>>d|0}else{b=-1>>>d|0;c=b|(1<>>0>=32){b=c<>>32-d|b<>>0>=32){b=-1<>>32-b}k=c&-2;c=f&31;if(f>>>0>=32){f=0;c=b>>>c|0}else{f=b>>>c|0;c=((1<>>c}b=c|i;Z=d|f;y[1690]=y[1690]&b;y[1691]=Z&j;b=h^1}if(b|e){continue}break}f=y[1690];c=y[1691]}e=H(c);d=63-((e|0)==32?H(f)+32|0:e)|0;f:{if(!(c|f)){g=0;break f}b=d<<4;g=y[b+5736>>2];if(!c&f>>>0<1073741824){break f}e=98;c=b+5728|0;if((c|0)==(g|0)){break f}while(1){b=_b(g,a);if(b){break a}g=y[g+8>>2];if((c|0)==(g|0)){break f}b=e;e=b-1|0;if(b){continue}break}}if(Md(a+48|0)){continue}break}if(!g){break b}e=(d<<4)+5728|0;if((e|0)==(g|0)){break b}while(1){b=_b(g,a);if(b){break a}g=y[g+8>>2];if((e|0)!=(g|0)){continue}break}}b=0}return b}function nb(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;g=Y-32|0;Y=g;a:{i=hc(d,e);if(!y[i+16>>2]|(b|0)<0|(!y[a+16>>2]|(c|0)<0)|y[a+20>>2]<=(b|0)){break a}f=y[a+24>>2];if((f|0)<=(c|0)){break a}b:{if(!(b&7)){e=y[a+28>>2];d=b>>>3|0;if(e>>>0<=d>>>0){break b}b=e-d|0;e=y[i+28>>2];h=b>>>0>>0?b:e;if(!h){break b}e=0;b=f-c|0;f=y[i+24>>2];b=(b|0)<(f|0)?b:f;k=(b|0)>0?b:0;while(1){if((e|0)==(k|0)){break a}dc(g+24|0,a,c+e|0);f=0;b=0;if(z[g+28|0]==1){j=y[a+16>>2];b=y[g+24>>2];if(j>>>0>>0){break b}f=y[a+28>>2];if(f>>>0>j-b>>>0){break b}b=b+y[a+12>>2]|0}if(d>>>0>f>>>0|h>>>0>f-d>>>0){break b}ja(g+16|0,i,e);if(h>>>0>B[g+20>>2]){break b}if(h){p(y[g+16>>2],b+d|0,h)}e=e+1|0;continue}}d=y[a+28>>2]/4|0;j=b>>>5|0;if(d>>>0<=j>>>0){break b}n=d-j|0;d=y[i+28>>2]/4|0;l=d>>>0>n>>>0?n:d;if(!l){break b}e=c;c=f-c|0;d=y[i+24>>2];c=(c|0)<(d|0)?c:d;ad(g+24|0,a,e,c);cc(g+16|0,i,0,c);h=y[g+20>>2];if(!h){break b}t=b&31;k=y[g+28>>2];u=j<<2;v=b^-1;c:while(1){if(!h){break a}if(k>>>0>>0|k-j>>>0>>0){break b}y[g+12>>2]=n;r=y[g+24>>2];y[g+8>>2]=r+u;if(h>>>0>>0){break b}c=y[g+16>>2];e=Ya(g+8|0);d=y[g+8>>2];q=y[g+12>>2];f=l>>>0>q>>>0?q:l;if(f>>>0>B[g+12>>2]){break b}s=c+(f<<2)|0;f=((Ae(e,16)^e)&-16711936)>>>8^Ae(e,8);e=c;while(1)if((e|0)==(s|0)){if(l>>>0>q>>>0){d=f<>>8^Ae(d,8),y[w>>2]=x}d=y[a+28>>2]/4|0;if(d>>>0>k>>>0){break b}y[g+24>>2]=(d<<2)+r;e=y[i+28>>2]/4|0;if(e>>>0>h>>>0){break b}y[g+16>>2]=c+(e<<2);h=h-e|0;k=k-d|0;continue c}else{m=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);m=m|f>>>1>>>v;w=e,x=((m^Ae(m,16))&-16711936)>>>8^Ae(m,8),y[w>>2]=x;d=d+4|0;e=e+4|0;continue}}}o()}Y=g+32|0;return i}function ue(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0;c=Y-48|0;Y=c;n=y[b+4>>2];t=y[b+12>>2];u=y[b+8>>2];i=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,i,d-1|0);ja(c+32|0,i,d-2|0);C=u+3882|0;l=y[c+36>>2];m=y[c+32>>2];D=t>>>0>1941;p=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,i,d);f=y[c+36>>2];q=y[c+32>>2];y[c+40>>2]=q;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[n|0]){break b}if(!D){break e}d=y[a+60>>2]^ka(n,C);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=q;y[c+28>>2]=f;y[c+8>>2]=q;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;v=0;x=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);x=y[c+32>>2];v=y[c+36>>2]}ja(c+32|0,i,d+w[a+17|0]|0);E=y[c+36>>2];F=y[c+32>>2];e=y[i+20>>2];g=!l|(e|0)<3?g:z[m|0]>>>5&1;g=!l|(e|0)<2?g:z[m|0]>>>5&2|g;g=!l|(e|0)<=0?g:z[m|0]>>>5&4|g;h=0;h=!j|(e|0)<3?h:z[k|0]>>>5&1;h=!j|(e|0)<2?h:z[k|0]>>>5&2|h;h=!j|(e|0)<=0?h:z[k|0]>>>5&4|h;s=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,x,v)){break h}}e=ia(e,w[a+16|0]+d|0,F,E);if(z[n|0]){break b}e=h<<4|g<<9|e<<3|s;if(e>>>0>=t>>>0){break e}r=ka(n,(e<<1)+u|0);e=y[i+20>>2];if(!r){break h}Ea(e,d,q,f,1);e=y[i+20>>2];r=1;break g}r=0}A=d+3|0;g=ia(e,A,m,l)|g<<1&14;h=ia(e,A,k,j)|h<<1&30;d=d+1|0;s=s<<1&6|r;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;p=3}y[a+52>>2]=p;break a}d=d+1|0;y[a+24>>2]=d;m=k;l=j;continue}break}p=-1}Y=c+48|0;return p|0}function Sd(a,b){a=a|0;b=+b;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;c=a<<3;o=c+6784|0;j=D[c+6816>>3];if(j!=0){l=D[o>>3];m=J(l,b);b=m-l;d=~~b>>>0;if(G(b)>=1){c=~~(b>0?I(K(b*2.3283064365386963e-10),4294967295):L((b-+(~~b>>>0>>>0))*2.3283064365386963e-10))>>>0}else{c=0}h=~~j>>>0;if(G(j)>=1){f=~~(j>0?I(K(j*2.3283064365386963e-10),4294967295):L((j-+(~~j>>>0>>>0))*2.3283064365386963e-10))>>>0}else{f=0}a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{k:{if(c){if(!h){break k}if(!f){break j}e=H(f)-H(c)|0;if(e>>>0<=31){break i}break c}if((f|0)==1|f>>>0>1){break c}Z=0;c=(d>>>0)/(h>>>0)|0;break a}if(!d){break h}if(!f|f-1&f){break g}c=c>>>Be(f)|0;Z=0;break a}if(!(h-1&h)){break f}k=(H(h)+33|0)-H(c)|0;i=0-k|0;break d}k=e+1|0;i=63-e|0;break d}Z=0;c=(c>>>0)/(f>>>0)|0;break a}e=H(f)-H(c)|0;if(e>>>0<31){break e}break c}if((h|0)==1){break b}h=Be(h);f=h&31;if((h&63)>>>0>=32){c=c>>>f|0}else{e=c>>>f|0;c=((1<>>f}Z=e;break a}k=e+1|0;i=63-e|0}e=k&63;g=e&31;if(e>>>0>=32){e=0;n=c>>>g|0}else{e=c>>>g|0;n=((1<>>g}i=i&63;g=i&31;if(i>>>0>=32){c=d<>>32-g|c<>>31;e=n<<1|c>>>31;g=s-(p+(e>>>0>i>>>0)|0)>>31;q=g&h;n=e-q|0;e=p-((f&g)+(e>>>0>>0)|0)|0;c=c<<1|d>>>31;d=r|d<<1;r=g&1;k=k-1|0;if(k){continue}break}}Z=c<<1|d>>>31;c=r|d<<1;break a}d=0;c=0}Z=c;c=d}d=Z;c=c+1|0;d=c?d:d+1|0;l=(+(c>>>0)+ +(d>>>0)*4294967296)*j+l;m=l-m}D[o>>3]=l;Q(a|0,+m)|0;a=(a|0)==2?27:(a|0)==1?26:14;c=a-1|0;l:{if(y[1692]>>>c&1){y[1694]=y[1694]|1<>2];if(c){_[c|0](a)}}}function se(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0;c=Y-48|0;Y=c;l=y[b+4>>2];s=y[b+12>>2];t=y[b+8>>2];i=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,i,d-1|0);ja(c+32|0,i,d-2|0);A=t+458|0;m=y[c+36>>2];n=y[c+32>>2];C=s>>>0>229;a:{while(1){b:{c:{if(B[a+8>>2]<=d>>>0){d=4}else{j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,i,d);f=y[c+36>>2];p=y[c+32>>2];y[c+40>>2]=p;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[l|0]){break b}if(!C){break e}d=y[a+60>>2]^ka(l,A);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=p;y[c+28>>2]=f;y[c+8>>2]=p;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;u=0;v=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);v=y[c+32>>2];u=y[c+36>>2]}ja(c+32|0,i,d+w[a+17|0]|0);D=y[c+36>>2];E=y[c+32>>2];e=y[i+20>>2];g=!m|(e|0)<2?g:z[n|0]>>>6&1;g=!m|(e|0)<=0?g:z[n|0]>>>6&2|g;h=0;h=!j|(e|0)<2?h:z[k|0]>>>6&1;h=!j|(e|0)<=0?h:z[k|0]>>>6&2|h;r=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,v,u)){break h}}e=ia(e,w[a+16|0]+d|0,E,D);if(z[l|0]){break b}e=h<<3|g<<7|e<<2|r;if(e>>>0>=s>>>0){break e}q=ka(l,(e<<1)+t|0);e=y[i+20>>2];if(!q){break h}Ea(e,d,p,f,1);e=y[i+20>>2];q=1;break g}q=0}x=d+2|0;g=ia(e,x,n,m)|g<<1&6;h=ia(e,x,k,j)|h<<1&14;d=d+1|0;r=r<<1&2|q;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;d=3}y[a+52>>2]=d;break a}d=d+1|0;y[a+24>>2]=d;n=k;m=j;continue}break}d=-1}Y=c+48|0;return d|0}function vc(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0;f=Y-32|0;Y=f;k=ua(a,a+4|0);y[f+28>>2]=k;a:{c=y[k+16>>2];if(!c){k=0;break a}Mb(y[k+12>>2],c,z[a+20|0]);b:{while(1){if(!m&l>>>0>=B[a+36>>2]|m){y[f+28>>2]=0;break a}e=y[b>>2];c=y[b+4>>2];d=0;y[f+24>>2]=0;y[f+16>>2]=0;y[f+20>>2]=0;c:{d:{if((c|0)==(e|0)){j=0;e=c;break d}c=c-e|0;e=c>>2;if(e>>>0>=536870912){break c}j=na(c<<1);y[f+16>>2]=j;d=(e<<3)+j|0;y[f+24>>2]=d;c=j;while(1){if((c|0)!=(d|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}y[f+20>>2]=d;c=y[b>>2];e=y[b+4>>2]}e=e-c|0;if(e>>2>>>0>d-j>>3>>>0){break b}e=c+e|0;d=j;while(1){if((c|0)==(e|0)){e:{c=y[a+44>>2];d=ze(l,m,A[a+48>>1],0);e=c+d|0;c=Z+(c>>31)|0;n=e;r=d>>>0>e>>>0?c+1|0:c;c=y[a+40>>2];d=ze(l,m,A[a+50>>1],0);e=c+d|0;c=Z+(c>>31)|0;s=e;t=d>>>0>e>>>0?c+1|0:c;e=0;c=0;f:while(1){if(!c&B[a+32>>2]<=e>>>0|c){break e}h=y[b>>2];p=y[b+4>>2]-h>>2;d=0;g=0;while(1){i=d&255;if(p>>>0<=i>>>0){d=y[a+12>>2]-1|0;d=y[y[y[a+16>>2]>>2]+((d>>>0>>0?d:g)<<2)>>2];h=ze(e,c,A[a+48>>1],0);i=h+s|0;g=Z+t|0;g=h>>>0>i>>>0?g+1|0:g;h=ze(e,c,A[a+50>>1],0);p=n-h|0;i=(g&255)<<24|i>>>8;q=g>>8;h=r-(Z+(h>>>0>n>>>0)|0)|0;g=h>>8;Ab(d,k,i,q,(h&255)<<24|p>>>8,g,y[a+24>>2]);e=e+1|0;c=e?c:c+1|0;continue f}else{q=g;g=(i<<3)+j|0;g=q|ia(y[y[(i<<2)+h>>2]+20>>2],e,y[g>>2],y[g+4>>2])<>2],l);n=y[f+12>>2];y[d>>2]=y[f+8>>2];y[d+4>>2]=n;d=d+8|0;c=c+4|0;continue}break}d=m;l=l+1|0;m=l?d:d+1|0;va(f+16|0);continue}break}za()}o()}la(f+28|0);Y=f+32|0;return k}function Zb(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;j=Y-48|0;Y=j;m=((b&536870911)<<3|a>>>29)&-8;o=d;d=-1;p=1;a:{b:while(1){k=p&1;q=k?326:325;r=k?1392:1056;s=k?325:326;t=k?1056:1392;c:while(1){i=y[c>>2];while(1){if(i>>>0>=m>>>0){break a}d:{i=0;e:{if(Ia(a,b,c)){break e}if(B[c>>2]>=m>>>0){break a}l=Ia(a,b,c);if(B[c>>2]>=m>>>0){break a}n=Ia(a,b,c);i=n?1:-1;if(l){break e}if(n){i=0;while(1){Yb(j+44|0,t,s,a,b,c);if(z[j+46|0]!=1){break a}l=A[j+44>>1];i=l+i|0;if((i|0)>(h|0)){break a}if(l>>>0>=64){continue}break}l=((d>>>31|0)+d|0)+i|0;if(!k){gb(o,e,h,d,l)}i=0;while(1){Yb(j+44|0,r,q,a,b,c);if(z[j+46|0]!=1){break a}d=A[j+44>>1];i=d+i|0;if((i|0)>(h|0)){break a}if(d>>>0>=64){continue}break}d=i+l|0;if(k){gb(o,e,h,l,d)}if((d|0)<(h|0)){continue c}break a}if(B[c>>2]>=m>>>0){break a}if(Ia(a,b,c)){y[j+32>>2]=f;y[j+36>>2]=g;y[j+16>>2]=f;y[j+20>>2]=g;Gc(j+16|0,h,d,k,j+44|0,j+40|0);i=y[j+40>>2];if(!k){gb(o,e,h,d,i)}d=i;if((i|0)<(h|0)){continue c}break a}if(B[c>>2]>=m>>>0){break a}l=Ia(a,b,c);if(B[c>>2]>=m>>>0){break a}n=Ia(a,b,c);i=n?2:-2;if(l){break e}i=y[c>>2];if(!n){break d}if(i>>>0>=m>>>0){break a}i=Ia(a,b,c)?3:-3}y[j+24>>2]=f;y[j+28>>2]=g;y[j+8>>2]=f;y[j+12>>2]=g;Gc(j+8|0,h,d,k,j+44|0,j+40|0);i=i+y[j+44>>2]|0;if(!k){gb(o,e,h,d,i)}if((h|0)<=(i|0)){break a}p=p^1;k=(d|0)<(i|0);d=i;if(k){continue b}break a}if(i>>>0>=m>>>0){break a}l=Ia(a,b,c);i=y[c>>2];if(l){i=i+3|0;y[c>>2]=i;continue}break}break}break}y[c>>2]=i+5}Y=j+48|0}function qe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0;c=Y-48|0;Y=c;i=y[b+4>>2];q=y[b+12>>2];r=y[b+8>>2];h=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,h,d-1|0);u=r+810|0;v=q>>>0>405;j=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){k=y[c+44>>2];l=y[c+40>>2];ja(c+32|0,h,d);e=y[c+36>>2];m=y[c+32>>2];y[c+40>>2]=m;y[c+44>>2]=e;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[i|0]){break b}if(!v){break e}d=y[a+60>>2]^ka(i,u);y[a+60>>2]=d}if(d){y[c+20>>2]=k;y[c+16>>2]=l;y[c+24>>2]=m;y[c+28>>2]=e;y[c+8>>2]=m;y[c+12>>2]=e;e=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=e;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;s=0;t=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);t=y[c+32>>2];s=y[c+36>>2]}ja(c+32|0,h,d+w[a+17|0]|0);x=y[c+36>>2];A=y[c+32>>2];f=y[h+20>>2];g=!k|(f|0)<2?g:z[l|0]>>>6&1;g=!k|(f|0)<=0?g:z[l|0]>>>6&2|g;p=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,t,s)){break h}}f=ia(f,w[a+16|0]+d|0,A,x);if(z[i|0]){break b}f=g<<5|f<<4|p;if(f>>>0>=q>>>0){break e}n=ka(i,(f<<1)+r|0);f=y[h+20>>2];if(!n){break h}Ea(f,d,m,e,1);f=y[h+20>>2];n=1;break g}n=0}g=ia(f,d+2|0,l,k)|g<<1&30;d=d+1|0;p=p<<1&14|n;continue}}o()}e=y[b+16>>2];if(!e){d=y[a+24>>2];break c}e=_[y[y[e>>2]+8>>2]](e)|0;d=y[a+24>>2];if(!e){break c}y[a+24>>2]=d+1;j=3}y[a+52>>2]=j;break a}d=d+1|0;y[a+24>>2]=d;continue}break}j=-1}Y=c+48|0;return j|0}function Od(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;c=y[b+60>>2];d=y[b+64>>2];f=b+52|0;Jc(c,d,f);j=((d&536870911)<<3|c>>>29)&-8;if(j>>>0<=B[b+52>>2]){y[a>>2]=0;y[a+4>>2]=0;return}Kc(y[b+68>>2],y[b+72>>2]);h=b+68|0;c=y[b+48>>2];a:{if((c|0)<0){e=y[b+68>>2];c=y[b+72>>2]-e|0;d=0;i=c;g=y[b+80>>2];c=y[b+84>>2]-g|0;Zb(y[b+60>>2],y[b+64>>2],f,e|d,i,d|g,c,y[b+4>>2]);Ic(b+80|0,h);break a}if(!c){d=y[b+68>>2];c=y[b+72>>2]-d|0;Hc(y[b+60>>2],y[b+64>>2],f,d,c,y[b+4>>2]);break a}e=Ia(y[b+60>>2],y[b+64>>2],f);g=y[b+68>>2];c=y[b+72>>2]-g|0;d=y[b+60>>2];k=y[b+64>>2];b:{if(e){Hc(d,k,f,g,c,y[b+4>>2]);break b}i=d;d=0;l=c;e=y[b+80>>2];c=y[b+84>>2]-e|0;Zb(i,k,f,d|g,l,d|e,c,y[b+4>>2])}Ic(b+80|0,h)}if(z[b+57|0]==1){Jc(y[b+60>>2],y[b+64>>2],f)}c:{d:{e:{if(z[b+56|0]!=1){break e}d=y[f>>2];if(j>>>0<=d>>>0){break e}e=d+7&-8;while(1){h=d>>>3|0;g=128>>>(d&7)|0;c=1;f:{while(1){if(!c|d>>>0>=e>>>0){break f}if(h>>>0>=B[b+64>>2]){break d}if(g&z[h+y[b+60>>2]|0]){c=0;w[b+56|0]=0;continue}break}d=d+1|0;continue}break}if(!c){break e}y[f>>2]=e}c=y[b+68>>2];if(!z[b+58|0]){d=y[b+72>>2]-c|0;break c}if(c&3){break d}d=y[b+72>>2]-c|0;e=c+(d&-4)|0;b=c;while(1){if((b|0)==(e|0)){break c}y[b>>2]=y[b>>2]^-1;b=b+4|0;continue}}o()}y[a+4>>2]=d;y[a>>2]=c}function ab(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=Y-32|0;Y=i;a:{b:{if(!y[a+16>>2]|y[a+24>>2]>=(b|0)){break b}e=y[a+28>>2];if((268435452/(e|0)|0)<(b|0)){break b}if((b|0)<0){break a}e=ze(e,e>>31,b,0);g=Z;if((g|0)==1|g>>>0>1){break a}k=y[a+12>>2];g=0;j=y[a+16>>2];d=y[a>>2];h=y[a+4>>2];c:{if((d|0)!=(h|0)){y[a+12>>2]=0;y[a+16>>2]=0;f=h-d|0;d:{if(f>>>0>>0){g=y[a+8>>2];if(e>>>0<=g-d>>>0){kd(a,e-f|0);break d}h=Fc(d,g,e);if(h){l=Ad(h)}k=e+l|0;g=f+l|0;d=g;while(1){if((d|0)!=(k|0)){w[d|0]=0;d=d+1|0;continue}break}d=y[a>>2];e=y[a+4>>2];g=g+(d-e|0)|0;f=g;while(1){if((d|0)!=(e|0)){w[f|0]=z[d|0];f=f+1|0;d=d+1|0;continue}break}y[a+4>>2]=k;y[a+8>>2]=h+l;e=y[a>>2];y[a>>2]=g;ma(e);break d}if(e>>>0>=f>>>0){break d}y[a+4>>2]=d+e}e=y[a>>2];y[a+12>>2]=e;d=y[a+4>>2]-e|0;y[a+16>>2]=d;break c}f=Gb(i,e);h=y[f>>2];y[i+12>>2]=h;e=y[f+4>>2];y[i+16>>2]=e;y[i+20>>2]=y[f+8>>2];y[f+8>>2]=0;y[f>>2]=0;y[f+4>>2]=0;y[i+28>>2]=e-h;y[i+24>>2]=h;e=i+12|0;ec(a,e);va(e);va(f);d=y[a+16>>2];if(j>>>0>d>>>0){break a}if(!(g|j)){break c}if(j){p(y[a+12>>2],k,j)}d=y[a+16>>2]}if(d>>>0>>0){break a}c=0-c|0;d=d-j|0;f=y[a+12>>2]+j|0;while(1){if((d|0)>0){w[f|0]=c;d=d-1|0;f=f+1|0;continue}break}y[a+24>>2]=b}Y=i+32|0;return}o()}function nd(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;d=Y-32|0;Y=d;h=y[a+24>>2];y[d+16>>2]=0;a:{b:{switch(z[a+3|0]){case 0:c=Bd(a)?3:4;break a;case 1:c=yd(a)?5:6;break a;case 2:c=Hb(a)?7:8;break a;default:break b}}c=Hb(a)?9:10}e=Y-48|0;Y=e;y[e+24>>2]=0;c:{if(c){y[e+16>>2]=0;y[e+12>>2]=c;y[e+8>>2]=2244;g=e+8|0;y[e+24>>2]=g;if(y[d+16>>2]==(d|0)){f=e+32|0;_[y[y[e+8>>2]+12>>2]](g,f);c=y[e+24>>2];_[y[y[c>>2]+16>>2]](c);y[e+24>>2]=0;c=y[d+16>>2];_[y[y[c>>2]+12>>2]](c,g);c=y[d+16>>2];_[y[y[c>>2]+16>>2]](c);y[d+16>>2]=0;y[e+24>>2]=g;_[y[y[e+32>>2]+12>>2]](f,d);_[y[y[e+32>>2]+16>>2]](f);y[d+16>>2]=d;break c}_[y[y[e+8>>2]+12>>2]](e+8|0,d);c=y[e+24>>2];_[y[y[c>>2]+16>>2]](c);y[e+24>>2]=y[d+16>>2];y[d+16>>2]=d;break c}c=y[d+16>>2];if((c|0)==(d|0)){f=e+8|0;_[y[y[c>>2]+12>>2]](c,f);c=y[d+16>>2];_[y[y[c>>2]+16>>2]](c);y[d+16>>2]=y[e+24>>2];y[e+24>>2]=f;break c}y[e+24>>2]=c;y[d+16>>2]=0}md(e+8|0);Y=e+48|0;c=y[y[b>>2]>>2];f=y[d+16>>2];y[d+28>>2]=b;if(f){f=_[y[y[f>>2]+24>>2]](f,a,d+28|0)|0;y[a+64>>2]=0;y[a+52>>2]=f;b=y[c+20>>2];y[a+68>>2]=h;y[a+72>>2]=b;y[a+76>>2]=y[a+24>>2];if((f|0)==4){y[a+24>>2]=0}md(d);Y=d+32|0;return f}a=vb(4);y[a>>2]=4876;P(a|0,4888,11);o()}function ie(a,b,c,d,e,f,g,h,i,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0;k=Y-16|0;Y=k;l=i?i:c;a:{if((l|0)<=0){break a}j=j?j:d;if((j|0)<=0|(j|l)>>>0>65535){break a}i=na(92);y[i+44>>2]=0;y[i+36>>2]=-1;y[i+40>>2]=0;w[i+32|0]=0;y[i+20>>2]=1;y[i+24>>2]=1;y[i+16>>2]=j;y[i+12>>2]=l;y[i+8>>2]=j;y[i+4>>2]=l;y[i+60>>2]=a;y[i+64>>2]=b;w[i+58|0]=(h|0)!=0;w[i+57|0]=(f|0)!=0;w[i+56|0]=(g|0)!=0;y[i+52>>2]=0;y[i+48>>2]=e;y[i>>2]=1728;a=(l>>>3|0)+3|0;a=((l<<29)-536870912>>>0<3758096384?a+1|0:a)&16380;y[i+28>>2]=a;Gb(i+68|0,a);Gb(i+80|0,y[i+28>>2]);e=c+7>>>3|0;W(E(e,d)|0);c=0;while(1){b:{if((c|0)==(d|0)){break b}c:{j=y[i+36>>2];a=c+1|0;if((j|0)==(a|0)){b=y[i+44>>2];j=y[i+40>>2];break c}d:{if(!((j|0)>=0&(c|0)>=(j|0))){j=0;if(!(_[y[y[i>>2]+12>>2]](i)|0)){break b}b=0;break d}b=1}while(1){if(!b){y[i+36>>2]=j;b=1;continue}e:{if((c|0)>(j|0)){_[y[y[i>>2]+16>>2]](k+8|0,i);j=y[i+36>>2]+1|0;break e}_[y[y[i>>2]+16>>2]](k+8|0,i);b=y[k+12>>2];j=y[k+8>>2];y[i+40>>2]=j;y[i+44>>2]=b;y[i+36>>2]=y[i+36>>2]+1;break c}b=0;continue}}if(!b){break b}V(j|0,e|0,E(c,e)|0);c=a;continue}break}_[y[y[i>>2]+4>>2]](i)}Y=k+16|0}function db(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,t=0,u=0;d=Y-32|0;Y=d;h=ua(a+4|0,a+8|0);y[d+8>>2]=h;i=y[h+12>>2];f=y[h+16>>2];g=f;a:{if(!f){oa(b,0);y[a+52>>2]=-1;break a}b:{f=y[h+28>>2];if((f|0)<0|!f){break b}j=y[c+12>>2]+(y[c+8>>2]<<3)|0;l=g;k=y[a+8>>2];q=y[a+4>>2];r=y[c>>2];s=y[c+4>>2];y[d+20>>2]=0;y[d+12>>2]=0;y[d+16>>2]=0;g=d+12|0;y[d+24>>2]=g;Eb(g,f);e=y[d+16>>2];g=f+e|0;while(1){if((e|0)!=(g|0)){w[e|0]=255;e=e+1|0;continue}break}w[d+28|0]=1;y[d+16>>2]=g;gc(d+24|0);m=y[d+12>>2];g=y[d+16>>2];y[d+24>>2]=j;t=(k|0)>0?k:0;e=0;g=g-m|0;k=e|m;while(1){if((n|0)!=(t|0)){j=i;e=f;if(l>>>0>>0){break b}while(1){if((e|0)>0){w[j|0]=255;e=e-1|0;j=j+1|0;continue}break}Zb(r,s,d+24|0,i|u,f,k,g,q);if(f>>>0>g>>>0){break b}if(f){p(m,i,f)}n=n+1|0;l=l-f|0;i=f+i|0;continue}break}i=y[d+24>>2];va(d+12|0);y[c+12>>2]=i&7;y[c+8>>2]=i>>>3;e=y[h+12>>2];c=e+y[h+16>>2]|0;while(1)if((c|0)==(e|0)){y[a+64>>2]=0;y[a+52>>2]=4;c=y[h+20>>2];y[a+68>>2]=0;y[a+72>>2]=c;y[a+76>>2]=y[h+24>>2];y[d+8>>2]=0;oa(b,h);break a}else{w[e|0]=z[e|0]^-1;e=e+1|0;continue}}o()}la(d+8|0);Y=d+32|0}function Ld(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;f=Y-16|0;Y=f;y[a+44>>2]=0;e=y[a+12>>2];b=y[a+8>>2];while(1){if((b|0)==(e|0)){a:{y[f+4>>2]=0;d=c+1|0;fc(a+60|0,d,f+4|0);e=y[a+12>>2];b=y[a+8>>2];while(1)if((b|0)==(e|0)){y[y[a+60>>2]>>2]=0;y[f+4>>2]=0;b=f+4|0;fc(a+48|0,d,b);y[f+4>>2]=0;fc(a+72|0,d,b);c=0;e=0;b=1;while(1){j=B[a+44>>2]>>0;if(!j){g=b<<2;h=g+y[a+60>>2]|0;i=y[h-4>>2];c=i+c|0;if((c|0)<0|c>>>0>>0){break a}c=c<<1;y[g+y[a+48>>2]>>2]=c;y[g+y[a+72>>2]>>2]=e;b=b+1|0;e=y[h>>2]+e|0;continue}break}Rb(a+84|0,e);b=0;y[f+12>>2]=0;y[f+4>>2]=0;y[f+8>>2]=0;e=0;if(d){e=f+4|0;ed(e,d);qd(e,d);e=y[f+4>>2]}g=y[a+8>>2];h=y[a+12>>2]-g>>3;while(1){if((b|0)==(h|0)){va(f+4|0);break a}c=g+(b<<3)|0;d=y[c>>2];if(d){i=c;d=d<<2;c=d+e|0;y[i+4>>2]=y[c>>2]+y[d+y[a+48>>2]>>2];y[(y[a+84>>2]+(y[d+y[a+72>>2]>>2]<<2)|0)+(y[c>>2]<<2)>>2]=b;y[c>>2]=y[c>>2]+1}b=b+1|0;continue}}else{c=y[a+60>>2]+(y[b>>2]<<2)|0;y[c>>2]=y[c>>2]+1;b=b+8|0;continue}}}else{d=y[b>>2];c=c>>>0>>0?d:c;y[a+44>>2]=c;b=b+8|0;continue}break}Y=f+16|0;return j}function Md(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0;b=y[1420];c=a+7&-8;a=c+7|0;d=a>>>0<7?1:d;e=a&-8;a=e+b|0;a:{b:{if(!(a>>>0>>0?d+1|0:d)){if(a>>>0<=$()<<16>>>0){break b}if(R(a|0)|0){break b}}y[1428]=48;b=-1;break a}y[1420]=a}d=b;if((d|0)!=-1){a=d+c|0;y[a-4>>2]=16;f=a-16|0;y[f>>2]=16;b=y[1688];if(b){e=y[b+8>>2]}else{e=0}c:{d:{if((e|0)==(d|0)){e=y[d-4>>2]&-2;g=d-e|0;h=y[g-4>>2];y[b+8>>2]=a;b=h&-2;a=g-b|0;if(y[(a+y[a>>2]|0)-4>>2]&1){f=y[a+4>>2];g=y[a+8>>2];y[f+8>>2]=g;y[g+4>>2]=f;b=(b+(c+e|0)|0)-16|0;y[a>>2]=b;break c}a=d-16|0;break d}y[d>>2]=16;y[d+8>>2]=a;y[d+4>>2]=b;y[d+12>>2]=16;y[1688]=d;a=d+16|0}b=f-a|0;y[a>>2]=b}y[((b&-4)+a|0)-4>>2]=b|1;c=y[a>>2]-8|0;e:{if(c>>>0<=127){b=(c>>>3|0)-1|0;break e}e=H(c);b=((c>>>29-e^4)-(e<<2)|0)+110|0;if(c>>>0<=4095){break e}b=((c>>>30-e^2)-(e<<1)|0)+71|0;b=b>>>0>=63?63:b}c=b<<4;y[a+4>>2]=c+5728;c=c+5736|0;y[a+8>>2]=y[c>>2];y[c>>2]=a;y[y[a+8>>2]+4>>2]=a;c=y[1691];a=b&31;if((b&63)>>>0>=32){b=1<>>32-a}y[1690]=e|y[1690];y[1691]=b|c}return(d|0)!=-1}function tb(a,b){var c=0,d=0,e=0;c=Y+-64|0;Y=c;d=y[a>>2];e=y[d-8>>2];d=y[d-4>>2];a:{if(y[d+4>>2]==y[b+4>>2]){a=e?0:a;break a}e=a+e|0;if((e|0)<=(a|0)){y[c+16>>2]=0;y[c+20>>2]=0;y[c+12>>2]=b;y[c+8>>2]=a;y[c+4>>2]=d;y[c+24>>2]=0;y[c+28>>2]=0;y[c+32>>2]=0;y[c+36>>2]=0;y[c+40>>2]=0;y[c+44>>2]=0;y[c+48>>2]=0;y[c+60>>2]=0;y[c+52>>2]=1;y[c+56>>2]=16777216;_[y[y[d>>2]+20>>2]](d,c+4|0,e,e,1,0);if(y[c+28>>2]){break a}}y[c+16>>2]=0;y[c+20>>2]=0;y[c+12>>2]=4984;y[c+8>>2]=a;y[c+4>>2]=b;y[c+24>>2]=0;y[c+28>>2]=0;y[c+32>>2]=0;y[c+36>>2]=0;y[c+40>>2]=0;y[c+44>>2]=0;y[c+48>>2]=0;y[c+52>>2]=0;a=0;w[c+55|0]=0;w[c+56|0]=0;w[c+57|0]=0;w[c+58|0]=0;y[c+60>>2]=0;w[c+59|0]=1;_[y[y[d>>2]+24>>2]](d,c+4|0,e,1,0);b:{switch(y[c+40>>2]){case 0:a=y[c+44>>2]==1?y[c+32>>2]==1?y[c+36>>2]==1?y[c+24>>2]:0:0:0;break a;case 1:break b;default:break a}}if(y[c+28>>2]!=1){if(y[c+44>>2]|y[c+32>>2]!=1|y[c+36>>2]!=1){break a}}a=y[c+20>>2]}Y=c- -64|0;return a}function _b(a,b){var c=0,d=0,e=0,f=0,g=0;e=a+4|0;d=e+7&-8;c=y[a>>2];if(d+b>>>0<=(c+a|0)-4>>>0){f=y[a+4>>2];g=y[a+8>>2];y[f+8>>2]=g;y[g+4>>2]=f;if((d|0)!=(e|0)){d=d-e|0;f=a-(y[a-4>>2]&-2)|0;e=d+y[f>>2]|0;y[f>>2]=e;y[(f+(e&-4)|0)-4>>2]=e;a=a+d|0;c=c-d|0;y[a>>2]=c}a:{if(b+24>>>0<=c>>>0){e=a+b|0;c=(c-b|0)-8|0;y[e+8>>2]=c;g=e+8|0;y[(g+(c&-4)|0)-4>>2]=c|1;d=y[e+8>>2]-8|0;b:{if(d>>>0<=127){c=(d>>>3|0)-1|0;break b}f=H(d);c=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break b}c=((d>>>30-f^2)-(f<<1)|0)+71|0;c=c>>>0>=63?63:c}d=c<<4;y[e+12>>2]=d+5728;d=d+5736|0;y[e+16>>2]=y[d>>2];y[d>>2]=g;y[y[e+16>>2]+4>>2]=g;d=y[1690];f=y[1691];e=c&31;if((c&63)>>>0>=32){c=1<>>32-e}y[1690]=g|d;y[1691]=c|f;c=b+8|0;y[a>>2]=c;b=(c&-4)+a|0;break a}b=a+c|0}y[b-4>>2]=c;a=a+4|0}else{a=0}return a}function ya(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;g=Y-16|0;Y=g;k=y[b+44>>2];f=1;while(1){a:{b:{if(f>>>0>k>>>0){e=-1;break b}e=-1;h=y[a>>2];j=y[h+8>>2];d=y[h>>2];if(B[h+4>>2]<=j>>>0){break b}d=z[d+j|0]>>>7-y[h+12>>2]&1;y[g+12>>2]=d;Tb(h);if((i|0)<0){break b}i=d|i<<1;if(B[b+44>>2]>>0){break a}d=f<<2;e=y[d+y[b+48>>2]>>2];if(e>>>0>i>>>0){break a}e=i-e|0;if(e>>>0>=B[d+y[b+60>>2]>>2]){break a}d=y[(y[b+84>>2]+(y[d+y[b+72>>2]>>2]<<2)|0)+(e<<2)>>2];e=1;if(z[b+1|0]==1&(d|0)==(y[b+4>>2]-1|0)){break b}e=-1;f=y[a>>2];a=d<<2;if((Fa(f,y[a+y[b+20>>2]>>2],g+12|0)|0)==-1){break b}e=0;f=y[a+y[b+32>>2]>>2];a=y[g+12>>2];y[c>>2]=f+((d|0)==(y[b+4>>2]+(z[b+1|0]?-3:-2)|0)?0-a|0:a)}Y=g+16|0;return e}f=f+1|0;continue}}function ma(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;if(a){b=a-4|0;f=y[b>>2];c=f;d=b;e=y[a-8>>2];a=e&-2;if((a|0)!=(e|0)){d=b-a|0;c=y[d+4>>2];e=y[d+8>>2];y[c+8>>2]=e;y[e+4>>2]=c;c=a+f|0}a=b+f|0;b=y[a>>2];if((b|0)!=y[(a+b|0)-4>>2]){f=y[a+4>>2];a=y[a+8>>2];y[f+8>>2]=a;y[a+4>>2]=f;c=b+c|0}y[d>>2]=c;y[((c&-4)+d|0)-4>>2]=c|1;b=y[d>>2]-8|0;a:{if(b>>>0<=127){a=(b>>>3|0)-1|0;break a}c=H(b);a=((b>>>29-c^4)-(c<<2)|0)+110|0;if(b>>>0<=4095){break a}a=((b>>>30-c^2)-(c<<1)|0)+71|0;a=a>>>0>=63?63:a}b=a<<4;y[d+4>>2]=b+5728;b=b+5736|0;y[d+8>>2]=y[b>>2];y[b>>2]=d;y[y[d+8>>2]+4>>2]=d;b=y[1690];c=y[1691];d=a&31;if((a&63)>>>0>=32){a=1<>>32-d}y[1690]=e|b;y[1691]=a|c}}function fd(a,b){var c=0,d=0,e=0,f=0,g=0;a:{b:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>3;c:{if(f>>>0>>0){d=y[a+8>>2]-d|0;if(d>>3>>>0>=b>>>0){b=(b-f<<3)+c|0;while(1){if((b|0)!=(c|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}y[a+4>>2]=b;return}if(b>>>0>=536870912){break b}c=d>>2;d=d>>>0>=2147483640?536870911:b>>>0>>0?c:b;if(d>>>0>=536870912){break a}c=b-f<<3;g=na(d<<3);b=g+e|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=g+(d<<3);if(!b){break c}ma(b);return}if(b>>>0>=f>>>0){break c}y[a+4>>2]=d+(b<<3)}return}za();o()}Wa();o()}function Wb(a,b,c,d,e){var f=0,g=0,h=0;a:{b:{c:{if((d|0)<0){break c}if((c|0)<=(d|0)){break a}g=0-(e^1)|0;e=d&7;if(e){f=d>>>3|0;if(f>>>0>=b>>>0){break c}e=(z[a+f|0]^g)&255>>>e;if(e){break b}d=d+7|0}e=d>>>3|0;h=c+7>>>3|0;while(1){d=e;e=d+4|0;if(h>>>0>>0){e=g&255;while(1){if(d>>>0>=h>>>0){break a}if(b>>>0<=d>>>0){break c}f=z[a+d|0];if((f|0)!=(e|0)){a=H((f^g)<<24)|d<<3;return(a|0)>(c|0)?c:a}else{d=d+1|0;continue}}}if(b>>>0>>0|b-d>>>0<=3){break c}f=a+d|0;f=z[f|0]|z[f+1|0]<<8|(z[f+2|0]<<16|z[f+3|0]<<24);f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);if((f|0)==(g|0)){continue}break}a=H(f^g)+(d<<3)|0;return(a|0)>(c|0)?c:a}o()}c=H(e<<24)|d&2147483640}return c}function gb(a,b,c,d,e){var f=0,g=0,h=0;a:{d=(d|0)>0?d:0;f=(e|0)>=0?(c|0)<(e|0)?c:e:0;if((d|0)>=(f|0)){break a}b:{h=d>>>3|0;if(h>>>0>=b>>>0){break b}e=d&7;c=a+h|0;g=f-1|0;f=g>>>3|0;if((f|0)==(h|0)){a=g&7;while(1){if(a>>>0>>0){break a}w[c|0]=z[c|0]+(-128>>e);e=e+1|0;continue}}d=z[c|0];while(1)if((e|0)==8){w[c|0]=d;if(b>>>0<=f>>>0){break b}d=(g&7)+1|0;g=a+f|0;c=z[g|0];e=0;while(1)if((d|0)==(e|0)){w[g|0]=c;c=h+1|0;if(c>>>0>=f>>>0){break a}e=f+(h^-1)|0;if(e>>>0>b-c>>>0){break b}c=a+c|0;while(1){if((e|0)<=0){break a}w[c|0]=0;e=e-1|0;c=c+1|0;continue}}else{c=(-128>>e)+c|0;e=e+1|0;continue}}else{d=(-128>>e)+d|0;e=e+1|0;continue}}o()}}function zb(a,b){var c=0,d=0,e=0,f=0,g=0;a:{b:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>2;c:{if(f>>>0>>0){d=y[a+8>>2]-d|0;if(d>>2>>>0>=b>>>0){b=(b-f<<2)+c|0;while(1){if((b|0)!=(c|0)){y[c>>2]=0;c=c+4|0;continue}break}y[a+4>>2]=b;return}if(b>>>0>=1073741824){break b}c=d>>1;d=d>>>0>=2147483644?1073741823:b>>>0>>0?c:b;if(d>>>0>=1073741824){break a}c=b-f<<2;g=na(d<<2);b=g+e|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){y[c>>2]=0;c=c+4|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=g+(d<<2);if(!b){break c}ma(b);return}if(b>>>0>=f>>>0){break c}y[a+4>>2]=d+(b<<2)}return}za();o()}Wa();o()}function ka(a,b){var c=0,d=0,e=0,f=0;c=z[b+1|0];if(c>>>0<47){c=E(c,6);e=A[c+1808>>1];d=y[a+8>>2]-e|0;y[a+8>>2]=d;c=c+1808|0;f=y[a+4>>2];a:{if(d>>>0>f>>>16>>>0){if(d&32768){return z[b|0]}if(d>>>0>>0){d=z[b|0]^1;e=z[c+3|0];if(z[c+4|0]==1){w[b|0]=d&1}w[b+1|0]=e;b=d&1;break a}w[b+1|0]=z[c+2|0];b=z[b|0];break a}y[a+4>>2]=f-(d<<16);b:{if(d>>>0>>0){w[b+1|0]=z[c+2|0];b=z[b|0];break b}d=z[b|0]^1;f=z[c+3|0];if(z[c+4|0]==1){w[b|0]=d&1}w[b+1|0]=f;b=d&1}y[a+8>>2]=e}c=y[a+12>>2];while(1){if(!c){Ec(a);c=y[a+12>>2]}c=c-1|0;y[a+12>>2]=c;d=y[a+8>>2];y[a+8>>2]=d<<1;y[a+4>>2]=y[a+4>>2]<<1;if(!(d&16384)){continue}break}return b}o()}function tc(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;e=Y-32|0;Y=e;x[a>>1]=0;t(a+4|0,0,92);y[e+28>>2]=0;y[e+20>>2]=0;y[e+24>>2]=0;a:{b:{d=c+2|0;if(d){if(d>>>0>=536870912){break b}d=d<<3;f=na(d);y[e+20>>2]=f;g=d+f|0;y[e+28>>2]=g;d=f;while(1){if((d|0)!=(g|0)){y[d>>2]=0;y[d+4>>2]=0;d=d+8|0;continue}break}y[e+24>>2]=g}i=b+c|0;d=0;while(1){if((b|0)==(i|0)){break a}j=z[b|0];h=(d<<3)+f|0;y[h+4>>2]=d;w[h+1|0]=0;w[h|0]=j;b=b+1|0;d=d+1|0;continue}}za();o()}b=(c<<3)+f|0;y[b+4>>2]=0;x[b>>1]=0;y[b+12>>2]=0;x[b+8>>1]=0;y[e+16>>2]=g-f>>3;y[e+12>>2]=f;w[e+8|0]=0;k=a,l=gd(a,e+8|0),w[k|0]=l;va(e+20|0);Y=e+32|0;return a}function _d(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;a:{if(Pa(a,y[b+8>>2],e)){if(y[b+28>>2]==1|y[b+4>>2]!=(c|0)){break a}y[b+28>>2]=d;return}if(Pa(a,y[b>>2],e)){if(!(y[b+16>>2]!=(c|0)&y[b+20>>2]!=(c|0))){if((d|0)!=1){break a}y[b+32>>2]=1;return}y[b+32>>2]=d;b:{if(y[b+44>>2]==4){break b}x[b+52>>1]=0;a=y[a+8>>2];_[y[y[a>>2]+20>>2]](a,b,c,c,1,e);if(z[b+53|0]==1){y[b+44>>2]=3;if(!z[b+52|0]){break b}break a}y[b+44>>2]=4}y[b+20>>2]=c;y[b+40>>2]=y[b+40>>2]+1;if(y[b+36>>2]!=1|y[b+24>>2]!=2){break a}w[b+54|0]=1;return}a=y[a+8>>2];_[y[y[a>>2]+24>>2]](a,b,c,d,e)}}function Yb(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0,l=0;l=((e&536870911)<<3|d>>>29)&-8;a:{b:{c:while(1){d:{if(c>>>0<=g>>>0){break d}j=z[b+g|0];if((j|0)==255){break b}h=y[f>>2];if(h>>>0>=l>>>0){break b}i=h>>>3|0;if(i>>>0>=e>>>0){break d}i=z[d+i|0];y[f>>2]=h+1;k=k<<1|(i<<(h&7)&128)>>>7;g=g+1|0;h=g+E(j,3)|0;while(1){if((g|0)>=(h|0)){continue c}if(c>>>0<=g>>>0){break d}if(z[b+g|0]==(k|0)){d=g+1|0;if(d>>>0>=c>>>0){break d}e=c;c=g+2|0;if(e>>>0<=c>>>0){break d}x[a>>1]=z[b+d|0]|z[b+c|0]<<8;b=1;break a}else{g=g+3|0;continue}}}break}o()}w[a|0]=0;b=0}w[a+2|0]=b}function Wc(a,b,c,d,e,f,g){y[a+8>>2]=0;a:{b:{if(!(b&255)){c:{switch(c|0){case 1:y[a+4>>2]=e;y[a>>2]=d;y[a+8>>2]=f-1;return;case 3:y[a+4>>2]=e;break a;case 0:y[a>>2]=d;y[a+8>>2]=f-1;y[a+4>>2]=(e-g|0)+1;return;case 2:break c;default:break b}}y[a+4>>2]=(e-g|0)+1;break a}d:{switch(c|0){case 1:y[a+4>>2]=d;y[a>>2]=e;y[a+8>>2]=g-1;return;case 3:y[a+4>>2]=d;y[a+8>>2]=g-1;y[a>>2]=(e-f|0)+1;return;case 0:y[a>>2]=e;y[a+4>>2]=(d-g|0)+1;return;case 2:break d;default:break b}}y[a+4>>2]=(d-g|0)+1;y[a>>2]=(e-f|0)+1}return}y[a>>2]=(d-f|0)+1}function fb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;a:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>1;if(f>>>0>>0){g=y[a+8>>2];if(g-d>>1>>>0>=b>>>0){b=(b-f<<1)+c|0;while(1){if((b|0)!=(c|0)){w[c|0]=0;w[c+1|0]=0;c=c+2|0;continue}break}y[a+4>>2]=b;return}d=Cc(d,g,b);if(d){h=Bc(d)}c=b-f<<1;b=e+h|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){w[c|0]=0;w[c+1|0]=0;c=c+2|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=(d<<1)+h;if(!b){break a}ma(b);return}if(b>>>0>=f>>>0){break a}y[a+4>>2]=d+(b<<1)}}function sd(a,b,c){var d=0,e=0;d=Y-32|0;Y=d;y[a+24>>2]=0;y[a+28>>2]=0;y[a+16>>2]=0;y[a+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{b:{if(b-2147483617>>>0<2147483680|(c|0)<=0){break b}e=b+31&2147483616;if(2147483616/(e>>>0)>>>0>>0){break b}y[a+24>>2]=c;y[a+20>>2]=b;b=e>>>3|0;y[a+28>>2]=b;b=ze(c,0,b,0);if(Z){break a}b=Gb(d,b);c=y[b>>2];y[d+12>>2]=c;e=y[b+4>>2];y[d+16>>2]=e;y[d+20>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;y[d+28>>2]=e-c;y[d+24>>2]=c;c=d+12|0;ec(a,c);va(c);va(b)}Y=d+32|0;return a}o()}function Fa(a,b,c){var d=0,e=0,f=0,g=0;e=-1;g=y[a>>2];a:{f=y[a+8>>2];d=y[a+4>>2];if(f>>>0>=d>>>0){break a}b:{if((d|0)==536870912|d>>>0>536870912){break b}f=y[a+12>>2]+(f<<3)|0;if(f>>>0>(((d&536870911)<<3|g>>>29)&-8)>>>0){break a}y[c>>2]=0;g=y[a>>2];d=y[a+4>>2];if((d|0)==536870912|d>>>0>536870912){break b}d=((d&536870911)<<3|g>>>29)&-8;e=d>>>0>>0?d-f|0:b;while(1){if(!e){e=0;break a}b=y[a+8>>2];if(b>>>0>=B[a+4>>2]){break b}y[c>>2]=z[b+y[a>>2]|0]>>>7-y[a+12>>2]&1|y[c>>2]<<1;Tb(a);e=e-1|0;continue}}o()}return e}function Aa(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;f=Y-16|0;Y=f;i=ka(b,y[a>>2]+2|0);d=i|2;while(1){a:{if((e|0)==5){e=5;break a}d=d<<1;g=ka(b,d+y[a>>2]|0);d=d|g;if(!g){break a}e=e+1|0;continue}break}g=0;e=e<<3;h=y[e+2092>>2];h=(h|0)>0?h:0;k=e+2092|0;e=0;while(1){if((e|0)!=(h|0)){d=d<<1;j=ka(b,d+y[a>>2]|0);d=j|d;d=(d|0)>255?d&255|256:d;g=g<<1|j;e=e+1|0;continue}break}w[f+8|0]=1;y[f+12>>2]=y[k+4>>2];e=0;d=0;if(z[La(f+8|0,g)|0]){a=y[f+12>>2];b=a>>31;e=i?b-(a^b)|0:a;d=!i|(e|0)!=0}y[c>>2]=e;Y=f+16|0;return d}function zc(a,b,c,d,e){var f=0,g=0;y[a>>2]=0;f=na(24);y[f+8>>2]=0;y[f+12>>2]=0;g=b;b=(c|0)==268435456|c>>>0<268435456;y[f>>2]=b?g:0;y[f+4>>2]=b?c:0;y[f+16>>2]=0;y[f+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a+4>>2]=f;y[a+16>>2]=0;y[a+20>>2]=0;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;c=na(64);y[a+36>>2]=c;b=c- -64|0;y[a+44>>2]=b;f=0;while(1){if((f|0)!=64){y[c+f>>2]=0;f=f+4|0;continue}break}y[a+52>>2]=10;w[a+49|0]=0;w[a+50|0]=0;w[a+48|0]=e;y[a+40>>2]=b;t(a+56|0,0,49);w[a+112|0]=0;y[a+108>>2]=d;return a}function Ob(a,b){var c=0,d=0,e=0,f=0;e=Y-16|0;Y=e;c=4;a:{b:{if(y[a+4>>2]-1>>>0>65534|y[a+8>>2]-1>>>0>=65535){break b}y[a+52>>2]=2;f=y[b>>2];d=y[f>>2];if(!d){c=ua(a+4|0,a+8|0);y[e+12>>2]=0;oa(f,c);la(e+12|0);d=y[f>>2]}c=y[d+16>>2];if(!c){oa(f,0);c=-1;break b}d=y[d+12>>2];while(1){if((c|0)>0){w[d|0]=0;c=c-1|0;d=d+1|0;continue}break}y[a+60>>2]=0;x[a+56>>1]=1;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;y[a+36>>2]=0;y[a+40>>2]=0;y[a+44>>2]=0;y[a+48>>2]=0;c=nd(a,b);break a}y[a+52>>2]=c}Y=e+16|0;return c}function _c(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;a:{c=y[a+4>>2];d=y[a+8>>2];b:{if(c>>>0>>0){e=y[b>>2];y[b>>2]=0;y[c>>2]=e;b=c+4|0;break b}f=y[a>>2];e=c-f|0;h=e>>2;c=h+1|0;if(c>>>0>=1073741824){break a}d=d-f|0;g=d>>1;g=d>>>0>=2147483644?1073741823:c>>>0>>0?g:c;c:{if(!g){c=e;d=0;break c}d=Fb(g);f=y[a>>2];c=y[a+4>>2]-f|0;h=c>>2}i=y[b>>2];y[b>>2]=0;b=d+e|0;y[b>>2]=i;e=b-(h<<2)|0;if(c){p(e,f,c)}y[a+8>>2]=(g<<2)+d;b=b+4|0;y[a+4>>2]=b;y[a>>2]=e;if(!f){break b}ma(f)}y[a+4>>2]=b;return}za();o()}function rb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Vb(c+80|0);ta(c+76|0);ta(c+72|0);Ha(c+60|0);b=y[c+36>>2];if(b){a=y[c+40>>2];while(1){if((a|0)!=(b|0)){a=pa(a-4|0);continue}break}y[c+40>>2]=b;ma(y[c+36>>2])}la(c+32|0);b=y[c+20>>2];if(b){a=y[c+24>>2];while(1){if((a|0)!=(b|0)){a=Ub(a-4|0);continue}break}y[c+24>>2]=b;ma(y[c+20>>2])}b=y[c+8>>2];if(b){a=y[c+12>>2];while(1){if((a|0)!=(b|0)){a=Vb(a-4|0);continue}break}y[c+12>>2]=b;ma(y[c+8>>2])}a=y[c+4>>2];y[c+4>>2]=0;if(a){ma(a)}ma(sb(c))}}function xd(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;d=Y-16|0;Y=d;a:{e=y[a+4>>2];b:{if(e>>>0>b>>>0){break b}c=e;if(y[a+8>>2]<<5>>>0>>0){if((b|0)<0){break a}f=(b-1>>>5|0)+1|0;g=na(f<<2);h=y[a>>2];c=y[a+4>>2];i=c?(c-1>>>3&536870908)+4|0:0;if(i){p(g,h,i)}y[a>>2]=g;y[d+4>>2]=h;y[a+4>>2]=c;y[d+8>>2]=c;c=y[a+8>>2];y[a+8>>2]=f;y[d+12>>2]=c;lc(d+4|0);c=y[a+4>>2]}if((b|0)==(e|0)){break b}$c(y[a>>2]+(c>>>3&536870908)|0,c&31,b-e|0)}y[a+4>>2]=b;Y=d+16|0;return}za();o()}function Hc(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;h=Y-16|0;Y=h;l=((b&536870911)<<3|a>>>29)&-8;j=1;while(1){a:{if(B[c>>2]>=l>>>0){break a}k=j&1;n=k?325:326;o=k?1056:1392;i=0;while(1){Yb(h+12|0,o,n,a,b,c);if(!z[h+14|0]){while(1){if(B[c>>2]>=l>>>0){break a}if(!Ia(a,b,c)){continue}break}break a}g=A[h+12>>1];i=g+i|0;if((i|0)>(f|0)){break a}if(g>>>0>=64){continue}break}g=i+m|0;if(!k){gb(d,e,f,m,g)}j=j^1;m=g;if((f|0)>(g|0)){continue}}break}Y=h+16|0}function te(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;e=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[e+16>>2];y[a+44>>2]=y[e+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){c=c-1|0;f=c>>>3|0;g=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(B[a+8>>2]<=c>>>0){b=4}else{if(!jc(a,b,2204,f,g)){return-1}Db(a,e);d=y[b+16>>2];c=y[a+24>>2];if(!d|(c>>>0)%50){break a}d=_[y[y[d>>2]+8>>2]](d)|0;c=y[a+24>>2];if(!d){break a}y[a+24>>2]=c+1;b=3}y[a+52>>2]=b;return b|0}c=c+1|0;y[a+24>>2]=c;continue}}o()}function xe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;d=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[d+16>>2];y[a+44>>2]=y[d+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){e=y[a+8>>2]&2147483647;c=c-1|0;f=c>>>3|0;g=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(c>>>0>=e>>>0){b=4}else{if(!jc(a,b,2140,f,g)){return-1}Db(a,d);c=y[b+16>>2];if(!c){break a}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break a}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}c=y[a+24>>2]+1|0;y[a+24>>2]=c;continue}}o()}function Xd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(y[b+4>>2]==5252){b=y[a+12>>2];a:{if(b){a=5240;if(tb(b,5128)){break a}}a=5248}y[c>>2]=a;return 1}e=y[a+8>>2];b:{c:{if(!(e&24)){d=tb(b,5080);if(!d){break c}c=1;if(!Pa(a,b,(y[d+8>>2]&24)!=0)){break c}break b}c=1;if(Pa(a,b,1)){break b}}c=0;b=tb(b,5180);if(!b){break b}d=y[b+8>>2];if(d&(e^-1)&7|(d^-1)&e&96|y[y[a+12>>2]+4>>2]!=y[y[b+12>>2]+4>>2]){break b}c=y[y[a+16>>2]+4>>2]==y[y[b+16>>2]+4>>2]}return c|0}function za(){var a=0,b=0,c=0,d=0;c=vb(8);y[c>>2]=5476;a=4523;b=0;a:{if(!z[4523]){break a}b:{c:{while(1){a=a+1|0;if(!(a&3)){break c}if(z[a|0]){continue}break}break b}while(1){b=a;a=a+4|0;d=y[b>>2];if(((16843008-d|d)&-2139062144)==-2139062144){continue}break}while(1){a=b;b=a+1|0;if(z[a|0]){continue}break}}b=a-4523|0}a=na(b+13|0);y[a+8>>2]=0;y[a+4>>2]=b;y[a>>2]=b;a=a+12|0;b=b+1|0;if(b){p(a,4523,b)}y[c>>2]=5524;y[c+4>>2]=a;P(c|0,5536,1);o()}function ve(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;d=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[d+16>>2];y[a+44>>2]=y[d+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){c=c-1|0;e=c>>>3|0;f=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(B[a+8>>2]<=c>>>0){b=4}else{if(!jc(a,b,2172,e,f)){return-1}Db(a,d);c=y[b+16>>2];if(!c){break a}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break a}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}c=y[a+24>>2]+1|0;y[a+24>>2]=c;continue}}o()}function Ec(a){var b=0,c=0,d=0,e=0,f=0;e=255;b=y[a+16>>2];a:{if(z[a+1|0]==255){f=8;d=y[b+8>>2];c=d+1|0;if(c>>>0>=B[b+4>>2]){break a}c=z[c+y[b>>2]|0];if(c>>>0>143){break a}Dc(b);w[a+1|0]=c;y[a+4>>2]=(y[a+4>>2]-(c<<9)|0)+65024;b=y[a+16>>2];d=y[b+8>>2];f=7;break a}Dc(b);f=8;b=y[a+16>>2];d=y[b+8>>2];c=y[b>>2];e=B[b+4>>2]>d>>>0?z[c+d|0]:e;w[a+1|0]=e;y[a+4>>2]=(y[a+4>>2]-(e<<8)|0)+65280}y[a+12>>2]=f;if(B[b+4>>2]<=d>>>0){w[a|0]=1}}function Jb(a,b){var c=0,d=0,e=0,f=0;a:{b:{if((a|0)!=(b|0)){f=y[b+4>>2];d=y[b>>2];b=f-d|0;c=y[a>>2];if(b>>>0<=y[a+8>>2]-c>>>0){e=y[a+4>>2]-c|0;if(e>>>0>>0){if(e){p(c,d,e)}b=y[a+4>>2];d=d+e|0;c=f-d|0;if(c){p(b,d,c)}break a}if(b){p(c,d,b)}break a}kc(a);e=Cc(y[a>>2],y[a+8>>2],b>>1);if((e|0)<0){break b}c=Bc(e);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=c+(e<<1);if(b){p(c,d,b)}y[a+4>>2]=b+c}return a}za();o()}y[a+4>>2]=b+c;return a}function Bb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0;f=b-1|0;g=y[d>>2];e=y[d+4>>2];a:{if((ia(a,f,g,e)|0)!=(c|0)){break a}if((ia(a,b,g,e)|0)!=(c|0)){break a}h=b+1|0;if((ia(a,h,g,e)|0)!=(c|0)){break a}g=y[d+8>>2];e=y[d+12>>2];if((ia(a,f,g,e)|0)!=(c|0)){break a}if((ia(a,h,g,e)|0)!=(c|0)){break a}e=f;f=y[d+16>>2];d=y[d+20>>2];if((ia(a,e,f,d)|0)!=(c|0)){break a}if((ia(a,b,f,d)|0)!=(c|0)){break a}i=(ia(a,h,f,d)|0)==(c|0)}return i}function Rb(a,b){var c=0,d=0,e=0,f=0,g=0;a:{c=y[a>>2];d=y[a+4>>2]-c|0;e=d>>2;if(e>>>0>>0){f=y[a+8>>2];if(f-c>>2>>>0>=b>>>0){qd(a,b-e|0);return}f=pd(c,f,b);g=Fb(f);c=d+g|0;e=c+(b-e<<2)|0;b=c;while(1){if((b|0)!=(e|0)){y[b>>2]=0;b=b+4|0;continue}break}d=c;b=y[a>>2];c=y[a+4>>2]-b|0;d=d-c|0;if(c){p(d,b,c)}y[a+4>>2]=e;y[a>>2]=d;y[a+8>>2]=(f<<2)+g;if(!b){break a}ma(b);return}if(b>>>0>=e>>>0){break a}y[a+4>>2]=c+(b<<2)}}function hd(a,b,c,d,e,f,g,h,i,j){var k=0,l=0;Ea(y[b+20>>2],d,i,j,e);i=y[a+12>>2];j=y[c>>2];b=ia(y[b+20>>2],d+2|0,g,h);y[c+4>>2]=e;y[c>>2]=b|j<<1&2;e=y[c+8>>2];a=y[a+20>>2];b=(d-i|0)+2|0;k=c,l=ia(y[a+20>>2],b,y[f>>2],y[f+4>>2])|e<<1&2,y[k+8>>2]=l;d=y[c+12>>2];k=c,l=ia(y[a+20>>2],b,y[f+8>>2],y[f+12>>2])|d<<1&6,y[k+12>>2]=l;d=y[c+16>>2];k=c,l=ia(y[a+20>>2],b,y[f+16>>2],y[f+20>>2])|d<<1&6,y[k+16>>2]=l}function mc(a){var b=0,c=0,d=0,e=0;e=Y-16|0;Y=e;b=na(32);y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;c=y[a+4>>2];d=y[a>>2];y[e+8>>2]=b;if((c|0)!=(d|0)){Eb(b,c-d|0);Xb(b,d,c)}w[e+12|0]=1;gc(e+8|0);c=y[b>>2];d=y[b+4>>2];a:{if((c|0)!=(d|0)){y[b+12>>2]=c;y[b+16>>2]=d-c;break a}c=y[a+16>>2];y[b+12>>2]=y[a+12>>2];y[b+16>>2]=c}y[b+20>>2]=y[a+20>>2];y[b+24>>2]=y[a+24>>2];y[b+28>>2]=y[a+28>>2];Y=e+16|0;return b}function Gc(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0;i=y[a+4>>2];a:{b:{c:{if((c|0)<0){h=y[a>>2];a=1;break c}g=c>>>3|0;if(g>>>0>=i>>>0){break b}h=y[a>>2];a=(z[g+h|0]<<(c&7)&128)>>>7|0}g=a^1;c=Wb(h,i,b,c+1|0,g);y[e>>2]=c;if((b|0)<=(c|0)){break a}d:{if((a|0)==(d|0)){g=a;break d}c=Wb(h,i,b,c+1|0,a);y[e>>2]=c}if((b|0)<=(c|0)){break a}j=f,k=Wb(h,i,b,c+1|0,g),y[j>>2]=k;return}o()}y[f>>2]=b;y[e>>2]=b}function Id(a,b){var c=0;c=y[b+4>>2];y[a>>2]=y[b>>2];y[a+4>>2]=c;c=y[a+8>>2];if(c){y[a+12>>2]=c;ma(c);y[a+16>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0}y[a+8>>2]=y[b+8>>2];y[a+12>>2]=y[b+12>>2];y[a+16>>2]=y[b+16>>2];y[b+16>>2]=0;y[b+8>>2]=0;y[b+12>>2]=0;$a(a+20|0,b+20|0);$a(a+32|0,b+32|0);y[a+44>>2]=y[b+44>>2];_a(a+48|0,b+48|0);_a(a+60|0,b+60|0);_a(a+72|0,b+72|0);_a(a+84|0,b+84|0)}function Wd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;a:{if(Pa(a,y[b+8>>2],e)){if(y[b+28>>2]==1|y[b+4>>2]!=(c|0)){break a}y[b+28>>2]=d;return}if(!Pa(a,y[b>>2],e)){break a}if(!(y[b+16>>2]!=(c|0)&y[b+20>>2]!=(c|0))){if((d|0)!=1){break a}y[b+32>>2]=1;return}y[b+20>>2]=c;y[b+32>>2]=d;y[b+40>>2]=y[b+40>>2]+1;if(!(y[b+36>>2]!=1|y[b+24>>2]!=2)){w[b+54|0]=1}y[b+44>>2]=4}}function ae(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=Y+-64|0;Y=d;a:{e=1;b:{if(y[a+4>>2]==y[b+4>>2]){break b}b=tb(b,5032);e=0;if(!b){break b}e=y[c>>2];if(!e){break a}t(d+8|0,0,56);w[d+59|0]=1;y[d+16>>2]=-1;y[d+12>>2]=a;y[d+4>>2]=b;y[d+52>>2]=1;_[y[y[b>>2]+28>>2]](b,d+4|0,e,1);a=y[d+28>>2];if((a|0)==1){y[c>>2]=y[d+20>>2]}e=(a|0)==1}Y=d- -64|0;return e|0}Sc();o()}function wd(a,b,c,d,e){var f=0,g=0,h=0,i=0;a:{if(!d){break a}g=c;h=b;if(e&1){if(f|g){f=32-g|0;e=d>>>0>>0?d:f;y[h>>2]=y[h>>2]|-1<>>f-e;h=h+4|0;e=d-e|0}else{e=d}f=e;i=f>>>5|0;e=i;g=h;while(1){if(e){y[g>>2]=-1;e=e-1|0;g=g+4|0;continue}break}f=f&31;if(!f){break a}e=(i<<2)+h|0;y[e>>2]=y[e>>2]|-1>>>32-f;break a}$c(h,g,d)}y[a>>2]=b;y[a+4>>2]=c;oc(a,d)}function ad(a,b,c,d){var e=0,f=0;e=Y-16|0;Y=e;dc(e+8|0,b,c);a:{b:{if(!z[e+12|0]){y[a>>2]=0;y[a+4>>2]=0;break b}if(!((d|0)>0&(y[b+24>>2]-c|0)>=(d|0))){y[a>>2]=0;y[a+4>>2]=0;break b}f=y[b+16>>2];c=y[e+8>>2];if(f>>>0>>0){break a}d=E(y[b+28>>2],d);if(d>>>0>f-c>>>0){break a}b=c+y[b+12>>2]|0;if(b&3){break a}y[a>>2]=b;y[a+4>>2]=d>>>2}Y=e+16|0;return}o()}function Rc(a,b,c,d){w[a+53|0]=1;a:{if(y[a+4>>2]!=(c|0)){break a}w[a+52|0]=1;c=y[a+16>>2];b:{if(!c){y[a+36>>2]=1;y[a+24>>2]=d;y[a+16>>2]=b;if((d|0)!=1){break a}if(y[a+48>>2]==1){break b}break a}if((b|0)==(c|0)){c=y[a+24>>2];if((c|0)==2){y[a+24>>2]=d;c=d}if(y[a+48>>2]!=1){break a}if((c|0)==1){break b}break a}y[a+36>>2]=y[a+36>>2]+1}w[a+54|0]=1}}function Fd(a,b){var c=0,d=0,e=0;d=a,e=Da(),y[d>>2]=e;d=a,e=Da(),y[d+4>>2]=e;d=a,e=Da(),y[d+8>>2]=e;d=a,e=Da(),y[d+12>>2]=e;d=a,e=Da(),y[d+16>>2]=e;d=a,e=Da(),y[d+20>>2]=e;d=a,e=Da(),y[d+24>>2]=e;d=a,e=Da(),y[d+28>>2]=e;d=a,e=Da(),y[d+32>>2]=e;c=na(16);w[c+12|0]=b;y[c+8>>2]=0;y[c>>2]=0;y[c+4>>2]=0;fb(c,1<>2]=c;return a}function ld(a,b){var c=0,d=0,e=0;c=Y-32|0;Y=c;a:{d=y[a+40>>2];if(d){e=y[a+48>>2];y[c+24>>2]=y[a+44>>2];y[c+28>>2]=e;b=y[b+28>>2];if(b>>>0>d>>>0){break a}a=y[a+36>>2];y[c+20>>2]=b;y[c+16>>2]=a;a=y[c+28>>2];y[c+8>>2]=y[c+24>>2];y[c+12>>2]=a;a=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=a;Ca(c+8|0,c)}Y=c+32|0;return}o()}function gd(a,b){var c=0,d=0,e=0;w[a+1|0]=z[b|0];c=y[b+8>>2];y[a+4>>2]=c;b=y[b+4>>2];fd(a+8|0,c);zb(a+20|0,c);zb(a+32|0,c);e=(c<<3)+b|0;c=0;while(1){if((b|0)==(e|0)){a=Ld(a)}else{y[y[a+8>>2]+(c<<3)>>2]=z[b|0];d=c<<2;y[d+y[a+20>>2]>>2]=z[b+1|0];y[y[a+32>>2]+d>>2]=y[b+4>>2];b=b+8|0;c=c+1|0;continue}break}return a}function qa(a,b){var c=0,d=0,e=0,f=0;a:{c=y[a+4>>2];d=y[a+8>>2];if(c>>>0>d+3>>>0){if(d>>>0>=c>>>0|d+1>>>0>=c>>>0|d+2>>>0>=c>>>0){break a}c=b;b=y[a>>2]+d|0;b=z[b|0]|z[b+1|0]<<8|(z[b+2|0]<<16|z[b+3|0]<<24);e=c,f=((Ae(b,16)^b)&-16711936)>>>8^Ae(b,8),y[e>>2]=f;y[a+8>>2]=y[a+8>>2]+4;a=0}else{a=-1}return a}o()}function ec(a,b){var c=0,d=0;if((a|0)!=(b|0)){kc(a);y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;c=y[a>>2];d=y[a+4>>2];a:{if((c|0)!=(d|0)){y[a+12>>2]=c;y[a+16>>2]=d-c;break a}c=y[b+16>>2];y[a+12>>2]=y[b+12>>2];y[a+16>>2]=c}y[b+12>>2]=0;y[b+16>>2]=0}}function fc(a,b,c){var d=0,e=0,f=0,g=0;d=y[a>>2];if(y[a+8>>2]-d>>2>>>0>=b>>>0){e=y[a+4>>2]-d>>2;f=b>>>0>e>>>0?e:b;g=d;while(1){if(f){y[g>>2]=y[c>>2];f=f-1|0;g=g+4|0;continue}break}if(b>>>0>e>>>0){dd(a,b-e|0,c);return}y[a+4>>2]=(b<<2)+d;return}kc(a);ed(a,pd(y[a>>2],y[a+8>>2],b));dd(a,b,c)}function Pa(a,b,c){var d=0;if(!c){return y[a+4>>2]==y[b+4>>2]}if((a|0)==(b|0)){return 1}c=y[a+4>>2];a=z[c|0];b=y[b+4>>2];d=z[b|0];a:{if(!a|(d|0)!=(a|0)){break a}while(1){d=z[b+1|0];a=z[c+1|0];if(!a){break a}b=b+1|0;c=c+1|0;if((a|0)==(d|0)){continue}break}}return(a|0)==(d|0)}function Ic(a,b){var c=0,d=0,e=0,f=0;if((a|0)!=(b|0)){f=y[b+4>>2];c=y[b>>2];b=f-c|0;d=y[a>>2];if(b>>>0<=y[a+8>>2]-d>>>0){e=y[a+4>>2]-d|0;if(b>>>0>e>>>0){if(e){p(d,c,e)}Xb(a,c+e|0,f);return}if(b){p(d,c,b)}y[a+4>>2]=b+d;return}kc(a);Eb(a,Fc(y[a>>2],y[a+8>>2],b));Xb(a,c,f)}}function Ca(a,b){var c=0,d=0,e=0;a:{c=y[a+4>>2];b:{if(!c){break b}if(!y[b+4>>2]){a=y[a>>2];while(1){if((c|0)<=0){break b}w[a|0]=0;c=c-1|0;a=a+1|0;continue}}e=y[b>>2];d=y[b+4>>2];c=y[a>>2];b=0;if(1&B[a+4>>2]>>0){break a}if(!(b|d)|!d){break b}p(c,e,d)}return}o()}function Dd(a){var b=0,c=0,d=0,e=0,f=0,g=0;d=Y-16|0;Y=d;e=nc();f=e+24|0;g=y[a+28>>2];c=y[a+24>>2];while(1){if((c|0)!=(g|0)){b=y[c>>2];if(b){b=mc(b)}else{b=0}y[d+12>>2]=b;b=d+12|0;_c(f,b);c=c+4|0;la(b);continue}break}c=Jb(e,a);Jb(c+12|0,a+12|0);Y=d+16|0;return c}function ja(a,b,c){var d=0,e=0,f=0;d=Y-16|0;Y=d;dc(d+8|0,b,c);a:{b:{if(!z[d+12|0]){y[a>>2]=0;y[a+4>>2]=0;break b}e=y[b+16>>2];c=y[d+8>>2];if(e>>>0>>0){break a}f=y[b+28>>2];if(f>>>0>e-c>>>0){break a}b=y[b+12>>2];y[a+4>>2]=f;y[a>>2]=b+c}Y=d+16|0;return}o()}function Qc(a,b,c){var d=0;d=y[a+36>>2];if(!d){y[a+24>>2]=c;y[a+16>>2]=b;y[a+36>>2]=1;y[a+20>>2]=y[a+56>>2];return}a:{if(!(y[a+20>>2]!=y[a+56>>2]|y[a+16>>2]!=(b|0))){if(y[a+24>>2]!=2){break a}y[a+24>>2]=c;return}w[a+54|0]=1;y[a+24>>2]=2;y[a+36>>2]=d+1}}function Na(a){var b=0,c=0,d=0,e=0;b=na(20);y[b+16>>2]=a;w[b|0]=0;c=255;d=y[a>>2];e=y[a+4>>2];a=y[a+8>>2];if(e>>>0>a>>>0){c=z[a+d|0]}w[b+1|0]=c;y[b+4>>2]=((c^-1)&255)<<16;Ec(b);y[b+8>>2]=32768;y[b+4>>2]=y[b+4>>2]<<7;y[b+12>>2]=y[b+12>>2]-7;return b}function $c(a,b,c){var d=0,e=0;if(b){d=32-b|0;e=c>>>0>>0?c:d;y[a>>2]=y[a>>2]&(-1<>>d-e^-1);c=c-e|0;a=a+4|0}e=c>>>5|0;b=e;d=a;while(1){if(b){y[d>>2]=0;b=b-1|0;d=d+4|0;continue}break}b=c&31;if(b){a=(e<<2)+a|0;y[a>>2]=y[a>>2]&(-1>>>32-b^-1)}}function ob(a,b){var c=0;c=1;a:{if(qa(y[a+4>>2],b)){break a}if(qa(y[a+4>>2],b+4|0)){break a}if(qa(y[a+4>>2],b+8|0)){break a}if(qa(y[a+4>>2],b+12|0)){break a}if(wa(y[a+4>>2],b+16|0)|(y[b>>2]>4096|y[b+4>>2]>4096)&z[a+112|0]==1){break a}c=0}return c}function jd(a,b,c,d){var e=0;e=Y-32|0;Y=e;ja(e+24|0,c,(b^-1)+d|0);y[a>>2]=y[e+24>>2];y[a+4>>2]=y[e+28>>2];b=d-b|0;ja(e+16|0,c,b);y[a+8>>2]=y[e+16>>2];y[a+12>>2]=y[e+20>>2];ja(e+8|0,c,b+1|0);y[a+16>>2]=y[e+8>>2];y[a+20>>2]=y[e+12>>2];Y=e+32|0}function Cd(a,b,c){var d=0,e=0;d=na(32);e=y[b+12>>2];y[d+16>>2]=y[b+8>>2];y[d+20>>2]=e;e=y[b+4>>2];y[d+8>>2]=y[b>>2];y[d+12>>2]=e;b=y[c>>2];y[c>>2]=0;y[d>>2]=a;y[d+24>>2]=b;b=y[a+4>>2];y[d+4>>2]=b;y[b>>2]=d;y[a+4>>2]=d;y[a+8>>2]=y[a+8>>2]+1}function Ga(a,b,c){var d=0,e=0,f=0,g=0;d=y[b+8>>2];g=(d|0)>0?d:0;d=0;while(1){a:{if((d|0)==(g|0)){e=0;break a}e=Ka(a,y[y[b+12>>2]+(d<<2)>>2]);if(!(!e|(z[e+4|0]&63)!=53)){if((c|0)==(f|0)){break a}f=f+1|0}d=d+1|0;continue}break}return e}function Kb(a,b){var c=0;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{if(b){if(b>>>0>=1073741824){break a}c=b<<2;b=na(c);y[a>>2]=b;c=b+c|0;y[a+8>>2]=c;while(1){if((b|0)!=(c|0)){y[b>>2]=0;b=b+4|0;continue}break}y[a+4>>2]=c}return a}za();o()}function cb(a,b){var c=0;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{if(b){if(b>>>0>=1073741824){break a}c=Fb(b);y[a>>2]=c;b=(b<<2)+c|0;y[a+8>>2]=b;while(1){if((b|0)!=(c|0)){y[c>>2]=0;c=c+4|0;continue}break}y[a+4>>2]=b}return a}za();o()}function Cb(a,b,c){var d=0;d=Y-32|0;Y=d;ja(d+24|0,b,c-1|0);y[a>>2]=y[d+24>>2];y[a+4>>2]=y[d+28>>2];ja(d+16|0,b,c);y[a+8>>2]=y[d+16>>2];y[a+12>>2]=y[d+20>>2];ja(d+8|0,b,c+1|0);y[a+16>>2]=y[d+8>>2];y[a+20>>2]=y[d+12>>2];Y=d+32|0}function Mc(a,b){var c=0,d=0,e=0;c=Y-16|0;Y=c;d=z[a+4|0];if((d|0)==255){a=vb(4);y[a>>2]=4936;P(a|0,4948,2);o()}y[c+4>>2]=c+3;e=y[b+4>>2];y[c+8>>2]=y[b>>2];y[c+12>>2]=e;a=_[y[(c+8|0)+(d<<2)>>2]](c+4|0,a)|0;Y=c+16|0;return a}function lb(){var a=0;a=na(80);x[a+56>>1]=0;y[a+12>>2]=0;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;y[a+36>>2]=0;y[a+40>>2]=0;y[a+44>>2]=0;y[a+48>>2]=0;y[a+60>>2]=0;y[a+64>>2]=0;y[a+68>>2]=0;y[a+72>>2]=0;y[a+76>>2]=0;return a}function ze(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0;e=c>>>16|0;f=a>>>16|0;j=E(e,f);g=c&65535;h=a&65535;i=E(g,h);f=(i>>>16|0)+E(f,g)|0;e=(f&65535)+E(e,h)|0;Z=(E(b,c)+j|0)+E(a,d)+(f>>>16)+(e>>>16)|0;return i&65535|e<<16}function Ea(a,b,c,d,e){var f=0;a:{if(!(!d|(b|0)<0|(a|0)<=(b|0))){f=b&7;a=b>>>3|0;if(e){if(a>>>0>=d>>>0){break a}a=a+c|0;w[a|0]=z[a|0]|128>>>f;return}if(a>>>0>=d>>>0){break a}a=a+c|0;w[a|0]=z[a|0]&-129>>f}return}o()}function od(a){var b=0,c=0,d=0;y[a+56>>2]=2;c=1;b=4;d=y[a+52>>2];a:{b:{if((d|0)==5){break b}if((d|0)>=3){y[a+52>>2]=5;break b}b=yc(a);c=!b;if(y[a+56>>2]==3){break a}y[a+52>>2]=5;b=b?-1:4}y[a+56>>2]=b}return c}function Ka(a,b){var c=0,d=0;c=y[a>>2];a:{if(c){c=Ka(c,b);if(c){break a}}d=y[a+12>>2];a=y[a+8>>2];while(1){if((a|0)==(d|0)){return 0}c=y[a>>2];a=a+4|0;if(y[c>>2]!=(b|0)){continue}break}}return c}function Bd(a){var b=0;a:{if(z[a+16|0]!=3|z[a+17|0]!=255|(z[a+18|0]!=253|z[a+19|0]!=255)){break a}if(z[a+20|0]!=2|z[a+21|0]!=254|(z[a+22|0]!=254|z[a+23|0]!=254)){break a}b=z[a+2|0]^1}return b&1}function Sa(a,b){var c=0,d=0,e=0;a:{c=y[a+8>>2];e=c+1|0;d=y[a+4>>2];if(e>>>0>>0){if(c>>>0>=d>>>0){break a}d=b;b=y[a>>2];x[d>>1]=z[b+c|0]<<8|z[b+e|0];y[a+8>>2]=c+2;a=0}else{a=-1}return a}o()}function id(a,b,c,d,e,f,g,h){return y[c+16>>2]|y[c+12>>2]<<3|y[c+8>>2]<<6|ia(y[y[a+20>>2]+20>>2],w[a+26|0]+(d-y[a+12>>2]|0)|0,e,f)<<8|y[c+4>>2]<<9|y[c>>2]<<10|ia(b,w[a+24|0]+d|0,g,h)<<12} +function Db(a,b){var c=0;c=y[a+40>>2];y[a+28>>2]=y[a+36>>2];y[a+32>>2]=c;c=y[a+48>>2];b=y[b+28>>2];if(c>>>0>>0){o()}y[a+40>>2]=c;y[a+48>>2]=c-b;c=y[a+44>>2];y[a+36>>2]=c;y[a+44>>2]=b+c}function ra(a,b){var c=0,d=0,e=0;d=Y-16|0;Y=d;e=b<<2;c=y[e+y[a+36>>2]>>2];if(!c){b=rd(na(96),b);c=y[a+36>>2];y[d+12>>2]=0;pb(c+e|0,b);pa(d+12|0);c=y[y[a+36>>2]+e>>2]}Y=d+16|0;return c}function $a(a,b){var c=0;c=y[a>>2];if(c){y[a+4>>2]=c;ma(c);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0}y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0}function Qb(){var a=0;a=na(180);y[a+144>>2]=0;y[a+148>>2]=0;x[a+28>>1]=0;y[a+152>>2]=0;y[a+156>>2]=0;y[a+160>>2]=0;y[a+164>>2]=0;y[a+168>>2]=0;y[a+172>>2]=0;t(a+32|0,0,104);return a}function Jc(a,b,c){var d=0,e=0;e=((b&536870911)<<3|a>>>29)&-8;d=y[c>>2];a:{while(1){if(B[c>>2]>=e>>>0){break a}if(!Ia(a,b,c)){continue}break}if(y[c>>2]-d>>>0>11){break a}y[c>>2]=d}}function xb(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];b=ze(c,c>>31,b,0);c=Z;d=!(b- -2147483648>>>0<2147483648?c+1|0:c);c=0|d;b=d?b:0}else{b=0}y[a>>2]=c;y[a+4>>2]=b;return a}function oc(a,b){var c=0;c=y[a+4>>2];a:{if((b|0)>=0){b=b+c|0;c=y[a>>2]+(b>>>3&536870908)|0;break a}b=b+c|0;c=y[a>>2]+((b-31|0)/32<<2)|0}y[a>>2]=c;y[a+4>>2]=b&31;return a}function Lc(a,b){var c=0,d=0,e=0;c=Y-16|0;Y=c;d=z[a+4|0];if((d|0)!=255){e=y[b+4>>2];y[c+8>>2]=y[b>>2];y[c+12>>2]=e;_[y[(c+8|0)+(d<<2)>>2]](c+7|0,a)}w[a+4|0]=255;Y=c+16|0}function Ab(a,b,c,d,e,f,g){var h=0,i=0;h=Y-16|0;Y=h;if(y[a+16>>2]){i=y[a+24>>2];y[h+8>>2]=y[a+20>>2];y[h+12>>2]=i;y[h>>2]=0;y[h+4>>2]=0;cd(a,b,c,d,e,f,g,h)}Y=h+16|0}function Xc(a,b){var c=0;c=Y-16|0;Y=c;y[c+8>>2]=0;a:{if(z[a+4|0]==1){oa(a,b);break a}Xa(a);w[a+4|0]=1;y[a>>2]=b}y[c+12>>2]=0;la(c+12|0);la(c+8|0);Y=c+16|0;return a}function Ac(a,b,c){var d=0,e=0,f=0;d=1;while(1){e=z[a+12|0];if(e>>>0<=(f&255)>>>0){y[c>>2]=(-1<>2]|0)|d;f=f+1|0;continue}break}}function wb(a,b,c,d){var e=0;e=Y-16|0;Y=e;y[e+12>>2]=d;d=1;w[e+8|0]=1;a:{if(!z[La(e+8|0,b>>c)|0]){d=0;w[a|0]=0;break a}y[a>>2]=y[e+12>>2]}w[a+4|0]=d;Y=e+16|0}function rc(a){var b=0;b=y[a+36>>2];y[a+36>>2]=0;if(b){ma(Ha(b))}Ba(a+32|0);Ba(a+28|0);Ba(a+24|0);Ba(a+20|0);Ba(a+16|0);Ba(a+12|0);Ba(a+8|0);Ba(a+4|0);Ba(a)}function Lb(a,b){var c=0;c=y[a+4>>2];if(b){c=c+1|0;y[a+4>>2]=c}b=y[a+12>>2]-y[a+8>>2]>>3;if(b>>>0<=c>>>0){b=b+16|0;fd(a+8|0,b);zb(a+20|0,b);zb(a+32|0,b)}}function yb(a,b,c){a:{switch(a|0){case 1:return b&c;case 2:return b^c;case 3:return b^c^-1;default:o();case 0:b=b|c;break;case 4:break a}}return b}function $d(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if(Pa(a,y[b+8>>2],f)){Rc(b,c,d,e);return}a=y[a+8>>2];_[y[y[a>>2]+20>>2]](a,b,c,d,e,f)}function Gb(a,b){var c=0;c=Y-16|0;Y=c;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;y[c+8>>2]=a;if(b){Eb(a,b);kd(a,b)}w[c+12|0]=1;gc(c+8|0);Y=c+16|0;return a}function xc(a){var b=0,c=0,d=0;c=E(z[a+1|0],y[a+4>>2]+1|0);if(c>>>0<=65535){d=z[a+2|0];b=lb();a=z[a|0];y[b+8>>2]=d;y[b+4>>2]=c;w[b|0]=a}return b}function Zc(a,b,c){var d=0;if(!(b&1)|z[a|0]!=1){b=0;c=0}else{b=y[a+4>>2];d=b;b=b+c|0;c=((d^b)&(b^c))>=0;b=c?b:0}y[a>>2]=c;y[a+4>>2]=b;return a}function me(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=y[a+4>>2];a=y[a+8>>2];b=(a>>1)+b|0;c=y[c>>2];if(a&1){d=y[d+y[b>>2]>>2]}return _[d|0](b,c)|0}function Zd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(y[a+4>>2]==y[y[b+8>>2]+4>>2]){Qc(b,c,d);return}a=y[a+8>>2];_[y[y[a>>2]+28>>2]](a,b,c,d)}function zd(a,b,c,d){if(b>>>0<=d>>>0){b=(b<<2)+a|0;while(1){if((a|0)!=(b|0)){y[c>>2]=y[a>>2];c=c+4|0;a=a+4|0;continue}break}return}o()}function ia(a,b,c,d){a:{if(!d|(b|0)<0|(a|0)<=(b|0)){a=0}else{a=b>>>3|0;if(a>>>0>=d>>>0){break a}a=z[a+c|0]>>>((b^-1)&7)&1}return a}o()}function dd(a,b,c){var d=0;d=y[a+4>>2];b=d+(b<<2)|0;while(1){if((b|0)==(d|0)){y[a+4>>2]=b}else{y[d>>2]=y[c>>2];d=d+4|0;continue}break}}function Oa(a){var b=0;va(a+84|0);va(a+72|0);va(a+60|0);va(a+48|0);Ha(a+32|0);Ha(a+20|0);b=y[a+8>>2];if(b){y[a+12>>2]=b;ma(b)}return a}function bd(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];d=c-b|0;c=((c^d)&(b^c))>=0;b=c?d:0}else{b=0;c=0}y[a>>2]=c;y[a+4>>2]=b;return a}function La(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];d=c+b|0;c=((d^c)&(b^d))>=0;b=c?d:0}else{b=0;c=0}y[a>>2]=c;y[a+4>>2]=b;return a}function dc(a,b,c){var d=0;a:{if(!(!(!y[b+16>>2]|(c|0)<0)&y[b+24>>2]>(c|0))){w[a|0]=0;break a}y[a>>2]=E(y[b+28>>2],c);d=1}w[a+4|0]=d}function Za(a){var b=0,c=0;b=y[a>>2];if(b){c=y[a+4>>2];while(1){if((c|0)!=(b|0)){c=la(c-4|0);continue}break}y[a+4>>2]=b;ma(y[a>>2])}}function wa(a,b){var c=0,d=0;d=y[a>>2];c=y[a+8>>2];if(c>>>0>2]){w[b|0]=z[d+c|0];y[a+8>>2]=y[a+8>>2]+1;a=0}else{a=-1}return a}function ye(){var a=0,b=0,c=0;while(1){b=a<<4;c=b+5728|0;y[b+5732>>2]=c;y[b+5736>>2]=c;a=a+1|0;if((a|0)!=64){continue}break}Md(48)}function Tc(a){a=a|0;var b=0,c=0,d=0;y[a>>2]=5476;b=y[a+4>>2];c=b-4|0;d=y[c>>2]-1|0;y[c>>2]=d;if((d|0)<0){ma(b-12|0)}return a|0}function qd(a,b){var c=0;c=y[a+4>>2];b=c+(b<<2)|0;while(1){if((b|0)==(c|0)){y[a+4>>2]=b}else{y[c>>2]=0;c=c+4|0;continue}break}}function kd(a,b){var c=0;c=b;b=y[a+4>>2];c=c+b|0;while(1){if((b|0)==(c|0)){y[a+4>>2]=c}else{w[b|0]=0;b=b+1|0;continue}break}}function Xb(a,b,c){var d=0;d=y[a+4>>2];while(1){if((b|0)!=(c|0)){w[d|0]=z[b|0];d=d+1|0;b=b+1|0;continue}break}y[a+4>>2]=d}function Ia(a,b,c){var d=0;d=c;c=y[c>>2];y[d>>2]=c+1;d=b;b=c>>>3|0;if(d>>>0<=b>>>0){o()}return(z[a+b|0]<<(c&7)&128)>>>7|0}function vd(a,b){var c=0;c=ze(b,0,a,0);if(Z|c>>>0>2147479550){a=0}else{b=E(a,b);a=hb(b);if(b?a:0){t(a,0,b)}}return a}function pd(a,b,c){if(c>>>0>=1073741824){za();o()}a=b-a|0;b=a>>1;return a>>>0>=2147483644?1073741823:b>>>0>c>>>0?b:c}function sa(a){var b=0,c=0;if(y[a+12>>2]){b=y[a+8>>2]+1|0;if(b){c=y[a+4>>2];y[a+8>>2]=b>>>0>>0?b:c}y[a+12>>2]=0}}function eb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){pa(c+68|0);la(c- -64|0);Sb(c+60|0);Va(c+56|0);va(c+12|0);ma(c)}}function md(a){var b=0;a:{b=a;a=y[a+16>>2];if((b|0)==(a|0)){b=16}else{if(!a){break a}b=20}_[y[b+y[a>>2]>>2]](a)}}function _a(a,b){kc(a);y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0}function Nb(a,b){var c=0;c=y[a+52>>2];if((c|0)==3){if(A[a+56>>1]!=1){y[a+52>>2]=-1;return-1}c=nd(a,b)}return c}function pe(a){a=a|0;var b=0,c=0;b=na(12);y[b>>2]=2244;c=y[a+8>>2];y[b+4>>2]=y[a+4>>2];y[b+8>>2]=c;return b|0}function cc(a,b,c,d){var e=0;e=Y-16|0;Y=e;ad(e+8|0,b,c,d);b=y[e+12>>2];y[a>>2]=y[e+8>>2];y[a+4>>2]=b;Y=e+16|0}function Tb(a){var b=0;b=y[a+12>>2];a:{if((b|0)==7){y[a+8>>2]=y[a+8>>2]+1;b=0;break a}b=b+1|0}y[a+12>>2]=b}function Fc(a,b,c){if((c|0)<0){za();o()}a=b-a|0;b=a<<1;return a>>>0>=1073741823?2147483647:b>>>0>c>>>0?b:c}function ed(a,b){var c=0;if(b>>>0>=1073741824){za();o()}c=Fb(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=(b<<2)+c}function Ya(a){var b=0,c=0;b=y[a+4>>2];if(!b){o()}c=y[a>>2];y[a>>2]=c+4;y[a+4>>2]=b-1;return y[c>>2]}function Yc(a,b){a:{b:{if(!z[a+4|0]){if(y[a>>2]!=(b|0)){break b}break a}Xa(a);w[a+4|0]=0}y[a>>2]=b}}function Cc(a,b,c){if((c|0)<0){za();o()}a=b-a|0;return a>>>0>=2147483646?2147483647:a>>>0>c>>>0?a:c}function rd(a,b){var c=0,d=0;x[a>>1]=0;t(a+4|0,0,92);c=a,d=gd(a,E(b,12)+2708|0),w[c|0]=d;return a}function Vd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if(Pa(a,y[b+8>>2],f)){Rc(b,c,d,e)}}function ib(a){var b=0,c=0;while(1){b=c;c=b+1|0;if(1<>>0>>0){continue}break}return b&255}function bc(a){var b=0,c=0;b=y[a+4>>2];if(!b){o()}c=y[a>>2];y[a>>2]=c+4;y[a+4>>2]=b-1;return c}function oe(a,b){a=a|0;b=b|0;var c=0;y[b>>2]=2244;c=y[a+8>>2];y[b+4>>2]=y[a+4>>2];y[b+8>>2]=c}function ud(a,b){var c=0;c=Y-16|0;Y=c;y[c+12>>2]=b;b=a+24|0;a=c+12|0;_c(b,a);la(a);Y=c+16|0}function kc(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0}}function Dc(a){var b=0,c=0;b=y[a+8>>2]+1|0;if(b){c=a;a=y[a+4>>2];y[c+8>>2]=a>>>0>b>>>0?b:a}}function Kd(a,b){var c=0;if((b|0)<0){za();o()}c=na(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=b+c}function Eb(a,b){var c=0;if((b|0)<0){za();o()}c=Ad(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=b+c}function Ae(a,b){var c=0,d=0;c=b&31;d=(-1<>>c|0;c=a;a=0-b&31;return d|(c&-1>>>a)<>2]==y[y[b+8>>2]+4>>2]){Qc(b,c,d)}}function Qd(a){a=a|0;var b=0;b=y[a+64>>2];a=y[a+52>>2]+7>>>3|0;return(a>>>0>b>>>0?b:a)|0}function Mb(a,b,c){c=0-c|0;while(1){if((b|0)>0){w[a|0]=c;b=b-1|0;a=a+1|0;continue}break}}function Kc(a,b){b=b-a|0;while(1){if((b|0)>0){w[a|0]=255;b=b-1|0;a=a+1|0;continue}break}}function Ib(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Za(c+24|0);Ha(c+12|0);ma(Ha(c))}}function Da(){var a=0;a=na(12);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;fb(a,512);return a}function uc(a,b,c,d,e,f,g,h){if(!(!y[a+16>>2]|!y[f+16>>2])){cd(f,a,b,c,d,e,h,g)}}function Ad(a){a:{if(a>>>0<=2147479550){a=hb(a);if(a){break a}}Jd();o()}return a}function Oc(a,b){var c=0;return(z[a+16|0]!=(b|0)|z[a+17|0]!=255?c:z[a+2|0]^1)&1}function Nc(a){a=a|0;y[a+40>>2]=0;y[a+44>>2]=0;va(a+80|0);va(a+68|0);return a|0}function qc(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){va(b+124|0);Oa(b+28|0);ma(b)}}function mb(a,b){var c=0;c=vd(b,2);if(!c){Jd();o()}y[a+4>>2]=b;y[a>>2]=c}function Pd(a){a=a|0;Kc(y[a+80>>2],y[a+84>>2]);y[a+52>>2]=0;return 1}function Pb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Za(c+4|0);ma(c)}}function Ha(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b)}return a}function oa(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){va(c);ma(c)}}function Ub(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(b)}return a}function wc(a){var b=0;b=na(16);y[b>>2]=a;cb(b+4|0,a);return b}function le(a,b){a=a|0;b=b|0;return(y[b+4>>2]==2532?a+4|0:0)|0}function pb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){ma(Oa(c))}}function Fb(a){if(a>>>0>=1073741824){Wa();o()}return na(a<<2)}function bb(a,b,c,d,e,f,g){if(y[a+16>>2]){Ab(f,a,b,c,d,e,g)}}function Wa(){var a=0;a=vb(4);y[a>>2]=5372;P(a|0,5428,2);o()}function Ba(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(Ha(b))}}function na(a){a=hb(a>>>0<=1?1:a);if(!a){Sc();o()}return a}function va(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b)}}function Ra(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){ma(c)}}function Ua(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(b)}}function kb(){var a=0;a=na(28);y[a+20>>2]=0;return a}function Be(a){if(a){return 31-H(a-1^a)|0}return 32} +function Bc(a){if((a|0)<0){Wa();o()}return na(a<<1)}function ua(a,b){return sd(na(32),y[a>>2],y[b>>2])}function Hd(a,b){var c=0;c=y[a>>2];y[a>>2]=b;ma(c)}function td(a){var b=0;b=na(4);y[b>>2]=a;return b}function nc(){var a=0;a=na(36);t(a,0,36);return a}function be(a,b,c){a=a|0;b=b|0;c=c|0;return 0}function Jd(){y[(Y-16|0)+12>>2]=5684;sc();o()}function Ma(a,b,c,d){return(yb(a,b,c)^c)&d^c}function ub(a,b){a=a|0;b=b|0;return y[b>>2]}function ne(a){a=a|0;_[y[y[a>>2]+4>>2]](a)}function gc(a){if(!z[a+4|0]){va(y[a>>2])}}function ee(a){a=a|0;T();S(a+128|0);o()}function hc(a,b){return sd(na(32),a,b)}function lc(a){a=y[a>>2];if(a){ma(a)}}function Td(a){a=a|0;return y[a+4>>2]}function vb(a){return hb(a|80)+80|0}function Nd(a){a=a|0;return hb(a)|0}function xa(a){return rd(na(96),a)}function je(a,b){a=a|0;b=b|0;la(b)}function ge(a,b){a=a|0;b=b|0;Ja(b)}function ac(a){return(Ta(a)|0)!=0}function ke(a){a=a|0;return 2512}function de(a){a=a|0;return 4530}function ce(a){a=a|0;return 4504}function Ud(a){a=a|0;return 4553}function Ta(a){return Mc(a,4584)}function $b(a){return Mc(a,4600)}function sb(a){rb(a,0);return a}function qb(a){a=a|0;return a|0}function pc(a){Va(a+24|0);ma(a)}function pa(a){pb(a,0);return a}function la(a){oa(a,0);return a}function Vb(a){eb(a,0);return a}function Rd(a){a=a|0;ma(Nc(a))}function Pc(a){a=a|0;ma(Tc(a))}function yd(a){return Oc(a,3)}function fe(a){a=a|0;sc();o()}function Hb(a){return Oc(a,2)}function Vc(a,b){a=a|0;b=b|0}function Qa(a){a=a|0;ma(a)}function Xa(a){Lc(a,4576)}function Uc(a){Lc(a,4592)}function ta(a){Ra(a,0)}function Va(a){Ib(a,0)}function Sc(){sc();o()}function Sb(a){Pb(a,0)}function Ja(a){Hd(a,0)}function sc(){U();o()}function ic(a){a=a|0} +// EMSCRIPTEN_END_FUNCS +a=z;m(n);var _=[null,Tc,qb,xe,we,ve,ue,te,se,re,qe,qb,Nc,Rd,Qd,Pd,Od,qb,Qa,pe,oe,ic,ne,me,le,ke,Vc,je,ub,ub,Vc,ge,ub,ub,ee,fe,Qa,de,Qa,ce,qb,Qa,ic,ic,be,Qa,Xd,Qa,Ud,Pc,Td,Pc,Qa,ae,Vd,Wd,Yd,Qa,$d,_d,Zd];function $(){return v.byteLength>>16}function ea(fa){fa=fa|0;var aa=$()|0;var ba=aa+fa|0;if(aa{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=instantiateSync(wasmBinaryFile,info);return receiveInstance(result[0])}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};function _createImageData(size){Module.imageData=new Uint8Array(size)}var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};function _setImageData(array_ptr,pitch8,pitch32,height){if(pitch32===pitch8){Module.imageData=new Uint8ClampedArray(HEAPU8.subarray(array_ptr,array_ptr+pitch32*height));return}const destSize=pitch8*height;const imageData=Module.imageData=new Uint8ClampedArray(destSize);for(let srcStart=array_ptr,destStart=0;destStart{HEAP8.set(array,buffer)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["writeArrayToMemory"]=writeArrayToMemory;var _malloc,_free,_jbig2_decode,_ccitt_decode,__emscripten_timeout,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_malloc=Module["_malloc"]=wasmExports["l"];_free=Module["_free"]=wasmExports["m"];_jbig2_decode=Module["_jbig2_decode"]=wasmExports["n"];_ccitt_decode=Module["_ccitt_decode"]=wasmExports["o"];__emscripten_timeout=wasmExports["p"];memory=wasmMemory=wasmExports["j"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:___cxa_throw,f:__abort_js,e:__emscripten_runtime_keepalive_clear,b:__setitimer_js,h:_createImageData,c:_emscripten_resize_heap,d:_proc_exit,i:_setImageData,g:_setLineData};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=createWasm();await run(); +;return Module}export default JBig2; diff --git a/lib/pdfjs/openjpeg_nowasm_fallback.js b/lib/pdfjs/openjpeg_nowasm_fallback.js new file mode 100644 index 000000000..3bc22534f --- /dev/null +++ b/lib/pdfjs/openjpeg_nowasm_fallback.js @@ -0,0 +1,17 @@ +/* THIS FILE IS GENERATED - DO NOT EDIT */ +async function OpenJPEG(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";var readAsync;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=( +// EMSCRIPTEN_START_ASM +function instantiate(Aa){var a;var b=new Uint8Array(123);for(var c=25;c>=0;--c){b[48+c]=52+c;b[65+c]=c;b[97+c]=26+c}b[43]=62;b[47]=63;function i(j,k,l){var d,e,c=0,f=k,g=l.length,h=k+(g*3>>2)-(l[g-2]=="=")-(l[g-1]=="=");for(;c>4;if(f>2;if(f>>0;A=A>>>0;if(z+A>a.length)throw"trap: invalid memory.fill";a.fill(v,z,z+A)}function B(z,C,A){a.copyWithin(z,C,C+A)}function D(){throw new Error("abort")}function za(n){var E=new ArrayBuffer(16777216);var F=new Int8Array(E);var G=new Int16Array(E);var H=new Int32Array(E);var I=new Uint8Array(E);var J=new Uint16Array(E);var K=new Uint32Array(E);var L=new Float32Array(E);var M=new Float64Array(E);var N=Math.imul;var O=Math.fround;var P=Math.abs;var Q=Math.clz32;var R=Math.min;var S=Math.max;var T=Math.floor;var U=Math.ceil;var V=Math.trunc;var W=Math.sqrt;var X=n.a;var Y=X.a;var Z=X.b;var _=X.c;var $=X.d;var aa=X.e;var ba=X.f;var ca=X.g;var da=X.h;var ea=X.i;var fa=X.j;var ga=X.k;var ha=X.l;var ia=X.m;var ja=X.n;var ka=X.o;var la=X.p;var ma=X.q;var na=94272;var oa=0;var pa=0;var qa=0; +// EMSCRIPTEN_START_FUNCS +function Sc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,M=0,P=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=O(0),ja=0,ka=0,la=0,ma=0,oa=0,pa=0,qa=0,sa=0,ta=0,ua=0,va=0,wa=0;aa=na-96|0;na=aa;D=H[a+8>>2];a:{b:{c:{if(!H[a>>2]){g=N(H[D+16>>2]-H[D+8>>2]|0,H[D+20>>2]-H[D+12>>2]|0)<<2;c=Ia(g);H[D+60>>2]=c;if(!c){Ba(H[a+32>>2],1,8023,0);d=a+28|0;break b}if(!g){break c}y(c,0,g);break c}c=H[D+60>>2];if(!c){break c}Ca(c);H[D+60>>2]=0}if(!H[H[a+28>>2]>>2]){break a}ma=H[a+16>>2];c=H[ma+28>>2]+N(H[ma+24>>2],152)|0;va=H[c-152>>2];wa=H[c-144>>2];oa=H[a+20>>2];pa=H[a+12>>2];ua=H[a+4>>2];d=a+28|0;d:{p=H[b+4>>2];e=0;e:{if((p|0)<=0){break e}l=H[b>>2];c=0;f:{while(1){g=l+N(c,12)|0;if(!H[g>>2]){break f}c=c+1|0;if((p|0)!=(c|0)){continue}break}e=0;break e}e=H[g+4>>2]}if(e){break d}e=Ea(1,156);if(!e){Ba(H[a+32>>2],1,6313,0);break b}H[e+140>>2]=0;c=0;l=H[b+4>>2];g:{if((l|0)==2147483647){break g}g=H[b>>2];if((l|0)>0){while(1){p=g+N(c,12)|0;if(!H[p>>2]){l=H[p+8>>2];if(l){ra[l|0](H[p+4>>2]);g=H[b>>2]}b=g+N(c,12)|0;H[b+8>>2]=15;H[b+4>>2]=e;c=1;break g}c=c+1|0;if((l|0)!=(c|0)){continue}break}}g=Ga(g,N(l,12)+12|0);c=0;if(!g){break g}H[b>>2]=g;c=H[b+4>>2];g=g+N(c,12)|0;H[g+8>>2]=15;H[g+4>>2]=e;H[g>>2]=0;H[b+4>>2]=c+1;c=1}if(c){break d}Ba(H[a+32>>2],1,8338,0);b=H[e+116>>2];if(b){Ca(b);H[e+116>>2]=0}b=H[e+120>>2];if(b){Ca(b);H[e+120>>2]=0}Ca(H[e+148>>2]);Ca(e);break b}H[e+144>>2]=H[a+24>>2];$=H[a+40>>2];ca=H[a+36>>2];W=H[a+32>>2];t=H[oa+808>>2];b=H[pa+16>>2];h:{ba=H[oa+16>>2];i:{if(ba&64){l=na-304|0;na=l;j:{if(t){if(ca){Ba(W,1,3219,0);break j}Ba(W,1,3219,0);break j}i=H[e+116>>2];c=H[D+20>>2]-H[D+12>>2]|0;b=H[D+16>>2]-H[D+8>>2]|0;g=N(c,b);k:{l:{if(g>>>0>K[e+132>>2]){Ca(i);f=g<<2;i=Ia(f);H[e+116>>2]=i;if(!i){i=0;break j}H[e+132>>2]=g;break l}if(!i){break k}f=g<<2}if(!f){break k}y(i,0,f)}i=H[e+120>>2];m:{if(K[e+136>>2]>2639){break m}Ca(i);i=Ia(10560);H[e+120>>2]=i;if(i){break m}i=0;break j}H[e+136>>2]=2640;y(i,0,10560);H[e+128>>2]=c;H[e+124>>2]=b;m=H[D+24>>2];if(!m){i=1;break j}p=H[D+28>>2];i=1;n:{o:{p:{q:{f=H[D+52>>2];r:{if(f){g=f&3;c=H[D+4>>2];i=0;s:{if(f>>>0>=4){b=f&-4;while(1){k=c+(i<<3)|0;j=H[k+28>>2]+(H[k+20>>2]+(H[k+12>>2]+(H[k+4>>2]+j|0)|0)|0)|0;i=i+4|0;r=r+4|0;if((b|0)!=(r|0)){continue}break}if(!g){break s}}while(1){j=H[(c+(i<<3)|0)+4>>2]+j|0;i=i+1|0;o=o+1|0;if((g|0)!=(o|0)){continue}break}}if(!H[e+144>>2]&(f|0)==1){break o}if(K[e+152>>2]>=j>>>0){break r}r=Ga(H[e+148>>2],j);if(r){break q}i=0;break j}if(!H[e+144>>2]){break j}}r=H[e+148>>2];if(r){break p}i=0;break j}H[e+152>>2]=j;H[e+148>>2]=r}if(!H[D+52>>2]){j=0;break n}f=H[D+4>>2];j=0;i=0;while(1){g=i<<3;c=g+f|0;b=H[c+4>>2];if(b){B(j+r|0,H[c>>2],b)}f=H[D+4>>2];j=H[(g+f|0)+4>>2]+j|0;i=i+1|0;if(i>>>0>2]){continue}break}break n}r=H[H[D+4>>2]>>2]}i=0;f=0;c=H[D+40>>2];g=0;t:{if(!c){break t}b=H[D>>2];f=H[b+8>>2];g=0;if((c|0)==1){break t}g=H[b+32>>2]}c=m-p|0;f=f+g|0;u:{if(!f){o=0;break u}i=1;b=H[D>>2];q=H[b>>2];o=0;if((f|0)==1){i=0;break u}o=H[b+24>>2]}Z=c+1|0;ja=H[e+116>>2];$=H[e+120>>2];S=H[D+12>>2];z=H[D+20>>2];ea=H[D+8>>2];A=H[D+16>>2];v:{w:{x:{y:{z:{A:{B:{C:{if(!(!i|o)){if(!ca){break C}Ba(W,2,10806,0);f=1;break B}if(f>>>0<4){break B}if(ca){H[l+112>>2]=f;Ba(W,1,9590,l+112|0);break v}H[l+96>>2]=f;Ba(W,1,9590,l+96|0);i=0;break j}Ba(W,2,10806,0);i=H[D+24>>2];if(i>>>0>30){break A}n=1;if(i>>>0>=Z>>>0){break y}break w}i=H[D+24>>2];if(i>>>0<=30){break z}if(!ca){break A}H[l+32>>2]=H[D+24>>2];Ba(W,1,12302,l+32|0);break v}H[l>>2]=i;Ba(W,1,12302,l);i=0;break j}if(i>>>0>>0){break x}if(f>>>0<2){n=f;break y}if((i|0)!=(Z|0)){n=f;break y}n=1;if(I[26416]){break y}if(!ca){F[26416]=1;H[l+64>>2]=f;Ba(W,2,10299,l- -64|0);break y}if(!I[26416]){F[26416]=1;H[l+80>>2]=f;Ba(W,2,10299,l+80|0)}}if(!(!(q>>>0<2|j>>>0>>0)&o+q>>>0<=j>>>0)){if(ca){i=0;Ba(W,1,9532,0);break j}i=0;Ba(W,1,9532,0);break j}V=q+r|0;b=I[V-1|0];k=b<<4|I[V-2|0]&15;if(!(!(k>>>0<2|(b|0)==255)&(k|0)<=(q|0))){if(ca){i=0;Ba(W,1,15305,0);break j}i=0;Ba(W,1,15305,0);break j}qa=H[D+28>>2];H[l+272>>2]=0;H[l+280>>2]=0;H[l+264>>2]=0;H[l+268>>2]=0;H[l+296>>2]=0;H[l+300>>2]=0;H[l+284>>2]=0;H[l+288>>2]=0;b=k-1|0;H[l+276>>2]=b;p=(q+r|0)-k|0;H[l+256>>2]=p;i=I[p|0];c=8;H[l+272>>2]=8;t=p+1|0;H[l+256>>2]=t;g=k-2|0;H[l+276>>2]=g;f=(b|0)==1?i|15:i;H[l+264>>2]=f;b=0;H[l+268>>2]=b;H[l+280>>2]=(f|0)==255;T=p&3;D:{E:{if((T|0)==3){break E}p=0;if(!((f|0)!=255|I[t|0]<=143)){break D}m=255;m=k>>>0>=3?I[t|0]:m;u=k-3|0;H[l+276>>2]=u;p=(f|0)==255;c=p?15:16;H[l+272>>2]=c;v=t+(k>>>0>2)|0;H[l+256>>2]=v;g=(g|0)==1?m|15:m;H[l+280>>2]=(g|0)==255;i=g;m=f;g=p?7:8;p=g&31;if((g&63)>>>0>=32){t=f<>>32-p|b<>2]=f;b=t|x;H[l+268>>2]=b;if((T|0)==2){break E}m=255;p=0;if(!((i|0)!=255|I[v|0]<=143)){break D}m=k>>>0>=4?I[v|0]:m;j=k-4|0;H[l+276>>2]=j;v=v+(k>>>0>3)|0;H[l+256>>2]=v;g=(u|0)==1?m|15:m;p=0;u=p;H[l+280>>2]=(g|0)==255;p=(i|0)==255;c=(p?7:8)+c|0;H[l+272>>2]=c;i=g;m=f;g=p?7:8;p=g&31;if((g&63)>>>0>=32){t=f<>>32-p|b<>2]=f;b=t|u;H[l+268>>2]=b;if((T|0)==1){break E}p=0;if(!((i|0)!=255|I[v|0]<=143)){break D}m=255;m=k>>>0>=5?I[v|0]:m;H[l+276>>2]=k-5;H[l+256>>2]=v+(k>>>0>4);p=0;g=(j|0)==1?m|15:m;H[l+280>>2]=(g|0)==255;i=(i|0)==255;c=(i?7:8)+c|0;H[l+272>>2]=c;m=f;i=i?7:8;t=i&31;if((i&63)>>>0>=32){T=f<>>32-t|b<>2]=f;b=p|T;H[l+268>>2]=b}g=64-c|0;c=f;i=g&31;if((g&63)>>>0>=32){x=c<>>32-i|b<>2]=b;H[l+268>>2]=x;p=1}if(!p){if(ca){i=0;Ba(W,1,11470,0);break j}i=0;Ba(W,1,11470,0);break j}C=A-ea|0;R=k-2|0;H[l+244>>2]=R;x=q+r|0;b=x-3|0;H[l+224>>2]=b;c=I[x-2|0];f=c>>>0>143;H[l+248>>2]=f;p=0;m=c>>>4|0;H[l+232>>2]=m;H[l+236>>2]=0;v=(m&7)==7?3:4;H[l+240>>2]=v;c=(b&3)+1|0;E=c>>>0>>0?c:R;F:{G:{if(!R){H[l+244>>2]=R-E;break G}c=x-4|0;H[l+224>>2]=c;g=I[b|0];i=g>>>0>143;H[l+248>>2]=i;p=v&31;if((v&63)>>>0>=32){t=g<>>32-p;p=g<>2]=m;p=t;H[l+236>>2]=p;v=(f?(g&127)==127?7:8:8)+v|0;H[l+240>>2]=v;H:{if((E|0)==1){f=i;break H}i=x-5|0;H[l+224>>2]=i;b=I[c|0];f=b>>>0>143;H[l+248>>2]=f;j=v&31;if((v&63)>>>0>=32){t=b<>>32-j;M=b<>2]=m;p=p|t;H[l+236>>2]=p;v=(g>>>0<=143?8:(b&127)==127?7:8)+v|0;H[l+240>>2]=v;if((E|0)==2){b=c;c=i;break H}g=x-6|0;H[l+224>>2]=g;j=I[i|0];f=j>>>0>143;H[l+248>>2]=f;c=j;u=c&255;T=v&31;if((v&63)>>>0>=32){t=u<>>32-T;M=u<>2]=m;p=p|t;H[l+236>>2]=p;v=(b>>>0<=143?8:(c&127)==127?7:8)+v|0;H[l+240>>2]=v;if((E|0)==3){b=i;c=g;break H}c=x-7|0;H[l+224>>2]=c;b=I[g|0];f=b>>>0>143;H[l+248>>2]=f;i=v&31;if((v&63)>>>0>=32){T=b<>>32-i;i=b<>2]=m;H[l+236>>2]=i;v=(j>>>0<=143?8:(b&127)==127?7:8)+v|0;H[l+240>>2]=v;b=g}g=R-E|0;H[l+244>>2]=g;if(v>>>0>32){break F}if(g>>>0>=4){h=H[b-4>>2];H[l+224>>2]=b-5;H[l+244>>2]=g-4;break G}if(!g){break G}I:{if((E|0)==(k-3|0)){t=24;break I}T=g&1;u=g&2;t=24;b=0;while(1){i=c-1|0;H[l+224>>2]=i;j=I[c|0];c=c-2|0;H[l+224>>2]=c;H[l+244>>2]=g-1;i=I[i|0];g=g-2|0;H[l+244>>2]=g;h=j<>2]=c-1;b=I[c|0];H[l+244>>2]=g-1;h=b<>2]=j>>>0>143;g=f?(h&2130706432)==2130706432?7:8:8;c=g+(h>>>0<=2415919103?8:(h&8323072)==8323072?7:8)|0;i=h>>>16&255;b=c+(i>>>0<=143?8:(h&32512)==32512?7:8)|0;t=h>>>8&255;H[l+240>>2]=b+((t>>>0<=143?8:(h&127)==127?7:8)+v|0);b=i<>>24|t<>>0>=32){t=b<>>32-c;b=b<>2]=b|m;H[l+236>>2]=p|t}Wb(l+192|0,r,q-k|0,255);u=0;J:{if(n>>>0<2){break J}Wb(l+160|0,V,o,0);u=0;if((n|0)==2){break J}m=0;p=0;f=0;u=0;H[l+152>>2]=1;H[l+144>>2]=0;H[l+136>>2]=0;H[l+140>>2]=0;i=o;c=i-1|0;H[l+148>>2]=c;b=(q+r|0)+i|0;g=b-1|0;H[l+128>>2]=g;q=g&3;K:{if((i|0)<=0){b=g;break K}b=b-2|0;H[l+128>>2]=b;m=I[g|0]}H[l+136>>2]=m;H[l+140>>2]=0;o=m>>>0>143;H[l+152>>2]=o;j=(m&127)==127?7:8;H[l+144>>2]=j;L:{if(!q){break L}h=i-2|0;H[l+148>>2]=h;M:{if((i|0)<2){k=b;break M}k=b-1|0;H[l+128>>2]=k;f=I[b|0]}o=f>>>0>143;H[l+152>>2]=o;b=j&31;if((j&63)>>>0>=32){t=f<>>32-b;b=f<>2]=p;b=t;H[l+140>>2]=b;j=(m>>>0<=143?8:(f&127)==127?7:8)+j|0;H[l+144>>2]=j;if((q|0)==1){b=k;m=p;p=t;i=c;c=h;break L}t=i-3|0;H[l+148>>2]=t;N:{if((i|0)<3){g=k;break N}g=k-1|0;H[l+128>>2]=g;u=I[k|0]}o=u>>>0>143;H[l+152>>2]=o;c=j&31;if((j&63)>>>0>=32){T=u<>>32-c;c=u<>2]=m;H[l+140>>2]=b;j=(f>>>0<=143?8:(u&127)==127?7:8)+j|0;H[l+144>>2]=j;if((q|0)==2){b=g;i=h;c=t;break L}c=i-4|0;H[l+148>>2]=c;f=0;O:{if((i|0)<4){b=g;break O}b=g-1|0;H[l+128>>2]=b;f=I[g|0]}o=f>>>0>143;H[l+152>>2]=o;g=j&31;if((j&63)>>>0>=32){x=f<>>32-g;g=f<>2]=m;H[l+140>>2]=g;j=(u>>>0<=143?8:(f&127)==127?7:8)+j|0;H[l+144>>2]=j;i=t}if(j>>>0<=32){P:{if((i|0)>=5){k=H[b-3>>2];H[l+148>>2]=i-5;H[l+128>>2]=b-4;break P}k=0;if((i|0)<2){break P}i=24;while(1){f=b-1|0;H[l+128>>2]=f;b=I[b|0];g=c-1|0;H[l+148>>2]=g;k=b<>>0>1;b=f;i=i-8|0;c=g;if(t){continue}break}}t=k&255;H[l+152>>2]=t>>>0>143;g=o?(k&2130706432)==2130706432?7:8:8;c=g+(k>>>0<=2415919103?8:(k&8323072)==8323072?7:8)|0;i=k>>>16&255;b=c+(i>>>0<=143?8:(k&32512)==32512?7:8)|0;f=k>>>8&255;H[l+144>>2]=b+((f>>>0<=143?8:(k&127)==127?7:8)+j|0);b=i<>>24|f<>>0>=32){t=b<>>32-c;b=b<>2]=b|m;H[l+140>>2]=p|t}u=1}da=z-S|0;U=Z+1|0;F[$+2112|0]=0;h=$+2112|0;g=Xa(l+256|0);if((C|0)>0){E=qa-1|0;o=$;f=h;b=0;c=ja;r=0;while(1){T=r;j=J[(b<<8|(kb(l+224|0)&127)<<1)+16656>>1];Q:{if(b){break Q}b=g-2|0;j=(b|0)==-1?j:0;if((g|0)>1){g=b;break Q}g=Xa(l+256|0)}m=H[l+232>>2];p=H[l+236>>2];i=H[l+240>>2];v=j>>>4|0;q=H[o>>2]|(v&3|j>>>2&48)<>2]=q;S=j&16;b=j>>>5&7|S>>>4;k=i;i=j&7;r=k-i|0;m=((1<>>i;p=p>>>i|0;k=m;i=0;if((C|0)>(T|2)){i=J[(b<<8|(k&127)<<1)+16656>>1];R:{if(b){break R}b=g-2|0;i=(b|0)==-1?i:0;if((g|0)>1){g=b;break R}g=Xa(l+256|0)}b=i&7;r=r-b|0;m=((1<>>b;p=p>>>b|0;k=m;b=i>>>4&1|i>>>5&7}H[o>>2]=q|(i<<2&768|i&48)<>>2&2|j>>>3&1;S:{if((q|0)!=3){break S}t=g-2|0;q=(t|0)==-1?4:3;if((g|0)>1){g=t;break S}g=Xa(l+256|0)}T:{if(!q){H[l+120>>2]=1;H[l+124>>2]=1;k=0;break T}if(q>>>0<=2){t=I[(k&7)+20804|0];x=t>>>2&7;z=t&3;t=(((-1<>>z)+(t>>>5|0)|0)+1|0;k=(q|0)==1;H[l+124>>2]=k?1:t;H[l+120>>2]=k?t:1;k=x+z|0;break T}x=k;k=I[(k&7)+20804|0];R=k&3;t=x>>>R|0;if((q|0)==3){V=(k>>>5|0)+1|0;if((R|0)==3){H[l+124>>2]=t&1|2;k=k>>>2&7;H[l+120>>2]=V+((-1<>>1);k=k+4|0;break T}z=I[(t&7)+20804|0];q=z&3;t=t>>>q|0;x=k>>>2&7;H[l+120>>2]=V+(t&(-1<>>2&7;H[l+124>>2]=(((-1<>>x)+(z>>>5|0)|0)+1;k=k+(q+(x+R|0)|0)|0;break T}z=I[(t&7)+20804|0];q=z&3;t=t>>>q|0;x=k>>>2&7;H[l+120>>2]=((t&(-1<>>5|0)|0)+3;k=z>>>2&7;H[l+124>>2]=(((-1<>>x)+(z>>>5|0)|0)+3;k=k+(x+(q+R|0)|0)|0}U:{V=H[l+120>>2];if(V>>>0<=U>>>0){x=H[l+124>>2];if(x>>>0<=U>>>0){break U}}if(ca){i=0;Ba(W,1,15756,0);break j}i=0;Ba(W,1,15756,0);break j}H[l+240>>2]=r-k;q=k&31;if((k&63)>>>0>=32){t=0;p=p>>>q|0}else{t=p>>>q|0;p=((1<>>q}H[l+232>>2]=p;H[l+236>>2]=t;r=T+4|0;p=(r|0)<=(C|0)?255:255>>>(r-C<<1)|0;R=(da|0)>1?p:p&85;if((i&240|v&15)&(R^-1)){if(ca){i=0;Ba(W,1,12194,0);break j}i=0;Ba(W,1,12194,0);break j}V:{W:{if(S){m=Ma(l+192|0);q=V-(j>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;v=(m&(-1<>>8&1)<>2]=v}X:{if(j&32){m=Ma(l+192|0);q=V-(j>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break X}if(!(R&2)){break X}H[(C<<2)+c>>2]=0}q=c+4|0;Y:{Z:{if(j&64){m=Ma(l+192|0);z=V-(j>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-z;k=H[l+204>>2];p=H[l+200>>2];S=z&31;if((z&63)>>>0>=32){t=0;p=k>>>S|0}else{t=k>>>S|0;p=((1<>>S}H[l+200>>2]=p;H[l+204>>2]=t;v=(m&(-1<>>10&1)<>2]=v}F[f+1|0]=0;_:{if(j&128){m=Ma(l+192|0);z=V-(j>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-z;k=H[l+204>>2];p=H[l+200>>2];S=z&31;if((z&63)>>>0>=32){t=0;p=k>>>S|0}else{t=k>>>S|0;p=((1<>>S}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>11&1)<>2]=p+2<>2]=0}j=c+8|0;$:{aa:{if(i&16){m=Ma(l+192|0);q=x-(i>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;q=(m&(-1<>>8&1)<>2]=q}ba:{if(i&32){m=Ma(l+192|0);q=x-(i>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break ba}if(!(R&32)){break ba}H[j+(C<<2)>>2]=0}j=c+12|0;ca:{da:{if(i&64){m=Ma(l+192|0);q=x-(i>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;q=(m&(-1<>>10&1)<>2]=q}f=f+2|0;F[f|0]=0;ea:{if(i&128){m=Ma(l+192|0);q=x-(i>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;i=m&(-1<>>11&1)<>2]=i+2<>>0<128){break ea}H[j+(C<<2)>>2]=0}s=s^16;o=(T&4)+o|0;c=c+16|0;if((r|0)<(C|0)){continue}break}}ka=ba&8;sa=$+1584|0;ta=$+1056|0;ga=$+528|0;if((da|0)>=3){M=N(C,12);la=C<<3;fa=qa-1|0;b=qa-2|0;Y=3<>>1&2147483644)+4|0;T=2;while(1){z=T;v=I[h|0];F[h|0]=0;s=s&-17^2;fa:{if((C|0)<=0){T=z+2|0;break fa}b=z&4?ga:$;T=z+2|0;f=ja+(N(z,C)<<2)|0;q=0;c=h;S=0;while(1){ba=q;v=v&255;i=I[c+1|0]>>>5&4|(v>>>7|S);j=J[(i<<8|(kb(l+224|0)&127)<<1)+18704>>1];ga:{if(i){break ga}i=g-2|0;j=(i|0)==-1?j:0;if((g|0)>1){g=i;break ga}g=Xa(l+256|0)}m=H[l+232>>2];p=H[l+236>>2];i=H[l+240>>2];o=H[b>>2]|(j>>>4&3|j>>>2&48)<>2]=o;R=j&64;V=j&128;S=R>>>5|V>>>6;k=i;i=j&7;X=k-i|0;m=((1<>>i;p=p>>>i|0;q=m;i=0;if((C|0)>(ba|2)){k=I[c+2|0]>>>5&4|I[c+1|0]>>>7|S;i=J[(k<<8|(m&127)<<1)+18704>>1];ha:{if(k){break ha}k=g-2|0;i=(k|0)==-1?i:0;if((g|0)>1){g=k;break ha}g=Xa(l+256|0)}k=i&7;X=X-k|0;S=(i>>>5|i>>>6)&2;m=((1<>>k;q=m;p=p>>>k|0}H[b>>2]=o|(i<<2&768|i&48)<>>2&2|j>>>3&1;switch(r|0){case 0:break ia;case 3:break ja;default:break ka}}k=I[(q&7)+20804|0];x=k>>>2&7;o=q;q=k&3;o=(((-1<>>q)+(k>>>5|0)|0)+1|0;t=(r|0)==1;k=t?1:o;o=t?o:1;r=q+x|0;break ia}E=I[(q&7)+20804|0];t=E&3;k=q>>>t|0;x=I[(k&7)+20804|0];o=x&3;q=E>>>2&7;r=q+(o+t|0)|0;t=x>>>2&7;r=r+t|0;k=k>>>o|0;o=((k&(-1<>>5|0)|0)+1|0;k=(((-1<>>q)+(x>>>5|0)|0)+1|0}H[l+240>>2]=X-r;t=r&31;if((r&63)>>>0>=32){x=0;p=p>>>t|0}else{x=p>>>t|0;p=((1<>>t}H[l+232>>2]=p;H[l+236>>2]=x;q=j&240;if(q-1&q){m=v&127;p=I[c+1|0]&127;m=m>>>0>p>>>0?m:p;p=m-2|0;o=(m>>>0>=p>>>0?p:0)+o|0}t=i&240;if(t-1&t){m=I[c+1|0]&127;p=I[c+2|0]&127;p=m>>>0>p>>>0?m:p;k=(p>>>0>2?p-2|0:0)+k|0}if(!(o>>>0<=U>>>0&k>>>0<=U>>>0)){if(ca){i=0;Ba(W,1,15856,0);break j}i=0;Ba(W,1,15856,0);break j}v=I[c+2|0];F[c+1|0]=0;F[c+2|0]=0;m=t|q>>>4;q=ba+4|0;p=(q|0)<=(C|0)?255:255>>>(q-C<<1)|0;P=(T|0)>(da|0)?p&85:p;if(m&(P^-1)){if(ca){i=0;Ba(W,1,12194,0);break j}i=0;Ba(W,1,12194,0);break j}la:{ma:{if(j&16){m=Ma(l+192|0);r=o-(j>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-r;t=H[l+204>>2];p=H[l+200>>2];E=r&31;if((r&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;X=(m&(-1<>>8&1)<>2]=X}na:{if(j&32){m=Ma(l+192|0);r=o-(j>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-r;t=H[l+204>>2];p=H[l+200>>2];E=r&31;if((r&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break na}if(!(P&2)){break na}H[(C<<2)+f>>2]=0}r=f+4|0;oa:{pa:{if(R){m=Ma(l+192|0);R=o-(j>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-R;t=H[l+204>>2];p=H[l+200>>2];E=R&31;if((R&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;X=(m&(-1<>>10&1)<>2]=X}qa:{if(V){m=Ma(l+192|0);o=o-(j>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];V=o&31;if((o&63)>>>0>=32){x=0;p=t>>>V|0}else{x=t>>>V|0;p=((1<>>V}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>11&1)<>2]=p+2<>2]=0}j=f+8|0;ra:{sa:{if(i&16){m=Ma(l+192|0);o=k-(i>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;o=(m&(-1<>>8&1)<>2]=o}ta:{if(i&32){m=Ma(l+192|0);o=k-(i>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break ta}if(!(P&32)){break ta}H[j+(C<<2)>>2]=0}j=f+12|0;ua:{va:{if(i&64){m=Ma(l+192|0);o=k-(i>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;o=(m&(-1<>>10&1)<>2]=o}c=c+2|0;wa:{if(i&128){m=Ma(l+192|0);t=k-(i>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-t;k=H[l+204>>2];p=H[l+200>>2];o=t&31;if((t&63)>>>0>=32){x=0;p=k>>>o|0}else{x=k>>>o|0;p=((1<>>o}H[l+200>>2]=p;H[l+204>>2]=x;i=m&(-1<>>11&1)<>2]=i+2<>>0<128){break wa}H[j+(C<<2)>>2]=0}s=s^16;b=(ba&4)+b|0;f=f+16|0;if((q|0)<(C|0)){continue}break}}xa:{if(!(z&2)|n>>>0<2){break xa}q=T&4;ya:{za:{Aa:{if(u){t=q?$:ga;r=0;if((C|0)<=0){break Aa}p=ja+(N(C,z-2|0)<<2)|0;while(1){i=kb(l+128|0);j=0;f=H[t>>2];if(f){j=p+(r<<2)|0;c=15;k=0;while(1){Ba:{if(!(c&f)){break Ba}m=c&286331153;if(m&f){H[j>>2]=w|H[j>>2]^((i^-1)&1)<>>1|0}if(f&m<<1){b=(C<<2)+j|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}if(f&m<<2){b=j+la|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}if(!(f&m<<3)){break Ba}b=j+M|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}j=j+4|0;c=c<<4;k=k+1|0;if((k|0)!=8){continue}break}j=he(f)}t=t+4|0;H[l+144>>2]=H[l+144>>2]-j;c=H[l+140>>2];b=H[l+136>>2];i=j&31;if((j&63)>>>0>=32){x=0;b=c>>>i|0}else{x=c>>>i|0;b=((1<>>i}H[l+136>>2]=b;H[l+140>>2]=x;r=r+8|0;if((C|0)>(r|0)){continue}break}}o=0;i=0;ha=q?ta:sa;j=ha;t=q?$:ga;c=t;if((C|0)>0){break za}}if(z>>>0<=5){break xa}v=q?ga:$;break ya}while(1){b=i>>>28|0;i=H[c>>2];b=i|(b|i<<4|i>>>4);H[j>>2]=b;b=b|H[c+4>>2]<<28;H[j>>2]=(b>>>1&2004318071|b<<1&-286331154|b)&(i^-1);j=j+4|0;c=c+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}if(z>>>0<6){break xa}b=0;k=0;j=t;X=q?sa:ta;i=X;v=q?ga:$;c=v;while(1){m=j+4|0;p=H[i>>2];f=H[j>>2];if(!ka){p=p|(f|(f<<4|b>>>28|f>>>4|H[m>>2]<<28))<<3&-2004318072}H[i>>2]=p&(H[c>>2]^-1);c=c+4|0;i=i+4|0;b=f;j=m;k=k+8|0;if((C|0)>(k|0)){continue}break}V=ja+(N(C,z-6|0)<<2)|0;_=0;b=v;while(1){f=0;c=H[X>>2];if(c){z=C-_|0;ba=(_<<2)+V|0;i=0;S=0;while(1){p=i;i=Ma(l+160|0);m=S+4|0;R=(C|0)>(m+_|0)?m:z;Ca:{if((R|0)<=(S|0)){j=0;break Ca}P=H[b>>2]^-1;x=S<<2;r=x+ba|0;j=0;k=S;q=15<>>1|0}m=E<<1;if(m&c){if(i&1){f=f|m;c=P&116<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=E<<2;if(m&c){if(i&1){f=f|m;c=P&232<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=E<<3;if(!(m&c)){break Da}if(i&1){f=f|m;c=P&192<<(k<<2)|c}j=j+1|0;i=i>>>1|0}o=o<<4;k=k+1|0;if((R|0)>(k|0)){continue}break}if(!(f>>>x&65535)){break Ca}while(1){Ea:{if(!(f&q)){break Ea}k=q&286331153;if(k&f){H[r>>2]=Y|(H[r>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<1&f){m=(C<<2)+r|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<2&f){m=r+la|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(!(k<<3&f)){break Ea}m=r+M|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}q=q<<4;r=r+4|0;S=S+1|0;if((R|0)>(S|0)){continue}break}}H[l+176>>2]=H[l+176>>2]-j;m=H[l+172>>2];i=H[l+168>>2];k=j&31;if((j&63)>>>0>=32){x=0;i=m>>>k|0}else{x=m>>>k|0;i=((1<>>k}H[l+168>>2]=i;H[l+172>>2]=x;i=1;S=4;if(!(p&1)){continue}break}H[X+4>>2]=H[X+4>>2]|(f>>>27&14|f>>>29|f>>>28)&(H[b+4>>2]^-1)}p=H[b>>2]|f;m=p>>>3&286331153;i=m>>>4|m<<4|m;if(_){c=ha-4|0;H[c>>2]=H[c>>2]|(H[t-4>>2]^-1)&m<<28}H[ha>>2]=H[ha>>2]|i&(H[t>>2]^-1);H[ha+4>>2]=H[ha+4>>2]|(H[t+4>>2]^-1)&p>>>31;X=X+4|0;b=b+4|0;ha=ha+4|0;t=t+4|0;_=_+8|0;if((C|0)>(_|0)){continue}break}}if(!Z){break xa}y(v,0,Z)}if((T|0)<(da|0)){continue}break}}Fa:{if(n>>>0<2){break Fa}f=(da&3)-1|0;Ga:{if(u&f>>>0<2){if((C|0)<=0){break Ga}h=1<>2];if(q){j=m+(S<<2)|0;c=15;k=0;while(1){Ha:{if(!(c&q)){break Ha}t=c&286331153;if(t&q){H[j>>2]=h|H[j>>2]^((i^-1)&1)<>>1|0}if(q&t<<1){b=(C<<2)+j|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}if(q&t<<2){b=g+j|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}if(!(q&t<<3)){break Ha}b=j+p|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}j=j+4|0;c=c<<4;k=k+1|0;if((k|0)!=8){continue}break}j=he(q)}n=n+4|0;H[l+144>>2]=H[l+144>>2]-j;c=H[l+140>>2];b=H[l+136>>2];i=j&31;if((j&63)>>>0>=32){x=0;b=c>>>i|0}else{x=c>>>i|0;b=((1<>>i}H[l+136>>2]=b;H[l+140>>2]=x;S=S+8|0;if((C|0)>(S|0)){continue}break}}if((C|0)<=0|f>>>0>1){break Ga}b=da&4;j=b?ga:$;c=b?sa:ta;o=0;i=0;while(1){b=i>>>28|0;i=H[j>>2];b=i|(b|i<<4|i>>>4);H[c>>2]=b;b=b|H[j+4>>2]<<28;H[c>>2]=(b>>>1&2004318071|b<<1&-286331154|b)&(i^-1);c=c+4|0;j=j+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}}b=(da|0)>6?(da-(da+1&3)|0)-3|0:0;if((da|0)<=(b|0)){break Fa}T=N(C,12);ba=C<<3;E=3<>>0>=3){s=-1;if((g|0)<5){break Ka}if(v){break Ia}g=b&4;j=g?ga:$;i=g?sa:ta;c=0;if(!ka){c=g?$:ga;o=0;f=0;while(1){g=f>>>28|0;s=-1;f=H[c>>2];H[i>>2]=(H[i>>2]|(f|(g|f<<4|f>>>4|H[c+4>>2]<<28))<<3&-2004318072)&(H[j>>2]^-1);j=j+4|0;i=i+4|0;c=c+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}break Ja}while(1){s=-1;H[i>>2]=H[i>>2]&(H[j>>2]^-1);j=j+4|0;i=i+4|0;c=c+8|0;if((C|0)>(c|0)){continue}break}break Ja}s=H[(c<<2)+20812>>2]}if(v){break Ia}}c=b&4;Z=c?ga:$;g=c?sa:ta;X=c?$:ga;_=c?ta:sa;u=ja+(N(b,C)<<2)|0;q=0;while(1){f=0;c=H[g>>2]&s;if(c){h=C-q|0;t=u+(q<<2)|0;i=0;n=0;while(1){p=i;i=Ma(l+160|0);m=n+4|0;S=(C|0)>(m+q|0)?m:h;La:{if((S|0)<=(n|0)){j=0;break La}z=n<<2;r=z+t|0;R=(H[Z>>2]^-1)&s;j=0;k=n;V=15<>>1|0}m=x<<1;if(m&c){if(i&1){f=f|m;c=R&116<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=x<<2;if(m&c){if(i&1){f=f|m;c=R&232<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=x<<3;if(!(m&c)){break Ma}if(i&1){f=f|m;c=R&192<<(k<<2)|c}j=j+1|0;i=i>>>1|0}o=o<<4;k=k+1|0;if((S|0)>(k|0)){continue}break}if(!(f>>>z&65535)){break La}while(1){Na:{if(!(f&V)){break Na}k=V&286331153;if(k&f){H[r>>2]=E|(H[r>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<1&f){m=(C<<2)+r|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<2&f){m=r+ba|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(!(k<<3&f)){break Na}m=r+T|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}V=V<<4;r=r+4|0;n=n+1|0;if((S|0)>(n|0)){continue}break}}H[l+176>>2]=H[l+176>>2]-j;m=H[l+172>>2];i=H[l+168>>2];k=j&31;if((j&63)>>>0>=32){x=0;i=m>>>k|0}else{x=m>>>k|0;i=((1<>>k}H[l+168>>2]=i;H[l+172>>2]=x;i=1;n=4;if(!(p&1)){continue}break}H[g+4>>2]=H[g+4>>2]|(f>>>27&14|f>>>29|f>>>28)&(H[Z+4>>2]^-1)}p=H[Z>>2]|f;m=p>>>3&286331153;i=m>>>4|m<<4|m;if(q){c=_-4|0;H[c>>2]=H[c>>2]|(H[X-4>>2]^-1)&m<<28}H[_>>2]=H[_>>2]|i&(H[X>>2]^-1);H[_+4>>2]=H[_+4>>2]|(H[X+4>>2]^-1)&p>>>31;g=g+4|0;Z=Z+4|0;_=_+4|0;X=X+4|0;q=q+8|0;if((C|0)>(q|0)){continue}break}}b=b+4|0;if((da|0)>(b|0)){continue}break}}i=1;if((da|0)<=0|(C|0)<=0){break j}m=C&2147483644;k=C&3;p=ea-A>>>0>4294967292;b=0;while(1){i=ja+(N(b,C)<<2)|0;j=0;Oa:{if(!p){while(1){g=H[i>>2];c=g&2147483647;H[i>>2]=(g|0)<0?0-c|0:c;g=H[i+4>>2];c=g&2147483647;H[i+4>>2]=(g|0)<0?0-c|0:c;g=H[i+8>>2];c=g&2147483647;H[i+8>>2]=(g|0)<0?0-c|0:c;g=H[i+12>>2];c=g&2147483647;H[i+12>>2]=(g|0)<0?0-c|0:c;i=i+16|0;j=j+4|0;if((m|0)!=(j|0)){continue}break}if(!k){break Oa}}j=0;while(1){g=H[i>>2];c=g&2147483647;H[i>>2]=(g|0)<0?0-c|0:c;i=i+4|0;j=j+1|0;if((k|0)!=(j|0)){continue}break}}i=1;b=b+1|0;if((da|0)!=(b|0)){continue}break}break j}if(!ca){break w}H[l+52>>2]=H[D+24>>2];H[l+48>>2]=Z;Ba(W,1,9686,l+48|0);break v}H[l+20>>2]=i;H[l+16>>2]=Z;Ba(W,1,9686,l+16|0);i=0;break j}i=0}na=l+304|0;if(i){break i}break b}H[e+108>>2]=(b<<9)+22336;c=0;b=H[e+116>>2];Pa:{Qa:{j=H[D+16>>2]-H[D+8>>2]|0;o=H[D+20>>2]-H[D+12>>2]|0;g=N(j,o);Ra:{Sa:{Ta:{if(g>>>0>K[e+132>>2]){Ca(b);b=Ia(g<<2);H[e+116>>2]=b;if(!b){break Ra}H[e+132>>2]=g;break Ta}if(!b){break Sa}}g=g<<2;if(!g){break Sa}y(b,0,g)}b=H[e+120>>2];h=j+2|0;k=o+3>>>2|0;g=N(h,k+2|0);if(g>>>0<=K[e+136>>2]){U=g<<2;break Qa}Ca(b);U=g<<2;b=Ia(U);H[e+120>>2]=b;if(b){break Qa}}b=0;break Pa}H[e+136>>2]=g;if(U){y(b,0,U)}Ua:{if(!h){break Ua}l=h&7;m=H[e+120>>2];b=m;p=j+1|0;Va:{if(p>>>0>=7){g=h&-8;while(1){H[b+24>>2]=1226833920;H[b+28>>2]=1226833920;H[b+16>>2]=1226833920;H[b+20>>2]=1226833920;H[b+8>>2]=1226833920;H[b+12>>2]=1226833920;H[b>>2]=1226833920;H[b+4>>2]=1226833920;b=b+32|0;c=c+8|0;if((g|0)!=(c|0)){continue}break}if(!l){break Va}}c=0;while(1){H[b>>2]=1226833920;b=b+4|0;c=c+1|0;if((l|0)!=(c|0)){continue}break}}l=h&7;b=m+(N(h,k+1|0)<<2)|0;Wa:{if(p>>>0>=7){g=h&-8;c=0;while(1){H[b+24>>2]=1226833920;H[b+28>>2]=1226833920;H[b+16>>2]=1226833920;H[b+20>>2]=1226833920;H[b+8>>2]=1226833920;H[b+12>>2]=1226833920;H[b>>2]=1226833920;H[b+4>>2]=1226833920;b=b+32|0;c=c+8|0;if((g|0)!=(c|0)){continue}break}if(!l){break Wa}}c=0;while(1){H[b>>2]=1226833920;b=b+4|0;c=c+1|0;if((l|0)!=(c|0)){continue}break}}b=o&3;if(!b){break Ua}l=(b|0)==1?1224736768:(b|0)==2?1207959552:1073741824;g=h&7;b=m+(N(h,k)<<2)|0;if(p>>>0>=7){c=h&-8;U=0;while(1){H[b+28>>2]=l;H[b+24>>2]=l;H[b+20>>2]=l;H[b+16>>2]=l;H[b+12>>2]=l;H[b+8>>2]=l;H[b+4>>2]=l;H[b>>2]=l;b=b+32|0;U=U+8|0;if((c|0)!=(U|0)){continue}break}if(!g){break Ua}}U=0;while(1){H[b>>2]=l;b=b+4|0;U=U+1|0;if((g|0)!=(U|0)){continue}break}}H[e+128>>2]=o;H[e+124>>2]=j;b=1}if(!b){break b}U=t+H[D+28>>2]|0;if((U|0)>=31){if(!ca){break h}H[aa+16>>2]=U;Ba(W,2,8716,aa+16|0);break b}Vb(e);Wa(e,18,46);Wa(e,17,3);Wa(e,0,4);if(H[D+64>>2]){break i}c=H[D+52>>2];Xa:{if(!(c>>>0<=1&(!H[e+144>>2]|(c|0)!=1))){l=c&3;b=H[D+4>>2];g=0;Ya:{if(c-1>>>0>=3){c=c&-4;while(1){p=(r<<3)+b|0;g=H[p+28>>2]+(H[p+20>>2]+(H[p+12>>2]+(H[p+4>>2]+g|0)|0)|0)|0;r=r+4|0;f=f+4|0;if((c|0)!=(f|0)){continue}break}if(!l){break Ya}}while(1){g=H[((r<<3)+b|0)+4>>2]+g|0;r=r+1|0;i=i+1|0;if((l|0)!=(i|0)){continue}break}}ka=H[e+148>>2];c=g+2|0;if(c>>>0>K[e+152>>2]){b=Ga(ka,c);if(!b){break b}H[e+148>>2]=b;b=b+g|0;F[b|0]=0;F[b+1|0]=0;H[e+152>>2]=c;ka=H[e+148>>2];if(!H[D+52>>2]){break Xa}b=H[D+4>>2]}g=0;r=0;while(1){l=r<<3;c=l+b|0;b=H[c+4>>2];if(b){B(g+ka|0,H[c>>2],b)}b=H[D+4>>2];g=H[(l+b|0)+4>>2]+g|0;r=r+1|0;if(r>>>0>2]){continue}break}break Xa}if((c|0)!=1){break i}ka=H[H[D+4>>2]>>2]}b=H[D+60>>2];if(b){S=H[e+116>>2];H[e+116>>2]=b}if(H[D+44>>2]){E=ba&8;ea=e+28|0;z=!(ba&2);_=2;while(1){l=ha+ka|0;la=H[D>>2]+N(R,24)|0;c=H[la>>2];Z=ba&(_>>>0<2&(H[D+28>>2]-4|0)>=(U|0));Za:{if(Z){H[e+20>>2]=l;b=c+l|0;H[e+24>>2]=b;G[e+112>>1]=I[b|0]|I[b+1|0]<<8;F[b|0]=255;F[H[e+24>>2]+1|0]=255;H[e+8>>2]=0;H[e>>2]=0;H[e+16>>2]=l;break Za}H[e+20>>2]=l;b=c+l|0;H[e+24>>2]=b;G[e+112>>1]=I[b|0]|I[b+1|0]<<8;F[b|0]=255;F[H[e+24>>2]+1|0]=255;H[e+104>>2]=e+28;H[e+16>>2]=l;H[e+12>>2]=0;b=c?I[l|0]<<16:16711680;H[e>>2]=b;i=1;c=l+1|0;g=I[l+1|0];_a:{if(I[l|0]==255){if(g>>>0>=144){H[e+12>>2]=1;b=b|65280;break _a}H[e+16>>2]=c;i=0;b=b+(g<<9)|0;break _a}H[e+16>>2]=c;b=b|g<<8}H[e+8>>2]=i;H[e+4>>2]=32768;H[e>>2]=b<<7}ja=H[la>>2];$a:{if(!H[la+8>>2]|(U|0)<=0){break $a}T=z|Z;V=0;while(1){ab:{bb:{cb:{db:{switch(_-1|0){default:if(!Z){break cb}b=1<>>1|b;o=H[e+124>>2];k=o<<2;b=(k+H[e+120>>2]|0)+12|0;g=H[e+116>>2];j=0;c=H[e+128>>2];if(c>>>0>=4){if(!o){break ab}d=N(o,12);m=o<<3;f=0-h|0;while(1){c=0;while(1){l=b;b=H[b>>2];eb:{if(!b){break eb}if(!(!(b&495)|b&2097168)){b=H[e>>2];i=H[e+8>>2];fb:{if(i){break fb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];gb:{if(i){i=8}else{if(b>>>0>143){break gb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break fb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;hb:{if(!(b>>>i&1)){break hb}ib:{if(i){break ib}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];jb:{if(i){i=8}else{if(b>>>0>143){break jb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ib}i=8;b=255}i=i-1|0;H[e+8>>2]=i;p=b>>>i&1;H[g>>2]=p?f:h;i=H[e+124>>2];b=l-4|0;H[b>>2]=H[b>>2]|32;H[l+4>>2]=H[l+4>>2]|8;H[l>>2]=H[l>>2]|p<<19|16;if(E){break hb}b=l+(-2-i<<2)|0;H[b+4>>2]=H[b+4>>2]|32768;H[b>>2]=H[b>>2]|p<<31|65536;b=b-4|0;H[b>>2]=H[b>>2]|131072}b=H[l>>2]|2097152;H[l>>2]=b}if(!(!(b&3960)|b&16777344)){b=H[e>>2];i=H[e+8>>2];kb:{if(i){break kb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];lb:{if(i){i=8}else{if(b>>>0>143){break lb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break kb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){mb:{if(i){break mb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];nb:{if(i){i=8}else{if(b>>>0>143){break nb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break mb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;i=b>>>i&1;H[g+k>>2]=i?f:h;b=l-4|0;H[b>>2]=H[b>>2]|256;H[l+4>>2]=H[l+4>>2]|64;b=H[l>>2]|i<<22|128}else{b=H[l>>2]}b=b|16777216;H[l>>2]=b}if(!(!(b&31680)|b&134218752)){b=H[e>>2];i=H[e+8>>2];ob:{if(i){break ob}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];pb:{if(i){i=8}else{if(b>>>0>143){break pb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ob}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){qb:{if(i){break qb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];rb:{if(i){i=8}else{if(b>>>0>143){break rb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break qb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;i=b>>>i&1;H[g+m>>2]=i?f:h;b=l-4|0;H[b>>2]=H[b>>2]|2048;H[l+4>>2]=H[l+4>>2]|512;b=H[l>>2]|i<<25|1024}else{b=H[l>>2]}b=b|134217728;H[l>>2]=b}if(!(b&253440)|b&1073750016){break eb}b=H[e>>2];i=H[e+8>>2];sb:{if(i){break sb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];tb:{if(i){i=8}else{if(b>>>0>143){break tb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break sb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){ub:{if(i){break ub}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];vb:{if(i){i=8}else{if(b>>>0>143){break vb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ub}i=8;b=255}i=i-1|0;H[e+8>>2]=i;p=b>>>i&1;H[d+g>>2]=p?f:h;i=H[e+124>>2];b=l-4|0;H[b>>2]=H[b>>2]|16384;H[l+4>>2]=H[l+4>>2]|4096;H[l>>2]=H[l>>2]|p<<28|8192;b=l+(i<<2)|0;H[b+4>>2]=H[b+4>>2]|4;H[b+12>>2]=H[b+12>>2]|1;H[b+8>>2]=H[b+8>>2]|p<<18|2}H[l>>2]=H[l>>2]|1073741824}g=g+4|0;b=l+4|0;c=c+1|0;if((o|0)!=(c|0)){continue}break}g=d+g|0;b=l+12|0;j=j+4|0;c=H[e+128>>2];if(j>>>0<(c&-4)>>>0){continue}break}}if(!o|c>>>0<=j>>>0){break bb}q=0;m=0-h|0;i=c;while(1){wb:{if((i|0)==(j|0)){i=j;break wb}d=b-4|0;k=H[b>>2];r=0;while(1){t=N(r,3);l=k>>>t|0;if(!(l&2097168|!(l&495))){c=H[e>>2];f=H[e+8>>2];xb:{if(f){break xb}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];yb:{if(i){f=8}else{if(c>>>0>143){break yb}f=7}H[e>>2]=c;H[e+16>>2]=l+1;break xb}f=8;c=255}f=f-1|0;H[e+8>>2]=f;zb:{if(!(c>>>f&1)){break zb}p=(N(o,r)<<2)+g|0;Ab:{if(f){break Ab}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];Bb:{if(i){f=8}else{if(c>>>0>143){break Bb}f=7}H[e>>2]=c;H[e+16>>2]=l+1;break Ab}f=8;c=255}l=f-1|0;H[e+8>>2]=l;i=c>>>l&1;H[p>>2]=i?m:h;l=H[e+124>>2];H[d>>2]=H[d>>2]|32<>2]=H[b>>2]|(i<<19|16)<>2]=H[b+4>>2]|8<>2]=H[c+4>>2]|32768;H[c>>2]=H[c>>2]|i<<31|65536;c=c-4|0;H[c>>2]=H[c>>2]|131072}if((r|0)!=3){break zb}c=(l<<2)+b|0;H[c+4>>2]=H[c+4>>2]|4;H[c+12>>2]=H[c+12>>2]|1;H[c+8>>2]=H[c+8>>2]|i<<18|2}k=H[b>>2]|2097152<>2]=k;c=H[e+128>>2]}i=c;r=r+1|0;if(r>>>0>>0){continue}break}}g=g+4|0;b=b+4|0;q=q+1|0;if((o|0)!=(q|0)){continue}break};break bb;case 1:v=0;t=0;Cb:{Db:{Eb:{Y=H[e+124>>2];if(!((Y|0)!=64|H[e+128>>2]!=64)){b=1<>>1|b;x=0-M|0;p=e+100|0;l=e+96|0;A=e+28|0;g=H[e+120>>2]+268|0;h=H[e+8>>2];b=H[e+4>>2];d=H[e>>2];i=H[e+104>>2];c=H[e+116>>2];if(ba&8){break Eb}while(1){s=0;while(1){k=c;f=g;g=H[g>>2];Fb:{Gb:{Hb:{if(!g){i=H[l>>2];g=H[i>>2];b=b-g|0;Ib:{if(d>>>16>>>0>>0){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?8:12)>>2];while(1){Jb:{if(h){break Jb}i=H[e+16>>2];b=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jb}H[e+16>>2]=b;d=(o<<9)+d|0;h=7;break Jb}H[e+16>>2]=b;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?m:!m;break Ib}d=d-(g<<16)|0;if(!(b&32768)){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?12:8)>>2];while(1){Kb:{if(h){break Kb}i=H[e+16>>2];g=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Kb}H[e+16>>2]=g;d=(o<<9)+d|0;h=7;break Kb}H[e+16>>2]=g;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!m:m;break Ib}c=H[i+4>>2]}if(!c){i=l;break Fb}c=H[p>>2];g=H[c>>2];b=b-g|0;Lb:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=b>>>0>>0;c=H[(i?8:12)+c>>2];H[p>>2]=c;while(1){Mb:{if(h){break Mb}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mb}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Mb}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=i?o:!o;break Lb}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];g=b>>>0>>0;c=H[(g?12:8)+c>>2];H[p>>2]=c;while(1){Nb:{if(h){break Nb}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Nb}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break Nb}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=g?!o:o;break Lb}m=H[c+4>>2]}g=H[c>>2];b=b-g|0;Ob:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?8:12)>>2];while(1){Pb:{if(h){break Pb}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Pb}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Pb}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break Ob}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?12:8)>>2];while(1){Qb:{if(h){break Qb}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Qb}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Qb}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Ob}c=H[c+4>>2]}g=0;i=p;Rb:{Sb:{Tb:{Ub:{Vb:{switch(c|m<<1){case 0:j=f-4|0;i=H[f+4>>2]>>>17&4|H[j>>2]>>>19&1;c=A+(I[i+24384|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;Wb:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){Xb:{if(h){break Xb}m=H[e+16>>2];b=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Xb}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Xb}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=c?o:!o;break Wb}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){Yb:{if(h){break Yb}m=H[e+16>>2];g=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Yb}H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Yb}H[e+16>>2]=g;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!o:o;break Wb}m=H[m+4>>2]}g=I[i+24640|0];H[k>>2]=(m|0)==(g|0)?M:x;H[j>>2]=H[j>>2]|32;H[f+4>>2]=H[f+4>>2]|8;c=f-268|0;H[c>>2]=H[c>>2]|131072;c=f-260|0;H[c>>2]=H[c>>2]|32768;c=f-264|0;i=c;o=H[c>>2];c=g^m;H[i>>2]=o|c<<31|65536;i=c<<19;r=H[e+108>>2];c=A+(I[r+2|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;Zb:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){_b:{if(h){break _b}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break _b}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break _b}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break Zb}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){$b:{if(h){break $b}m=H[e+16>>2];g=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break $b}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break $b}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Zb}c=H[m+4>>2]}g=i|16;if(!c){break Ub}break;case 1:break Vb;case 2:break Tb;case 3:break Rb;default:break Gb}}n=f-4|0;m=H[f+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|g>>>3&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;ac:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){bc:{if(h){break bc}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break bc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break bc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break ac}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){cc:{if(h){break cc}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break cc}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break cc}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break ac}i=H[o+4>>2]}c=I[m+24640|0];H[k+256>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|256;H[f+4>>2]=H[f+4>>2]|64;r=H[e+108>>2];g=(c^i)<<22|g|128}i=A+(I[(g>>>6&495)+r|0]<<2)|0;m=H[i>>2];c=H[m>>2];b=b-c|0;dc:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[m+(i?8:12)>>2];while(1){ec:{if(h){break ec}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ec}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break ec}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=i?o:!o;break dc}d=d-(c<<16)|0;if(!(b&32768)){o=H[m+4>>2];c=b>>>0>>0;H[i>>2]=H[m+(c?12:8)>>2];while(1){fc:{if(h){break fc}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break fc}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break fc}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break dc}c=H[m+4>>2]}if(!c){break Sb}}n=f-4|0;m=H[f+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|g>>>6&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;gc:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){hc:{if(h){break hc}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break hc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break gc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){ic:{if(h){break ic}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ic}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break ic}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break gc}i=H[o+4>>2]}c=I[m+24640|0];H[k+512>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^i)<<25|g|1024;r=H[e+108>>2]}i=A+(I[(g>>>9&495)+r|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;jc:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){kc:{if(h){break kc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break kc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break kc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break jc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){lc:{if(h){break lc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break lc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break jc}c=H[o+4>>2]}if(!c){break Gb}}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|g>>>9&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0;break Hb}mc:{if(g&2097168){break mc}i=A+(I[H[e+108>>2]+(g&495)|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;nc:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){oc:{if(h){break oc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break oc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break oc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break nc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){pc:{if(h){break pc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break pc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break pc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break nc}c=H[o+4>>2]}if(!c){break mc}q=f-4|0;o=H[f+4>>2]>>>17&4|(H[q>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;qc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){rc:{if(h){break rc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break rc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break rc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;j=m?n:!n;break qc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){sc:{if(h){break sc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break sc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break sc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=c?!n:n;break qc}j=H[j+4>>2]}m=I[o+24640|0];H[k>>2]=(j|0)==(m|0)?M:x;H[q>>2]=H[q>>2]|32;H[f+4>>2]=H[f+4>>2]|8;c=f-268|0;H[c>>2]=H[c>>2]|131072;c=f-260|0;H[c>>2]=H[c>>2]|32768;c=f-264|0;o=c;w=H[c>>2];c=j^m;H[o>>2]=w|c<<31|65536;g=c<<19|g|16}tc:{if(g&16777344){break tc}o=g>>>3|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;uc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){vc:{if(h){break vc}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break vc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break vc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break uc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){wc:{if(h){break wc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break wc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break wc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break uc}c=H[j+4>>2]}if(!c){break tc}q=f-4|0;o=H[f+4>>2]>>>20&4|(H[q>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;xc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){yc:{if(h){break yc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break yc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break yc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break xc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){zc:{if(h){break zc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break zc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break zc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break xc}m=H[j+4>>2]}c=I[o+24640|0];H[k+256>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|256;H[f+4>>2]=H[f+4>>2]|64;g=(c^m)<<22|g|128}Ac:{if(g&134218752){break Ac}o=g>>>6|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Bc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Cc:{if(h){break Cc}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Cc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Cc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Bc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Dc:{if(h){break Dc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Dc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Dc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Bc}c=H[j+4>>2]}if(!c){break Ac}q=f-4|0;o=H[f+4>>2]>>>23&4|(H[q>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Ec:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Fc:{if(h){break Fc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Fc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Fc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Ec}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Gc:{if(h){break Gc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Gc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break Gc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Ec}m=H[j+4>>2]}c=I[o+24640|0];H[k+512>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^m)<<25|g|1024}if(g&1073750016){break Gb}o=g>>>9|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Hc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Ic:{if(h){break Ic}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Ic}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Ic}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Hc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Jc:{if(h){break Jc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Jc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Hc}c=H[j+4>>2]}if(!c){break Gb}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0}Kc:{if(d>>>16>>>0>>0){j=H[r+4>>2];m=b>>>0>>0;H[i>>2]=H[(m?8:12)+r>>2];while(1){Lc:{if(h){break Lc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Lc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Lc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?j:!j;break Kc}d=d-(c<<16)|0;if(!(b&32768)){j=H[r+4>>2];c=b>>>0>>0;H[i>>2]=H[(c?12:8)+r>>2];while(1){Mc:{if(h){break Mc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Mc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!j:j;break Kc}m=H[r+4>>2]}c=I[q+24640|0];H[k+768>>2]=(m|0)==(c|0)?M:x;H[v>>2]=H[v>>2]|16384;H[f+4>>2]=H[f+4>>2]|4096;H[f+260>>2]=H[f+260>>2]|4;H[f+268>>2]=H[f+268>>2]|1;c=c^m;H[f+264>>2]=H[f+264>>2]|c<<18|2;g=c<<28|g|8192}H[f>>2]=g&-1226833921}g=f+4|0;c=k+4|0;s=s+1|0;if((s|0)!=64){continue}break}g=f+12|0;c=k+772|0;m=t>>>0<60;t=t+4|0;if(m){continue}break}break Db}b=1<>>1|b;l=H[e+120>>2];c=(l+(Y<<2)|0)+12|0;g=H[e+128>>2];h=H[e+8>>2];b=H[e+4>>2];d=H[e>>2];i=H[e+104>>2];o=H[e+116>>2];if(ba&8){Nc:{if(g>>>0<4){break Nc}if(Y){m=e+100|0;p=e+96|0;x=N(Y,12);u=Y<<3;A=0-w|0;P=e+28|0;while(1){M=0;while(1){k=c;c=H[c>>2];Oc:{Pc:{Qc:{if(c){Rc:{if(c&2097168){break Rc}i=P+(I[H[e+108>>2]+(c&495)|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Sc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Sc}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){Tc:{if(h){break Tc}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Tc}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Tc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Sc}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){Uc:{if(h){break Uc}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Uc}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Uc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Rc}q=k-4|0;f=H[k+4>>2]>>>17&4|(H[q>>2]>>>19&1|(c>>>14&16|(c>>>16&64|c&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;Vc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break Vc}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){Wc:{if(h){break Wc}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break Wc}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break Wc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break Vc}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){Xc:{if(h){break Xc}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break Xc}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break Xc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[o>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|32;H[k+4>>2]=H[k+4>>2]|8;c=(g^l)<<19|c|16}Yc:{if(c&16777344){break Yc}f=c>>>3|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;Zc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break Zc}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){_c:{if(h){break _c}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break _c}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break _c}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break Zc}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){$c:{if(h){break $c}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break $c}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break $c}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Yc}q=k-4|0;f=H[k+4>>2]>>>20&4|(H[q>>2]>>>22&1|(c>>>15&16|(c>>>19&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=(Y<<2)+o|0;ad:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ad}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){bd:{if(h){break bd}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break bd}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break bd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ad}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){cd:{if(h){break cd}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break cd}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break cd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|256;H[k+4>>2]=H[k+4>>2]|64;c=(g^l)<<22|c|128}dd:{if(c&134218752){break dd}f=c>>>6|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ed:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ed}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){fd:{if(h){break fd}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break fd}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break fd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ed}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){gd:{if(h){break gd}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break gd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break gd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break dd}q=k-4|0;f=H[k+4>>2]>>>23&4|(H[q>>2]>>>25&1|(c>>>18&16|(c>>>22&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=o+u|0;hd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break hd}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){id:{if(h){break id}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break id}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break id}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break hd}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){jd:{if(h){break jd}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break jd}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break jd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024}if(c&1073750016){break Pc}f=c>>>9|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;kd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break kd}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ld:{if(h){break ld}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break ld}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break ld}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break kd}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){md:{if(h){break md}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break md}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break md}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Pc}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|f&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0;break Qc}l=H[p>>2];c=H[l>>2];b=b-c|0;nd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;g=H[l+4>>2];if(b&32768){break nd}i=H[l+4>>2];c=b>>>0>>0;H[p>>2]=H[l+(c?12:8)>>2];while(1){od:{if(h){break od}l=H[e+16>>2];g=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=g;h=8;d=(f<<8)+d|0;break od}if(f>>>0<=143){H[e+16>>2]=g;d=(f<<9)+d|0;h=7;break od}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!i:i;break nd}i=H[l+4>>2];g=b>>>0>>0;H[p>>2]=H[l+(g?8:12)>>2];while(1){pd:{if(h){break pd}l=H[e+16>>2];b=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=b;h=8;d=(f<<8)+d|0;break pd}if(f>>>0<=143){H[e+16>>2]=b;d=(f<<9)+d|0;h=7;break pd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;g=g?i:!i}if(!g){i=p;break Oc}g=H[m>>2];c=H[g>>2];b=b-c|0;qd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;l=H[g+4>>2];if(b&32768){break qd}f=H[g+4>>2];c=b>>>0>>0;g=H[(c?12:8)+g>>2];H[m>>2]=g;while(1){rd:{if(h){break rd}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break rd}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break rd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=c?!f:f;break qd}f=H[g+4>>2];l=b>>>0>>0;g=H[(l?8:12)+g>>2];H[m>>2]=g;while(1){sd:{if(h){break sd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break sd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break sd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;l=l?f:!f}c=H[g>>2];b=b-c|0;td:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;i=H[g+4>>2];if(b&32768){break td}f=H[g+4>>2];c=b>>>0>>0;H[m>>2]=H[(c?12:8)+g>>2];while(1){ud:{if(h){break ud}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break ud}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break ud}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!f:f;break td}f=H[g+4>>2];i=g;g=b>>>0>>0;H[m>>2]=H[i+(g?8:12)>>2];while(1){vd:{if(h){break vd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break vd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break vd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=g?f:!f}g=i;c=0;i=m;wd:{xd:{yd:{zd:{Ad:{switch(g|l<<1){case 0:j=k-4|0;l=H[k+4>>2]>>>17&4|H[j>>2]>>>19&1;g=P+(I[l+24384|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Bd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Bd}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Cd:{if(h){break Cd}i=H[e+16>>2];g=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(n<<8)+d|0;break Cd}if(n>>>0<=143){H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Cd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Bd}f=H[i+4>>2];q=g;g=b>>>0>>0;H[q>>2]=H[i+(g?8:12)>>2];while(1){Dd:{if(h){break Dd}i=H[e+16>>2];b=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Dd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Dd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=I[l+24640|0];H[o>>2]=(g|0)==(c|0)?w:A;H[j>>2]=H[j>>2]|32;H[k+4>>2]=H[k+4>>2]|8;l=(c^g)<<19;r=H[e+108>>2];g=P+(I[r+2|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ed:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ed}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Fd:{if(h){break Fd}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break Fd}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Fd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ed}f=H[i+4>>2];j=g;g=b>>>0>>0;H[j>>2]=H[i+(g?8:12)>>2];while(1){Gd:{if(h){break Gd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Gd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Gd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=l|16;if(!g){break zd}break;case 1:break Ad;case 2:break yd;case 3:break wd;default:break Pc}}n=k-4|0;i=H[k+4>>2]>>>20&4|(H[n>>2]>>>22&1|(c>>>15&16|(c>>>19&64|c>>>3&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=(Y<<2)+o|0;Hd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Hd}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Id:{if(h){break Id}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Id}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Id}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Hd}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Jd:{if(h){break Jd}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Jd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Jd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|256;H[k+4>>2]=H[k+4>>2]|64;r=H[e+108>>2];c=(g^l)<<22|c|128}l=P+(I[(c>>>6&495)+r|0]<<2)|0;i=H[l>>2];g=H[i>>2];b=b-g|0;Kd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;f=H[i+4>>2];if(b&32768){break Kd}f=H[i+4>>2];g=b>>>0>>0;H[l>>2]=H[i+(g?12:8)>>2];while(1){Ld:{if(h){break Ld}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ld}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ld}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=g?!f:f;break Kd}f=H[i+4>>2];j=l;l=b>>>0>>0;H[j>>2]=H[i+(l?8:12)>>2];while(1){Md:{if(h){break Md}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Md}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Md}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;f=l?f:!f}if(!f){break xd}}n=k-4|0;i=H[k+4>>2]>>>23&4|(H[n>>2]>>>25&1|(c>>>18&16|(c>>>22&64|c>>>6&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=o+u|0;Nd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Nd}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Od:{if(h){break Od}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Od}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Od}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Nd}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Pd:{if(h){break Pd}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Pd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Pd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024;r=H[e+108>>2]}i=P+(I[(c>>>9&495)+r|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Qd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Qd}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){Rd:{if(h){break Rd}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Rd}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Rd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Qd}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){Sd:{if(h){break Sd}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Sd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Sd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Pc}}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|c>>>9&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0}X=o+x|0;Td:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[r+4>>2];if(b&32768){break Td}j=H[r+4>>2];g=b>>>0>>0;H[i>>2]=H[(g?12:8)+r>>2];while(1){Ud:{if(h){break Ud}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Ud}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Ud}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Td}j=H[r+4>>2];l=b>>>0>>0;H[i>>2]=H[(l?8:12)+r>>2];while(1){Vd:{if(h){break Vd}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Vd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Vd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}g=I[s+24640|0];H[X>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|16384;H[k+4>>2]=H[k+4>>2]|4096;f=k+(H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|4;H[f+12>>2]=H[f+12>>2]|1;g=g^l;H[f+8>>2]=H[f+8>>2]|g<<18|2;c=g<<28|c|8192}H[k>>2]=c&-1226833921}c=k+4|0;o=o+4|0;M=M+1|0;if((Y|0)!=(M|0)){continue}break}c=k+12|0;o=o+x|0;t=t+4|0;g=H[e+128>>2];if(t>>>0<(g&-4)>>>0){continue}break}break Nc}c=(g&-4)-1|0;t=(c&-4)+4|0;c=(l+(c<<1&-8)|0)+20|0}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i;if(!Y|g>>>0<=t>>>0){break Cb}while(1){h=0;if(H[e+128>>2]!=(t|0)){while(1){Tb(e,c,(N(h,Y)<<2)+o|0,w,h,1);h=h+1|0;if(h>>>0>2]-t>>>0){continue}break}}H[c>>2]=H[c>>2]&-1226833921;o=o+4|0;c=c+4|0;v=v+1|0;if((Y|0)!=(v|0)){continue}break}break Cb}Wd:{if(g>>>0<4){break Wd}if(Y){m=e+100|0;p=e+96|0;x=N(Y,12);u=Y<<3;A=0-w|0;P=e+28|0;while(1){M=0;while(1){k=c;c=H[c>>2];Xd:{Yd:{Zd:{if(c){_d:{if(c&2097168){break _d}i=P+(I[H[e+108>>2]+(c&495)|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;$d:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break $d}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){ae:{if(h){break ae}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break ae}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break ae}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break $d}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){be:{if(h){break be}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break be}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break be}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break _d}q=k-4|0;f=H[k+4>>2]>>>17&4|(H[q>>2]>>>19&1|(c>>>14&16|(c>>>16&64|c&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ce:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ce}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){de:{if(h){break de}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break de}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break de}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ce}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ee:{if(h){break ee}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break ee}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break ee}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[o>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|32;H[k+4>>2]=H[k+4>>2]|8;f=k+(-2-H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|32768;l=g^l;H[f>>2]=H[f>>2]|l<<31|65536;g=f-4|0;H[g>>2]=H[g>>2]|131072;c=l<<19|c|16}fe:{if(c&16777344){break fe}f=c>>>3|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ge:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ge}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){he:{if(h){break he}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break he}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break he}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ge}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ie:{if(h){break ie}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break ie}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break ie}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break fe}q=k-4|0;f=H[k+4>>2]>>>20&4|(H[q>>2]>>>22&1|(c>>>15&16|(c>>>19&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=(Y<<2)+o|0;je:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break je}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ke:{if(h){break ke}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break ke}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break ke}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break je}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){le:{if(h){break le}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break le}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break le}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|256;H[k+4>>2]=H[k+4>>2]|64;c=(g^l)<<22|c|128}me:{if(c&134218752){break me}f=c>>>6|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ne:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ne}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){oe:{if(h){break oe}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break oe}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break oe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ne}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){pe:{if(h){break pe}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break pe}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break pe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break me}q=k-4|0;f=H[k+4>>2]>>>23&4|(H[q>>2]>>>25&1|(c>>>18&16|(c>>>22&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=o+u|0;qe:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break qe}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){re:{if(h){break re}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break re}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break re}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break qe}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){se:{if(h){break se}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break se}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break se}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024}if(c&1073750016){break Yd}f=c>>>9|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;te:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break te}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ue:{if(h){break ue}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break ue}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break ue}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break te}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ve:{if(h){break ve}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break ve}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break ve}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Yd}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|f&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0;break Zd}l=H[p>>2];c=H[l>>2];b=b-c|0;we:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;g=H[l+4>>2];if(b&32768){break we}i=H[l+4>>2];c=b>>>0>>0;H[p>>2]=H[l+(c?12:8)>>2];while(1){xe:{if(h){break xe}l=H[e+16>>2];g=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=g;h=8;d=(f<<8)+d|0;break xe}if(f>>>0<=143){H[e+16>>2]=g;d=(f<<9)+d|0;h=7;break xe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!i:i;break we}i=H[l+4>>2];g=b>>>0>>0;H[p>>2]=H[l+(g?8:12)>>2];while(1){ye:{if(h){break ye}l=H[e+16>>2];b=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=b;h=8;d=(f<<8)+d|0;break ye}if(f>>>0<=143){H[e+16>>2]=b;d=(f<<9)+d|0;h=7;break ye}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;g=g?i:!i}if(!g){i=p;break Xd}g=H[m>>2];c=H[g>>2];b=b-c|0;ze:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;l=H[g+4>>2];if(b&32768){break ze}f=H[g+4>>2];c=b>>>0>>0;g=H[(c?12:8)+g>>2];H[m>>2]=g;while(1){Ae:{if(h){break Ae}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ae}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ae}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=c?!f:f;break ze}f=H[g+4>>2];l=b>>>0>>0;g=H[(l?8:12)+g>>2];H[m>>2]=g;while(1){Be:{if(h){break Be}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Be}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Be}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;l=l?f:!f}c=H[g>>2];b=b-c|0;Ce:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;i=H[g+4>>2];if(b&32768){break Ce}f=H[g+4>>2];c=b>>>0>>0;H[m>>2]=H[(c?12:8)+g>>2];while(1){De:{if(h){break De}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break De}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break De}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!f:f;break Ce}f=H[g+4>>2];i=g;g=b>>>0>>0;H[m>>2]=H[i+(g?8:12)>>2];while(1){Ee:{if(h){break Ee}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Ee}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ee}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=g?f:!f}g=i;c=0;i=m;Fe:{Ge:{He:{Ie:{Je:{switch(g|l<<1){case 0:j=k-4|0;l=H[k+4>>2]>>>17&4|H[j>>2]>>>19&1;g=P+(I[l+24384|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ke:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ke}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Le:{if(h){break Le}i=H[e+16>>2];g=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(n<<8)+d|0;break Le}if(n>>>0<=143){H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Le}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ke}f=H[i+4>>2];q=g;g=b>>>0>>0;H[q>>2]=H[i+(g?8:12)>>2];while(1){Me:{if(h){break Me}i=H[e+16>>2];b=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Me}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Me}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=I[l+24640|0];H[o>>2]=(g|0)==(c|0)?w:A;H[j>>2]=H[j>>2]|32;H[k+4>>2]=H[k+4>>2]|8;l=k+(-2-H[e+124>>2]<<2)|0;H[l+4>>2]=H[l+4>>2]|32768;g=c^g;H[l>>2]=H[l>>2]|g<<31|65536;c=l-4|0;H[c>>2]=H[c>>2]|131072;l=g<<19;r=H[e+108>>2];g=P+(I[r+2|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ne:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ne}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Oe:{if(h){break Oe}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break Oe}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Oe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ne}f=H[i+4>>2];j=g;g=b>>>0>>0;H[j>>2]=H[i+(g?8:12)>>2];while(1){Pe:{if(h){break Pe}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Pe}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Pe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=l|16;if(!g){break Ie}break;case 1:break Je;case 2:break He;case 3:break Fe;default:break Yd}}n=k-4|0;i=H[k+4>>2]>>>20&4|(H[n>>2]>>>22&1|(c>>>15&16|(c>>>19&64|c>>>3&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=(Y<<2)+o|0;Qe:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Qe}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Re:{if(h){break Re}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Re}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Re}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Qe}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Se:{if(h){break Se}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Se}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Se}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|256;H[k+4>>2]=H[k+4>>2]|64;r=H[e+108>>2];c=(g^l)<<22|c|128}l=P+(I[(c>>>6&495)+r|0]<<2)|0;i=H[l>>2];g=H[i>>2];b=b-g|0;Te:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;f=H[i+4>>2];if(b&32768){break Te}f=H[i+4>>2];g=b>>>0>>0;H[l>>2]=H[i+(g?12:8)>>2];while(1){Ue:{if(h){break Ue}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ue}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ue}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=g?!f:f;break Te}f=H[i+4>>2];j=l;l=b>>>0>>0;H[j>>2]=H[i+(l?8:12)>>2];while(1){Ve:{if(h){break Ve}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Ve}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ve}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;f=l?f:!f}if(!f){break Ge}}n=k-4|0;i=H[k+4>>2]>>>23&4|(H[n>>2]>>>25&1|(c>>>18&16|(c>>>22&64|c>>>6&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=o+u|0;We:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break We}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Xe:{if(h){break Xe}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Xe}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Xe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break We}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Ye:{if(h){break Ye}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Ye}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Ye}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024;r=H[e+108>>2]}i=P+(I[(c>>>9&495)+r|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Ze:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Ze}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){_e:{if(h){break _e}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break _e}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break _e}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Ze}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){$e:{if(h){break $e}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break $e}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break $e}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Yd}}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|c>>>9&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0}X=o+x|0;af:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[r+4>>2];if(b&32768){break af}j=H[r+4>>2];g=b>>>0>>0;H[i>>2]=H[(g?12:8)+r>>2];while(1){bf:{if(h){break bf}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break bf}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break bf}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break af}j=H[r+4>>2];l=b>>>0>>0;H[i>>2]=H[(l?8:12)+r>>2];while(1){cf:{if(h){break cf}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break cf}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break cf}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}g=I[s+24640|0];H[X>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|16384;H[k+4>>2]=H[k+4>>2]|4096;f=k+(H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|4;H[f+12>>2]=H[f+12>>2]|1;g=g^l;H[f+8>>2]=H[f+8>>2]|g<<18|2;c=g<<28|c|8192}H[k>>2]=c&-1226833921}c=k+4|0;o=o+4|0;M=M+1|0;if((Y|0)!=(M|0)){continue}break}c=k+12|0;o=o+x|0;t=t+4|0;g=H[e+128>>2];if(t>>>0<(g&-4)>>>0){continue}break}break Wd}c=(g&-4)-1|0;t=(c&-4)+4|0;c=(l+(c<<1&-8)|0)+20|0}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i;if(!Y|g>>>0<=t>>>0){break Cb}while(1){h=0;if(H[e+128>>2]!=(t|0)){while(1){Tb(e,c,(N(h,Y)<<2)+o|0,w,h,0);h=h+1|0;if(h>>>0>2]-t>>>0){continue}break}}H[c>>2]=H[c>>2]&-1226833921;o=o+4|0;c=c+4|0;v=v+1|0;if((Y|0)!=(v|0)){continue}break}break Cb}while(1){s=0;while(1){k=c;f=g;g=H[g>>2];df:{ef:{ff:{if(!g){i=H[l>>2];g=H[i>>2];b=b-g|0;gf:{if(d>>>16>>>0>>0){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?8:12)>>2];while(1){hf:{if(h){break hf}i=H[e+16>>2];b=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hf}H[e+16>>2]=b;d=(o<<9)+d|0;h=7;break hf}H[e+16>>2]=b;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?m:!m;break gf}d=d-(g<<16)|0;if(!(b&32768)){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?12:8)>>2];while(1){jf:{if(h){break jf}i=H[e+16>>2];g=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break jf}H[e+16>>2]=g;d=(o<<9)+d|0;h=7;break jf}H[e+16>>2]=g;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!m:m;break gf}c=H[i+4>>2]}if(!c){i=l;break df}c=H[p>>2];g=H[c>>2];b=b-g|0;kf:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=b>>>0>>0;c=H[(i?8:12)+c>>2];H[p>>2]=c;while(1){lf:{if(h){break lf}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lf}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break lf}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=i?o:!o;break kf}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];g=b>>>0>>0;c=H[(g?12:8)+c>>2];H[p>>2]=c;while(1){mf:{if(h){break mf}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break mf}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break mf}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=g?!o:o;break kf}m=H[c+4>>2]}g=H[c>>2];b=b-g|0;nf:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?8:12)>>2];while(1){of:{if(h){break of}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break of}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break of}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break nf}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?12:8)>>2];while(1){pf:{if(h){break pf}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break pf}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break pf}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break nf}c=H[c+4>>2]}g=0;i=p;qf:{rf:{sf:{tf:{uf:{switch(c|m<<1){case 0:j=f-4|0;i=H[f+4>>2]>>>17&4|H[j>>2]>>>19&1;c=A+(I[i+24384|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;vf:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){wf:{if(h){break wf}m=H[e+16>>2];b=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break wf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break wf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;g=c?o:!o;break vf}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){xf:{if(h){break xf}m=H[e+16>>2];g=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break xf}H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break xf}H[e+16>>2]=g;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!o:o;break vf}g=H[m+4>>2]}c=I[i+24640|0];H[k>>2]=(g|0)==(c|0)?M:x;H[j>>2]=H[j>>2]|32;H[f+4>>2]=H[f+4>>2]|8;i=(c^g)<<19;r=H[e+108>>2];c=A+(I[r+2|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;yf:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){zf:{if(h){break zf}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break zf}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break zf}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break yf}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){Af:{if(h){break Af}m=H[e+16>>2];g=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Af}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Af}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break yf}c=H[m+4>>2]}g=i|16;if(!c){break tf}break;case 1:break uf;case 2:break sf;case 3:break qf;default:break ef}}n=f-4|0;m=H[f+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|g>>>3&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Bf:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){Cf:{if(h){break Cf}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Cf}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Cf}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break Bf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Df:{if(h){break Df}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Df}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break Df}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break Bf}i=H[o+4>>2]}c=I[m+24640|0];H[k+256>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|256;H[f+4>>2]=H[f+4>>2]|64;r=H[e+108>>2];g=(c^i)<<22|g|128}i=A+(I[(g>>>6&495)+r|0]<<2)|0;m=H[i>>2];c=H[m>>2];b=b-c|0;Ef:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[m+(i?8:12)>>2];while(1){Ff:{if(h){break Ff}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Ff}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ff}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=i?o:!o;break Ef}d=d-(c<<16)|0;if(!(b&32768)){o=H[m+4>>2];c=b>>>0>>0;H[i>>2]=H[m+(c?12:8)>>2];while(1){Gf:{if(h){break Gf}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Gf}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break Gf}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Ef}c=H[m+4>>2]}if(!c){break rf}}n=f-4|0;m=H[f+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|g>>>6&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Hf:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){If:{if(h){break If}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break If}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break If}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break Hf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Jf:{if(h){break Jf}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jf}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break Jf}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break Hf}i=H[o+4>>2]}c=I[m+24640|0];H[k+512>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^i)<<25|g|1024;r=H[e+108>>2]}i=A+(I[(g>>>9&495)+r|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Kf:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){Lf:{if(h){break Lf}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Lf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Lf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break Kf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Mf:{if(h){break Mf}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mf}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Mf}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break Kf}c=H[o+4>>2]}if(!c){break ef}}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|g>>>9&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0;break ff}Nf:{if(g&2097168){break Nf}i=A+(I[H[e+108>>2]+(g&495)|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Of:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){Pf:{if(h){break Pf}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Pf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Pf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break Of}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Qf:{if(h){break Qf}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Qf}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Qf}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break Of}c=H[o+4>>2]}if(!c){break Nf}q=f-4|0;o=H[f+4>>2]>>>17&4|(H[q>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Rf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Sf:{if(h){break Sf}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Sf}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Sf}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Rf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Tf:{if(h){break Tf}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Tf}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break Tf}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Rf}m=H[j+4>>2]}c=I[o+24640|0];H[k>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|32;H[f+4>>2]=H[f+4>>2]|8;g=(c^m)<<19|g|16}Uf:{if(g&16777344){break Uf}o=g>>>3|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Vf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Wf:{if(h){break Wf}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Wf}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Wf}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Vf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Xf:{if(h){break Xf}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Xf}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Xf}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Vf}c=H[j+4>>2]}if(!c){break Uf}q=f-4|0;o=H[f+4>>2]>>>20&4|(H[q>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Yf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Zf:{if(h){break Zf}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Zf}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Zf}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Yf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){_f:{if(h){break _f}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break _f}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break _f}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Yf}m=H[j+4>>2]}c=I[o+24640|0];H[k+256>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|256;H[f+4>>2]=H[f+4>>2]|64;g=(c^m)<<22|g|128}$f:{if(g&134218752){break $f}o=g>>>6|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;ag:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){bg:{if(h){break bg}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break bg}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break bg}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break ag}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){cg:{if(h){break cg}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break cg}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break cg}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break ag}c=H[j+4>>2]}if(!c){break $f}q=f-4|0;o=H[f+4>>2]>>>23&4|(H[q>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;dg:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){eg:{if(h){break eg}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break eg}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break eg}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break dg}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){fg:{if(h){break fg}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break fg}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break fg}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break dg}m=H[j+4>>2]}c=I[o+24640|0];H[k+512>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^m)<<25|g|1024}if(g&1073750016){break ef}o=g>>>9|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;gg:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){hg:{if(h){break hg}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hg}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break hg}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break gg}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){ig:{if(h){break ig}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ig}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break ig}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break gg}c=H[j+4>>2]}if(!c){break ef}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0}jg:{if(d>>>16>>>0>>0){j=H[r+4>>2];m=b>>>0>>0;H[i>>2]=H[(m?8:12)+r>>2];while(1){kg:{if(h){break kg}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break kg}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break kg}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?j:!j;break jg}d=d-(c<<16)|0;if(!(b&32768)){j=H[r+4>>2];c=b>>>0>>0;H[i>>2]=H[(c?12:8)+r>>2];while(1){lg:{if(h){break lg}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lg}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break lg}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!j:j;break jg}m=H[r+4>>2]}c=I[q+24640|0];H[k+768>>2]=(m|0)==(c|0)?M:x;H[v>>2]=H[v>>2]|16384;H[f+4>>2]=H[f+4>>2]|4096;H[f+260>>2]=H[f+260>>2]|4;H[f+268>>2]=H[f+268>>2]|1;c=c^m;H[f+264>>2]=H[f+264>>2]|c<<18|2;g=c<<28|g|8192}H[f>>2]=g&-1226833921}g=f+4|0;c=k+4|0;s=s+1|0;if((s|0)!=64){continue}break}g=f+12|0;c=k+772|0;m=t>>>0<60;t=t+4|0;if(m){continue}break}}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i}mg:{if(!(ba&32)){break mg}H[e+104>>2]=e+100;g=H[e+100>>2];b=H[g>>2];d=H[e+4>>2]-b|0;H[e+4>>2]=d;h=H[e>>2];ng:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){og:{if(d){break og}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break og}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break og}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break ng}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break ng}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){pg:{if(b){break pg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break pg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break pg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;qg:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){rg:{if(d){break rg}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break rg}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break rg}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break qg}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break qg}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){sg:{if(b){break sg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break sg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break sg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;tg:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){ug:{if(d){break ug}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break ug}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break ug}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break tg}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break tg}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){vg:{if(b){break vg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break vg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break vg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;if(h>>>16>>>0>>0){H[e+4>>2]=b;H[e+100>>2]=H[(b>>>0>d>>>0?8:12)+g>>2];d=H[e+8>>2];while(1){wg:{if(d){break wg}g=H[e+16>>2];c=g+1|0;l=I[g+1|0];if(I[g|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break wg}H[e+16>>2]=c;h=(l<<9)+h|0;d=7;break wg}H[e+16>>2]=c;d=8;h=(l<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}break mg}c=h-(b<<16)|0;H[e>>2]=c;if(d&32768){break mg}H[e+100>>2]=H[(b>>>0>d>>>0?12:8)+g>>2];h=H[e+8>>2];while(1){xg:{if(h){break xg}g=H[e+16>>2];b=g+1|0;l=I[g+1|0];if(I[g|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;c=c+65280|0;h=8;break xg}H[e+16>>2]=b;c=(l<<9)+c|0;h=7;break xg}H[e+16>>2]=b;h=8;c=(l<<8)+c|0}h=h-1|0;H[e+8>>2]=h;c=c<<1;H[e>>2]=c;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}break bb;case 0:break db}}if(Z){u=1<>>1|0;n=H[e+124>>2];d=n<<2;b=(d+H[e+120>>2]|0)+12|0;g=H[e+116>>2];k=0;c=H[e+128>>2];if(c>>>0>=4){if(!n){break ab}t=N(n,12);m=n<<3;o=0-u|0;while(1){c=0;while(1){l=b;b=H[b>>2];yg:{if(!b){break yg}if((b&2097168)==16){b=H[e>>2];h=H[e+8>>2];zg:{if(h){break zg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Ag:{if(p){h=8}else{if(b>>>0>143){break Ag}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break zg}h=8;b=255}i=h-1|0;H[e+8>>2]=i;i=b>>>i&1;b=H[g>>2];H[g>>2]=((i|0)==(b>>>31|0)?o:u)+b;b=H[l>>2]|1048576;H[l>>2]=b}if((b&16777344)==128){b=H[e>>2];h=H[e+8>>2];Bg:{if(h){break Bg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Cg:{if(p){h=8}else{if(b>>>0>143){break Cg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Bg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=d+g|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);b=H[l>>2]|8388608;H[l>>2]=b}if((b&134218752)==1024){b=H[e>>2];h=H[e+8>>2];Dg:{if(h){break Dg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Eg:{if(p){h=8}else{if(b>>>0>143){break Eg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Dg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=g+m|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);b=H[l>>2]|67108864;H[l>>2]=b}if((b&1073750016)!=8192){break yg}b=H[e>>2];h=H[e+8>>2];Fg:{if(h){break Fg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Gg:{if(p){h=8}else{if(b>>>0>143){break Gg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Fg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=g+t|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);H[l>>2]=H[l>>2]|536870912}g=g+4|0;b=l+4|0;c=c+1|0;if((n|0)!=(c|0)){continue}break}g=g+t|0;b=l+12|0;k=k+4|0;c=H[e+128>>2];if(k>>>0<(c&-4)>>>0){continue}break}}if(!n|c>>>0<=k>>>0){break bb}q=0;p=0-u|0;d=c;while(1){Hg:{if((d|0)==(k|0)){d=k;break Hg}h=H[b>>2];r=0;while(1){d=N(r,3);if((2097168<>2];j=H[e+8>>2];Ig:{if(j){break Ig}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];Jg:{if(i){j=8}else{if(c>>>0>143){break Jg}j=7}H[e>>2]=c;H[e+16>>2]=l+1;break Ig}j=8;c=255}l=j-1|0;H[e+8>>2]=l;l=c>>>l&1;c=H[m>>2];H[m>>2]=((l|0)==(c>>>31|0)?p:u)+c;h=H[b>>2]|1048576<>2]=h;c=H[e+128>>2]}r=r+1|0;d=c;if(r>>>0>>0){continue}break}}g=g+4|0;b=b+4|0;q=q+1|0;if((n|0)!=(q|0)){continue}break}break bb}i=H[e+120>>2];d=H[e+116>>2];v=H[e+124>>2];c=H[e+128>>2];if(!((v|0)!=64|(c|0)!=64)){c=i+268|0;t=0;q=1<>>1|0;n=0-q|0;r=H[e+8>>2];g=H[e+4>>2];b=H[e>>2];k=H[e+104>>2];while(1){j=0;while(1){p=d;i=c;d=H[c>>2];if(d){l=c;if((d&2097168)==16){k=ea+((d&1048576?16:d&495?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Kg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Lg:{if(r){break Lg}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Lg}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Lg}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Kg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Mg:{if(r){break Mg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Mg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Mg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Kg}m=H[f+4>>2]}c=H[p>>2];H[p>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|1048576}if((d&16777344)==128){k=ea+((d&8388608?16:d&3960?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Ng:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Og:{if(r){break Og}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Og}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Og}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Ng}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Pg:{if(r){break Pg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Pg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Pg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Ng}m=H[f+4>>2]}c=H[p+256>>2];H[p+256>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|8388608}if((d&134218752)==1024){k=ea+((d&67108864?16:d&31680?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Qg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Rg:{if(r){break Rg}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Rg}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Rg}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Qg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Sg:{if(r){break Sg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Sg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Sg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Qg}m=H[f+4>>2]}c=H[p+512>>2];H[p+512>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|67108864}if((d&1073750016)==8192){k=ea+((d&536870912?16:d&253440?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Tg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Ug:{if(r){break Ug}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Ug}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Ug}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Tg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Vg:{if(r){break Vg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Vg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Vg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Tg}m=H[f+4>>2]}c=H[p+768>>2];H[p+768>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|536870912}H[l>>2]=d}c=i+4|0;d=p+4|0;j=j+1|0;if((j|0)!=64){continue}break}c=i+12|0;d=p+772|0;l=t>>>0<60;t=t+4|0;if(l){continue}break}H[e+8>>2]=r;H[e+4>>2]=g;H[e>>2]=b;H[e+104>>2]=k;break bb}s=1<>>1|0;t=v<<2;f=(t+i|0)+12|0;r=H[e+8>>2];g=H[e+4>>2];b=H[e>>2];k=H[e+104>>2];o=0;Wg:{if(c>>>0<4){break Wg}if(v){n=N(v,12);m=v<<3;u=0-s|0;while(1){j=0;while(1){l=f;i=H[f>>2];if(i){if((i&2097168)==16){k=ea+((i&1048576?16:i&495?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Xg:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break Xg}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Yg:{if(r){break Yg}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break Yg}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break Yg}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break Xg}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){Zg:{if(r){break Zg}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break Zg}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break Zg}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}c=H[d>>2];H[d>>2]=((p|0)==(c>>>31|0)?u:s)+c;i=i|1048576}if((i&16777344)==128){k=ea+((i&8388608?16:i&3960?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;_g:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break _g}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){$g:{if(r){break $g}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break $g}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break $g}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break _g}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){ah:{if(r){break ah}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break ah}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break ah}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+t|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|8388608}if((i&134218752)==1024){k=ea+((i&67108864?16:i&31680?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;bh:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break bh}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){ch:{if(r){break ch}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break ch}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break ch}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break bh}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){dh:{if(r){break dh}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break dh}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break dh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+m|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|67108864}if((i&1073750016)==8192){k=ea+((i&536870912?16:i&253440?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;eh:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break eh}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){fh:{if(r){break fh}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break fh}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break fh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break eh}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){gh:{if(r){break gh}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break gh}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break gh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+n|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|536870912}H[l>>2]=i}f=l+4|0;d=d+4|0;j=j+1|0;if((v|0)!=(j|0)){continue}break}f=l+12|0;d=d+n|0;o=o+4|0;c=H[e+128>>2];if(o>>>0<(c&-4)>>>0){continue}break}break Wg}l=(c&-4)-1|0;o=(l&-4)+4|0;f=(i+(l<<1&-8)|0)+20|0}H[e+8>>2]=r;H[e+4>>2]=g;H[e>>2]=b;H[e+104>>2]=k;if(!v|c>>>0<=o>>>0){break bb}t=0;l=0-s|0;b=c;while(1){hh:{if((b|0)==(o|0)){b=o;break hh}r=H[f>>2];h=0;while(1){n=N(h,3);if((2097168<>>n|0;i=ea+((b&1048576?16:b&495?15:14)<<2)|0;H[e+104>>2]=i;p=H[i>>2];b=H[p>>2];c=H[e+4>>2]-b|0;H[e+4>>2]=c;g=H[e>>2];ih:{if(g>>>16>>>0>>0){m=H[p+4>>2];H[e+4>>2]=b;c=b>>>0>c>>>0;H[i>>2]=H[p+(c?8:12)>>2];r=H[e+8>>2];while(1){jh:{if(r){break jh}p=H[e+16>>2];i=p+1|0;j=I[p+1|0];if(I[p|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;g=g+65280|0;r=8;break jh}H[e+16>>2]=i;g=(j<<9)+g|0;r=7;break jh}H[e+16>>2]=i;r=8;g=(j<<8)+g|0}r=r-1|0;H[e+8>>2]=r;g=g<<1;H[e>>2]=g;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}c=c?m:!m;break ih}g=g-(b<<16)|0;H[e>>2]=g;if(!(c&32768)){m=H[p+4>>2];b=b>>>0>c>>>0;H[i>>2]=H[p+(b?12:8)>>2];r=H[e+8>>2];while(1){kh:{if(r){break kh}p=H[e+16>>2];i=p+1|0;j=I[p+1|0];if(I[p|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;g=g+65280|0;r=8;break kh}H[e+16>>2]=i;g=(j<<9)+g|0;r=7;break kh}H[e+16>>2]=i;r=8;g=(j<<8)+g|0}r=r-1|0;H[e+8>>2]=r;g=g<<1;H[e>>2]=g;c=c<<1;H[e+4>>2]=c;if(c>>>0<32768){continue}break}c=b?!m:m;break ih}c=H[p+4>>2]}b=H[k>>2];H[k>>2]=((c|0)==(b>>>31|0)?l:s)+b;r=H[f>>2]|1048576<>2]=r;c=H[e+128>>2]}h=h+1|0;b=c;if(h>>>0>>0){continue}break}}f=f+4|0;d=d+4|0;t=t+1|0;if((v|0)!=(t|0)){continue}break}break bb}i=0;t=0;q=0;lh:{mh:{nh:{A=H[e+124>>2];if(!((A|0)!=64|H[e+128>>2]!=64)){b=1<>>1|b;l=0-i|0;s=e+28|0;g=H[e+120>>2]+268|0;f=H[e+8>>2];c=H[e+4>>2];k=H[e>>2];j=H[e+104>>2];b=H[e+116>>2];if(ba&8){break nh}while(1){q=0;while(1){p=b;m=g;g=H[g>>2];if(g){oh:{if(g&2097168){break oh}b=g&495;if(!b){break oh}j=s+(I[b+H[e+108>>2]|0]<<2)|0;h=H[j>>2];b=H[h>>2];c=c-b|0;ph:{if(k>>>16>>>0>>0){o=H[h+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[h+(d?8:12)>>2];while(1){qh:{if(f){break qh}f=H[e+16>>2];c=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break qh}H[e+16>>2]=c;k=(h<<9)+k|0;f=7;break qh}H[e+16>>2]=c;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?o:!o;break ph}k=k-(b<<16)|0;if(!(c&32768)){o=H[h+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[h+(b?12:8)>>2];while(1){rh:{if(f){break rh}f=H[e+16>>2];d=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break rh}H[e+16>>2]=d;k=(h<<9)+k|0;f=7;break rh}H[e+16>>2]=d;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!o:o;break ph}b=H[h+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>17&4|(H[n>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];sh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){th:{if(f){break th}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break th}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break th}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;d=d?h:!h;break sh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){uh:{if(f){break uh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break uh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break uh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}d=b?!h:h;break sh}d=H[u+4>>2]}H[p>>2]=(o|0)==(d|0)?i:l;H[n>>2]=H[n>>2]|32;H[m+4>>2]=H[m+4>>2]|8;b=m-268|0;H[b>>2]=H[b>>2]|131072;b=m-260|0;H[b>>2]=H[b>>2]|32768;b=m-264|0;h=b;x=H[b>>2];b=d^o;H[h>>2]=x|b<<31|65536;g=b<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){o=g>>>3|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;vh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){wh:{if(f){break wh}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break wh}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break wh}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break vh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){xh:{if(f){break xh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break xh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break xh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break vh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];yh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){zh:{if(f){break zh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break zh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break zh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break yh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Ah:{if(f){break Ah}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ah}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Ah}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break yh}b=H[u+4>>2]}H[p+256>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^o)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){o=g>>>6|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Bh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ch:{if(f){break Ch}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ch}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ch}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Bh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Dh:{if(f){break Dh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Dh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Dh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Bh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Eh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Fh:{if(f){break Fh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Fh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Fh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Eh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Gh:{if(f){break Gh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Gh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Gh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Eh}b=H[u+4>>2]}H[p+512>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^o)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){o=g>>>9|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Hh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ih:{if(f){break Ih}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ih}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ih}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Hh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Jh:{if(f){break Jh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Jh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Jh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Hh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>26&4|(H[n>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Kh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Lh:{if(f){break Lh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Lh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Lh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Kh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Mh:{if(f){break Mh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Mh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Mh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Kh}b=H[u+4>>2]}H[p+768>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;H[m+260>>2]=H[m+260>>2]|4;H[m+268>>2]=H[m+268>>2]|1;b=b^o;H[m+264>>2]=H[m+264>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;b=p+4|0;q=q+1|0;if((q|0)!=64){continue}break}g=m+12|0;b=p+772|0;p=t>>>0<60;t=t+4|0;if(p){continue}break}break mh}b=1<>>1|b;p=H[e+120>>2];g=(p+(A<<2)|0)+12|0;b=H[e+128>>2];f=H[e+8>>2];c=H[e+4>>2];k=H[e>>2];j=H[e+104>>2];o=H[e+116>>2];Nh:{if(ba&8){Oh:{if(b>>>0<4){break Oh}if(A){x=N(A,12);u=A<<3;p=0-l|0;M=e+28|0;while(1){t=0;while(1){m=g;g=H[g>>2];if(g){Ph:{if(g&2097168){break Ph}b=g&495;if(!b){break Ph}j=M+(I[b+H[e+108>>2]|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Qh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;h=H[n+4>>2];if(c&32768){break Qh}h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Rh:{if(f){break Rh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(n<<8)+k|0;break Rh}if(n>>>0<=143){H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Rh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}h=b?!h:h;break Qh}h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Sh:{if(f){break Sh}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(n<<8)+k|0;break Sh}if(n>>>0<=143){H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Sh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;h=d?h:!h}if(h){s=m-4|0;d=H[m+4>>2]>>>17&4|(H[s>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=M+(I[d+24384|0]<<2)|0;v=H[j>>2];b=H[v>>2];c=c-b|0;h=I[d+24640|0];Th:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[v+4>>2];if(c&32768){break Th}n=H[v+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[v+(b?12:8)>>2];while(1){Uh:{if(f){break Uh}f=H[e+16>>2];d=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(v<<8)+k|0;break Uh}if(v>>>0<=143){H[e+16>>2]=d;k=(v<<9)+k|0;f=7;break Uh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Th}n=H[v+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[v+(d?8:12)>>2];while(1){Vh:{if(f){break Vh}f=H[e+16>>2];c=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(v<<8)+k|0;break Vh}if(v>>>0<=143){H[e+16>>2]=c;k=(v<<9)+k|0;f=7;break Vh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}b=w;H[o>>2]=(h|0)==(b|0)?l:p;H[s>>2]=H[s>>2]|32;H[m+4>>2]=H[m+4>>2]|8;g=(b^h)<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){h=g>>>3|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Wh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Wh}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Xh:{if(f){break Xh}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Xh}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Xh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Wh}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Yh:{if(f){break Yh}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Yh}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Yh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>20&4|(H[v>>2]>>>22&1|(g>>>15&16|(g>>>19&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=(A<<2)+o|0;h=I[d+24640|0];Zh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Zh}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){_h:{if(f){break _h}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break _h}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break _h}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Zh}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){$h:{if(f){break $h}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break $h}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break $h}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^h)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){h=g>>>6|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;ai:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break ai}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){bi:{if(f){break bi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break bi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break bi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break ai}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){ci:{if(f){break ci}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break ci}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break ci}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>23&4|(H[v>>2]>>>25&1|(g>>>18&16|(g>>>22&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+u|0;h=I[d+24640|0];di:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break di}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){ei:{if(f){break ei}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break ei}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break ei}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break di}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){fi:{if(f){break fi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break fi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break fi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^h)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){h=g>>>9|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;gi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break gi}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){hi:{if(f){break hi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break hi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break hi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break gi}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){ii:{if(f){break ii}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break ii}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break ii}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+x|0;h=I[d+24640|0];ji:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break ji}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){ki:{if(f){break ki}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break ki}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break ki}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break ji}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){li:{if(f){break li}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break li}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break li}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;d=m+(H[e+124>>2]<<2)|0;H[d+4>>2]=H[d+4>>2]|4;H[d+12>>2]=H[d+12>>2]|1;b=b^h;H[d+8>>2]=H[d+8>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;o=o+4|0;t=t+1|0;if((A|0)!=(t|0)){continue}break}g=m+12|0;o=o+x|0;i=i+4|0;b=H[e+128>>2];if(i>>>0<(b&-4)>>>0){continue}break}break Oh}g=(b&-4)-1|0;i=(g&-4)+4|0;g=(p+(g<<1&-8)|0)+20|0}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j;if(!A|b>>>0<=i>>>0){break Nh}while(1){c=(b|0)==(i|0);f=0;b=i;if(!c){while(1){Ub(e,g,(N(f,A)<<2)+o|0,l,f,H[e+124>>2]+2|0,1);f=f+1|0;b=H[e+128>>2];if(f>>>0>>0){continue}break}}g=g+4|0;o=o+4|0;q=q+1|0;if((A|0)!=(q|0)){continue}break}break Nh}mi:{if(b>>>0<4){break mi}if(A){x=N(A,12);u=A<<3;p=0-l|0;M=e+28|0;while(1){t=0;while(1){m=g;g=H[g>>2];if(g){ni:{if(g&2097168){break ni}b=g&495;if(!b){break ni}j=M+(I[b+H[e+108>>2]|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;oi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;h=H[n+4>>2];if(c&32768){break oi}h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){pi:{if(f){break pi}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(n<<8)+k|0;break pi}if(n>>>0<=143){H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break pi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}h=b?!h:h;break oi}h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){qi:{if(f){break qi}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(n<<8)+k|0;break qi}if(n>>>0<=143){H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break qi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;h=d?h:!h}if(h){s=m-4|0;d=H[m+4>>2]>>>17&4|(H[s>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=M+(I[d+24384|0]<<2)|0;v=H[j>>2];b=H[v>>2];c=c-b|0;h=I[d+24640|0];ri:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;d=H[v+4>>2];if(c&32768){break ri}n=H[v+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[v+(b?12:8)>>2];while(1){si:{if(f){break si}f=H[e+16>>2];d=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(v<<8)+k|0;break si}if(v>>>0<=143){H[e+16>>2]=d;k=(v<<9)+k|0;f=7;break si}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}d=b?!n:n;break ri}n=H[v+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[v+(d?8:12)>>2];while(1){ti:{if(f){break ti}f=H[e+16>>2];c=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(v<<8)+k|0;break ti}if(v>>>0<=143){H[e+16>>2]=c;k=(v<<9)+k|0;f=7;break ti}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;d=d?n:!n}H[o>>2]=(h|0)==(d|0)?l:p;H[s>>2]=H[s>>2]|32;H[m+4>>2]=H[m+4>>2]|8;b=m+(-2-H[e+124>>2]<<2)|0;H[b+4>>2]=H[b+4>>2]|32768;d=d^h;H[b>>2]=H[b>>2]|d<<31|65536;b=b-4|0;H[b>>2]=H[b>>2]|131072;g=d<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){h=g>>>3|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;ui:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break ui}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){vi:{if(f){break vi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break vi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break vi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break ui}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){wi:{if(f){break wi}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break wi}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break wi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>20&4|(H[v>>2]>>>22&1|(g>>>15&16|(g>>>19&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=(A<<2)+o|0;h=I[d+24640|0];xi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break xi}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){yi:{if(f){break yi}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break yi}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break yi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break xi}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){zi:{if(f){break zi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break zi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break zi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^h)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){h=g>>>6|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Ai:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Ai}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Bi:{if(f){break Bi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Bi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Bi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Ai}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Ci:{if(f){break Ci}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Ci}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Ci}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>23&4|(H[v>>2]>>>25&1|(g>>>18&16|(g>>>22&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+u|0;h=I[d+24640|0];Di:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Di}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){Ei:{if(f){break Ei}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break Ei}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break Ei}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Di}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){Fi:{if(f){break Fi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break Fi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break Fi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^h)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){h=g>>>9|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Gi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Gi}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Hi:{if(f){break Hi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Hi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Hi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Gi}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Ii:{if(f){break Ii}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Ii}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Ii}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+x|0;h=I[d+24640|0];Ji:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Ji}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){Ki:{if(f){break Ki}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break Ki}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break Ki}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Ji}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){Li:{if(f){break Li}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break Li}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break Li}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;d=m+(H[e+124>>2]<<2)|0;H[d+4>>2]=H[d+4>>2]|4;H[d+12>>2]=H[d+12>>2]|1;b=b^h;H[d+8>>2]=H[d+8>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;o=o+4|0;t=t+1|0;if((A|0)!=(t|0)){continue}break}g=m+12|0;o=o+x|0;i=i+4|0;b=H[e+128>>2];if(i>>>0<(b&-4)>>>0){continue}break}break mi}g=(b&-4)-1|0;i=(g&-4)+4|0;g=(p+(g<<1&-8)|0)+20|0}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j;if(!A|b>>>0<=i>>>0){break Nh}while(1){c=(b|0)==(i|0);f=0;b=i;if(!c){while(1){Ub(e,g,(N(f,A)<<2)+o|0,l,f,H[e+124>>2]+2|0,0);f=f+1|0;b=H[e+128>>2];if(f>>>0>>0){continue}break}}g=g+4|0;o=o+4|0;q=q+1|0;if((A|0)!=(q|0)){continue}break}}break lh}while(1){q=0;while(1){p=b;m=g;g=H[g>>2];if(g){Mi:{if(g&2097168){break Mi}b=g&495;if(!b){break Mi}j=s+(I[b+H[e+108>>2]|0]<<2)|0;h=H[j>>2];b=H[h>>2];c=c-b|0;Ni:{if(k>>>16>>>0>>0){o=H[h+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[h+(d?8:12)>>2];while(1){Oi:{if(f){break Oi}f=H[e+16>>2];c=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Oi}H[e+16>>2]=c;k=(h<<9)+k|0;f=7;break Oi}H[e+16>>2]=c;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?o:!o;break Ni}k=k-(b<<16)|0;if(!(c&32768)){o=H[h+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[h+(b?12:8)>>2];while(1){Pi:{if(f){break Pi}f=H[e+16>>2];d=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Pi}H[e+16>>2]=d;k=(h<<9)+k|0;f=7;break Pi}H[e+16>>2]=d;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!o:o;break Ni}b=H[h+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>17&4|(H[n>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Qi:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Ri:{if(f){break Ri}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ri}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Ri}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Qi}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Si:{if(f){break Si}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Si}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Si}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Qi}b=H[u+4>>2]}H[p>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|32;H[m+4>>2]=H[m+4>>2]|8;g=(b^o)<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){o=g>>>3|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Ti:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ui:{if(f){break Ui}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ui}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ui}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Ti}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Vi:{if(f){break Vi}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Vi}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Vi}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Ti}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Wi:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Xi:{if(f){break Xi}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Xi}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Xi}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Wi}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Yi:{if(f){break Yi}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Yi}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Yi}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Wi}b=H[u+4>>2]}H[p+256>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^o)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){o=g>>>6|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Zi:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){_i:{if(f){break _i}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break _i}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break _i}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Zi}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){$i:{if(f){break $i}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break $i}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break $i}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Zi}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];aj:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){bj:{if(f){break bj}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break bj}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break bj}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break aj}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){cj:{if(f){break cj}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break cj}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break cj}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break aj}b=H[u+4>>2]}H[p+512>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^o)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){o=g>>>9|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;dj:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){ej:{if(f){break ej}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break ej}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break ej}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break dj}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){fj:{if(f){break fj}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break fj}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break fj}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break dj}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>26&4|(H[n>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];gj:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){hj:{if(f){break hj}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break hj}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break hj}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break gj}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){ij:{if(f){break ij}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break ij}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break ij}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break gj}b=H[u+4>>2]}H[p+768>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;H[m+260>>2]=H[m+260>>2]|4;H[m+268>>2]=H[m+268>>2]|1;b=b^o;H[m+264>>2]=H[m+264>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;b=p+4|0;q=q+1|0;if((q|0)!=64){continue}break}g=m+12|0;b=p+772|0;p=t>>>0<60;t=t+4|0;if(p){continue}break}}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j}}if(T){break ab}Vb(e);Wa(e,18,46);Wa(e,17,3);Wa(e,0,4)}b=_+1|0;c=(b|0)==3;_=c?0:b;U=U-c|0;V=V+1|0;if(V>>>0>=K[la+8>>2]){break $a}if((U|0)>0){continue}break}}ha=ha+ja|0;c=H[e+24>>2];b=J[e+112>>1];F[c|0]=b;F[c+1|0]=b>>>8;R=R+1|0;if(R>>>0>2]){continue}break}}jj:{if(!$){break jj}kj:{c=H[e+24>>2];g=H[e+16>>2];if(c>>>0>g+2>>>0){if(!ca){break kj}g=H[e+16>>2];c=H[e+24>>2];b=H[e+20>>2];H[aa+56>>2]=c-b;H[aa+52>>2]=g-b;H[aa+48>>2]=(c-g|0)-2;Ba(W,2,15235,aa+48|0);break jj}b=H[e+12>>2];if(b>>>0<3){break jj}if(ca){H[aa+80>>2]=H[e+12>>2];Ba(W,2,7107,aa+80|0);break jj}H[aa+64>>2]=b;Ba(W,2,7107,aa- -64|0);break jj}b=H[e+20>>2];H[aa+40>>2]=c-b;H[aa+36>>2]=g-b;H[aa+32>>2]=(c-g|0)-2;Ba(W,2,15235,aa+32|0)}if(!H[D+60>>2]){break i}H[e+116>>2]=S}l=H[pa+4>>2];g=H[D+12>>2];j=H[D+8>>2]-H[pa>>2]|0;c=H[pa+16>>2];if(c&1){b=H[ma+28>>2]+N(ua,152)|0;j=(H[b-144>>2]+j|0)-H[b-152>>2]|0}i=g-l|0;if(c&2){b=H[ma+28>>2]+N(ua,152)|0;i=(H[b-140>>2]+i|0)-H[b-148>>2]|0}k=H[D+60>>2];r=k?k:H[e+116>>2];n=H[e+128>>2];q=H[e+124>>2];m=H[oa+808>>2];lj:{if(!m){break lj}h=0;b=(n|0)!=0&(q|0)!=0;if((m|0)<=30){if(!b){break lj}while(1){l=(N(h,q)<<2)+r|0;b=0;while(1){g=l+(b<<2)|0;p=H[g>>2];c=p>>31;c=(c^p)-c|0;if(c>>>m|0){c=c>>>H[oa+808>>2]|0;H[g>>2]=(p|0)<0?0-c|0:c}b=b+1|0;if((q|0)!=(b|0)){continue}break}h=h+1|0;if((n|0)!=(h|0)){continue}break}break lj}if(!b){break lj}b=N(n,q)<<2;if(!b){break lj}y(r,0,b)}if(k){b=N(n,q);if(H[oa+20>>2]==1){if(!b){break a}e=0;if((b|0)!=1){c=b&1;b=b&-2;g=0;while(1){l=(e<<2)+r|0;H[l>>2]=H[l>>2]/2;H[l+4>>2]=H[l+4>>2]/2;e=e+2|0;g=g+2|0;if((b|0)!=(g|0)){continue}break}if(!c){break a}}b=(e<<2)+r|0;H[b>>2]=H[b>>2]/2;break a}if(!b){break a}ia=O(L[pa+32>>2]*O(.5));g=b&3;if(b>>>0>=4){c=b&-4;b=0;while(1){L[r>>2]=ia*O(H[r>>2]);L[r+4>>2]=ia*O(H[r+4>>2]);L[r+8>>2]=ia*O(H[r+8>>2]);L[r+12>>2]=ia*O(H[r+12>>2]);r=r+16|0;b=b+4|0;if((c|0)!=(b|0)){continue}break}if(!g){break a}}b=0;while(1){L[r>>2]=ia*O(H[r>>2]);r=r+4|0;b=b+1|0;if((g|0)!=(b|0)){continue}break}break a}o=wa-va|0;if(H[oa+20>>2]==1){if(!n){break a}f=(H[ma+36>>2]+(N(i,o)<<2)|0)+(j<<2)|0;d=q&-4;i=0;while(1){b=0;if(d){k=f+(N(i,o)<<2)|0;m=(N(i,q)<<2)+r|0;while(1){p=b<<2;t=p+m|0;l=H[t+4>>2];g=H[t+8>>2];c=H[t+12>>2];p=k+p|0;H[p>>2]=H[t>>2]/2;H[p+12>>2]=(c|0)/2;H[p+8>>2]=(g|0)/2;H[p+4>>2]=(l|0)/2;b=b+4|0;if(d>>>0>b>>>0){continue}break}}mj:{if(b>>>0>=q>>>0){break mj}c=b+1|0;l=f+(N(i,o)<<2)|0;g=(N(i,q)<<2)+r|0;if(q-b&1){b=b<<2;H[b+l>>2]=H[b+g>>2]/2;b=c}if((c|0)==(q|0)){break mj}while(1){c=b<<2;H[c+l>>2]=H[c+g>>2]/2;c=c+4|0;H[c+l>>2]=H[c+g>>2]/2;b=b+2|0;if((q|0)!=(b|0)){continue}break}}i=i+1|0;if((n|0)!=(i|0)){continue}break}break a}if(!n|!q){break a}ia=O(L[pa+32>>2]*O(.5));i=(H[ma+36>>2]+(N(i,o)<<2)|0)+(j<<2)|0;g=q&-4;l=q&3;f=0;c=q>>>0<4;while(1){b=i;e=0;nj:{if(!c){while(1){L[b>>2]=ia*O(H[r>>2]);L[b+4>>2]=ia*O(H[r+4>>2]);L[b+8>>2]=ia*O(H[r+8>>2]);L[b+12>>2]=ia*O(H[r+12>>2]);b=b+16|0;r=r+16|0;e=e+4|0;if((g|0)!=(e|0)){continue}break}if(!l){break nj}}e=0;while(1){L[b>>2]=ia*O(H[r>>2]);b=b+4|0;r=r+4|0;e=e+1|0;if((l|0)!=(e|0)){continue}break}}i=(o<<2)+i|0;f=f+1|0;if((n|0)!=(f|0)){continue}break}break a}H[aa>>2]=U;Ba(W,2,8716,aa)}H[H[d>>2]>>2]=0}Ca(a);na=aa+96|0} +function hb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,J=0,M=0,P=0,Q=0,R=0,S=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=O(0),ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,oa=0,pa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ka=0,La=0,Ma=0,Na=0,Oa=0,Qa=0,Ta=0,Va=0,Wa=0,Xa=O(0),Za=O(0),_a=0,$a=0,ab=0,bb=0,eb=0,fb=0,gb=0,hb=0,jb=0,kb=0,lb=0,ob=O(0),pb=0,qb=0,rb=0,sb=0,tb=0;za=na-16|0;na=za;a:{if(!(I[a+8|0]&128)|H[a+228>>2]!=(b|0)){break a}ya=H[a+180>>2]+N(b,5644)|0;V=H[ya+5596>>2];if(!V){ib(ya);break a}g=H[a+100>>2];if(!g){g=H[a+96>>2]}m=H[g>>2];o=H[g+4>>2];n=H[g+8>>2];l=H[g+12>>2];g=H[a+60>>2];h=H[a+64>>2];j=H[ya+5600>>2];pa=na-16|0;na=pa;D=H[a+232>>2];H[D+36>>2]=b;i=H[H[D+28>>2]+76>>2];H[D+64>>2]=1;H[D+60>>2]=l;H[D+56>>2]=n;H[D+52>>2]=o;H[D+48>>2]=m;H[D+32>>2]=i+N(b,5644);Ca(H[D+68>>2]);o=0;H[D+68>>2]=0;b:{if(g){o=Ea(4,H[H[D+24>>2]+16>>2]);if(!o){break b}m=g&3;c:{if(g>>>0>=4){n=g&-4;b=0;while(1){g=h+(X<<2)|0;H[(H[g>>2]<<2)+o>>2]=1;H[(H[g+4>>2]<<2)+o>>2]=1;H[(H[g+8>>2]<<2)+o>>2]=1;H[(H[g+12>>2]<<2)+o>>2]=1;X=X+4|0;b=b+4|0;if((n|0)!=(b|0)){continue}break}if(!m){break c}}while(1){H[(H[h+(X<<2)>>2]<<2)+o>>2]=1;X=X+1|0;v=v+1|0;if((m|0)!=(v|0)){continue}break}}H[D+68>>2]=o}d:{E=H[D+24>>2];s=H[E+16>>2];e:{if(!s){break e}X=0;f:{while(1){g:{if(H[(X<<2)+o>>2]?0:o){break g}h=H[E+24>>2]+N(X,52)|0;b=H[h+4>>2];n=b-1|0;m=H[D+60>>2];l=n+m|0;g=0-!b|0;w=fe(l,m>>>0>l>>>0?g+1|0:g,b,0);h=H[h>>2];v=h-1|0;l=H[D+56>>2];i=v+l|0;m=0-!h|0;r=fe(i,i>>>0>>0?m+1|0:m,h,0);l=n;n=H[D+52>>2];l=l+n|0;n=fe(l,l>>>0>>0?g+1|0:g,b,0);b=H[H[H[D+20>>2]>>2]+20>>2]+N(X,76)|0;g=H[b+20>>2]-H[b+24>>2]|0;if(g>>>0>31){break g}l=H[b+12>>2];i=H[b+8>>2];u=H[b+4>>2];Y=v;v=H[D+48>>2];t=Y+v|0;h=fe(t,t>>>0>>0?m+1|0:m,h,0);b=h-H[b>>2]|0;h:{if((b>>>0<=h>>>0?b:0)>>>g|0){break h}b=n-u|0;if((b>>>0<=n>>>0?b:0)>>>g|0){break h}b=i-r|0;if((b>>>0<=i>>>0?b:0)>>>g|0){break h}b=l-w|0;if(!((b>>>0<=l>>>0?b:0)>>>g|0)){break g}}H[D+64>>2]=0;break f}X=X+1|0;if((s|0)!=(X|0)){continue}break}if(!H[D+64>>2]){break f}v=0;while(1){b=H[H[H[D+20>>2]>>2]+20>>2]+N(v,76)|0;g=H[b+28>>2]+N(H[b+24>>2],152)|0;h=H[g-148>>2];m=H[g-140>>2];o=H[g-152>>2];g=H[g-144>>2];n=H[D+68>>2];i:{if(H[n+(v<<2)>>2]?0:n){break i}n=m-h|0;g=g-o|0;be(n,0,g);if(!(!qa|(h|0)==(m|0))){X=0;Ba(f,1,2982,0);break b}g=N(g,n);if(g>>>0>=1073741824){X=0;Ba(f,1,2982,0);break b}g=g<<2;H[b+44>>2]=g;j:{k:{l:{h=H[b+36>>2];if(h){if(g>>>0<=K[b+48>>2]){break i}if(H[b+40>>2]){break l}}g=Ia(g);H[b+36>>2]=g;l=g;g=H[b+44>>2];if(!(g?l:1)){break k}H[b+40>>2]=1;H[b+48>>2]=g;break i}Ca(h);g=Ia(H[b+44>>2]);H[b+36>>2]=g;if(g){break j}H[b+48>>2]=0;H[b+40>>2]=0;H[b+44>>2]=0}X=0;Ba(f,1,2982,0);break b}H[b+40>>2]=1;H[b+48>>2]=H[b+44>>2]}v=v+1|0;E=H[D+24>>2];if(v>>>0>2]){continue}break}break e}P=H[E+24>>2];A=H[H[H[D+20>>2]>>2]+20>>2];b=0;while(1){m:{if(H[(b<<2)+o>>2]?0:o){break m}g=A+N(b,76)|0;m=H[g>>2];n=P+N(b,52)|0;h=H[n>>2];v=h-1|0;l=H[D+48>>2];w=v+l|0;i=0-!h|0;l=fe(w,l>>>0>w>>>0?i+1|0:i,h,0);m=m>>>0>l>>>0?m:l;H[g+56>>2]=m;l=H[g+4>>2];n=H[n+4>>2];r=n-1|0;u=H[D+52>>2];t=r+u|0;w=0-!n|0;u=fe(t,t>>>0>>0?w+1|0:w,n,0);l=l>>>0>u>>>0?l:u;H[g+60>>2]=l;u=H[g+8>>2];Y=v;v=H[D+56>>2];t=Y+v|0;h=fe(t,t>>>0>>0?i+1|0:i,h,0);h=h>>>0>u>>>0?u:h;H[g+64>>2]=h;i=H[g+12>>2];v=H[D+60>>2];r=r+v|0;n=fe(r,r>>>0>>0?w+1|0:w,n,0);n=i>>>0>>0?i:n;H[g+68>>2]=n;if(h>>>0>>0|l>>>0>n>>>0){break d}r=H[g+20>>2];if(!r){break m}u=n-1|0;C=0-!n|0;S=h-1|0;J=0-!h|0;t=l-1|0;M=0-!l|0;Z=m-1|0;Q=0-!m|0;R=H[g+28>>2];n=0;h=0;while(1){l=R+N(n,152)|0;m=r+(n^-1)|0;g=m&31;if((m&63)>>>0>=32){i=1<>>32-g}g=u+w|0;v=i+C|0;W=g>>>0>>0?v+1|0:v;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+148>>2]=g;g=i+J|0;v=g+1|0;Y=g;g=w+S|0;W=g>>>0>>0?v:Y;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+144>>2]=g;g=i+M|0;v=g+1|0;Y=g;g=t+w|0;W=g>>>0>>0?v:Y;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+140>>2]=g;g=i+Q|0;w=w+Z|0;i=w>>>0>>0?g+1|0:g;g=m&31;if((m&63)>>>0>=32){g=i>>>g|0}else{g=((1<>>g}H[l+136>>2]=g;n=n+1|0;h=n?h:h+1|0;if(h|(n|0)!=(r|0)){continue}break}}b=b+1|0;if((s|0)!=(b|0)){continue}break}}X=0;H[pa+8>>2]=0;b=H[D+28>>2];W=Ea(1,8);if(W){H[W+4>>2]=b;H[W>>2]=E}if(!W){break b}S=H[H[D+20>>2]>>2];t=na-144|0;na=t;P=H[D+36>>2];b=N(P,5644);h=H[W+4>>2];J=b+H[h+76>>2]|0;E=H[J+420>>2];n=0;m=0;r=na-32|0;na=r;C=b+H[h+76>>2]|0;Q=H[C+420>>2];Z=H[W>>2];u=H[Z+16>>2];l=Fa(N(u,528));n:{if(!l){break n}b=Fa(u<<2);o:{if(!b){b=l;break o}o=H[h+76>>2]+N(P,5644)|0;w=H[o+420>>2];i=w+1|0;g=Ea(i,240);p:{if(g){q:{if(i){s=H[Z+16>>2];v=g;while(1){H[v+236>>2]=f;i=Ea(s,16);H[v+200>>2]=i;if(!i){break q}A=H[Z+16>>2];H[v+196>>2]=A;s=0;i=0;if(A){while(1){s=H[v+200>>2]+(i<<4)|0;A=H[o+5584>>2]+N(i,1080)|0;M=Ea(H[A+4>>2],16);H[s+12>>2]=M;if(!M){break q}H[s+8>>2]=H[A+4>>2];i=i+1|0;s=H[Z+16>>2];if(i>>>0>>0){continue}break}}v=v+240|0;i=(k|0)==(w|0);k=k+1|0;if(!i){continue}break}}break p}o=H[g+4>>2];if(o){Ca(o);H[g+4>>2]=0}v=g;o=0;while(1){i=H[v+200>>2];if(i){s=0;k=H[v+196>>2];if(k){while(1){A=H[i+12>>2];if(A){Ca(A);H[i+12>>2]=0;k=H[v+196>>2]}i=i+16|0;s=s+1|0;if(s>>>0>>0){continue}break}i=H[v+200>>2]}Ca(i);H[v+200>>2]=0}v=v+240|0;i=(o|0)==(w|0);o=o+1|0;if(!i){continue}break}Ca(g)}g=0}if(g){r:{if(!u){break r}w=u&7;o=l;if(u>>>0>=8){v=u&-8;while(1){i=(q<<2)+b|0;H[i+28>>2]=o+3696;H[i+24>>2]=o+3168;H[i+20>>2]=o+2640;H[i+16>>2]=o+2112;H[i+12>>2]=o+1584;H[i+8>>2]=o+1056;H[i+4>>2]=o+528;H[i>>2]=o;q=q+8|0;o=o+4224|0;n=n+8|0;if((v|0)!=(n|0)){continue}break}if(!w){break r}}while(1){H[(q<<2)+b>>2]=o;q=q+1|0;o=o+528|0;p=p+1|0;if((w|0)!=(p|0)){continue}break}}w=b;s=0;v=H[(H[h+76>>2]+N(P,5644)|0)+5584>>2];o=H[Z+24>>2];b=H[h+24>>2];n=(P>>>0)/(b>>>0)|0;b=H[h+4>>2]+N(H[h+12>>2],P-N(b,n)|0)|0;i=H[Z>>2];H[r+20>>2]=b>>>0>i>>>0?b:i;i=b+H[h+12>>2]|0;b=b>>>0>i>>>0?-1:i;i=H[Z+8>>2];H[r+16>>2]=b>>>0>>0?b:i;b=H[h+8>>2]+N(n,H[h+16>>2])|0;n=H[Z+4>>2];H[r+12>>2]=b>>>0>n>>>0?b:n;h=b+H[h+16>>2]|0;b=b>>>0>h>>>0?-1:h;h=H[Z+12>>2];H[r+8>>2]=b>>>0>>0?b:h;H[r+24>>2]=0;H[r+28>>2]=0;H[r+4>>2]=2147483647;H[r>>2]=2147483647;if(H[Z+16>>2]){while(1){h=w?H[w+(s<<2)>>2]:0;b=H[o+4>>2];P=b-1|0;n=H[r+8>>2];p=P+n|0;i=0-!b|0;p=fe(p,n>>>0>p>>>0?i+1|0:i,b,0);n=H[o>>2];A=n-1|0;k=H[r+16>>2];M=A+k|0;q=0-!n|0;k=fe(M,k>>>0>M>>>0?q+1|0:q,n,0);Y=P;P=H[r+12>>2];M=Y+P|0;i=fe(M,P>>>0>M>>>0?i+1|0:i,b,0);b=H[r+20>>2];P=b+A|0;n=fe(P,b>>>0>P>>>0?q+1|0:q,n,0);b=H[v+4>>2];if(b>>>0>K[r+28>>2]){H[r+28>>2]=b;b=H[v+4>>2]}if(b){$=v+944|0;ba=v+812|0;_=p-1|0;ca=0-!p|0;aa=k-1|0;ia=0-!k|0;ka=i-1|0;fa=0-!i|0;ja=n-1|0;ga=0-!n|0;k=0;while(1){n=k<<2;P=H[n+$>>2];A=H[n+ba>>2];q=0;if(h){H[h+4>>2]=P;H[h>>2]=A;q=h+8|0}b=b-1|0;h=A+b|0;s:{if(h>>>0>31){break s}n=H[o>>2];if(n>>>0>-1>>>h>>>0){break s}i=H[r+4>>2];h=n<>2]=h>>>0>i>>>0?i:h}h=b+P|0;t:{if(h>>>0>31){break t}n=H[o+4>>2];if(n>>>0>-1>>>h>>>0){break t}i=H[r>>2];h=n<>2]=h>>>0>i>>>0?i:h}h=0;n=b&31;if((b&63)>>>0>=32){i=1<>>32-n}R=p;p=_+R|0;n=i;i=ca+i|0;z=p>>>0<_>>>0?i+1|0:i;M=b&31;i=P&31;if((P&63)>>>0>=32){i=1<>>32-i}if((b&63)>>>0>=32){z=z>>>M|0}else{z=((1<>>M}p=Y+z|0;la=p-1|0;p=(p>>>0>>0?i+1|0:i)-!p|0;i=P&31;M=n+fa|0;Y=R+ka|0;M=Y>>>0>>0?M+1|0:M;if((P&63)>>>0>=32){i=p>>>i|0}else{i=((1<>>i}p=i;i=b&31;if((b&63)>>>0>=32){i=M>>>i|0}else{i=((1<>>i}M=(i|0)!=(z|0)?p-(i>>>P|0)&-1>>>P:0;i=n+ia|0;Y=i+1|0;p=i;i=R+aa|0;z=i>>>0>>0?Y:p;p=i;P=b&31;i=A&31;if((A&63)>>>0>=32){i=1<>>32-i}if((b&63)>>>0>=32){z=z>>>P|0}else{z=((1<>>P}p=Y+z|0;la=p-1|0;p=(p>>>0>>0?i+1|0:i)-!p|0;i=A&31;n=n+ga|0;R=R+ja|0;P=R>>>0>>0?n+1|0:n;n=b&31;if((A&63)>>>0>=32){i=p>>>i|0}else{i=((1<>>i}if((b&63)>>>0>=32){n=P>>>n|0}else{n=((1<>>n}n=(n|0)!=(z|0)?i-(n>>>A|0)&-1>>>A:0;if(q){H[q+4>>2]=M;H[q>>2]=n;h=q+8|0}n=N(n,M);if(n>>>0>K[r+24>>2]){H[r+24>>2]=n}k=k+1|0;if(k>>>0>2]){continue}break}}o=o+52|0;v=v+1080|0;s=s+1|0;if(s>>>0>2]){continue}break}}A=Q+1|0;k=H[r+28>>2];s=H[r+24>>2];H[g+4>>2]=0;b=H[C+8>>2]+1|0;M=N(s,u);P=N(M,k);be(b,0,P);u:{if(!qa){b=N(b,P);H[g+8>>2]=b;b=Ea(b,2);H[g+4>>2]=b;if(b){break u}}Ca(l);Ca(w);b=H[g+4>>2];if(b){Ca(b);H[g+4>>2]=0}if(!A){b=g;break o}b=0;n=g;while(1){o=H[n+200>>2];if(o){p=0;q=H[n+196>>2];if(q){while(1){h=H[o+12>>2];if(h){Ca(h);H[o+12>>2]=0;q=H[n+196>>2]}o=o+16|0;p=p+1|0;if(q>>>0>p>>>0){continue}break}o=H[n+200>>2]}Ca(o);H[n+200>>2]=0}n=n+240|0;h=(b|0)==(Q|0);b=b+1|0;if(!h){continue}break}b=g;break o}i=H[Z+24>>2];R=H[r+20>>2];H[g+204>>2]=R;_=H[r+12>>2];H[g+208>>2]=_;aa=H[r+16>>2];H[g+212>>2]=aa;ka=H[r+8>>2];H[g+216>>2]=ka;H[g+12>>2]=P;H[g+16>>2]=M;H[g+20>>2]=s;m=1;H[g+24>>2]=1;if(u){n=H[g+200>>2];v=0;b=i;while(1){o=H[w+(v<<2)>>2];H[n>>2]=H[b>>2];H[n+4>>2]=H[b+4>>2];h=H[n+8>>2];v:{if(!h){break v}p=H[n+12>>2];if((h|0)!=1){ja=h&1;h=h&-2;q=0;while(1){H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2];H[p+16>>2]=H[o+16>>2];H[p+20>>2]=H[o+20>>2];H[p+24>>2]=H[o+24>>2];H[p+28>>2]=H[o+28>>2];p=p+32|0;o=o+32|0;q=q+2|0;if((h|0)!=(q|0)){continue}break}if(!ja){break v}}H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2]}b=b+52|0;n=n+16|0;v=v+1|0;if((u|0)!=(v|0)){continue}break}}if(A>>>0>1){h=g;while(1){H[h+456>>2]=ka;H[h+452>>2]=aa;H[h+448>>2]=_;H[h+444>>2]=R;H[h+264>>2]=1;H[h+260>>2]=s;H[h+256>>2]=M;H[h+252>>2]=P;if(u){n=H[h+440>>2];v=0;b=i;while(1){o=H[w+(v<<2)>>2];H[n>>2]=H[b>>2];H[n+4>>2]=H[b+4>>2];q=H[n+8>>2];w:{if(!q){break w}p=H[n+12>>2];if((q|0)!=1){A=q&1;ja=q&-2;q=0;while(1){H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2];H[p+16>>2]=H[o+16>>2];H[p+20>>2]=H[o+20>>2];H[p+24>>2]=H[o+24>>2];H[p+28>>2]=H[o+28>>2];p=p+32|0;o=o+32|0;q=q+2|0;if((ja|0)!=(q|0)){continue}break}if(!A){break w}}H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2]}b=b+52|0;n=n+16|0;v=v+1|0;if((u|0)!=(v|0)){continue}break}}b=H[h+8>>2];H[h+244>>2]=H[h+4>>2];H[h+248>>2]=b;b=(m|0)!=(Q|0);h=h+240|0;m=m+1|0;if(b){continue}break}}Ca(l);Ca(w);b=H[C+420>>2];x:{if(I[C+5640|0]&4){if((b|0)==-1){break x}p=C+424|0;h=H[C+8>>2];q=0;o=g;while(1){m=H[p+36>>2];H[o+44>>2]=1;H[o+84>>2]=m;H[o+48>>2]=H[p>>2];m=H[p+4>>2];H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=m;H[o+60>>2]=H[p+12>>2];H[o+64>>2]=H[p+16>>2];m=H[p+8>>2];H[o+76>>2]=s;H[o+56>>2]=h>>>0>m>>>0?m:h;p=p+148|0;o=o+240|0;m=(b|0)==(q|0);q=q+1|0;if(!m){continue}break}break x}if((b|0)==-1){break x}h=H[C+8>>2];m=H[C+4>>2];o=g;if(b){n=b+1&-2;p=0;while(1){H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=0;H[o+44>>2]=1;H[o+48>>2]=0;H[o+84>>2]=m;H[o+60>>2]=k;H[o+324>>2]=m;H[o+76>>2]=s;H[o+56>>2]=h;H[o+308>>2]=0;H[o+312>>2]=0;H[o+292>>2]=0;H[o+284>>2]=1;H[o+288>>2]=0;H[o+300>>2]=k;H[o+296>>2]=h;H[o+316>>2]=s;H[o+64>>2]=H[o+196>>2];H[o+304>>2]=H[o+436>>2];o=o+480|0;p=p+2|0;if((n|0)!=(p|0)){continue}break}if(b&1){break x}}H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=0;H[o+44>>2]=1;H[o+48>>2]=0;H[o+84>>2]=m;H[o+60>>2]=k;H[o+76>>2]=s;H[o+56>>2]=h;H[o+64>>2]=H[o+196>>2]}m=g;break n}Ca(l)}Ca(b)}na=r+32|0;g=m;y:{z:{if(!g){break z}A=E+1|0;w=g;v=V;A:{B:{while(1){if(H[w+84>>2]==-1){break A}l=Fa(H[Z+16>>2]<<2);if(!l){break A}b=H[Z+16>>2]<<2;if(b){y(l,1,b)}if(Sb(w)){while(1){n=H[S+20>>2];C:{D:{if(K[w+40>>2]>=K[J+12>>2]){break D}b=H[w+32>>2];h=N(H[w+28>>2],76)+n|0;if(b>>>0>=K[h+24>>2]){break D}h=H[h+28>>2]+N(b,152)|0;if(!H[h+24>>2]){break D}m=h+28|0;i=0;E:{while(1){o=m+N(i,36)|0;b=H[o+20>>2]+N(H[w+36>>2],40)|0;if(!wb(D,H[w+28>>2],H[w+32>>2],H[o+16>>2],H[b>>2],H[b+4>>2],H[b+8>>2],H[b+12>>2])){i=i+1|0;if(i>>>0>2]){continue}break E}break}H[l+(H[w+28>>2]<<2)>>2]=0;H[t+136>>2]=0;if(!Rb(H[W+4>>2],H[S+20>>2],J,w,t+140|0,v,t+136|0,j,f)){break B}i=H[w+32>>2];q=H[w+28>>2];u=H[t+136>>2];if(H[t+140>>2]){H[t+136>>2]=0;C=H[(H[S+20>>2]+N(q,76)|0)+28>>2]+N(i,152)|0;n=H[C+24>>2];if(n){P=j-u|0;M=j+v|0;q=C+28|0;k=0;Q=u+v|0;p=Q;r=0;while(1){F:{if(H[q+8>>2]==H[q>>2]|H[q+12>>2]==H[q+4>>2]){break F}b=H[q+20>>2]+N(H[w+36>>2],40)|0;R=N(H[b+20>>2],H[b+16>>2]);if(!R){break F}n=H[b+24>>2];h=0;while(1){o=H[n+36>>2];if(o){G:{if(k|H[n+64>>2]){H[n+52>>2]=0;i=1;b=64;break G}i=H[n>>2];b=H[n+40>>2];H:{if(b){i=N(b,24)+i|0;if(H[i-20>>2]!=H[i-12>>2]){i=i-24|0;break H}b=b+1|0}else{b=1}H[n+40>>2]=b}b=H[i+20>>2];I:{J:{if(b>>>0>(p^-1)>>>0){break J}m=i+20|0;while(1){if(M>>>0>>0){break J}s=H[n+4>>2];k=H[n+52>>2];if((k|0)!=H[n+56>>2]){m=o}else{b=k<<1|1;s=Ga(s,b<<3);if(!s){Ba(f,1,1024,0);break B}H[n+56>>2]=b;H[n+4>>2]=s;k=H[n+52>>2];b=H[m>>2];m=H[n+36>>2]}o=(k<<3)+s|0;H[o+4>>2]=b;H[o>>2]=p;H[n+52>>2]=k+1;H[i>>2]=H[i>>2]+b;s=H[i+16>>2];k=s+H[i+4>>2]|0;H[i+4>>2]=k;o=m-s|0;H[n+36>>2]=o;H[i+8>>2]=k;p=b+p|0;k=0;if((m|0)==(s|0)){break I}H[n+40>>2]=H[n+40>>2]+1;m=i+44|0;b=H[i+44>>2];i=i+24|0;if((p^-1)>>>0>=b>>>0){continue}break}}m=H[w+28>>2];o=H[w+32>>2];i=H[w+36>>2];if(H[H[W+4>>2]+104>>2]){H[t+120>>2]=m;H[t+116>>2]=o;H[t+112>>2]=r;H[t+108>>2]=i;H[t+104>>2]=h;H[t+100>>2]=P;H[t+96>>2]=b;Ba(f,1,14693,t+96|0);break B}H[t+88>>2]=m;H[t+84>>2]=o;H[t+80>>2]=r;H[t+76>>2]=i;H[t+72>>2]=h;H[t+68>>2]=P;H[t+64>>2]=b;Ba(f,2,14693,t- -64|0);H[n+52>>2]=0;H[n+64>>2]=1;k=1}i=H[n+40>>2];b=44}H[b+n>>2]=i}n=n+68|0;h=h+1|0;if((R|0)!=(h|0)){continue}break}n=H[C+24>>2]}q=q+36|0;r=r+1|0;if(r>>>0>>0){continue}break}i=H[w+32>>2];q=H[w+28>>2];b=k?P:p-Q|0}else{b=0}u=b+u|0}h=H[Z+24>>2]+N(q,52)|0;b=H[h+36>>2];H[h+36>>2]=b>>>0>>0?i:b;break C}n=H[S+20>>2]}H[t+136>>2]=0;if(!Rb(H[W+4>>2],n,J,w,t+140|0,v,t+136|0,j,f)){break B}q=H[w+28>>2];u=H[t+136>>2];if(!H[t+140>>2]){break C}M=H[w+32>>2];b=H[(H[S+20>>2]+N(q,76)|0)+28>>2]+N(M,152)|0;Q=H[b+24>>2];if(!Q){break C}P=j-u|0;s=b+28|0;C=H[w+36>>2];i=0;k=0;K:{L:{while(1){M:{if(H[s+8>>2]==H[s>>2]|H[s+12>>2]==H[s+4>>2]){break M}b=H[s+20>>2]+N(C,40)|0;R=N(H[b+20>>2],H[b+16>>2]);if(!R){break M}o=H[b+24>>2];r=0;while(1){b=H[o+36>>2];if(b){n=H[o>>2];h=H[o+40>>2];N:{if(h){n=N(h,24)+n|0;if(H[n-20>>2]!=H[n-12>>2]){n=n-24|0;break N}h=h+1|0}else{h=1}H[o+40>>2]=h}p=H[n+20>>2];i=p+i|0;if(i>>>0

      >>0|i>>>0>P>>>0){break K}while(1){O:{p=H[n+16>>2];H[n+4>>2]=p+H[n+4>>2];m=b-p|0;if((b|0)==(p|0)){break O}h=h+1|0;H[o+40>>2]=h;p=H[n+44>>2];i=p+i|0;if(i>>>0

      >>0){break L}n=n+24|0;b=m;if(i>>>0<=P>>>0){continue}break L}break}H[o+36>>2]=m}o=o+68|0;r=r+1|0;if((R|0)!=(r|0)){continue}break}}s=s+36|0;k=k+1|0;if((Q|0)!=(k|0)){continue}break}u=i+u|0;break C}H[o+36>>2]=m}if(!H[H[W+4>>2]+104>>2]){H[t+24>>2]=q;H[t+20>>2]=M;H[t+16>>2]=k;H[t+12>>2]=C;H[t+8>>2]=r;H[t+4>>2]=P;H[t>>2]=p;Ba(f,2,14608,t);q=H[w+28>>2];u=u+P|0;break C}H[t+56>>2]=q;H[t+52>>2]=M;H[t+48>>2]=k;H[t+44>>2]=C;H[t+40>>2]=r;H[t+36>>2]=P;H[t+32>>2]=p;Ba(f,1,14608,t+32|0);break B}P:{if(!H[l+(q<<2)>>2]){break P}b=H[Z+24>>2]+N(q,52)|0;if(H[b+36>>2]){break P}H[b+36>>2]=H[(H[S+20>>2]+N(q,76)|0)+24>>2]-1}j=j-u|0;v=u+v|0;if(Sb(w)){continue}break}}Ca(l);w=w+240|0;x=x+1|0;if(x>>>0<=K[J+420>>2]){continue}break}xb(g,A);H[pa+8>>2]=v-V;b=1;break y}xb(g,A);Ca(l);break z}xb(g,A)}b=0}na=t+144|0;cb(W);if(!b){break b}X=H[H[D+32>>2]+5584>>2];p=H[H[D+20>>2]>>2];z=H[p+20>>2];H[pa+12>>2]=1;v=0;b=H[D+32>>2];s=H[X+16>>2]>>>4&1&H[b+12>>2]==H[b+8>>2];E=H[p+16>>2];Q:{if(!E){break Q}while(1){b=H[D+68>>2];if(!(H[b+(v<<2)>>2]?0:b)){r=pa+12|0;E=0;b=H[z+24>>2];R:{if(!b){break R}q=H[D+44>>2];while(1){m=H[z+28>>2]+N(E,152)|0;o=H[m+24>>2];if(o){k=m+28|0;b=H[m+20>>2];w=H[m+16>>2];l=0;while(1){if(N(b,w)){i=k+N(l,36)|0;n=0;while(1){g=H[i+20>>2]+N(n,40)|0;j=wb(D,H[z+16>>2],E,H[i+16>>2],H[g>>2],H[g+4>>2],H[g+8>>2],H[g+12>>2]);h=H[g+16>>2];o=H[g+20>>2];b=N(h,o);S:{if(j){if(!b){break S}h=0;while(1){j=H[g+24>>2]+N(h,68)|0;T:{if(!wb(D,H[z+16>>2],E,H[i+16>>2],H[j+8>>2],H[j+12>>2],H[j+16>>2],H[j+20>>2])){b=H[j+60>>2];if(!b){break T}Ca(b);H[j+60>>2]=0;break T}if(!H[D+64>>2]){if(H[j+60>>2]|H[j+16>>2]==H[j+8>>2]|H[j+20>>2]==H[j+12>>2]){break T}}b=Ea(1,44);if(!b){H[pa+12>>2]=0;break R}o=H[D+64>>2];H[b+36>>2]=0;H[b+28>>2]=r;H[b+20>>2]=X;H[b+16>>2]=z;H[b+12>>2]=i;H[b+8>>2]=j;H[b+4>>2]=E;H[b>>2]=o;H[b+40>>2]=s;H[b+32>>2]=f;H[b+24>>2]=H[q+4>>2]>1;db(q,14,b);if(!H[pa+12>>2]){break R}}h=h+1|0;if(h>>>0>2],H[g+16>>2])>>>0){continue}break}break S}if(!b){break S}w=0;while(1){b=H[g+24>>2]+N(w,68)|0;j=H[b+60>>2];if(j){Ca(j);H[b+60>>2]=0;o=H[g+20>>2];h=H[g+16>>2]}w=w+1|0;if(w>>>0>>0){continue}break}}n=n+1|0;b=H[m+20>>2];w=H[m+16>>2];if(n>>>0>>0){continue}break}o=H[m+24>>2]}l=l+1|0;if(l>>>0>>0){continue}break}b=H[z+24>>2]}E=E+1|0;if(E>>>0>>0){continue}break}}if(!H[pa+12>>2]){break Q}E=H[p+16>>2]}X=X+1080|0;z=z+76|0;v=v+1|0;if(E>>>0>v>>>0){continue}break}}X=0;Ra(H[D+44>>2]);if(!H[pa+12>>2]){break b}U:{if(H[D+64>>2]){break U}v=H[D+24>>2];if(!H[v+16>>2]){break U}z=0;while(1){b=H[H[H[D+20>>2]>>2]+20>>2]+N(z,76)|0;g=H[b+28>>2]+N(H[(H[v+24>>2]+N(z,52)|0)+36>>2],152)|0;j=H[g+136>>2];h=H[g+144>>2];m=H[g+140>>2];g=H[g+148>>2];Ca(H[b+52>>2]);H[b+52>>2]=0;V:{o=H[D+68>>2];if((h|0)==(j|0)|(g|0)==(m|0)|(H[o+(z<<2)>>2]?0:o)){break V}g=g-m|0;j=h-j|0;be(g,0,j);if(qa){Ba(f,1,2982,0);break b}g=N(g,j);if(g>>>0>=1073741824){Ba(f,1,2982,0);break b}j=b;b=Ia(g<<2);H[j+52>>2]=b;if(b){break V}Ba(f,1,2982,0);break b}z=z+1|0;v=H[D+24>>2];if(z>>>0>2]){continue}break}}v=H[D+32>>2];w=H[H[D+20>>2]>>2];if(H[w+16>>2]){z=H[w+20>>2];v=H[v+5584>>2];E=H[H[D+24>>2]+24>>2];o=0;while(1){W:{b=H[D+68>>2];if(H[b+(o<<2)>>2]?0:b){break W}m=H[E+36>>2]+1|0;if(H[v+20>>2]==1){Z=m;b=0;$=na-32|0;na=$;X:{Y:{if(H[D+64>>2]){g=1;if((m|0)==1){break X}n=H[z+28>>2];b=n+N(H[z+24>>2],152)|0;h=H[b-144>>2];q=H[b-152>>2];if((h|0)==(q|0)){break X}s=m-1|0;b=0;l=H[D+44>>2];p=H[l+4>>2];j=n;Z:{if((m|0)!=2){m=s&1;i=s&-2;g=0;while(1){r=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+312>>2]-H[j+304>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+316>>2]-H[j+308>>2]|0;b=b>>>0>r>>>0?b:r;j=j+304|0;g=g+2|0;if((i|0)!=(g|0)){continue}break}if(!m){break Z}}g=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>g>>>0?b:g;g=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>g>>>0?b:g}g=0;if(b>>>0>134217727){break X}j=H[n+4>>2];m=H[n+12>>2];r=H[n>>2];u=H[n+8>>2];k=b<<5;i=nb(k);H[$+16>>2]=i;if(!i){break X}q=h-q|0;g=m-j|0;m=u-r|0;H[$>>2]=i;while(1){r=H[z+36>>2];h=g;H[$+8>>2]=g;b=m;H[$+24>>2]=b;g=H[n+156>>2];j=H[n+164>>2];m=H[n+160>>2];u=H[n+152>>2];H[$+28>>2]=(u|0)%2;m=m-u|0;H[$+20>>2]=m-b;t=(p|0)<2;g=j-g|0;_:{if(!(!t&g>>>0>1)){j=0;if(!g){break _}while(1){Yb($+16|0,r+(N(j,q)<<2)|0);j=j+1|0;if((j|0)!=(g|0)){continue}break}break _}u=g>>>0

      >>0?g:p;Z=u-1|0;V=(g>>>0)/(u>>>0)|0;b=0;while(1){j=Fa(36);if(!j){break Y}W=H[$+28>>2];H[j+8>>2]=H[$+24>>2];H[j+12>>2]=W;W=H[$+20>>2];H[j>>2]=H[$+16>>2];H[j+4>>2]=W;H[j+24>>2]=r;H[j+20>>2]=q;H[j+16>>2]=m;H[j+28>>2]=N(b,V);W=(b|0)==(Z|0);b=b+1|0;H[j+32>>2]=W?g:N(V,b);W=nb(k);H[j>>2]=W;if(!W){g=0;Ra(l);Ca(j);Ca(i);break X}db(l,10,j);if((b|0)!=(u|0)){continue}break}Ra(l)}H[$+4>>2]=g-h;H[$+12>>2]=H[n+156>>2]%2;$:{if(!(!t&m>>>0>1)){b=8;j=0;if(m>>>0>=8){while(1){mb($,r+(j<<2)|0,q,8);j=b;b=b+8|0;if(m>>>0>=b>>>0){continue}break}}if(j>>>0>=m>>>0){break $}mb($,r+(j<<2)|0,q,m-j|0);break $}h=m>>>0

      >>0?m:p;V=h-1|0;u=(m>>>0)/(h>>>0)|0;b=0;while(1){j=Fa(36);if(!j){break Y}t=H[$+12>>2];H[j+8>>2]=H[$+8>>2];H[j+12>>2]=t;t=H[$+4>>2];H[j>>2]=H[$>>2];H[j+4>>2]=t;H[j+24>>2]=r;H[j+20>>2]=q;H[j+16>>2]=g;H[j+28>>2]=N(b,u);t=(b|0)==(V|0);b=b+1|0;H[j+32>>2]=t?m:N(u,b);t=nb(k);H[j>>2]=t;if(!t){g=0;Ra(l);Ca(j);Ca(i);break X}db(l,11,j);if((b|0)!=(h|0)){continue}break}Ra(l)}n=n+152|0;s=s-1|0;if(s){continue}break}g=1;Ca(i);break X}g=1;n=H[z+28>>2];oa=n+N(Z,152)|0;La=oa-152|0;if(H[La>>2]==H[oa-144>>2]){break X}Ma=oa-148|0;if(H[Ma>>2]==H[oa-140>>2]){break X}h=H[n+4>>2];m=H[n+12>>2];l=H[n>>2];i=H[n+8>>2];W=H[z+68>>2];P=H[z+64>>2];A=H[z+60>>2];C=H[z+56>>2];ia=Xb(z,Z);if(!ia){g=0;break X}aa:{ba:{if((Z|0)!=1){j=n;ca:{if((Z|0)!=2){g=Z-1|0;p=g&1;q=g&-2;g=0;while(1){s=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+312>>2]-H[j+304>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+316>>2]-H[j+308>>2]|0;b=b>>>0>s>>>0?b:s;j=j+304|0;g=g+2|0;if((q|0)!=(g|0)){continue}break}if(!p){break ca}}g=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>g>>>0?b:g;g=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>g>>>0?b:g}if(b>>>0>=268435456){break aa}k=nb(b<<4);if(!k){break aa}da:{if(!Z){break da}p=m-h|0;u=i-l|0;Na=k+28|0;Q=k+24|0;Ka=k+16|0;Y=k+4|0;ra=1;ea:while(1){b=H[n+156>>2];ha=(b|0)%2|0;g=H[n+152>>2];ma=(g|0)%2|0;S=H[n+164>>2]-b|0;ca=S-p|0;x=H[n+160>>2]-g|0;ba=x-u|0;j=C;g=j;r=A;m=r;b=P;ea=b;h=W;s=h;l=H[z+20>>2];fa:{if((l|0)==(ra|0)){break fa}q=l-ra|0;m=0;g=0;if(j){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=j+(g^-1)|0;g=i^-1;j=b>>>0>>0?g+1|0:g;g=q&31;if((q&63)>>>0>=32){g=j>>>g|0}else{g=((1<>>g}}if(A){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=A+(j^-1)|0;j=i^-1;h=b>>>0>>0?j+1|0:j;j=q&31;if((q&63)>>>0>=32){m=h>>>j|0}else{m=((1<>>j}}h=0;b=0;if(P){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=P+(j^-1)|0;j=i^-1;l=b>>>0

      >>0?j+1|0:j;j=q&31;if((q&63)>>>0>=32){b=l>>>j|0}else{b=((1<>>j}}if(W){j=q&31;if((q&63)>>>0>=32){i=-1<>>32-j}j=W+(h^-1)|0;h=i^-1;l=j>>>0>>0?h+1|0:h;h=q&31;if((q&63)>>>0>=32){h=l>>>h|0}else{h=((1<>>h}}ea=0;j=0;V=1<>>0>>0){j=q&31;if((q&63)>>>0>=32){i=-1<>>32-j}l=l^-1;j=l+(C-V|0)|0;i=i^-1;i=j>>>0>>0?i+1|0:i;l=q&31;if((q&63)>>>0>=32){j=i>>>l|0}else{j=((1<>>l}}if(P>>>0>V>>>0){l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}s=s^-1;l=s+(P-V|0)|0;i=i^-1;s=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){ea=s>>>i|0}else{ea=((1<>>i}}s=0;r=0;if(A>>>0>V>>>0){l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}r=r^-1;l=r+(A-V|0)|0;i=i^-1;r=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){r=r>>>i|0}else{r=((1<>>i}}if(W>>>0<=V>>>0){break fa}l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}s=s^-1;l=s+(W-V|0)|0;i=i^-1;s=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){s=s>>>i|0}else{s=((1<>>i}}l=H[n+180>>2];i=ea-l|0;i=i>>>0<=ea>>>0?i:0;q=i+2|0;i=i>>>0>q>>>0?-1:q;fa=i>>>0>>0?i:ba;i=H[n+216>>2];q=b-i|0;b=b>>>0>=q>>>0?q:0;q=b+2|0;b=b>>>0>q>>>0?-1:q;ga=b>>>0>>0?b:u;b=(ma?fa:ga)<<1;q=(ma?ga:fa)<<1|1;ta=b>>>0>q>>>0?b:q;b=ta>>>0>>0;l=j-l|0;j=j>>>0>=l>>>0?l:0;l=j-2|0;q=j>>>0>=l>>>0?l:0;j=g-i|0;g=g>>>0>=j>>>0?j:0;j=g-2|0;j=g>>>0>=j>>>0?j:0;g=(ma?q:j)<<1;l=(ma?j:q)<<1|1;t=g>>>0>>0;V=H[n+184>>2];i=m-V|0;m=i>>>0<=m>>>0?i:0;i=m-2|0;m=i>>>0<=m>>>0?i:0;R=m;J=H[n+220>>2];i=r-J|0;i=i>>>0<=r>>>0?i:0;r=i-2|0;i=i>>>0>=r>>>0?r:0;_=i;r=h-V|0;h=h>>>0>=r>>>0?r:0;r=h+2|0;h=h>>>0>r>>>0?-1:r;V=h>>>0

      >>0?h:p;aa=V;h=s-J|0;h=h>>>0<=s>>>0?h:0;s=h+2|0;h=h>>>0>s>>>0?-1:s;la=h>>>0>>0?h:ca;s=la;if(ha){R=i;_=m;aa=s;s=V}Aa=b?ta:x;l=t?g:l;Oa=p+la|0;Qa=i+p|0;if(S){sa=k+(j<<3)|0;b=k+(ba<<3)|0;ua=b-4|0;g=(j|0)<(ba|0);_a=g?sa+4|0:ua;va=u-1|0;$a=(fa|0)<(va|0)?fa:va;t=0;ab=(u|0)>1|(ba|0)>0;h=ma<<2;bb=(Y-h|0)+(q<<3)|0;eb=h+sa|0;M=(ba|0)>(ga|0)?ga:ba;ka=j+1|0;fb=u+fa|0;Ta=q+u|0;gb=k+(l<<2)|0;Va=!u&(ba|0)==1;h=k+(u<<3)|0;ja=h-8|0;wa=h-4|0;xa=b-8|0;Wa=k+(Aa<<2)|0;hb=Wa-4|0;jb=(k+((g?j:ba)<<3)|0)-4|0;while(1){ga:{if(!(t>>>0>>0&m>>>0<=t>>>0|t>>>0>>0&t>>>0>=Qa>>>0)){J=t+1|0;break ga}if(x>>>0>ta>>>0){H[hb>>2]=0;H[Wa>>2]=0}J=t+1|0;Ha(ia,j,t,ga,J,eb,2,0);Ha(ia,Ta,t,fb,J,bb,2,0);ha:{ia:{ja:{if(!ma){if(!ab){break ha}if((j|0)>=(ga|0)){break ia}ka:{la:{if((j|0)>0){b=H[jb>>2];break la}b=H[Y>>2];g=b;if((j|0)<0){break ka}}g=b;b=H[_a>>2]}H[sa>>2]=H[sa>>2]-((b+g|0)+2>>2);b=ka;h=b;g=j;if((b|0)>=(M|0)){break ja}while(1){b=g;g=h;h=k+(g<<3)|0;H[h>>2]=H[h>>2]-((H[(k+(b<<3)|0)+4>>2]+H[h+4>>2]|0)+2>>2);h=g+1|0;if((M|0)!=(h|0)){continue}break}b=M;break ja}ma:{if(!Va){b=j;if((ga|0)<=(b|0)){break ma}while(1){g=k+(b<<3)|0;h=H[g+4>>2];r=xa;na:{oa:{if((b|0)>=0){ea=H[((b|0)<(ba|0)?g:xa)>>2];b=b+1|0;break oa}ea=H[k>>2];if((b|0)!=-1){b=b+1|0;r=k;break na}b=0}if((b|0)>=(ba|0)){break na}r=k+(b<<3)|0}H[g+4>>2]=h-((H[r>>2]+ea|0)+2>>2);if((b|0)<(ga|0)){continue}break}break ma}H[k>>2]=H[k>>2]/2;break ha}b=q;if((fa|0)<=(b|0)){break ha}while(1){g=k+(b<<3)|0;h=H[g>>2];pa:{if((b|0)<0){r=H[Y>>2];ea=Y;break pa}r=H[((b|0)<(u|0)?g+4|0:wa)>>2];ea=Y;if(!b){break pa}ea=(b|0)>(u|0)?wa:g-4|0}H[g>>2]=h+(H[ea>>2]+r>>1);b=b+1|0;if((fa|0)!=(b|0)){continue}break}break ha}if((b|0)>=(ga|0)){break ia}while(1){g=k+(b<<3)|0;r=H[g>>2];qa:{ra:{if((b|0)>0){h=H[(k+(((b|0)<(ba|0)?b:ba)<<3)|0)-4>>2];break ra}h=H[Y>>2];ea=Y;if((b|0)<0){break qa}}ea=ua;if((b|0)>=(ba|0)){break qa}ea=(k+(b<<3)|0)+4|0}H[g>>2]=r-((H[ea>>2]+h|0)+2>>2);b=b+1|0;if((ga|0)!=(b|0)){continue}break}}if((q|0)>=(fa|0)){break ha}b=q;if((va|0)>(b|0)){while(1){g=k+(b<<3)|0;b=b+1|0;H[g+4>>2]=H[g+4>>2]+(H[k+(b<<3)>>2]+H[g>>2]>>1);if((b|0)<($a|0)){continue}break}}if((b|0)>=(fa|0)){break ha}while(1){g=ja;r=b;sa:{ta:{if((b|0)>=0){h=H[((b|0)<(u|0)?k+(b<<3)|0:g)>>2];b=b+1|0;break ta}h=H[k>>2];if((r|0)!=-1){b=r+1|0;g=k;break sa}b=0}if((u|0)<=(b|0)){break sa}g=k+(b<<3)|0}r=k+(r<<3)|0;H[r+4>>2]=H[r+4>>2]+(H[g>>2]+h>>1);if((b|0)<(fa|0)){continue}break}}if(!Ya(ia,l,t,Aa,J,gb,1,0)){break ba}}t=J;if((S|0)!=(t|0)){continue}break}}n=n+152|0;b=aa<<1;g=s<<1|1;b=b>>>0>g>>>0?b:g;_a=b>>>0>>0?b:S;J=k+(m<<5)|0;b=k+(ca<<5)|0;ta=b-4|0;g=(m|0)<(ca|0);$a=g?J+28|0:ta;va=b-8|0;ab=g?J+24|0:va;wa=b-12|0;bb=g?J+20|0:wa;xa=b-16|0;eb=(m|0)<0?Ka:g?J+16|0:xa;ua=p-1|0;fb=(la|0)<(ua|0)?la:ua;j=k+((g?m:ca)<<5)|0;Ta=(m|0)<=0;gb=Ta?Ka:j-16|0;Va=(ca|0)>0;Wa=Va|(p|0)>1;hb=J+(ha<<4)|0;jb=(k+(4-(ha<<2)<<2)|0)+(i<<5)|0;q=(V|0)<(ca|0)?V:ca;u=m+1|0;g=R<<1;h=_<<1|1;lb=g>>>0>>0?g:h;qb=k+(lb<<4)|0;rb=!p&(ca|0)==1;g=k+(p<<5)|0;M=g-20|0;R=g-24|0;_=g-28|0;aa=g-32|0;ka=g-4|0;ja=g-8|0;ba=g-12|0;fa=g-16|0;ga=b-20|0;ea=b-24|0;ma=b-28|0;sa=b-32|0;sb=j-4|0;t=j-8|0;tb=j-12|0;while(1){s=l;ua:{va:{wa:{xa:{if(l>>>0>>0){b=Aa-l|0;l=(b>>>0>=4?4:b)+l|0;Ha(ia,s,m,l,V,hb,1,8);Ha(ia,s,Qa,l,Oa,jb,1,8);if(!ha){if(!Wa){break ua}if((m|0)>=(V|0)){break va}H[J>>2]=H[J>>2]-((H[gb>>2]+H[eb>>2]|0)+2>>2);ya:{if(!Ta){b=H[tb>>2];h=t;g=sb;break ya}b=H[k+20>>2];if((m|0)<0){break xa}h=Q;g=Na}H[J+4>>2]=H[J+4>>2]-((H[bb>>2]+b|0)+2>>2);H[J+8>>2]=H[J+8>>2]-((H[h>>2]+H[ab>>2]|0)+2>>2);b=H[$a>>2];g=H[g>>2];break wa}if(rb){H[k>>2]=H[k>>2]/2;H[k+4>>2]=H[k+4>>2]/2;H[k+8>>2]=H[k+8>>2]/2;H[k+12>>2]=H[k+12>>2]/2;break ua}g=m;if((V|0)>(g|0)){while(1){b=k+(g<<5)|0;za:{if((g|0)<0){h=H[k>>2];j=Va|(g|0)!=-1;H[b+16>>2]=H[b+16>>2]-(((j?h:H[sa>>2])+h|0)+2>>2);h=H[Y>>2];H[b+20>>2]=H[b+20>>2]-(((j?h:H[ma>>2])+h|0)+2>>2);h=H[k+8>>2];H[b+24>>2]=H[b+24>>2]-(((j?h:H[ea>>2])+h|0)+2>>2);h=b;r=H[b+28>>2];b=H[k+12>>2];H[h+28>>2]=r-(((j?b:H[ga>>2])+b|0)+2>>2);g=g+1|0;break za}j=g+1|0;Aa:{if((j|0)<(ca|0)){g=k+(j<<5)|0;H[b+16>>2]=H[b+16>>2]-((H[b>>2]+H[g>>2]|0)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[b+4>>2]+H[g+4>>2]|0)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[b+8>>2]+H[g+8>>2]|0)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[b+12>>2]+H[g+12>>2]|0)+2>>2);break Aa}h=H[b+16>>2];if((g|0)>=(ca|0)){H[b+16>>2]=h-((H[sa>>2]<<1)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[ma>>2]<<1)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[ea>>2]<<1)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[ga>>2]<<1)+2>>2);break Aa}H[b+16>>2]=h-((H[b>>2]+H[sa>>2]|0)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[b+4>>2]+H[ma>>2]|0)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[b+8>>2]+H[ea>>2]|0)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[b+12>>2]+H[ga>>2]|0)+2>>2)}g=j}if((V|0)!=(g|0)){continue}break}}g=i;if((la|0)<=(g|0)){break ua}while(1){b=k+(g<<5)|0;Ba:{if((g|0)<0){H[b>>2]=H[b>>2]+(H[k+16>>2]<<1>>1);H[b+4>>2]=H[b+4>>2]+(H[k+20>>2]<<1>>1);H[b+8>>2]=H[b+8>>2]+(H[k+24>>2]<<1>>1);H[b+12>>2]=H[b+12>>2]+(H[k+28>>2]<<1>>1);break Ba}j=H[b>>2];if(!g){h=(g|0)<(p|0);H[b>>2]=j+(H[k+16>>2]+H[(h?b+16|0:fa)>>2]>>1);H[b+4>>2]=H[b+4>>2]+(H[k+20>>2]+H[(h?b+20|0:ba)>>2]>>1);H[b+8>>2]=H[b+8>>2]+(H[k+24>>2]+H[(h?b+24|0:ja)>>2]>>1);H[b+12>>2]=H[b+12>>2]+(H[k+28>>2]+H[(h?b+28|0:ka)>>2]>>1);break Ba}if((g|0)<=(p|0)){h=(g|0)<(p|0);H[b>>2]=j+(H[b-16>>2]+H[(h?b+16|0:fa)>>2]>>1);H[b+4>>2]=H[b+4>>2]+(H[b-12>>2]+H[(h?b+20|0:ba)>>2]>>1);H[b+8>>2]=H[b+8>>2]+(H[b-8>>2]+H[(h?b+24|0:ja)>>2]>>1);H[b+12>>2]=H[b+12>>2]+(H[b-4>>2]+H[(h?b+28|0:ka)>>2]>>1);break Ba}H[b>>2]=j+(H[fa>>2]<<1>>1);H[b+4>>2]=H[b+4>>2]+(H[ba>>2]<<1>>1);H[b+8>>2]=H[b+8>>2]+(H[ja>>2]<<1>>1);H[b+12>>2]=H[b+12>>2]+(H[ka>>2]<<1>>1)}g=g+1|0;if((la|0)!=(g|0)){continue}break}break ua}p=S;u=x;ra=ra+1|0;if((Z|0)!=(ra|0)){continue ea}break da}H[J+4>>2]=H[J+4>>2]-((b<<1)+2>>2);H[J+8>>2]=H[J+8>>2]-((H[Q>>2]<<1)+2>>2);b=H[Na>>2];g=b}H[J+12>>2]=H[J+12>>2]-((b+g|0)+2>>2);j=m;b=u;g=b;if((b|0)<(q|0)){while(1){g=k+(b<<5)|0;j=k+(j<<5)|0;H[g>>2]=H[g>>2]-((H[j+16>>2]+H[g+16>>2]|0)+2>>2);H[g+4>>2]=H[g+4>>2]-((H[j+20>>2]+H[g+20>>2]|0)+2>>2);H[g+8>>2]=H[g+8>>2]-((H[j+24>>2]+H[g+24>>2]|0)+2>>2);H[g+12>>2]=H[g+12>>2]-((H[j+28>>2]+H[g+28>>2]|0)+2>>2);j=b;b=b+1|0;if((q|0)!=(b|0)){continue}break}g=q}if((g|0)>=(V|0)){break va}while(1){b=k+(g<<5)|0;j=(g|0)<(ca|0);Ca:{if((g|0)<=0){h=H[Ka>>2];if((g|0)>=0){H[b>>2]=H[b>>2]-((h+H[(j?b+16|0:xa)>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[k+20>>2]+H[(j?b+20|0:wa)>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[k+24>>2]+H[(j?b+24|0:va)>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[k+28>>2]+H[(j?b+28|0:ta)>>2]|0)+2>>2);break Ca}H[b>>2]=H[b>>2]-((h<<1)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[k+20>>2]<<1)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[k+24>>2]<<1)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[k+28>>2]<<1)+2>>2);break Ca}h=k+((j?g:ca)<<5)|0;r=H[h-16>>2];if(!j){H[b>>2]=H[b>>2]-((r+H[xa>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[h-12>>2]+H[wa>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[h-8>>2]+H[va>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[h-4>>2]+H[ta>>2]|0)+2>>2);break Ca}H[b>>2]=H[b>>2]-((r+H[b+16>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[h-12>>2]+H[b+20>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[h-8>>2]+H[b+24>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[h-4>>2]+H[b+28>>2]|0)+2>>2)}g=g+1|0;if((V|0)!=(g|0)){continue}break}}if((i|0)>=(la|0)){break ua}g=i;if((ua|0)>(g|0)){while(1){b=k+(g<<5)|0;H[b+16>>2]=H[b+16>>2]+(H[b+32>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[b+36>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[b+40>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[b+44>>2]+H[b+12>>2]>>1);g=g+1|0;if((fb|0)>(g|0)){continue}break}}if((g|0)>=(la|0)){break ua}while(1){b=k+(g<<5)|0;Da:{Ea:{Fa:{if((g|0)<0){j=H[k>>2];if((g|0)!=-1){break Fa}if((p|0)<=0){H[b+16>>2]=H[b+16>>2]+(j+H[aa>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[_>>2]+H[k+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[R>>2]+H[k+8>>2]>>1);r=H[k+12>>2];j=H[M>>2];break Ea}break Fa}j=g+1|0;Ga:{if((j|0)<(p|0)){g=k+(j<<5)|0;H[b+16>>2]=H[b+16>>2]+(H[g>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[g+4>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[g+8>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[g+12>>2]+H[b+12>>2]>>1);break Ga}if((g|0)>=(p|0)){H[b+16>>2]=H[b+16>>2]+H[aa>>2];H[b+20>>2]=H[b+20>>2]+H[_>>2];H[b+24>>2]=H[b+24>>2]+H[R>>2];H[b+28>>2]=H[b+28>>2]+H[M>>2];break Ga}H[b+16>>2]=H[b+16>>2]+(H[aa>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[_>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[R>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[M>>2]+H[b+12>>2]>>1)}g=j;break Da}H[b+16>>2]=j+H[b+16>>2];H[b+20>>2]=H[b+20>>2]+H[k+4>>2];H[b+24>>2]=H[b+24>>2]+H[k+8>>2];r=H[k+12>>2];j=r}H[b+28>>2]=H[b+28>>2]+(j+r>>1);g=g+1|0}if((la|0)!=(g|0)){continue}break}}if(Ya(ia,s,lb,l,_a,qb,1,4)){continue}break}break}break ba}Ca(k);g=1}b=H[oa-16>>2];j=H[La>>2];h=H[Ma>>2];m=H[oa-8>>2];Ha(ia,b-j|0,H[oa-12>>2]-h|0,m-j|0,H[oa-4>>2]-h|0,H[z+52>>2],1,m-b|0);Ua(ia);break X}Ua(ia);Ca(k);g=0;break X}Ua(ia);g=0;break X}g=0;Ra(l);Ca(i)}na=$+32|0;if(g){break W}break b}h=0;A=na+-64|0;na=A;Ha:{Ia:{if(H[D+64>>2]){n=H[z+28>>2];j=n+N(H[z+24>>2],152)|0;l=H[j-152>>2];k=1;V=H[D+44>>2];P=H[V+4>>2];if((m|0)==1){break Ha}p=m-1|0;b=n;Ja:{if((m|0)!=2){m=p&1;i=p&-2;g=0;while(1){q=H[b+160>>2]-H[b+152>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+164>>2]-H[b+156>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+312>>2]-H[b+304>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+316>>2]-H[b+308>>2]|0;h=h>>>0>q>>>0?h:q;b=b+304|0;g=g+2|0;if((i|0)!=(g|0)){continue}break}if(!m){break Ja}}g=H[b+160>>2]-H[b+152>>2]|0;g=g>>>0>>0?h:g;b=H[b+164>>2]-H[b+156>>2]|0;h=b>>>0>>0?g:b}k=0;if(h>>>0>134217727){break Ha}b=H[j-144>>2];g=H[n+4>>2];j=H[n+12>>2];i=H[n>>2];q=H[n+8>>2];R=h<<5;r=Ia(R);H[A+32>>2]=r;if(!r){break Ha}m=j-g|0;j=q-i|0;g=P>>>1|0;_=g>>>0<=2?2:g;s=b-l|0;ka=s<<5;ja=N(s,28);$=N(s,24);ba=N(s,20);ca=s<<4;C=N(s,12);S=s<<3;H[A>>2]=r;aa=r+32|0;b=H[z+36>>2];while(1){q=m;H[A+8>>2]=m;h=j;H[A+40>>2]=h;Z=H[n+156>>2];W=H[n+164>>2];u=H[n+160>>2];g=H[n+152>>2];H[A+56>>2]=0;H[A+52>>2]=h;H[A+48>>2]=0;J=(g|0)%2|0;H[A+44>>2]=J;j=u-g|0;k=j-h|0;H[A+60>>2]=k;H[A+36>>2]=k;M=(P|0)<2;m=W-Z|0;Ka:{if(!(!M&m>>>0>15)){l=0;i=b;if(m>>>0<8){break Ka}t=j&-2;x=j&1;k=0;J=(u|0)==(g+1|0);while(1){g=A+32|0;yb(g,i,s,8);Sa(g);if(j){h=0;l=0;La:{if(!J){while(1){g=(h<<2)+i|0;u=r+(h<<5)|0;L[g>>2]=L[u>>2];Q=s<<2;L[Q+g>>2]=L[u+4>>2];L[g+S>>2]=L[u+8>>2];L[g+C>>2]=L[u+12>>2];u=h|1;g=(u<<2)+i|0;u=r+(u<<5)|0;L[g>>2]=L[u>>2];L[g+Q>>2]=L[u+4>>2];L[g+S>>2]=L[u+8>>2];L[g+C>>2]=L[u+12>>2];h=h+2|0;l=l+2|0;if((t|0)!=(l|0)){continue}break}if(!x){break La}}g=(h<<2)+i|0;h=r+(h<<5)|0;L[g>>2]=L[h>>2];L[g+(s<<2)>>2]=L[h+4>>2];L[g+S>>2]=L[h+8>>2];L[g+C>>2]=L[h+12>>2]}h=0;while(1){g=(h<<2)+i|0;l=r+(h<<5)|0;L[g+ca>>2]=L[l+16>>2];L[g+ba>>2]=L[l+20>>2];L[g+$>>2]=L[l+24>>2];L[g+ja>>2]=L[l+28>>2];h=h+1|0;if((j|0)!=(h|0)){continue}break}}i=i+ka|0;g=k+15|0;l=k+8|0;k=l;if(g>>>0>>0){continue}break}break Ka}g=m>>>3|0;t=g>>>0

      >>0?g:P;x=(m>>>0)/(t>>>0)&-8;l=m&-8;g=0;i=b;while(1){u=Fa(48);if(!u){break Ia}Q=Ia(R);H[u>>2]=Q;if(!Q){k=0;Ra(V);Ca(u);Ca(r);break Ha}H[u+40>>2]=i;H[u+36>>2]=s;H[u+32>>2]=j;H[u+28>>2]=k;H[u+24>>2]=0;H[u+20>>2]=h;H[u+16>>2]=0;H[u+12>>2]=J;H[u+8>>2]=h;H[u+4>>2]=k;Y=l-N(g,x)|0;g=g+1|0;Q=(t|0)==(g|0)?Y:x;H[u+44>>2]=Q;db(V,12,u);i=(N(s,Q)<<2)+i|0;if((g|0)!=(t|0)){continue}break}Ra(V)}Ma:{if(m>>>0<=l>>>0){break Ma}h=A+32|0;g=m-l|0;yb(h,i,s,g);Sa(h);if(!j){break Ma}J=g&-4;x=g&3;k=0;Q=Z+(l-W|0)>>>0>4294967292;while(1){u=(k<<2)+i|0;t=r+(k<<5)|0;h=0;l=0;g=0;Na:{if(!Q){while(1){L[u+(N(h,s)<<2)>>2]=L[t+(h<<2)>>2];g=h|1;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=h|2;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=h|3;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];h=h+4|0;l=l+4|0;if((J|0)!=(l|0)){continue}break}g=h;if(!x){break Na}}h=0;while(1){L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=g+1|0;h=h+1|0;if((x|0)!=(h|0)){continue}break}}k=k+1|0;if((k|0)!=(j|0)){continue}break}}u=m-q|0;H[A+4>>2]=u;g=H[n+156>>2];H[A+28>>2]=u;H[A+24>>2]=0;H[A+20>>2]=q;H[A+16>>2]=0;J=(g|0)%2|0;H[A+12>>2]=J;Oa:{if(!(!M&j>>>0>15)){g=b;if(j>>>0<8){break Oa}ia=m&-2;fa=m&1;ga=u&-2;Y=u&1;la=q&-2;oa=q&1;h=J<<5;M=aa-h|0;Q=h+r|0;ea=N(q,s)<<2;ma=W-1|0;ra=(ma|0)==(q+Z|0);l=j;while(1){h=0;i=0;Pa:{Qa:{switch(q|0){default:while(1){k=(N(h,s)<<2)+g|0;x=H[k+28>>2];t=Q+(h<<6)|0;H[t+24>>2]=H[k+24>>2];H[t+28>>2]=x;x=H[k+20>>2];H[t+16>>2]=H[k+16>>2];H[t+20>>2]=x;x=H[k+12>>2];H[t+8>>2]=H[k+8>>2];H[t+12>>2]=x;x=H[k+4>>2];H[t>>2]=H[k>>2];H[t+4>>2]=x;t=h|1;k=Q+(t<<6)|0;t=(N(t,s)<<2)+g|0;x=H[t+28>>2];H[k+24>>2]=H[t+24>>2];H[k+28>>2]=x;x=H[t+20>>2];H[k+16>>2]=H[t+16>>2];H[k+20>>2]=x;x=H[t+12>>2];H[k+8>>2]=H[t+8>>2];H[k+12>>2]=x;x=H[t+4>>2];H[k>>2]=H[t>>2];H[k+4>>2]=x;h=h+2|0;i=i+2|0;if((la|0)!=(i|0)){continue}break};if(!oa){break Pa}break;case 0:break Pa;case 1:break Qa}}i=Q+(h<<6)|0;h=(N(h,s)<<2)+g|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}Ra:{if((m|0)==(q|0)){break Ra}t=g+ea|0;h=0;x=0;if(!ra){while(1){i=t+(N(h,s)<<2)|0;ha=H[i+28>>2];k=M+(h<<6)|0;H[k+24>>2]=H[i+24>>2];H[k+28>>2]=ha;ha=H[i+20>>2];H[k+16>>2]=H[i+16>>2];H[k+20>>2]=ha;ha=H[i+12>>2];H[k+8>>2]=H[i+8>>2];H[k+12>>2]=ha;ha=H[i+4>>2];H[k>>2]=H[i>>2];H[k+4>>2]=ha;k=h|1;i=M+(k<<6)|0;k=t+(N(k,s)<<2)|0;ha=H[k+28>>2];H[i+24>>2]=H[k+24>>2];H[i+28>>2]=ha;ha=H[k+20>>2];H[i+16>>2]=H[k+16>>2];H[i+20>>2]=ha;ha=H[k+12>>2];H[i+8>>2]=H[k+8>>2];H[i+12>>2]=ha;ha=H[k+4>>2];H[i>>2]=H[k>>2];H[i+4>>2]=ha;h=h+2|0;x=x+2|0;if((ga|0)!=(x|0)){continue}break}if(!Y){break Ra}}i=M+(h<<6)|0;h=t+(N(h,s)<<2)|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}Sa(A);Sa:{if(!m){break Sa}h=0;i=0;if((Z|0)!=(ma|0)){while(1){k=r+(h<<5)|0;x=H[k+28>>2];t=(N(h,s)<<2)+g|0;H[t+24>>2]=H[k+24>>2];H[t+28>>2]=x;x=H[k+20>>2];H[t+16>>2]=H[k+16>>2];H[t+20>>2]=x;x=H[k+12>>2];H[t+8>>2]=H[k+8>>2];H[t+12>>2]=x;x=H[k+4>>2];H[t>>2]=H[k>>2];H[t+4>>2]=x;t=h|1;k=(N(t,s)<<2)+g|0;t=r+(t<<5)|0;x=H[t+28>>2];H[k+24>>2]=H[t+24>>2];H[k+28>>2]=x;x=H[t+20>>2];H[k+16>>2]=H[t+16>>2];H[k+20>>2]=x;x=H[t+12>>2];H[k+8>>2]=H[t+8>>2];H[k+12>>2]=x;x=H[t+4>>2];H[k>>2]=H[t>>2];H[k+4>>2]=x;h=h+2|0;i=i+2|0;if((ia|0)!=(i|0)){continue}break}if(!fa){break Sa}}i=(N(h,s)<<2)+g|0;h=r+(h<<5)|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}g=g+32|0;l=l-8|0;if(l>>>0>7){continue}break}break Oa}g=j>>>3|0;l=g>>>0<_>>>0?g:_;i=(j>>>0)/(l>>>0)&-8;t=j&-8;k=0;g=b;while(1){h=Fa(48);if(!h){break Ia}x=Ia(R);H[h>>2]=x;if(!x){k=0;Ra(V);Ca(h);Ca(r);break Ha}H[h+40>>2]=g;H[h+36>>2]=s;H[h+32>>2]=m;H[h+28>>2]=u;H[h+24>>2]=0;H[h+20>>2]=q;H[h+16>>2]=0;H[h+12>>2]=J;H[h+8>>2]=q;H[h+4>>2]=u;Y=t-N(i,k)|0;k=k+1|0;x=(l|0)==(k|0)?Y:i;H[h+44>>2]=x;db(V,13,h);g=(x<<2)+g|0;if((k|0)!=(l|0)){continue}break}Ra(V)}k=j&7;Ta:{if(!k){break Ta}x=J<<5;Ua:{if(!q){break Ua}t=r+x|0;i=k<<2;h=0;if((q|0)!=1){J=q&1;M=q&-2;l=0;while(1){Q=!i;if(!Q){B(t+(h<<6)|0,(N(h,s)<<2)+g|0,i)}if(!Q){Q=h|1;B(t+(Q<<6)|0,(N(s,Q)<<2)+g|0,i)}h=h+2|0;l=l+2|0;if((M|0)!=(l|0)){continue}break}if(!J){break Ua}}if(!i){break Ua}B(t+(h<<6)|0,(N(h,s)<<2)+g|0,i)}Va:{if((m|0)==(q|0)){break Va}t=aa-x|0;x=(N(q,s)<<2)+g|0;i=k<<2;h=0;if((Z|0)!=(W+(q^-1)|0)){q=u&1;u=u&-2;l=0;while(1){J=!i;if(!J){B(t+(h<<6)|0,x+(N(h,s)<<2)|0,i)}if(!J){J=h|1;B(t+(J<<6)|0,x+(N(s,J)<<2)|0,i)}h=h+2|0;l=l+2|0;if((u|0)!=(l|0)){continue}break}if(!q){break Va}}if(!i){break Va}B(t+(h<<6)|0,x+(N(h,s)<<2)|0,i)}Sa(A);if(!m){break Ta}i=k<<2;h=0;if((W|0)!=(Z+1|0)){q=m&1;k=m&-2;l=0;while(1){u=!i;if(!u){B((N(h,s)<<2)+g|0,r+(h<<5)|0,i)}if(!u){u=h|1;B((N(u,s)<<2)+g|0,r+(u<<5)|0,i)}h=h+2|0;l=l+2|0;if((k|0)!=(l|0)){continue}break}if(!q){break Ta}}if(!i){break Ta}B((N(h,s)<<2)+g|0,r+(h<<5)|0,i)}n=n+152|0;p=p-1|0;if(p){continue}break}k=1;Ca(r);break Ha}k=1;j=H[z+28>>2];J=j+N(m,152)|0;ga=J-152|0;if(H[ga>>2]==H[J-144>>2]){break Ha}Y=J-148|0;if(H[Y>>2]==H[J-140>>2]){break Ha}g=H[j+4>>2];n=H[j+12>>2];l=H[j>>2];i=H[j+8>>2];u=H[z+68>>2];V=H[z+64>>2];t=H[z+60>>2];Z=H[z+56>>2];S=Xb(z,m);if(!S){k=0;break Ha}if((m|0)==1){b=H[J-16>>2];g=H[ga>>2];j=H[Y>>2];h=H[J-8>>2];Ha(S,b-g|0,H[J-12>>2]-j|0,h-g|0,H[J-4>>2]-j|0,H[z+52>>2],1,h-b|0);Ua(S);break Ha}k=0;b=j;Wa:{if((m|0)!=2){h=m-1|0;p=h&1;q=h&-2;h=0;while(1){s=H[b+160>>2]-H[b+152>>2]|0;s=k>>>0>s>>>0?k:s;r=H[b+164>>2]-H[b+156>>2]|0;s=r>>>0>>0?s:r;r=H[b+312>>2]-H[b+304>>2]|0;s=r>>>0>>0?s:r;r=H[b+316>>2]-H[b+308>>2]|0;k=r>>>0>>0?s:r;b=b+304|0;h=h+2|0;if((q|0)!=(h|0)){continue}break}if(!p){break Wa}}h=H[b+160>>2]-H[b+152>>2]|0;h=h>>>0>>0?k:h;b=H[b+164>>2]-H[b+156>>2]|0;k=b>>>0>>0?h:b}Xa:{if(k>>>0>=134217728){break Xa}M=Ia(k<<5);H[A+32>>2]=M;if(!M){break Xa}H[A>>2]=M;Ya:{if(m){n=n-g|0;b=i-l|0;la=M+32|0;P=m;oa=H[z+20>>2];ka=1;ba=0;while(1){H[A+8>>2]=n;H[A+40>>2]=b;h=H[j+164>>2];m=H[j+160>>2];g=H[j+156>>2];l=H[j+152>>2];R=(l|0)%2|0;H[A+44>>2]=R;ea=(g|0)%2|0;H[A+12>>2]=ea;W=m-l|0;_=W-b|0;H[A+36>>2]=_;s=h-g|0;aa=s-n|0;H[A+4>>2]=aa;l=Z;x=l;r=t;h=r;g=V;k=g;m=u;q=m;Za:{if(!ba&(ka|0)==(oa|0)){break Za}C=oa-ka|0;h=0;x=0;if(l){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=l+(m^-1)|0;m=i^-1;l=g>>>0>>0?m+1|0:m;m=C&31;if((C&63)>>>0>=32){x=l>>>m|0}else{x=((1<>>m}}if(t){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=t+(h^-1)|0;h=i^-1;m=g>>>0>>0?h+1|0:h;h=C&31;if((C&63)>>>0>=32){h=m>>>h|0}else{h=((1<>>h}}m=0;g=0;if(V){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=V+(l^-1)|0;l=i^-1;i=g>>>0>>0?l+1|0:l;l=C&31;if((C&63)>>>0>=32){g=i>>>l|0}else{g=((1<>>l}}if(u){m=C&31;if((C&63)>>>0>=32){i=-1<>>32-m}m=u+(l^-1)|0;l=i^-1;i=m>>>0>>0?l+1|0:l;l=C&31;if((C&63)>>>0>=32){m=i>>>l|0}else{m=((1<>>l}}k=0;l=0;Q=1<>>0>>0){l=C&31;if((C&63)>>>0>=32){i=-1<>>32-l}p=p^-1;l=p+(Z-Q|0)|0;i=i^-1;p=l>>>0

      >>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){l=p>>>i|0}else{l=((1<>>i}}if(Q>>>0>>0){i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}q=p^-1;p=q+(V-Q|0)|0;i=i^-1;q=q>>>0>p>>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){k=q>>>i|0}else{k=((1<>>i}}q=0;r=0;if(t>>>0>Q>>>0){i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}r=p^-1;p=r+(t-Q|0)|0;i=i^-1;r=p>>>0>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){r=r>>>i|0}else{r=((1<>>i}}if(u>>>0<=Q>>>0){break Za}i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}q=p^-1;p=q+(u-Q|0)|0;i=i^-1;q=q>>>0>p>>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){q=q>>>i|0}else{q=((1<>>i}}i=H[j+180>>2];p=k-i|0;p=k>>>0>=p>>>0?p:0;k=p+4|0;p=k>>>0

      >>0?-1:k;fa=p>>>0<_>>>0?p:_;p=H[j+216>>2];k=g-p|0;g=g>>>0>=k>>>0?k:0;k=g+4|0;g=g>>>0>k>>>0?-1:k;Q=b>>>0>g>>>0?g:b;g=(R?fa:Q)<<1;k=(R?Q:fa)<<1|1;_=g>>>0>k>>>0?g:k;$=_>>>0>>0;g=l-i|0;g=g>>>0<=l>>>0?g:0;l=g-4|0;ca=g>>>0>=l>>>0?l:0;g=x-p|0;g=g>>>0<=x>>>0?g:0;l=g-4|0;C=g>>>0>=l>>>0?l:0;x=(R?ca:C)<<1;ma=(R?C:ca)<<1|1;ra=x>>>0>>0;i=H[j+184>>2];g=h-i|0;g=g>>>0<=h>>>0?g:0;h=g-4|0;g=g>>>0>=h>>>0?h:0;p=g;h=H[j+220>>2];l=r-h|0;l=l>>>0<=r>>>0?l:0;r=l-4|0;ja=l>>>0>=r>>>0?r:0;l=ja;i=m-i|0;m=i>>>0<=m>>>0?i:0;i=m+4|0;m=i>>>0>>0?-1:i;i=m>>>0>>0?m:n;k=i;h=q-h|0;h=h>>>0<=q>>>0?h:0;m=h+4|0;h=h>>>0>m>>>0?-1:m;ia=h>>>0>>0?h:aa;q=ia;if(ea){k=q;q=i;p=l;l=g}$=$?_:W;x=ra?x:ma;H[A+60>>2]=fa;H[A+56>>2]=ca;H[A+52>>2]=Q;H[A+48>>2]=C;_a:{if(s>>>0<8){m=7;h=0;break _a}h=R<<5;_=(la-h|0)+(ca<<6)|0;ma=_+28|0;aa=(h+M|0)+(C<<6)|0;ra=aa+28|0;ha=_+24|0;Aa=aa+24|0;Ka=_+20|0;sa=aa+20|0;Na=_+16|0;ta=aa+16|0;Oa=_+12|0;Qa=aa+12|0;va=_+8|0;wa=aa+8|0;xa=_+4|0;ua=aa+4|0;r=b+fa|0;R=b+ca|0;fa=n+ia|0;La=n+ja|0;Ma=M+(x<<5)|0;b=0;while(1){h=b|7;$a:{if(!(b>>>0>>0&h>>>0>=g>>>0|b>>>0>>0&h>>>0>=La>>>0)){h=b+8|0;break $a}h=b+1|0;Ha(S,C,b,Q,h,aa,16,0);Ha(S,R,b,r,h,_,16,0);m=b+2|0;Ha(S,C,h,Q,m,ua,16,0);Ha(S,R,h,r,m,xa,16,0);h=b+3|0;Ha(S,C,m,Q,h,wa,16,0);Ha(S,R,m,r,h,va,16,0);m=b+4|0;Ha(S,C,h,Q,m,Qa,16,0);Ha(S,R,h,r,m,Oa,16,0);h=b+5|0;Ha(S,C,m,Q,h,ta,16,0);Ha(S,R,m,r,h,Na,16,0);m=b+6|0;Ha(S,C,h,Q,m,sa,16,0);Ha(S,R,h,r,m,Ka,16,0);ca=b+7|0;Ha(S,C,m,Q,ca,Aa,16,0);Ha(S,R,m,r,ca,ha,16,0);h=b+8|0;Ha(S,C,ca,Q,h,ra,16,0);Ha(S,R,ca,r,h,ma,16,0);Sa(A+32|0);if(!Ya(S,x,b,$,h,Ma,8,1)){break Ya}}b=h;m=b|7;if(s>>>0>m>>>0){continue}break}}if(!(!(h>>>0>>0&g>>>0<=m>>>0)&(n+ia>>>0<=h>>>0|n+ja>>>0>m>>>0)|h>>>0>=s>>>0)){b=A+32|0;m=0;Q=s-h|0;if(Q){while(1){r=h+m|0;C=r+1|0;R=H[b+16>>2];_=m<<2;Ha(S,R,r,H[b+20>>2],C,_+((H[b>>2]+(H[b+12>>2]<<5)|0)+(R<<6)|0)|0,16,0);R=H[b+24>>2];aa=H[b+8>>2];Ha(S,R+aa|0,r,aa+H[b+28>>2]|0,C,(_+((H[b>>2]-(H[b+12>>2]<<5)|0)+(R<<6)|0)|0)+32|0,16,0);m=m+1|0;if((Q|0)!=(m|0)){continue}break}}Sa(b);if(!Ya(S,x,h,$,s,M+(x<<5)|0,8,1)){break Ya}}H[A+28>>2]=ia;H[A+24>>2]=ja;H[A+20>>2]=i;H[A+16>>2]=g;if(x>>>0<$>>>0){b=k<<1;h=q<<1|1;b=b>>>0>h>>>0?b:h;h=b>>>0>>0?b:s;b=ea<<5;m=(la-b|0)+(ja<<6)|0;q=(b+M|0)+(g<<6)|0;r=n+ia|0;n=n+ja|0;b=p<<1;l=l<<1|1;l=b>>>0>>0?b:l;p=M+(l<<5)|0;while(1){b=$-x|0;b=(b>>>0>=8?8:b)+x|0;Ha(S,x,g,b,i,q,1,16);Ha(S,x,n,b,r,m,1,16);Sa(A);if(!Ya(S,x,l,b,h,p,1,8)){break Ya}x=x+8|0;if($>>>0>x>>>0){continue}break}}j=j+152|0;n=s;b=W;ka=ka+1|0;ba=ka?ba:ba+1|0;if(ba|(P|0)!=(ka|0)){continue}break}}k=1;b=H[J-16>>2];g=H[ga>>2];j=H[Y>>2];h=H[J-8>>2];Ha(S,b-g|0,H[J-12>>2]-j|0,h-g|0,H[J-4>>2]-j|0,H[z+52>>2],1,h-b|0);Ua(S);Ca(M);break Ha}Ua(S);Ca(M);k=0;break Ha}Ua(S);k=0;break Ha}k=0;Ra(V);Ca(r)}na=A- -64|0;if(k){break W}break b}v=v+1080|0;E=E+52|0;z=z+76|0;o=o+1|0;if(o>>>0>2]){continue}break}v=H[D+32>>2];w=H[H[D+20>>2]>>2]}n=H[v+16>>2];ab:{if(H[D+68>>2]|!n){break ab}E=H[w+20>>2];g=H[E+28>>2];bb:{cb:{j=H[D+64>>2];if(j){o=H[w+16>>2];if(o>>>0<3){break bb}b=H[E+24>>2];if(!((b|0)==H[E+100>>2]&(b|0)==H[E+176>>2])){Ba(f,1,10089,0);break b}h=H[H[D+24>>2]+24>>2];m=H[h+36>>2];db:{if((m|0)!=H[h+88>>2]|(m|0)!=H[h+140>>2]){break db}h=N(b,152);b=h+g|0;b=N(H[b-140>>2]-H[b-148>>2]|0,H[b-144>>2]-H[b-152>>2]|0);g=h+H[E+104>>2]|0;if((b|0)!=(N(H[g-140>>2]-H[g-148>>2]|0,H[g-144>>2]-H[g-152>>2]|0)|0)){break db}g=h+H[E+180>>2]|0;if((N(H[g-140>>2]-H[g-148>>2]|0,H[g-144>>2]-H[g-152>>2]|0)|0)==(b|0)){break cb}}Ba(f,1,10089,0);break b}o=H[w+16>>2];if(o>>>0<3){break bb}h=H[H[D+24>>2]+24>>2];b=H[h+36>>2];eb:{if((b|0)!=H[h+88>>2]){break eb}h=H[h+140>>2];if((h|0)!=(b|0)){break eb}m=N(b,152);b=g+m|0;b=N(H[b+148>>2]-H[b+140>>2]|0,H[b+144>>2]-H[b+136>>2]|0);g=m+H[E+104>>2]|0;if((b|0)!=(N(H[g+148>>2]-H[g+140>>2]|0,H[g+144>>2]-H[g+136>>2]|0)|0)){break eb}g=H[E+180>>2]+N(h,152)|0;if((N(H[g+148>>2]-H[g+140>>2]|0,H[g+144>>2]-H[g+136>>2]|0)|0)==(b|0)){break cb}}Ba(f,1,10089,0);break b}if((n|0)==2){if(!H[v+5608>>2]){break ab}j=Fa(o<<2);if(!j){break b}l=H[w+16>>2];fb:{if(!l){break fb}gb:{hb:{if(H[D+64>>2]){o=l&3;g=0;if(l>>>0>=4){break hb}z=0;break gb}o=l&3;g=0;ib:{if(l>>>0<4){z=0;break ib}n=l&-4;z=0;h=0;while(1){m=j+(z<<2)|0;H[m>>2]=H[E+52>>2];H[m+4>>2]=H[E+128>>2];H[m+8>>2]=H[E+204>>2];H[m+12>>2]=H[E+280>>2];z=z+4|0;E=E+304|0;h=h+4|0;if((n|0)!=(h|0)){continue}break}if(!o){break fb}}while(1){H[j+(z<<2)>>2]=H[E+52>>2];z=z+1|0;E=E+76|0;g=g+1|0;if((o|0)!=(g|0)){continue}break}break fb}n=l&-4;z=0;h=0;while(1){m=j+(z<<2)|0;H[m>>2]=H[E+36>>2];H[m+4>>2]=H[E+112>>2];H[m+8>>2]=H[E+188>>2];H[m+12>>2]=H[E+264>>2];z=z+4|0;E=E+304|0;h=h+4|0;if((n|0)!=(h|0)){continue}break}if(!o){break fb}}while(1){H[j+(z<<2)>>2]=H[E+36>>2];z=z+1|0;E=E+76|0;g=g+1|0;if((o|0)!=(g|0)){continue}break}}h=H[v+5608>>2];g=j;n=0;i=Fa(l<<3);j=0;jb:{if(!i){break jb}if(!(!b|!l)){j=b;k=i+(l<<2)|0;s=l&-4;p=l&3;r=l>>>0<4;while(1){w=0;o=0;b=0;kb:{if(!r){while(1){b=w<<2;L[b+i>>2]=L[H[b+g>>2]>>2];m=b|4;L[m+i>>2]=L[H[g+m>>2]>>2];m=b|8;L[m+i>>2]=L[H[g+m>>2]>>2];b=b|12;L[b+i>>2]=L[H[b+g>>2]>>2];w=w+4|0;o=o+4|0;if((s|0)!=(o|0)){continue}break}b=w;if(!p){break kb}}w=0;while(1){m=b<<2;L[m+i>>2]=L[H[g+m>>2]>>2];b=b+1|0;w=w+1|0;if((p|0)!=(w|0)){continue}break}}m=0;b=h;while(1){u=m<<2;v=u+k|0;H[v>>2]=0;da=O(0);w=0;o=0;lb:{if(!r){while(1){q=i+(w<<2)|0;da=O(O(L[b>>2]*L[q>>2])+da);L[v>>2]=da;da=O(O(L[b+4>>2]*L[q+4>>2])+da);L[v>>2]=da;da=O(O(L[b+8>>2]*L[q+8>>2])+da);L[v>>2]=da;da=O(O(L[b+12>>2]*L[q+12>>2])+da);L[v>>2]=da;w=w+4|0;b=b+16|0;o=o+4|0;if((s|0)!=(o|0)){continue}break}o=w;if(!p){break lb}}w=0;while(1){da=O(O(L[b>>2]*L[i+(o<<2)>>2])+da);L[v>>2]=da;o=o+1|0;b=b+4|0;w=w+1|0;if((p|0)!=(w|0)){continue}break}}w=g+u|0;o=H[w>>2];H[w>>2]=o+4;L[o>>2]=da;m=m+1|0;if((l|0)!=(m|0)){continue}break}n=n+1|0;if((j|0)!=(n|0)){continue}break}}Ca(i);j=1}b=j;Ca(g);if(b){break ab}break b}if(H[H[v+5584>>2]+20>>2]==1){if(j){$b(H[E+36>>2],H[E+112>>2],H[E+188>>2],b);break ab}$b(H[E+52>>2],H[E+128>>2],H[E+204>>2],b);break ab}if(j){_b(H[E+36>>2],H[E+112>>2],H[E+188>>2],b);break ab}_b(H[E+52>>2],H[E+128>>2],H[E+204>>2],b);break ab}H[pa>>2]=o;Ba(f,1,10150,pa)}q=H[H[D+20>>2]>>2];if(!H[q+16>>2]){X=1;break b}s=H[D+68>>2];n=H[q+20>>2];b=H[H[D+32>>2]+5584>>2];m=H[H[D+24>>2]+24>>2];j=0;while(1){mb:{if(H[s+(j<<2)>>2]?0:s){break mb}h=H[n+28>>2];g=h+N(H[m+36>>2],152)|0;nb:{if(!H[D+64>>2]){h=H[g+148>>2]-H[g+140>>2]|0;w=H[g+144>>2]-H[g+136>>2]|0;l=0;o=52;break nb}h=h+N(H[n+24>>2],152)|0;w=H[g+8>>2]-H[g>>2]|0;l=H[h-144>>2]-(w+H[h-152>>2]|0)|0;h=H[g+12>>2]-H[g+4>>2]|0;o=36}g=H[m+24>>2];ob:{if(H[m+32>>2]){g=1<>2];if(H[b+20>>2]==1){v=w&-2;p=w&1;E=0;l=l<<2;while(1){o=0;pb:{if((w|0)!=1){while(1){g=H[b+1076>>2]+H[X>>2]|0;H[X>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;g=H[b+1076>>2]+H[X+4>>2]|0;H[X+4>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;X=X+8|0;o=o+2|0;if((v|0)!=(o|0)){continue}break}if(!p){break pb}}g=H[b+1076>>2]+H[X>>2]|0;H[X>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;X=X+4|0}X=l+X|0;E=E+1|0;if((E|0)!=(h|0)){continue}break}break mb}r=i>>31;g=0;while(1){o=0;while(1){da=L[X>>2];p=z;qb:{if(da>O(2147483648)){break qb}p=i;if(da>2];Xa=O(T(da));ob=O(da-Xa);if(obO(.5)){break rb}da=O(Xa*O(.5));Za=O(da-O(T(da)))==O(0)?Xa:Za}}p=~~Za;v=k+p|0;k=(k>>31)+(p>>31)|0;p=p>>>0>v>>>0?k+1|0:k;p=(p|0)<=(r|0)&i>>>0>v>>>0|(p|0)<(r|0)?i:v>>>0>>0&(p|0)<=0|(p|0)<0?v:z}H[X>>2]=p;X=X+4|0;o=o+1|0;if((w|0)!=(o|0)){continue}break}X=(l<<2)+X|0;g=g+1|0;if((h|0)!=(g|0)){continue}break}}n=n+76|0;b=b+1080|0;m=m+52|0;X=1;j=j+1|0;if(j>>>0>2]){continue}break}break b}X=0;Ba(f,1,3372,0)}na=pa+16|0;if(!X){ib(ya);H[a+8>>2]=H[a+8>>2]|32768;Ba(f,1,11451,0);break a}sb:{if(!c){break sb}b=0;m=H[a+232>>2];g=Ob(m,1);if(!((g|0)==-1|d>>>0>>0)){tb:{b=1;d=H[m+24>>2];if(!H[d+16>>2]){break tb}h=H[d+24>>2];n=H[H[H[m+20>>2]>>2]+20>>2];while(1){b=H[h+24>>2];j=b&7;l=b>>>3|0;d=H[n+28>>2];b=d+N(H[h+36>>2],152)|0;ub:{if(H[m+64>>2]){g=d+N(H[n+24>>2],152)|0;d=H[b+8>>2]-H[b>>2]|0;o=H[g-144>>2]-(d+H[g-152>>2]|0)|0;g=H[b+12>>2]-H[b+4>>2]|0;b=36;break ub}g=H[b+148>>2]-H[b+140>>2]|0;d=H[b+144>>2]-H[b+136>>2]|0;o=0;b=52}b=H[b+n>>2];vb:{wb:{switch((l+((j|0)!=0)|0)-1|0){case 2:case 3:if(!g){break vb}d=d<<2;if((g|0)!=1){j=g&1;l=g&-2;g=0;while(1){i=!d;if(!i){B(c,b,d)}w=o<<2;b=w+(b+d|0)|0;c=c+d|0;if(!i){B(c,b,d)}c=c+d|0;b=w+(b+d|0)|0;g=g+2|0;if((l|0)!=(g|0)){continue}break}if(!j){break vb}}if(d){B(c,b,d)}c=c+d|0;break vb;case 0:j=!g|!d;if(H[h+32>>2]){if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;xb:{if(!w){while(1){F[c|0]=H[b>>2];F[c+1|0]=H[b+4>>2];F[c+2|0]=H[b+8>>2];F[c+3|0]=H[b+12>>2];F[c+4|0]=H[b+16>>2];F[c+5|0]=H[b+20>>2];F[c+6|0]=H[b+24>>2];F[c+7|0]=H[b+28>>2];c=c+8|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break xb}}d=0;while(1){F[c|0]=H[b>>2];c=c+1|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}break vb}if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;o=o<<2;while(1){d=0;yb:{if(!w){while(1){F[c|0]=H[b>>2];F[c+1|0]=H[b+4>>2];F[c+2|0]=H[b+8>>2];F[c+3|0]=H[b+12>>2];F[c+4|0]=H[b+16>>2];F[c+5|0]=H[b+20>>2];F[c+6|0]=H[b+24>>2];F[c+7|0]=H[b+28>>2];c=c+8|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break yb}}d=0;while(1){F[c|0]=H[b>>2];c=c+1|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=b+o|0;j=j+1|0;if((j|0)!=(g|0)){continue}break};break vb;case 1:break wb;default:break vb}}j=!g|!d;if(H[h+32>>2]){if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;zb:{if(!w){while(1){G[c>>1]=H[b>>2];G[c+2>>1]=H[b+4>>2];G[c+4>>1]=H[b+8>>2];G[c+6>>1]=H[b+12>>2];G[c+8>>1]=H[b+16>>2];G[c+10>>1]=H[b+20>>2];G[c+12>>1]=H[b+24>>2];G[c+14>>1]=H[b+28>>2];c=c+16|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break zb}}d=0;while(1){G[c>>1]=H[b>>2];c=c+2|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}break vb}if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;Ab:{if(!w){while(1){G[c>>1]=H[b>>2];G[c+2>>1]=H[b+4>>2];G[c+4>>1]=H[b+8>>2];G[c+6>>1]=H[b+12>>2];G[c+8>>1]=H[b+16>>2];G[c+10>>1]=H[b+20>>2];G[c+12>>1]=H[b+24>>2];G[c+14>>1]=H[b+28>>2];c=c+16|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break Ab}}d=0;while(1){G[c>>1]=H[b>>2];c=c+2|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}}n=n+76|0;h=h+52|0;b=1;pb=pb+1|0;if(pb>>>0>2]+16>>2]){continue}break}}}if(!b){break a}b=H[ya+5596>>2];if(!b){break sb}Ca(b);H[ya+5596>>2]=0;H[ya+5600>>2]=0}F[a+92|0]=I[a+92|0]&254;H[a+8>>2]=H[a+8>>2]&-129;kb=1;c=Pa(e);b=H[a+8>>2];if(!(c|qa)&(b|0)==64|(b|0)==256){break a}if((Ja(e,za+10|0,2,f)|0)!=2){Ba(f,H[a+208>>2]?1:2,2472,0);kb=!H[a+208>>2];break a}Da(za+10|0,za+12|0,2);b=H[za+12>>2];if((b|0)==65424){break a}if((b|0)==65497){H[a+8>>2]=256;H[a+228>>2]=0;break a}if(!(Pa(e)|qa)){H[a+8>>2]=64;Ba(f,2,8419,0);break a}kb=0;Ba(f,1,8306,0)}na=za+16|0;return kb|0}function bb(a,b,c,d,e,f,g,h,i,j,k){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,J=0,M=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,oa=0,pa=0,sa=O(0);s=na-80|0;na=s;H[s+40>>2]=65424;u=N(H[a+132>>2],H[a+128>>2]);a:{b:{c:{n=H[a+8>>2];d:{if((n|0)!=8){j=0;if((n|0)!=256){break a}H[s+40>>2]=65497;break d}if(F[a+92|0]&1){break d}T=u&-2;A=u&1;M=s+77|0;S=s+76|0;P=s+72|0;n=65424;e:{f:{while(1){g:{h:{i:{j:{k:{l:{m:{n:{o=H[a+84>>2];if(!o){break n}l=H[a+80>>2];if(o>>>0<=l>>>0){break n}n=H[a+88>>2]+(l<<3)|0;o=H[n>>2];n=H[n+4>>2];H[a+80>>2]=l+1;if(!ab(j,o,n,k)){Ba(k,1,5440,0);j=0;break a}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]==65424){break m}Ba(k,1,4073,0);j=0;break a}if((n|0)==65427){break l}}while(1){if(!(Pa(j)|qa)){H[a+8>>2]=64;break l}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+36|0,2);if(K[s+36>>2]<=1){Ba(k,1,6048,0);j=0;break a}o:{if(H[s+40>>2]!=32896){break o}if(Pa(j)|qa){break o}H[a+8>>2]=64;break l}m=H[a+8>>2];p:{if(!(m&16)){n=H[s+36>>2];break p}n=H[s+36>>2];l=H[a+24>>2];if(!l){break p}o=n+2|0;if(o>>>0>l>>>0){Ba(k,1,8370,0);j=0;break a}H[a+24>>2]=l-o}l=n-2|0;H[s+36>>2]=l;n=24912;w=H[s+40>>2];while(1){o=n;p=H[n>>2];if(p){n=n+12|0;if((p|0)!=(w|0)){continue}}break}if(!(m&H[o+4>>2])){Ba(k,1,5397,0);j=0;break a}q:{if(K[a+20>>2]>=l>>>0){n=H[a+16>>2];break q}n=Pa(j);m=qa;if((m|0)<0){l=1}else{l=(m|0)<=0&l>>>0>n>>>0}if(l){Ba(k,1,5797,0);j=0;break a}n=Ga(H[a+16>>2],H[s+36>>2]);if(!n){Ca(H[a+16>>2]);H[a+16>>2]=0;H[a+20>>2]=0;Ba(k,1,4973,0);j=0;break a}H[a+16>>2]=n;l=H[s+36>>2];H[a+20>>2]=l}n=Ja(j,n,l,k);if((n|0)!=H[s+36>>2]){Ba(k,1,2472,0);j=0;break a}l=H[o+8>>2];if(!l){Ba(k,1,11725,0);j=0;break a}if(!(ra[l|0](a,H[a+16>>2],n,k)|0)){H[s+32>>2]=H[s+40>>2];Ba(k,1,13959,s+32|0);j=0;break a}q=H[j+56>>2];m=H[s+36>>2];w=H[a+224>>2];l=H[w+40>>2];v=H[a+228>>2];r=N(v,40);n=l+r|0;x=H[n+20>>2];C=x+1|0;o=H[n+28>>2];r:{if(C>>>0<=o>>>0){n=H[n+24>>2];break r}l=~~O(O(o>>>0)+O(100))>>>0;H[n+28>>2]=l;n=Ga(H[n+24>>2],N(l,24));l=H[w+40>>2];o=r+l|0;if(!n){break k}H[o+24>>2]=n;x=H[o+20>>2];C=x+1|0}n=N(x,24)+n|0;H[n+16>>2]=m+4;o=(q-m|0)-4|0;H[n+8>>2]=o;H[n+12>>2]=o>>31;G[n>>1]=p;n=l+r|0;H[n+20>>2]=C;s:{if((p|0)!=65424){break s}l=H[n+16>>2];t:{if(!l){break t}p=H[n+12>>2];if(p>>>0>=K[n+4>>2]){break t}n=l+N(p,24)|0;H[n>>2]=o;H[n+4>>2]=0}n=(H[j+56>>2]-H[s+36>>2]|0)-4|0;l=H[a+48>>2];o=H[a+52>>2];if((o|0)>0){l=1}else{l=(o|0)>=0&l>>>0>=n>>>0}if(l){break s}H[a+48>>2]=n;H[a+52>>2]=0}if(I[a+92|0]&4){if((qb(j,H[a+24>>2],k)|0)!=H[a+24>>2]|qa){Ba(k,1,2472,0);j=0;break a}H[s+40>>2]=65427;break l}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]!=65427){continue}break}}if(!(!(Pa(j)|qa)&H[a+8>>2]==64)){n=I[a+92|0];if(!(n&4)){l=N(H[a+228>>2],5644);o=H[a+180>>2];u:{v:{if(H[a+56>>2]){n=Pa(j);break v}n=H[a+24>>2];if(n>>>0<2){break u}}n=n-2|0;H[a+24>>2]=n}l=l+o|0;if(!n){break j}o=Pa(j);p=qa;if((p|0)<0){m=1}else{m=(p|0)<=0&n>>>0>o>>>0}if(m){if(H[a+208>>2]){Ba(k,1,5842,0);j=0;break a}Ba(k,2,5842,0)}o=H[a+24>>2];if(o>>>0>=4294967294){Ba(k,1,1480,0);j=0;break a}p=H[l+5596>>2];w:{if(p){m=H[l+5600>>2];if(m>>>0>-3-o>>>0){Ba(k,1,1211,0);j=0;break a}o=Ga(p,(m+o|0)+2|0);if(o){H[l+5596>>2]=o;break j}Ca(H[l+5596>>2]);H[l+5596>>2]=0;break w}o=Fa(o+2|0);H[l+5596>>2]=o;if(o){break j}}Ba(k,1,6176,0);j=0;break a}H[a+8>>2]=8;F[a+92|0]=n&250;break i}n=H[s+40>>2];break g}Ca(H[o+24>>2]);a=H[w+40>>2]+N(v,40)|0;H[a+28>>2]=0;H[a+20>>2]=0;H[a+24>>2]=0;Ba(k,1,3863,0);j=0;break a}o=H[j+60>>2];w=o;r=H[j+56>>2];p=r-2|0;q=o-(r>>>0<2)|0;v=H[a+224>>2];E=H[v+40>>2];U=H[a+228>>2];D=N(U,40);o=E+D|0;m=H[o+16>>2];x:{if(!m){break x}x=H[o+12>>2];if(x>>>0>=K[o+4>>2]){break x}m=m+N(x,24)|0;H[m+8>>2]=p;H[m+12>>2]=q;x=r;r=H[a+24>>2];q=x+r|0;H[m+16>>2]=q;H[m+20>>2]=q>>>0>>0?w+1|0:w}m=H[a+24>>2];x=H[o+20>>2];C=x+1|0;w=H[o+28>>2];y:{if(C>>>0<=w>>>0){q=H[o+24>>2];break y}w=~~O(O(w>>>0)+O(100))>>>0;H[o+28>>2]=w;q=Ga(H[o+24>>2],N(w,24));E=H[v+40>>2];o=D+E|0;if(!q){break f}H[o+24>>2]=q;x=H[o+20>>2];C=x+1|0}o=N(x,24)+q|0;H[o+16>>2]=m+2;H[o+8>>2]=p;H[o+12>>2]=p>>31;G[o>>1]=65427;H[(D+E|0)+20>>2]=C;z:{if(n){q=Ja(j,H[l+5596>>2]+H[l+5600>>2]|0,H[a+24>>2],k);m=8;if((q|0)==H[a+24>>2]){break z}m=64;if((q|0)!=-1){break z}Ba(k,1,2472,0);j=0;break a}q=0;m=H[a+24>>2]?64:8}H[a+8>>2]=m;H[l+5600>>2]=H[l+5600>>2]+q;A:{if(F[a+92|0]&1){break A}n=H[a+44>>2];if(H[a+76>>2]|((n|0)<0|(n|0)!=H[a+228>>2])){break A}if(!zb(j)){break A}l=H[a+228>>2];o=H[a+180>>2]+N(l,5644)|0;n=H[o+5592>>2];l=H[H[a+224>>2]+40>>2]+N(l,40)|0;if((n|0)!=H[l+4>>2]){break A}o=H[o+5588>>2]+1|0;if(n>>>0<=o>>>0){break A}B:{n=H[l+16>>2]+N(o,24)|0;l=H[n>>2];n=H[n+4>>2];if((l|0)==H[j+56>>2]&(n|0)==H[j+60>>2]){break B}if(ab(j,l,n,k)){break B}Ba(k,1,5440,0);j=0;break a}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]==65424){break h}Ba(k,1,4073,0);j=0;break a}n=I[a+92|0];if((n&9)!=1){break i}F[a+92|0]=n|8;o=H[a+228>>2];if(H[(H[a+180>>2]+N(o,5644)|0)+5592>>2]==1){break i}if(!zb(j)){break i}n=H[j+60>>2];p=n;m=H[j+56>>2];if((n&m)==-1){break i}C:{while(1){n=1;l=s+70|0;if((Ja(j,l,2,k)|0)!=2){break C}Da(l,s- -64|0,2);if(H[s+64>>2]!=65424){break C}q=2472;if((Ja(j,l,2,k)|0)!=2){break c}Da(l,s+60|0,2);if(H[s+60>>2]!=10){q=6048;break c}H[s+60>>2]=8;l=Ja(j,s+70|0,8,k);if((l|0)!=H[s+60>>2]){break c}if((l|0)!=8){q=4047;break c}Da(s+70|0,s+56|0,2);Da(P,s+52|0,4);Da(S,s+48|0,1);Da(M,s+44|0,1);if((o|0)!=H[s+56>>2]){l=H[s+52>>2];if(l>>>0<14){break C}l=l-12|0;H[s+52>>2]=l;l=qb(j,l,k);if(!qa&H[s+52>>2]==(l|0)){continue}break C}break}n=H[s+48>>2]!=H[s+44>>2]}if(!hc(j,m,p,k)){break b}if(n){break i}F[a+92|0]=I[a+92|0]&238|16;D:{if(!u){break D}o=H[a+180>>2];q=0;n=0;if((u|0)!=1){while(1){l=o+N(q,5644)|0;p=H[l+5592>>2];if(p){H[l+5592>>2]=p+1}p=H[l+11236>>2];if(p){H[l+11236>>2]=p+1}q=q+2|0;n=n+2|0;if((T|0)!=(n|0)){continue}break}if(!A){break D}}n=o+N(q,5644)|0;l=H[n+5592>>2];if(!l){break D}H[n+5592>>2]=l+1}Ba(k,2,9035,0)}if(F[a+92|0]&1){break h}if((Ja(j,H[a+16>>2],2,k)|0)!=2){if(!(!u|(u|0)!=(H[a+228>>2]+1|0))){j=H[a+180>>2];n=0;while(1){l=j+N(n,5644)|0;if(!(H[l+5588>>2]|H[l+5592>>2])){break e}n=n+1|0;if((u|0)!=(n|0)){continue}break}}Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2)}n=H[s+40>>2];if(F[a+92|0]&1){break g}if((n|0)!=65497){continue}}break}if(H[a+8>>2]==256|(n|0)!=65497){break d}H[a+8>>2]=256;H[a+228>>2]=0;break d}Ca(H[o+24>>2]);a=H[v+40>>2]+N(U,40)|0;H[a+28>>2]=0;H[a+20>>2]=0;H[a+24>>2]=0;Ba(k,1,3863,0);j=0;break a}H[s+16>>2]=n;Ba(k,4,11004,s+16|0);H[a+228>>2]=n;H[s+40>>2]=65497;H[a+8>>2]=256}n=H[a+228>>2];j=H[a+180>>2];E:{F:{if(F[a+92|0]&1){break F}G:{H:{if(n>>>0>=u>>>0){break H}q=j+N(n,5644)|0;while(1){if(H[q+5596>>2]){break H}n=n+1|0;H[a+228>>2]=n;q=q+5644|0;if((n|0)!=(u|0)){continue}break}break G}if((n|0)!=(u|0)){break F}}H[i>>2]=0;break E}I:{J:{o=j+N(n,5644)|0;if(H[o+5172>>2]){a=6837}else{if(!(I[o+5640|0]&2)){break I}l=H[o+5160>>2];K:{if(!l){q=0;break K}p=l&3;m=H[o+5164>>2];j=0;q=0;n=0;if(l>>>0>=4){w=l&-4;l=0;while(1){u=m+(n<<3)|0;q=H[u+28>>2]+(H[u+20>>2]+(H[u+12>>2]+(H[u+4>>2]+q|0)|0)|0)|0;n=n+4|0;l=l+4|0;if((w|0)!=(l|0)){continue}break}if(!p){break K}}while(1){q=H[(m+(n<<3)|0)+4>>2]+q|0;n=n+1|0;j=j+1|0;if((p|0)!=(j|0)){continue}break}}j=Fa(q);H[o+5172>>2]=j;if(j){break J}a=4009}Ba(k,1,a,0);Ba(k,1,8059,0);j=0;break a}H[o+5180>>2]=q;q=H[o+5164>>2];j=H[o+5160>>2];if(j){l=0;n=0;while(1){u=n<<3;p=u+q|0;m=H[p>>2];if(m){j=H[p+4>>2];if(j){B(H[o+5172>>2]+l|0,m,j)}j=u+H[o+5164>>2]|0;p=H[j+4>>2];Ca(H[j>>2]);q=H[o+5164>>2];j=u+q|0;H[j>>2]=0;H[j+4>>2]=0;l=l+p|0;j=H[o+5160>>2]}n=n+1|0;if(n>>>0>>0){continue}break}}H[o+5160>>2]=0;Ca(q);H[o+5164>>2]=0;H[o+5168>>2]=H[o+5172>>2];H[o+5176>>2]=H[o+5180>>2]}n=H[a+232>>2];U=H[n+28>>2];l=H[a+228>>2];C=H[(H[U+76>>2]+N(l,5644)|0)+5584>>2];j=H[n+24>>2];W=H[j+24>>2];u=H[U+24>>2];o=(l>>>0)/(u>>>0)|0;T=H[H[n+20>>2]>>2];p=H[U+12>>2];n=H[U+4>>2]+N(p,l-N(o,u)|0)|0;l=H[j>>2];l=l>>>0>>0?n:l;H[T>>2]=l;u=n+p|0;n=n>>>0>u>>>0?-1:u;u=H[j+8>>2];n=n>>>0>>0?n:u;H[T+8>>2]=n;L:{M:{if(!((l|0)<(n|0)&(l|0)>=0)){Ba(k,1,6682,0);break M}q=H[T+20>>2];u=H[U+16>>2];n=H[U+8>>2]+N(o,u)|0;l=H[j+4>>2];l=l>>>0>>0?n:l;H[T+4>>2]=l;o=n+u|0;n=n>>>0>o>>>0?-1:o;j=H[j+12>>2];j=j>>>0>n>>>0?n:j;H[T+12>>2]=j;if(!((j|0)>(l|0)&(l|0)>=0)){Ba(k,1,6644,0);break M}N:{if(H[C+4>>2]){if(H[T+16>>2]){break N}j=1;break L}Ba(k,1,5358,0);break M}O:{P:{while(1){H[W+36>>2]=0;j=H[W>>2];l=j>>31;p=j-1|0;n=H[T>>2];o=p+n|0;m=l-!j|0;u=m+(n>>31)|0;oa=q,pa=ee(o,n>>>0>o>>>0?u+1|0:u,j,l),H[oa>>2]=pa;n=H[W+4>>2];o=n>>31;u=n-1|0;w=H[T+4>>2];r=u+w|0;v=o-!n|0;x=v+(w>>31)|0;oa=q,pa=ee(r,r>>>0>>0?x+1|0:x,n,o),H[oa+4>>2]=pa;x=p;p=H[T+8>>2];w=x+p|0;m=(p>>31)+m|0;oa=q,pa=ee(w,p>>>0>w>>>0?m+1|0:m,j,l),H[oa+8>>2]=pa;j=H[T+12>>2];H[q+16>>2]=ba;l=v+(j>>31)|0;j=j+u|0;l=j>>>0>>0?l+1|0:l;oa=q,pa=ee(j,l,n,o),H[oa+12>>2]=pa;j=H[C+4>>2];H[q+20>>2]=j;n=H[U+80>>2];H[q+24>>2]=j>>>0>>0?1:j-n|0;Ca(H[q+52>>2]);H[q+68>>2]=0;H[q+60>>2]=0;H[q+64>>2]=0;H[q+52>>2]=0;H[q+56>>2]=0;j=N(j,152);n=H[q+28>>2];Q:{if(!n){n=Fa(j);H[q+28>>2]=n;if(!n){break M}H[q+32>>2]=j;if(!j){break Q}y(n,0,j);break Q}if(j>>>0<=K[q+32>>2]){break Q}n=Ga(n,j);if(!n){Ba(k,1,3090,0);Ca(H[q+28>>2]);H[q+28>>2]=0;H[q+32>>2]=0;break M}H[q+28>>2]=n;l=H[q+32>>2];o=j-l|0;if(o){y(l+n|0,0,o)}H[q+32>>2]=j}j=H[q+20>>2];if(j){da=C+944|0;ea=C+812|0;$=C+28|0;x=H[q+28>>2];X=0;while(1){n=j-1|0;l=n&31;if((n&63)>>>0>=32){l=-1<>>32-l}o=o^-1;m=H[q>>2];p=o+m|0;u=l^-1;l=u+(m>>31)|0;m=m>>>0>p>>>0?l+1|0:l;l=n&31;if((n&63)>>>0>=32){r=m>>l}else{r=((1<>>l}H[x>>2]=r;p=H[q+4>>2];l=p+o|0;m=(p>>31)+u|0;m=l>>>0

      >>0?m+1|0:m;p=n&31;if((n&63)>>>0>=32){v=m>>p}else{v=((1<>>p}H[x+4>>2]=v;p=H[q+8>>2];l=p+o|0;m=(p>>31)+u|0;m=l>>>0

      >>0?m+1|0:m;p=n&31;if((n&63)>>>0>=32){m=m>>p}else{m=((1<>>p}H[x+8>>2]=m;p=H[q+12>>2];l=p+o|0;w=(p>>31)+u|0;w=l>>>0

      >>0?w+1|0:w;p=n&31;if((n&63)>>>0>=32){w=w>>p}else{w=((1<>>p}H[x+12>>2]=w;D=m>>31;A=X<<2;E=H[A+ea>>2];l=E&31;if((E&63)>>>0>=32){l=1<>>32-l}M=p;p=M+m|0;S=p-1|0;l=l+D|0;p=(p>>>0>>0?l+1|0:l)-!p|0;l=E&31;if((E&63)>>>0>=32){l=p>>l}else{l=((1<>>l}M=l<>31;D=H[A+da>>2];l=D&31;if((D&63)>>>0>=32){l=-1<>>32-l}A=p^-1;p=A+w|0;l=(l^-1)+S|0;A=p>>>0>>0?l+1|0:l;l=D&31;if((D&63)>>>0>=32){l=A>>l}else{l=((1<>>l}l=l<>D:0;H[x+20>>2]=p;Z=r&-1<>E:0;H[x+16>>2]=l;be(l,0,p);if(!(!l|!qa)){break P}_=N(l,p);if(_>>>0>=107374183){break P}A=N(_,40);if(X){D=D-1|0;E=E-1|0;l=Y>>31;m=l;p=l+1|0;l=Y+1|0;Y=((l?m:p)&1)<<31|l>>>1;l=Z>>31;m=l;p=l+1|0;l=Z+1|0;Z=((l?m:p)&1)<<31|l>>>1;l=3}else{l=1}H[x+24>>2]=l;fa=1<>>0>=32){l=1<>>32-l}ca=j;w=l;j=H[C+12>>2];M=j>>>0>>0?j:D;j=M&31;if((M&63)>>>0>=32){l=-1<>>32-j}ha=m^-1;ia=l^-1;j=H[C+8>>2];S=j>>>0>>0?j:E;j=S&31;if((S&63)>>>0>=32){l=-1<>>32-j}ja=m^-1;ka=l^-1;aa=0;while(1){R:{if(!X){l=H[q+4>>2];j=l+o|0;m=(l>>31)+u|0;m=j>>>0>>0?m+1|0:m;l=n&31;if((n&63)>>>0>=32){Q=m>>l}else{Q=((1<>>l}l=H[q>>2];j=l+o|0;m=(l>>31)+u|0;m=j>>>0>>0?m+1|0:m;l=n&31;if((n&63)>>>0>=32){z=m>>l}else{z=((1<>>l}j=0;m=o;P=m;l=u;R=l;r=n;break R}j=aa+1|0;r=j>>>1|0;m=n&31;if((n&63)>>>0>=32){l=r<>>32-m;m=r<>2];m=P+r|0;l=(l^-1)+w|0;R=t>>>0>P>>>0?l+1|0:l;l=R+(r>>31)|0;r=m>>>0>>0?l+1|0:l;l=p&31;if((p&63)>>>0>=32){Q=r>>l}else{Q=((1<>>l}r=j&1;m=n&31;if((n&63)>>>0>=32){l=r<>>32-m;m=r<>2];r=m+t|0;l=(l^-1)+w|0;l=m>>>0>>0?l+1|0:l;z=l+(t>>31)|0;z=r>>>0>>0?z+1|0:z;t=p&31;if((p&63)>>>0>=32){z=z>>t}else{z=((1<>>t}r=p}t=r;J=H[q+8>>2];V=J>>31;r=H[q+12>>2];H[v+4>>2]=Q;H[v>>2]=z;H[v+16>>2]=j;Q=(r>>31)+R|0;z=r;r=r+P|0;Q=z>>>0>r>>>0?Q+1|0:Q;P=t&31;if((t&63)>>>0>=32){r=Q>>P}else{r=((1<>>P}H[v+12>>2]=r;l=l+V|0;z=l+1|0;r=l;l=m+J|0;r=l>>>0>>0?z:r;m=t&31;if((t&63)>>>0>=32){l=r>>m}else{l=((1<>>m}H[v+8>>2]=l;l=H[W+24>>2]+(!H[C+20>>2]|!j?0:(j|0)==3?2:1)|0;j=H[$>>2];oa=v,sa=O((+H[$+4>>2]*.00048828125+1)*tc(l-j|0)),L[oa+32>>2]=sa;H[v+28>>2]=(j+H[C+804>>2]|0)-1;j=H[v+20>>2];S:{T:{if(!(j|!_)){j=Fa(A);H[v+20>>2]=j;if(!j){Ba(k,1,2854,0);break M}if(A){y(j,0,A)}H[v+24>>2]=A;break T}if(A>>>0>K[v+24>>2]){j=Ga(j,A);if(!j){Ba(k,1,2854,0);Ca(H[v+20>>2]);H[v+20>>2]=0;H[v+24>>2]=0;break M}H[v+20>>2]=j;l=H[v+24>>2];m=A-l|0;if(m){y(j+l|0,0,m)}H[v+24>>2]=A}if(!_){break S}}j=H[v+20>>2];P=0;while(1){m=H[x+16>>2];l=(P>>>0)/(m>>>0)|0;m=(P-N(l,m)<>2];Q=(m|0)>(r|0)?m:r;H[j>>2]=Q;l=(l<>2];z=(l|0)>(r|0)?l:r;H[j+4>>2]=z;m=m+ga|0;r=H[v+8>>2];m=(m|0)<(r|0)?m:r;H[j+8>>2]=m;l=l+fa|0;r=H[v+12>>2];r=(l|0)<(r|0)?l:r;H[j+12>>2]=r;l=(m>>31)+ka|0;R=l+1|0;t=l;l=m+ja|0;t=m>>>0>l>>>0?R:t;Q=Q>>S;m=S&31;if((S&63)>>>0>=32){l=t>>m}else{l=((1<>>m}t=l-Q<>S;H[j+16>>2]=t;l=(r>>31)+ia|0;R=l+1|0;m=l;l=r+ha|0;r=l>>>0>>0?R:m;z=z>>M;m=M&31;if((M&63)>>>0>=32){l=r>>m}else{l=((1<>>m}l=l-z<>M;H[j+20>>2]=l;r=N(l,t);be(r,0,68);if(qa){Ba(k,1,2935,0);break M}l=N(r,68);m=H[j+24>>2];U:{V:{if(!(m|!r)){m=Fa(l);H[j+24>>2]=m;if(!m){break M}if(!l){break V}y(m,0,l);break V}if(l>>>0<=K[j+28>>2]){break U}m=Ga(m,l);if(!m){Ca(H[j+24>>2]);H[j+24>>2]=0;H[j+28>>2]=0;Ba(k,1,2549,0);break M}H[j+24>>2]=m;t=H[j+28>>2];R=l-t|0;if(!R){break V}y(m+t|0,0,R)}H[j+28>>2]=l}l=H[j+20>>2];m=H[j+16>>2];t=H[j+32>>2];W:{if(!t){l=dc(m,l,k);break W}l=bc(t,m,l,k)}H[j+32>>2]=l;l=H[j+20>>2];m=H[j+16>>2];t=H[j+36>>2];X:{if(!t){l=dc(m,l,k);break X}l=bc(t,m,l,k)}H[j+36>>2]=l;if(r){l=0;while(1){V=H[j+16>>2];R=(l>>>0)/(V>>>0)|0;m=H[j+24>>2]+N(l,68)|0;J=H[m>>2];Y:{if(J){la=H[m+56>>2];ma=H[m+4>>2];t=H[m+48>>2];Ca(H[m+60>>2]);H[m+48>>2]=0;H[m+52>>2]=0;H[m+64>>2]=0;H[m+56>>2]=0;H[m+60>>2]=0;H[m+40>>2]=0;H[m+44>>2]=0;H[m+32>>2]=0;H[m+36>>2]=0;H[m+24>>2]=0;H[m+28>>2]=0;H[m+16>>2]=0;H[m+20>>2]=0;H[m+8>>2]=0;H[m+12>>2]=0;H[m>>2]=J;H[m+48>>2]=t;Z:{if(!t){break Z}t=N(t,24);if(!t){break Z}y(J,0,t)}H[m+56>>2]=la;H[m+4>>2]=ma;break Y}t=Ea(10,24);H[m>>2]=t;if(!t){break M}H[m+48>>2]=10}t=(l-N(R,V)|0)+Q|0;J=t<>2];H[m+8>>2]=(J|0)>(V|0)?J:V;R=z+R|0;J=R<>2];H[m+12>>2]=(J|0)>(V|0)?J:V;t=t+1<>2];H[m+16>>2]=(t|0)<(J|0)?t:J;J=m;m=R+1<>2];H[J+20>>2]=(m|0)<(t|0)?m:t;l=l+1|0;if((r|0)!=(l|0)){continue}break}}j=j+40|0;P=P+1|0;if((P|0)!=(_|0)){continue}break}}$=$+8|0;v=v+36|0;aa=aa+1|0;if(aa>>>0>2]){continue}break}x=x+152|0;j=n;X=X+1|0;if(X>>>0>2]){continue}break}}W=W+52|0;q=q+76|0;C=C+1080|0;ba=ba+1|0;if(ba>>>0>2]){continue}break}j=1;break L}Ba(k,1,2982,0);break M}Ba(k,1,2373,0)}j=0}if(!j){Ba(k,1,3668,0);j=0;break a}j=H[a+228>>2];H[s+4>>2]=N(H[a+128>>2],H[a+132>>2]);H[s>>2]=j+1;Ba(k,4,11825,s);H[b>>2]=H[a+228>>2];H[i>>2]=1;if(c){b=Ob(H[a+232>>2],0);H[c>>2]=b;j=0;if((b|0)==-1){break a}}b=H[H[H[a+232>>2]+20>>2]>>2];H[d>>2]=H[b>>2];H[e>>2]=H[b+4>>2];H[f>>2]=H[b+8>>2];H[g>>2]=H[b+12>>2];H[h>>2]=H[b+16>>2];H[a+8>>2]=H[a+8>>2]|128}j=1;break a}Ba(k,1,q,0)}Ba(k,1,3702,0);j=0}a=j;na=s+80|0;return a|0}function Sb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,L=0,M=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{switch(H[a+84>>2]){case 0:k:{b=H[a+52>>2];c=H[a+196>>2];if(b>>>0>>0){d=H[a+64>>2];if(d>>>0>>0){break k}}Ba(H[a+236>>2],1,8491,0);break b}if(!H[a+44>>2]){p=H[a+36>>2];c=0;break i}H[a+44>>2]=0;q=H[a+68>>2];c=1;break i;case 1:l:{b=H[a+52>>2];c=H[a+196>>2];if(b>>>0>>0){d=H[a+64>>2];if(d>>>0>>0){break l}}Ba(H[a+236>>2],1,8536,0);break b}if(!H[a+44>>2]){n=H[a+36>>2];c=0;break e}H[a+44>>2]=0;w=H[a+48>>2];c=1;break e;case 2:m:{B=H[a+52>>2];z=H[a+196>>2];if(B>>>0>>0){P=H[a+64>>2];if(P>>>0>>0){break m}}Ba(H[a+236>>2],1,8671,0);break b}if(!H[a+44>>2]){p=H[a+40>>2];break f}H[a+228>>2]=0;H[a+232>>2]=0;H[a+44>>2]=0;i=H[a+200>>2];while(1){O=i+(w<<4)|0;j=H[O+8>>2];if(j){v=H[O+12>>2];c=0;while(1){f=j+(c^-1)|0;d=v+(c<<4)|0;s=f+H[d>>2]|0;n:{if(s>>>0>31){break n}b=H[O>>2];if(b>>>0>-1>>>s>>>0){break n}b=b<>>0>n>>>0?n:b:b;H[a+228>>2]=n}f=f+H[d+4>>2]|0;o:{if(f>>>0>31){break o}b=H[O+4>>2];if(b>>>0>-1>>>f>>>0){break o}b=b<>>0>t>>>0?t:b:b;H[a+232>>2]=t}c=c+1|0;if((j|0)!=(c|0)){continue}break}}w=w+1|0;if((z|0)!=(w|0)){continue}break};if(!n|!t){break d}if(!I[a|0]){H[a+108>>2]=H[a+208>>2];H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}n=H[a+48>>2];c=1;break f;case 3:p:{B=H[a+52>>2];j=H[a+196>>2];if(B>>>0>>0){P=H[a+64>>2];if(P>>>0>>0){break p}}Ba(H[a+236>>2],1,8626,0);break b}if(!H[a+44>>2]){w=H[a+200>>2];e=H[a+28>>2];Q=w+(e<<4)|0;E=H[a+40>>2];break g}H[a+228>>2]=0;H[a+232>>2]=0;H[a+44>>2]=0;w=H[a+200>>2];while(1){z=(q<<4)+w|0;s=H[z+8>>2];if(s){v=H[z+12>>2];c=0;while(1){f=s+(c^-1)|0;d=v+(c<<4)|0;i=f+H[d>>2]|0;q:{if(i>>>0>31){break q}b=H[z>>2];if(b>>>0>-1>>>i>>>0){break q}b=b<>>0>p>>>0?p:b:b;H[a+228>>2]=p}f=f+H[d+4>>2]|0;r:{if(f>>>0>31){break r}b=H[z+4>>2];if(b>>>0>-1>>>f>>>0){break r}b=b<>>0>n>>>0?n:b:b;H[a+232>>2]=n}c=c+1|0;if((s|0)!=(c|0)){continue}break}}q=q+1|0;if((j|0)!=(q|0)){continue}break};if(!p|!n){break d}s:{if(I[a|0]){n=H[a+108>>2];break s}n=H[a+208>>2];H[a+108>>2]=n;H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}c=1;break g;case 4:break j;default:break d}}t:{q=H[a+52>>2];b=H[a+196>>2];if(q>>>0>>0){k=H[a+64>>2];if(k>>>0>>0){break t}}Ba(H[a+236>>2],1,8581,0);break d}if(!H[a+44>>2]){q=H[a+28>>2];r=H[a+200>>2]+(q<<4)|0;t=H[a+40>>2];break h}H[a+28>>2]=q;H[a+44>>2]=0;e=1;break h}u:while(1){v:{w:{if(!c){p=p+1|0;break w}H[a+40>>2]=q;if(K[a+56>>2]<=q>>>0){break b}n=H[a+48>>2];c=0;break v}c=1}x:while(1){y:{z:{A:{B:{if(!c){H[a+32>>2]=n;if(K[a+60>>2]<=n>>>0){break B}H[a+28>>2]=b;c=b;e=0;break y}H[a+36>>2]=p;if(K[a+76>>2]<=p>>>0){c=H[a+28>>2];e=1;break y}c=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],H[a+40>>2])|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],p)|0;if(c>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(c<<1)|0;if(J[c>>1]){break A}break a}q=H[a+40>>2]+1|0;break z}c=0;continue u}c=1;continue u}while(1){C:{D:{E:{if(!e){if(c>>>0>=d>>>0){break E}e=H[a+32>>2];f=H[a+200>>2]+(c<<4)|0;if(e>>>0>=K[f+8>>2]){break C}if(!I[a|0]){c=H[f+12>>2]+(e<<4)|0;H[a+76>>2]=N(H[c+12>>2],H[c+8>>2])}p=H[a+72>>2];c=1;continue x}c=c+1|0;H[a+28>>2]=c;break D}n=H[a+32>>2]+1|0;c=0;continue x}e=0;continue}e=1;continue}}}}F:while(1){G:{H:{if(!e){t=t+1|0;H[a+40>>2]=t;break H}if(k>>>0<=q>>>0){break b}H[a+228>>2]=0;H[a+232>>2]=0;r=H[a+200>>2]+(q<<4)|0;s=H[r+8>>2];if(!s){break b}v=H[r+12>>2];p=0;n=0;e=0;while(1){f=s+(e^-1)|0;d=v+(e<<4)|0;i=f+H[d>>2]|0;I:{if(i>>>0>31){break I}b=H[r>>2];if(b>>>0>-1>>>i>>>0){break I}b=b<>>0>n>>>0?n:b:b;H[a+228>>2]=n}f=f+H[d+4>>2]|0;J:{if(f>>>0>31){break J}b=H[r+4>>2];if(b>>>0>-1>>>f>>>0){break J}b=b<>>0>p>>>0?p:b:b;H[a+232>>2]=p}e=e+1|0;if((s|0)!=(e|0)){continue}break}if(!n|!p){break d}K:{if(I[a|0]){w=H[a+108>>2];break K}w=H[a+208>>2];H[a+108>>2]=w;H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}e=0;break G}e=1}L:while(1){M:{N:{O:{P:{if(!e){H[a+224>>2]=w;if(K[a+112>>2]<=w>>>0){break P}Q=H[a+100>>2];e=0;break M}if(K[a+56>>2]<=t>>>0){c=H[a+32>>2];e=1;break M}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],t)|0)+N(H[a+20>>2],q)|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}b=H[a+4>>2]+(b<<1)|0;if(J[b>>1]){break O}G[b>>1]=1;return 1}q=q+1|0;H[a+28>>2]=q;break N}e=0;continue F}e=1;continue F}while(1){Q:{R:{S:{T:{if(!e){H[a+220>>2]=Q;if(K[a+104>>2]<=Q>>>0){break S}c=H[a+48>>2];break T}c=c+1|0}H[a+32>>2]=c;b=H[a+60>>2];d=H[r+8>>2];if((b>>>0>>0?b:d)>>>0>c>>>0){f=H[r>>2];e=f;o=d+(c^-1)|0;l=o;d=l&31;if((l&63)>>>0>=32){b=f<>>32-d;x=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break Q}b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}e=H[r+4>>2];if((b&e)!=(e|0)){break Q}d=l&31;if((l&63)>>>0>=32){b=e<>>32-d;y=e<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,y,b);b=h;L=H[a+208>>2];d=F+L|0;b=L>>>0>d>>>0?b+1|0:b;s=fe(d,b,y,C);B=x-1|0;i=H[a+212>>2];j=B+i|0;d=g-!x|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,x,g);A=H[a+204>>2];i=A+B|0;b=A>>>0>i>>>0?b+1|0:b;i=fe(i,b,x,g);D=H[r+12>>2]+(c<<4)|0;M=H[D>>2];u=M+o|0;b=u&31;if((u&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break Q}h=e;P=H[D+4>>2];o=P+o|0;m=o&31;if((o&63)>>>0>=32){b=e<>>32-m;m=e<>>0>=32){e=b>>>j|0}else{e=((1<>>j}if((h|0)!=(e|0)){break Q}j=H[a+224>>2];m=!!(ge(j,m,b)|qa);b=o&31;if((o&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=o<>>32-m|b<>2];if((u&63)>>>0>=32){b=f<>>32-o;f=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=i<>>32-u|b<>2];if(!o|(!H[D+12>>2]|(i|0)==(z|0))){break Q}if((s|0)==(O|0)){break Q}t=H[a+68>>2];H[a+40>>2]=t;b=d;e=e+B|0;b=e>>>0>>0?b+1|0:b;f=(fe(e,b,x,g)>>>M)-(i>>>M)|0;b=v;e=j+F|0;b=e>>>0>>0?b+1|0:b;T=a,U=N(o,(fe(e,b,y,C)>>>P)-(s>>>P)|0)+f|0,H[T+36>>2]=U;e=1;continue L}e=H[a+220>>2];b=H[a+228>>2];Q=e+b-(e>>>0)%(b>>>0)|0;break R}e=H[a+224>>2];b=H[a+232>>2];w=e+b-(e>>>0)%(b>>>0)|0;e=0;continue L}e=0;continue}e=1;continue}}}}U:while(1){V:{W:{if(!c){E=E+1|0;H[a+40>>2]=E;break W}H[a+224>>2]=n;if(K[a+112>>2]<=n>>>0){break b}x=H[a+100>>2];c=0;break V}c=1}X:while(1){Y:{Z:{_:{$:{if(!c){H[a+220>>2]=x;if(K[a+104>>2]<=x>>>0){break $}H[a+28>>2]=B;e=B;c=0;break Y}if(K[a+56>>2]<=E>>>0){t=H[a+32>>2];c=1;break Y}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],E)|0)+N(H[a+20>>2],e)|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(b<<1)|0;if(J[c>>1]){break _}break a}c=H[a+224>>2];b=H[a+232>>2];n=c+b-(c>>>0)%(b>>>0)|0;break Z}c=0;continue U}c=1;continue U}while(1){aa:{ba:{ca:{da:{if(!c){if(e>>>0>=P>>>0){break ca}t=H[a+48>>2];H[a+32>>2]=t;Q=(e<<4)+w|0;break da}t=t+1|0;H[a+32>>2]=t}b=H[a+60>>2];d=H[Q+8>>2];if((b>>>0>>0?b:d)>>>0>t>>>0){f=H[Q>>2];c=f;g=d+(t^-1)|0;m=g;d=g&31;if((g&63)>>>0>=32){b=c<>>32-d;q=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break aa}b=m&31;if((m&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}c=H[Q+4>>2];if((b&c)!=(c|0)){break aa}d=m&31;if((m&63)>>>0>=32){b=c<>>32-d;r=c<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,r,b);b=h;y=H[a+208>>2];d=y+F|0;b=y>>>0>d>>>0?b+1|0:b;s=fe(d,b,r,o);C=q-1|0;i=H[a+212>>2];j=C+i|0;d=u-!q|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,q,u);L=H[a+204>>2];i=C+L|0;b=L>>>0>i>>>0?b+1|0:b;i=fe(i,b,q,u);A=H[Q+12>>2]+(t<<4)|0;D=H[A>>2];l=D+g|0;b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break aa}h=c;M=H[A+4>>2];g=M+g|0;k=g&31;if((g&63)>>>0>=32){b=c<>>32-k;k=c<>>0>=32){c=b>>>j|0}else{c=((1<>>j}if((h|0)!=(c|0)){break aa}j=H[a+224>>2];k=!!(ge(j,k,b)|qa);b=g&31;if((g&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-k|b<>2];if((l&63)>>>0>=32){b=f<>>32-g;g=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-l|b<>2];if(!g|(!H[A+12>>2]|(i|0)==(z|0))){break aa}if((s|0)==(O|0)){break aa}E=H[a+68>>2];H[a+40>>2]=E;b=d;c=c+C|0;b=c>>>0>>0?b+1|0:b;f=(fe(c,b,q,u)>>>D)-(i>>>D)|0;b=v;c=j+F|0;b=c>>>0>>0?b+1|0:b;T=a,U=N(g,(fe(c,b,r,o)>>>M)-(s>>>M)|0)+f|0,H[T+36>>2]=U;c=1;continue X}e=e+1|0;H[a+28>>2]=e;break ba}c=H[a+220>>2];b=H[a+228>>2];x=c+b-(c>>>0)%(b>>>0)|0;c=0;continue X}c=0;continue}c=1;continue}}}}ea:while(1){fa:{ga:{if(!c){p=p+1|0;H[a+40>>2]=p;break ga}H[a+32>>2]=n;if(K[a+60>>2]<=n>>>0){break b}q=H[a+108>>2];c=0;break fa}c=1}ha:while(1){ia:{ja:{ka:{la:{if(!c){H[a+224>>2]=q;if(K[a+112>>2]<=q>>>0){break la}E=H[a+100>>2];c=0;break ia}if(K[a+56>>2]<=p>>>0){e=H[a+28>>2];c=1;break ia}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],p)|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(b<<1)|0;if(J[c>>1]){break ka}break a}n=H[a+32>>2]+1|0;break ja}c=0;continue ea}c=1;continue ea}while(1){ma:{na:{oa:{pa:{if(!c){H[a+220>>2]=E;if(K[a+104>>2]<=E>>>0){break oa}H[a+28>>2]=B;e=B;break pa}e=e+1|0;H[a+28>>2]=e}if(e>>>0

      >>0){l=H[a+32>>2];k=H[a+200>>2]+(e<<4)|0;b=H[k+8>>2];if(l>>>0>=b>>>0){break ma}f=H[k>>2];c=f;g=b+(l^-1)|0;m=g;d=g&31;if((g&63)>>>0>=32){b=c<>>32-d;r=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break ma}b=m&31;if((m&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}c=H[k+4>>2];if((b&c)!=(c|0)){break ma}d=m&31;if((m&63)>>>0>=32){b=c<>>32-d;x=c<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,x,b);b=h;y=H[a+208>>2];d=y+F|0;b=y>>>0>d>>>0?b+1|0:b;s=fe(d,b,x,o);C=r-1|0;i=H[a+212>>2];j=C+i|0;d=u-!r|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,r,u);L=H[a+204>>2];i=C+L|0;b=L>>>0>i>>>0?b+1|0:b;i=fe(i,b,r,u);A=H[k+12>>2]+(l<<4)|0;D=H[A>>2];l=D+g|0;b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break ma}h=c;M=H[A+4>>2];g=M+g|0;k=g&31;if((g&63)>>>0>=32){b=c<>>32-k;k=c<>>0>=32){c=b>>>j|0}else{c=((1<>>j}if((h|0)!=(c|0)){break ma}j=H[a+224>>2];k=!!(ge(j,k,b)|qa);b=g&31;if((g&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-k|b<>2];if((l&63)>>>0>=32){b=f<>>32-g;g=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-l|b<>2];if(!g|(!H[A+12>>2]|(i|0)==(z|0))){break ma}if((s|0)==(O|0)){break ma}p=H[a+68>>2];H[a+40>>2]=p;b=d;c=c+C|0;b=c>>>0>>0?b+1|0:b;f=(fe(c,b,r,u)>>>D)-(i>>>D)|0;b=v;c=j+F|0;b=c>>>0>>0?b+1|0:b;T=a,U=N(g,(fe(c,b,x,o)>>>M)-(s>>>M)|0)+f|0,H[T+36>>2]=U;c=1;continue ha}c=H[a+220>>2];b=H[a+228>>2];E=c+b-(c>>>0)%(b>>>0)|0;break na}c=H[a+224>>2];b=H[a+232>>2];q=c+b-(c>>>0)%(b>>>0)|0;c=0;continue ha}c=0;continue}c=1;continue}}}}qa:while(1){ra:{sa:{if(!c){n=n+1|0;break sa}H[a+32>>2]=w;if(K[a+60>>2]<=w>>>0){break b}p=H[a+68>>2];c=0;break ra}c=1}ta:while(1){ua:{va:{wa:{xa:{if(!c){H[a+40>>2]=p;if(K[a+56>>2]<=p>>>0){break xa}H[a+28>>2]=b;c=b;e=0;break ua}H[a+36>>2]=n;if(K[a+76>>2]<=n>>>0){c=H[a+28>>2];e=1;break ua}c=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],H[a+40>>2])|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],n)|0;if(c>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(c<<1)|0;if(J[c>>1]){break wa}break a}w=H[a+32>>2]+1|0;break va}c=0;continue qa}c=1;continue qa}while(1){ya:{za:{Aa:{if(!e){if(c>>>0>=d>>>0){break Aa}e=H[a+32>>2];f=H[a+200>>2]+(c<<4)|0;if(e>>>0>=K[f+8>>2]){break ya}if(!I[a|0]){c=H[f+12>>2]+(e<<4)|0;H[a+76>>2]=N(H[c+12>>2],H[c+8>>2])}n=H[a+72>>2];c=1;continue ta}c=c+1|0;H[a+28>>2]=c;break za}p=H[a+40>>2]+1|0;c=0;continue ta}e=0;continue}e=1;continue}}}}return 0}Ba(H[a+236>>2],1,1343,0)}return 0}G[c>>1]=1;return 1}function gd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=O(0),w=0,x=0,z=0,A=O(0),C=O(0),D=0,E=0,G=0,J=0,K=0,L=O(0),M=O(0),P=0,Q=0,R=0;n=na-8320|0;na=n;H[n+64>>2]=0;j=2;g=H[a>>2];a:{b:{if((g|0)==176622093){break b}if((g|0)!=1375686655){if(!((g|0)!=201326592|H[a+4>>2]!=538988650)&H[a+8>>2]==176622093){break b}Y(1101);j=1;break a}j=0}g=Ea(1,96);h=0;c:{if(!g){break c}H[g+76>>2]=1;d:{e:{f:{switch(j|0){case 0:H[g+88>>2]=60;H[g+84>>2]=61;H[g+80>>2]=62;H[g+16>>2]=63;H[g+4>>2]=64;H[g+28>>2]=65;H[g+24>>2]=66;H[g+20>>2]=67;H[g>>2]=68;H[g+92>>2]=69;H[g+44>>2]=70;H[g+40>>2]=71;H[g+36>>2]=72;H[g+32>>2]=73;H[g+12>>2]=74;H[g+8>>2]=75;h=Gb();H[g+48>>2]=h;if(h){break e}break d;case 2:break f;default:break d}}H[g+88>>2]=76;H[g+84>>2]=77;H[g+80>>2]=78;H[g+16>>2]=79;H[g+4>>2]=80;H[g+92>>2]=81;H[g+44>>2]=82;H[g+40>>2]=83;H[g+36>>2]=84;H[g+32>>2]=85;H[g+28>>2]=86;H[g+24>>2]=87;H[g+20>>2]=88;H[g+12>>2]=89;H[g+8>>2]=90;H[g>>2]=91;h=Ea(1,136);g:{if(h){k=Gb();H[h>>2]=k;h:{if(!k){break h}F[h+124|0]=0;H[h+116>>2]=0;H[h+120>>2]=0;H[h+108>>2]=0;H[h+112>>2]=0;k=pb();H[h+4>>2]=k;if(!k){break h}k=pb();H[h+8>>2]=k;if(!k){break h}break g}xc(h)}h=0}H[g+48>>2]=h;if(!h){break d}}H[g+72>>2]=1;H[g+64>>2]=1;H[g+60>>2]=0;H[g+52>>2]=0;H[g+56>>2]=0;H[g+68>>2]=1;h=g;break c}Ca(g);h=0}g=h;if(g){H[g+60>>2]=0;H[g+72>>2]=92}if(g){H[g+56>>2]=0;H[g+68>>2]=93}if(g){H[g+52>>2]=0;H[g+64>>2]=94}h=n+68|0;if(h){y(h,0,8248);H[h+8248>>2]=0;H[h+8200>>2]=-1;H[h+8204>>2]=-1}if(d){H[n+8316>>2]=H[n+8316>>2]|1}H[n+60>>2]=b;H[n+56>>2]=a;H[n+52>>2]=a;j=1;b=0;h=n+52|0;i:{if(!h){break i}a=Ea(1,72);if(a){j:{H[a+64>>2]=1048576;k=Fa(1048576);H[a+32>>2]=k;if(!k){Ca(a);a=0;break j}H[a+36>>2]=k;H[a+28>>2]=2;H[a+24>>2]=3;H[a+20>>2]=4;H[a+16>>2]=5;H[a+44>>2]=6;H[a+40>>2]=8;H[a+68>>2]=H[a+68>>2]|2}}else{a=0}if(!a){break i}if(a){H[a+4>>2]=0;H[a>>2]=h}if(a){H[a+8>>2]=H[h+8>>2];H[a+12>>2]=0}if(I[a+68|0]&2){H[a+16>>2]=56}if(a){H[a+24>>2]=58}if(a){H[a+28>>2]=59}b=a}a=n+68|0;if(!g|!a){a=0}else{k:{if(!H[g+76>>2]){Ba(g+52|0,1,9902,0);a=0;break k}ra[H[g+24>>2]](H[g+48>>2],a);a=1}}if(!a){Y(1153);eb(b);gb(g);break a}if(!b|!g){a=0}else{l:{if(!H[g+76>>2]){Ba(g+52|0,1,9983,0);a=0;break l}a=ra[H[g>>2]](b,H[g+48>>2],n- -64|0,g+52|0)|0}}if(!a){Y(1181);eb(b);gb(g);Ta(H[n+64>>2]);break a}m:{if(!f){break m}if(g){a=ra[H[g+40>>2]](H[g+48>>2],f,g+52|0)|0}else{a=0}if(a){break m}Y(1116);eb(b);gb(g);Ta(H[n+64>>2]);break a}a=H[n+64>>2];n:{if(!H[g+76>>2]|(!g|!b)){a=0}else{a=ra[H[g+4>>2]](H[g+48>>2],b,a,g+52|0)|0}if(a){if(!H[g+76>>2]|(!g|!b)){a=0}else{a=ra[H[g+16>>2]](H[g+48>>2],b,g+52|0)|0}if(a){break n}}Y(1316);gb(g);eb(b);Ta(H[n+64>>2]);break a}eb(b);gb(g);l=H[n+64>>2];a=H[l+28>>2];if(a){Ca(a);l=H[n+64>>2];H[l+28>>2]=0;H[l+32>>2]=0}u=H[l+16>>2];o:{p:{if(!c){if(!(!e|(u|0)!=4)){p=1;u=4;break o}q:{b=H[l+20>>2];if(!((b|0)==3|(u|0)!=3)){a=H[l+24>>2];if(H[a>>2]!=H[a+4>>2]|H[a+52>>2]==1){break q}H[l+20>>2]=3;break p}if(u>>>0>2){break q}H[l+20>>2]=2;break o}r:{switch(b-3|0){case 2:s:{t:{if(u>>>0<4){break t}b=H[l+24>>2];a=H[b>>2];if((a|0)!=H[b+52>>2]|(a|0)!=H[b+104>>2]|(a|0)!=H[b+156>>2]){break t}a=H[b+4>>2];if((a|0)!=H[b+56>>2]|(a|0)!=H[b+108>>2]){break t}if((a|0)==H[b+160>>2]){break s}}H[n+20>>2]=1053;H[n+16>>2]=1373;Bb(8179,n+16|0);break o}f=N(H[b+12>>2],H[b+8>>2]);A=O(O(1)/O((-1<>2]^-1)>>>0));C=O(O(1)/O((-1<>2]^-1)>>>0));L=O(O(1)/O((-1<>2]^-1)>>>0));M=O(O(1)/O((-1<>2]^-1)>>>0));a=0;while(1){if((a|0)!=(f|0)){c=a<<2;g=c+H[b+148>>2]|0;h=H[g>>2];k=c+H[b+96>>2]|0;j=H[k>>2];m=c+H[b+44>>2]|0;v=O(O(1)-O(A*O(H[c+H[b+200>>2]>>2])));H[m>>2]=~~O(O(O(O(1)-O(M*O(H[m>>2])))*O(255))*v);H[k>>2]=~~O(O(O(O(1)-O(L*O(j|0)))*O(255))*v);H[g>>2]=~~O(O(O(O(1)-O(C*O(h|0)))*O(255))*v);a=a+1|0;continue}break};Ca(H[b+200>>2]);a=H[l+24>>2];H[a+128>>2]=8;H[a+76>>2]=8;H[a+24>>2]=8;H[a+200>>2]=0;H[l+20>>2]=1;a=H[l+16>>2]-1|0;H[l+16>>2]=a;i=3;while(1){if(a>>>0<=i>>>0){break o}a=H[l+24>>2]+N(i,52)|0;B(a,a+52|0,52);i=i+1|0;a=H[l+16>>2];continue};case 0:break p;case 1:break r;default:break o}}b=H[l+24>>2];a=H[b>>2];u:{v:{if((a|0)!=H[b+52>>2]|(a|0)!=H[b+104>>2]){break v}a=H[b+4>>2];if((a|0)!=H[b+56>>2]){break v}if((a|0)==H[b+108>>2]){break u}}H[n+36>>2]=1115;H[n+32>>2]=1373;Bb(8221,n+32|0);break o}a=H[b+24>>2];c=-1<>2]?0:a;h=H[b+84>>2]?0:a;k=N(H[b+12>>2],H[b+8>>2]);a=0;while(1){if((a|0)!=(k|0)){f=a<<2;j=f+H[b+44>>2]|0;m=f+H[b+148>>2]|0;v=O(H[m>>2]-g|0);i=f+H[b+96>>2]|0;A=O(H[i>>2]-h|0);C=O(H[j>>2]);f=~~O(O(O(v*O(1.4019900560379028))+O(O(A*O(-3680000008898787e-20))+C))+O(.5));H[j>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;f=~~O(O(O(v*O(-.7141128182411194))+O(O(C*O(1.0003000497817993))+O(A*O(-.34412500262260437))))+O(.5));H[i>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;f=~~O(O(O(v*O(-7999999979801942e-21))+O(O(C*O(.9998229742050171))+O(A*O(1.7720400094985962))))+O(.5));H[m>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;a=a+1|0;continue}break}H[l+20>>2]=1;break o}u=c>>>0>u>>>0?u:c;p=1;break o}w:{x:{c=H[l+24>>2];if(H[c>>2]!=1){break x}y:{switch(H[c+52>>2]-1|0){case 1:if(H[c+104>>2]!=2){break x}if(!(H[c+4>>2]!=1|H[c+56>>2]!=2|H[c+108>>2]!=2)){b=H[c+24>>2];i=H[c+148>>2];a=H[c+96>>2];j=H[c+44>>2];D=H[c+60>>2];q=H[c+8>>2];f=H[c+12>>2];c=N(q,f)<<2;g=Ia(c);h=Ia(c);k=Ia(c);if(!(!g|!h|!k)){m=-1<>2]&1;J=f-b|0;G=H[l>>2]&1;w=q-G|0;if(!b){c=g;f=h;b=k;break w}c=g;f=h;b=k;while(1){if((p|0)==(q|0)){break w}Ka(o,m,H[j>>2],0,0,c,f,b);p=p+1|0;b=b+4|0;f=f+4|0;c=c+4|0;j=j+4|0;continue}}Ca(g);Ca(h);Ca(k);break o}if(H[c+4>>2]!=1|H[c+56>>2]!=1|H[c+108>>2]!=1){break x}a=H[c+24>>2];f=H[c+148>>2];b=H[c+96>>2];i=H[c+44>>2];s=H[c+60>>2];g=H[c+8>>2];w=H[c+12>>2];c=N(g,w)<<2;h=Ia(c);k=Ia(c);m=Ia(c);if(!(!h|!k|!m)){o=-1<>2]&1;a=g-x|0;t=a>>>1|0;D=a&1;E=a&-2;a=h;j=k;c=m;while(1){if((q|0)!=(w|0)){if(x){Ka(r,o,H[i>>2],0,0,a,j,c);c=c+4|0;j=j+4|0;i=i+4|0;a=a+4|0}g=0;while(1){if(g>>>0>>0){Ka(r,o,H[i>>2],H[b>>2],H[f>>2],a,j,c);Ka(r,o,H[i+4>>2],H[b>>2],H[f>>2],a+4|0,j+4|0,c+4|0);g=g+2|0;f=f+4|0;b=b+4|0;c=c+8|0;j=j+8|0;a=a+8|0;i=i+8|0;continue}break}z:{if(!D){break z}g=H[i>>2];A:{if((s|0)==(t|0)){Ka(r,o,g,0,0,a,j,c);break A}Ka(r,o,g,H[b>>2],H[f>>2],a,j,c)}c=c+4|0;j=j+4|0;a=a+4|0;i=i+4|0;if(s>>>0<=t>>>0){break z}f=f+4|0;b=b+4|0}q=q+1|0;continue}break}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=h;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=k;Ca(H[a+148>>2]);a=H[l+24>>2];H[a+148>>2]=m;b=H[a+8>>2];H[a+112>>2]=b;H[a+60>>2]=b;b=H[a+12>>2];H[a+116>>2]=b;H[a+64>>2]=b;b=H[a>>2];H[a+104>>2]=b;H[a+52>>2]=b;b=H[a+4>>2];H[a+108>>2]=b;H[a+56>>2]=b;H[l+20>>2]=1;break o}Ca(h);Ca(k);Ca(m);break o;case 0:break y;default:break x}}if(H[c+104>>2]!=1|H[c+4>>2]!=1|(H[c+56>>2]!=1|H[c+108>>2]!=1)){break x}b=H[c+24>>2];i=H[c+148>>2];a=H[c+96>>2];j=H[c+44>>2];o=N(H[c+12>>2],H[c+8>>2]);c=o<<2;h=Ia(c);k=Ia(c);m=Ia(c);if(!(!h|!k|!m)){q=-1<>2],H[a>>2],H[i>>2],f,b,g);c=c+1|0;g=g+4|0;b=b+4|0;f=f+4|0;i=i+4|0;a=a+4|0;j=j+4|0;continue}break}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=h;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=k;Ca(H[a+148>>2]);H[H[l+24>>2]+148>>2]=m;H[l+20>>2]=1;break o}Ca(h);Ca(k);Ca(m);break o}H[n+4>>2]=463;H[n>>2]=1373;Bb(8264,n);break o}E=w>>>1|0;x=w&-2;P=J&-2;t=q<<2;while(1){if(K>>>0

      >>0){p=b+t|0;r=f+t|0;q=c+t|0;s=j+t|0;if(G){Ka(o,m,H[j>>2],0,0,c,f,b);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p);p=p+4|0;r=r+4|0;q=q+4|0;s=s+4|0;f=f+4|0;c=c+4|0;j=j+4|0;b=b+4|0}z=0;while(1){if(x>>>0>z>>>0){Ka(o,m,H[j>>2],H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[j+4>>2],H[a>>2],H[i>>2],c+4|0,f+4|0,b+4|0);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p);Ka(o,m,H[s+4>>2],H[a>>2],H[i>>2],q+4|0,r+4|0,p+4|0);z=z+2|0;i=i+4|0;a=a+4|0;p=p+8|0;r=r+8|0;q=q+8|0;s=s+8|0;b=b+8|0;f=f+8|0;c=c+8|0;j=j+8|0;continue}break}B:{if((w|0)==(x|0)){break B}z=H[j>>2];C:{if((D|0)==(E|0)){Ka(o,m,z,0,0,c,f,b);Ka(o,m,H[s>>2],0,0,q,r,p);break C}Ka(o,m,z,H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p)}b=b+4|0;f=f+4|0;c=c+4|0;j=j+4|0;if(D>>>0<=E>>>0){break B}i=i+4|0;a=a+4|0}K=K+2|0;b=b+t|0;f=f+t|0;c=c+t|0;j=j+t|0;continue}break}D:{if(!(J&1)){break D}if(G){Ka(o,m,H[j>>2],0,0,c,f,b);f=f+4|0;c=c+4|0;j=j+4|0;b=b+4|0}p=0;while(1){if(p>>>0>>0){Ka(o,m,H[j>>2],H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[j+4>>2],H[a>>2],H[i>>2],c+4|0,f+4|0,b+4|0);p=p+2|0;i=i+4|0;a=a+4|0;b=b+8|0;f=f+8|0;c=c+8|0;j=j+8|0;continue}break}if((w|0)==(x|0)){break D}j=H[j>>2];if((D|0)==(E|0)){Ka(o,m,j,0,0,c,f,b);break D}Ka(o,m,j,H[a>>2],H[i>>2],c,f,b)}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=g;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=h;Ca(H[a+148>>2]);a=H[l+24>>2];H[a+148>>2]=k;b=H[a+8>>2];H[a+112>>2]=b;H[a+60>>2]=b;b=H[a+12>>2];H[a+116>>2]=b;H[a+64>>2]=b;b=H[a>>2];H[a+104>>2]=b;H[a+52>>2]=b;b=H[a+4>>2];H[a+108>>2]=b;H[a+56>>2]=b;H[l+20>>2]=1;p=0}c=H[n+64>>2];if(!d){b=0;while(1){if((b|0)!=(u|0)){a=H[c+24>>2]+N(b,52)|0;d=H[a+24>>2];if((d|0)!=8){E:{if(d>>>0<=7){f=N(H[a+12>>2],H[a+8>>2]);g=H[a+44>>2];if(H[a+32>>2]){h=1<>2];k=d>>31<<7|d>>>25;Q=m,R=ee(d<<7,k,h,0),H[Q>>2]=R;i=i+1|0;continue}}d=-1<>2],0,255),qa,d,0);H[h>>2]=k;i=i+1|0;continue}}d=d-8|0;f=N(H[a+12>>2],H[a+8>>2]);g=H[a+44>>2];i=0;if(H[a+32>>2]){while(1){if((f|0)==(i|0)){break E}h=g+(i<<2)|0;H[h>>2]=H[h>>2]>>d;i=i+1|0;continue}}while(1){if((f|0)==(i|0)){break E}h=g+(i<<2)|0;H[h>>2]=H[h>>2]>>>d;i=i+1|0;continue}}H[a+24>>2]=8}b=b+1|0;continue}break}c=H[n+64>>2]}a=H[c+24>>2];b=N(H[a+12>>2],H[a+8>>2]);F:{if(!p){if(H[c+20>>2]==2){if(H[c+16>>2]==1){ma(H[a+44>>2],b|0);break F}if(!e){break F}da(H[a+44>>2],H[a+96>>2],b|0);break F}ca(H[a+44>>2],H[a+96>>2],H[a+148>>2],b|0);break F}G:{switch(u-1|0){case 0:ba(H[a+44>>2],b|0);break F;case 2:aa(H[a+44>>2],H[a+96>>2],H[a+148>>2],b|0);break F;case 3:break G;default:break F}}$(H[a+44>>2],H[a+96>>2],H[a+148>>2],H[a+200>>2],b|0)}Ta(H[n+64>>2]);j=0}na=n+8320|0;return j|0}function Zb(a,b,c,d,e,f,g,h,i){var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,F=0,G=0,I=0,J=0,L=0,M=0,O=0;j=H[a>>2];a:{if(j>>>0>>0|b>>>0>=d>>>0|b>>>0>=j>>>0){break a}j=H[a+4>>2];if(j>>>0>>0|c>>>0>=e>>>0|c>>>0>=j>>>0){break a}C=(c>>>0)/K[a+12>>2]|0;s=H[a+8>>2];E=(b>>>0)/(s>>>0)|0;J=(N(s,E)-b|0)+s|0;F=(g|0)!=8;w=c;while(1){k=H[a+12>>2];j=k;j=(c|0)==(w|0)?j-((c>>>0)%(j>>>0)|0)|0:j;m=e-w|0;r=j>>>0>>0?j:m;x=r&-4;t=r&3;L=r&-8;G=r&7;v=r-1|0;M=(g|0)==2&(r|0)==1;I=N(k-j|0,s);z=(N(w-c|0,h)<<2)+f|0;D=E;u=b;while(1){j=(b|0)==(u|0)?J:s;k=d-u|0;p=j>>>0>>0?j:k;k=s-j|0;m=D<<2;j=H[m+(H[a+24>>2]+(N(H[a+16>>2],C)<<2)|0)>>2];b:{c:{d:{e:{f:{g:{h:{i:{j:{k:{l:{m:{n:{o:{p:{q:{r:{s:{if(i){if(j){l=((I<<2)+j|0)+(k<<2)|0;j=u-b|0;if((g|0)==1){break s}m=(N(g,j)<<2)+z|0;if((p|0)==1){break r}if(M){break q}if(p>>>0<=7|F){break p}if(!r){break b}o=p&-4;k=0;while(1){j=0;while(1){H[(j<<5)+m>>2]=H[(j<<2)+l>>2];n=j|1;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];n=j|2;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];n=j|3;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];j=j+4|0;if(o>>>0>j>>>0){continue}break}if(j>>>0

      >>0){while(1){H[(j<<5)+m>>2]=H[(j<<2)+l>>2];j=j+1|0;if((p|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;k=k+1|0;if((r|0)!=(k|0)){continue}break}break b}if((g|0)==1){break o}if(!r|!p){break b}q=p&-4;n=p&3;l=(N(u-b|0,g)<<2)+z|0;o=0;A=p-1>>>0<3;while(1){j=0;k=0;m=0;t:{if(!A){while(1){H[(N(g,j)<<2)+l>>2]=0;H[(N(j|1,g)<<2)+l>>2]=0;H[(N(j|2,g)<<2)+l>>2]=0;H[(N(j|3,g)<<2)+l>>2]=0;j=j+4|0;k=k+4|0;if((q|0)!=(k|0)){continue}break}m=j;if(!n){break t}}j=0;while(1){H[(N(g,m)<<2)+l>>2]=0;m=m+1|0;j=j+1|0;if((n|0)!=(j|0)){continue}break}}l=(h<<2)+l|0;o=o+1|0;if((r|0)!=(o|0)){continue}break}break b}if(!j){j=Ea(1,N(H[a+8>>2],H[a+12>>2])<<2);if(!j){return 0}H[m+(H[a+24>>2]+(N(H[a+16>>2],C)<<2)|0)>>2]=j}l=((I<<2)+j|0)+(k<<2)|0;j=u-b|0;u:{v:{if((g|0)!=1){m=(N(g,j)<<2)+z|0;if((p|0)==1){break v}if(p>>>0<=7|F){break u}if(!r){break b}o=p&-4;k=0;while(1){j=0;while(1){H[(j<<2)+l>>2]=H[(j<<5)+m>>2];n=j|1;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];n=j|2;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];n=j|3;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];j=j+4|0;if(o>>>0>j>>>0){continue}break}if(j>>>0

      >>0){while(1){H[(j<<2)+l>>2]=H[(j<<5)+m>>2];j=j+1|0;if((p|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;k=k+1|0;if((r|0)!=(k|0)){continue}break}break b}j=(j<<2)+z|0;if((p|0)==4){break m}if(!r){break b}m=p<<2;o=0;if(v>>>0>=3){break h}break g}if(!r){break b}o=0;if(v>>>0>=3){while(1){H[l>>2]=H[m>>2];j=s<<2;l=j+l|0;k=h<<2;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}j=0;while(1){H[l>>2]=H[m>>2];l=(s<<2)+l|0;m=(h<<2)+m|0;j=j+1|0;if((t|0)!=(j|0)){continue}break}break b}if(!r|!p){break b}A=p&-4;q=p&3;n=0;break k}j=(j<<2)+z|0;if((p|0)==4){break n}if(!r){break b}m=p<<2;o=0;if(v>>>0>=3){break d}break c}if(!r){break b}k=0;if(v>>>0>=3){while(1){H[m>>2]=H[l>>2];j=h<<2;o=j+m|0;m=s<<2;l=m+l|0;H[o>>2]=H[l>>2];o=j+o|0;l=l+m|0;H[o>>2]=H[l>>2];o=j+o|0;l=l+m|0;H[o>>2]=H[l>>2];l=l+m|0;m=j+o|0;k=k+4|0;if((x|0)!=(k|0)){continue}break}if(!t){break b}}j=0;while(1){H[m>>2]=H[l>>2];l=(s<<2)+l|0;m=(h<<2)+m|0;j=j+1|0;if((t|0)!=(j|0)){continue}break}break b}j=0;k=p&-4;if(k){while(1){H[(j<<3)+m>>2]=H[(j<<2)+l>>2];o=j|1;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];o=j|2;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];o=j|3;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];j=j+4|0;if(k>>>0>j>>>0){continue}break}}if(j>>>0>=p>>>0){break b}o=0;k=j;n=p-j&3;if(n){while(1){H[(k<<3)+m>>2]=H[(k<<2)+l>>2];k=k+1|0;o=o+1|0;if((n|0)!=(o|0)){continue}break}}if(j-p>>>0>4294967292){break b}while(1){H[(k<<3)+m>>2]=H[(k<<2)+l>>2];j=k+1|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];j=k+2|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];j=k+3|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];k=k+4|0;if((p|0)!=(k|0)){continue}break}break b}if(!r|!p){break b}A=p&-4;q=p&3;n=0;O=p-1>>>0<3;break l}if(!r){break b}m=p<<2;k=(u-b<<2)+z|0;o=0;if(v>>>0>=7){break f}break e}if(!r){break b}o=0;if(v>>>0>=3){while(1){k=H[l+12>>2];H[j+8>>2]=H[l+8>>2];H[j+12>>2]=k;k=H[l+4>>2];H[j>>2]=H[l>>2];H[j+4>>2]=k;k=l;l=s<<2;k=k+l|0;n=H[k+12>>2];m=h<<2;j=m+j|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;k=l+k|0;n=H[k+12>>2];j=j+m|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;k=l+k|0;n=H[k+12>>2];j=j+m|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;l=l+k|0;j=j+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}m=0;while(1){k=H[l+12>>2];H[j+8>>2]=H[l+8>>2];H[j+12>>2]=k;k=H[l+4>>2];H[j>>2]=H[l>>2];H[j+4>>2]=k;l=(s<<2)+l|0;j=(h<<2)+j|0;m=m+1|0;if((t|0)!=(m|0)){continue}break}break b}if(!r){break b}o=0;if(v>>>0>=3){break j}break i}while(1){j=0;o=0;k=0;w:{if(!O){while(1){H[(N(g,j)<<2)+m>>2]=H[(j<<2)+l>>2];k=j|1;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];k=j|2;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];k=j|3;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];j=j+4|0;o=o+4|0;if((A|0)!=(o|0)){continue}break}k=j;if(!q){break w}}j=0;while(1){H[(N(g,k)<<2)+m>>2]=H[(k<<2)+l>>2];k=k+1|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;n=n+1|0;if((r|0)!=(n|0)){continue}break}break b}while(1){j=0;o=0;k=0;x:{if(p>>>0>=4){while(1){H[(j<<2)+l>>2]=H[(N(g,j)<<2)+m>>2];k=j|1;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=j|2;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=j|3;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];j=j+4|0;o=o+4|0;if((A|0)!=(o|0)){continue}break}k=j;if(!q){break x}}j=0;while(1){H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=k+1|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;n=n+1|0;if((r|0)!=(n|0)){continue}break}break b}while(1){k=H[j+12>>2];H[l+8>>2]=H[j+8>>2];H[l+12>>2]=k;k=H[j+4>>2];H[l>>2]=H[j>>2];H[l+4>>2]=k;m=s<<2;k=m+l|0;l=h<<2;j=l+j|0;n=H[j+12>>2];H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;n=H[j+12>>2];k=k+m|0;H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;n=H[j+12>>2];k=k+m|0;H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;l=k+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}m=0;while(1){k=H[j+12>>2];H[l+8>>2]=H[j+8>>2];H[l+12>>2]=k;k=H[j+4>>2];H[l>>2]=H[j>>2];H[l+4>>2]=k;j=(h<<2)+j|0;l=(s<<2)+l|0;m=m+1|0;if((t|0)!=(m|0)){continue}break}break b}while(1){k=!m;if(!k){B(l,j,m)}q=j;j=h<<2;n=q+j|0;q=l;l=s<<2;q=q+l|0;if(!k){B(q,n,m)}n=j+n|0;q=l+q|0;if(!k){B(q,n,m)}n=j+n|0;q=l+q|0;if(!k){B(q,n,m)}j=j+n|0;l=l+q|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}k=0;while(1){if(m){B(l,j,m)}j=(h<<2)+j|0;l=(s<<2)+l|0;k=k+1|0;if((t|0)!=(k|0)){continue}break}break b}while(1){j=!m;if(!j){y(k,0,m)}l=k;k=h<<2;l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}k=l+k|0;o=o+8|0;if((L|0)!=(o|0)){continue}break}if(!G){break b}}j=0;while(1){if(m){y(k,0,m)}k=(h<<2)+k|0;j=j+1|0;if((G|0)!=(j|0)){continue}break}break b}while(1){k=!m;if(!k){B(j,l,m)}q=l;l=s<<2;n=q+l|0;q=j;j=h<<2;q=q+j|0;if(!k){B(q,n,m)}n=l+n|0;q=j+q|0;if(!k){B(q,n,m)}n=l+n|0;q=j+q|0;if(!k){B(q,n,m)}l=l+n|0;j=j+q|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}k=0;while(1){if(m){B(j,l,m)}l=(s<<2)+l|0;j=(h<<2)+j|0;k=k+1|0;if((t|0)!=(k|0)){continue}break}}D=D+1|0;u=p+u|0;if(u>>>0>>0){continue}break}C=C+1|0;w=r+w|0;if(w>>>0>>0){continue}break}}return 1}function Gd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;if(!c){return 0}if(!Fb(H[a>>2],b,c,d)){Ba(d,1,6210,0);return 0}n=c;g=na-240|0;na=g;q=1;o=a;a:{if(H[H[a>>2]+60>>2]|H[a+128>>2]){break a}b:{c=H[a+116>>2];c:{if(!c){a=H[a+120>>2];break c}b=H[n+16>>2];a=H[o+120>>2];if(!(!a|!H[a+12>>2])){b=I[a+18|0]}d:{f=J[c+4>>1];if(f){c=H[c>>2];while(1){h=c+N(e,6)|0;i=J[h>>1];if(i>>>0>=b>>>0){H[g+180>>2]=b;H[g+176>>2]=i;Ba(d,1,13715,g+176|0);q=0;break a}e:{h=J[h+4>>1];if((h+1&65535)>>>0<2){break e}h=h-1|0;if(h>>>0>>0){break e}H[g+164>>2]=b;H[g+160>>2]=h;Ba(d,1,13715,g+160|0);q=0;break a}e=e+1|0;if((f|0)!=(e|0)){continue}break}break d}if(b){break b}break c}while(1){b=b-1|0;e=0;while(1){if(J[c+N(e,6)>>1]!=(b|0)){e=e+1|0;if((f|0)!=(e|0)){continue}break b}break}if(b){continue}break}}f:{if(!a){break f}c=H[a+12>>2];if(!c){break f}g:{b=I[a+18|0];h:{if(b){e=0;i=1;while(1){a=H[n+16>>2];f=J[c+(e<<2)>>1];if(a>>>0<=f>>>0){H[g+148>>2]=a;H[g+144>>2]=f;Ba(d,1,13715,g+144|0);i=0}e=e+1|0;if((b|0)!=(e|0)){continue}break}a=Ea(b,4);if(!a){break h}e=0;while(1){f=c+(e<<2)|0;h=I[f+2|0];i:{if(h>>>0>=2){H[g+68>>2]=h;H[g+64>>2]=e;Ba(d,1,12094,g- -64|0);i=0;break i}f=I[f+3|0];if(f>>>0>=b>>>0){H[g+128>>2]=f;Ba(d,1,12038,g+128|0);i=0;break i}j=(h|0)!=1;m=(f<<2)+a|0;if(!(j|!H[m>>2])){H[g+80>>2]=f;Ba(d,1,11527,g+80|0);i=0;break i}if(!(h|!f)){H[g+100>>2]=f;H[g+96>>2]=e;Ba(d,1,11901,g+96|0);i=0;break i}if(!(j|(e|0)==(f|0))){H[g+120>>2]=f;H[g+116>>2]=e;H[g+112>>2]=e;Ba(d,1,11937,g+112|0);i=0;break i}H[m>>2]=1}e=e+1|0;if((b|0)!=(e|0)){continue}break}i=!i;e=0;while(1){j:{f=e<<2;if(I[(c+f|0)+2|0]?H[f+a>>2]:1){e=e+1|0;if((b|0)!=(e|0)){continue}if(i&1){break j}if(H[n+16>>2]!=1){break g}e=0;while(1){if(H[(e<<2)+a>>2]){e=e+1|0;if((b|0)!=(e|0)){continue}break g}break}h=0;Ba(d,2,9253,0);f=b&3;e=0;if(b>>>0>=4){i=b&252;b=0;while(1){j=c+(e<<2)|0;F[j+3|0]=e;F[j+2|0]=1;j=e|1;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;j=e|2;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;j=e|3;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;e=e+4|0;b=b+4|0;if((i|0)!=(b|0)){continue}break}if(!f){break g}}while(1){b=c+(e<<2)|0;F[b+3|0]=e;F[b+2|0]=1;e=e+1|0;h=h+1|0;if((f|0)!=(h|0)){continue}break}break g}H[g+48>>2]=e;i=1;Ba(d,1,11101,g+48|0);e=e+1|0;if((b|0)!=(e|0)){continue}}break}Ca(a);q=0;break a}a=Ea(b,4);if(a){break g}}q=0;Ba(d,1,12285,0);break a}Ca(a)}a=H[o+120>>2];k:{if(!a){break k}l=H[a+12>>2];if(!l){Ca(H[a+4>>2]);Ca(H[H[o+120>>2]+8>>2]);Ca(H[H[o+120>>2]>>2]);a=H[o+120>>2];b=H[a+12>>2];if(b){Ca(b);a=H[o+120>>2]}Ca(a);H[o+120>>2]=0;break k}m=H[n+24>>2];l:{i=I[a+18|0];m:{if(i){u=H[a>>2];h=H[a+4>>2];j=H[a+8>>2];e=0;n:{while(1){if(H[(m+N(J[l+(e<<2)>>1],52)|0)+44>>2]){e=e+1|0;if((i|0)!=(e|0)){continue}break n}break}H[g+32>>2]=e;Ba(d,1,13877,g+32|0);q=0;break a}f=Fa(N(i,52));if(!f){break m}b=0;while(1){a=l+(b<<2)|0;e=J[a>>1];c=N(I[a+2|0]?I[a+3|0]:b,52)+f|0;a=m+N(e,52)|0;H[c+48>>2]=H[a+48>>2];e=H[a+44>>2];H[c+40>>2]=H[a+40>>2];H[c+44>>2]=e;e=H[a+36>>2];H[c+32>>2]=H[a+32>>2];H[c+36>>2]=e;e=H[a+28>>2];H[c+24>>2]=H[a+24>>2];H[c+28>>2]=e;e=H[a+20>>2];H[c+16>>2]=H[a+16>>2];H[c+20>>2]=e;e=H[a+12>>2];H[c+8>>2]=H[a+8>>2];H[c+12>>2]=e;e=H[a+4>>2];H[c>>2]=H[a>>2];H[c+4>>2]=e;c=N(b,52)+f|0;a=Ia(N(H[a+8>>2],H[a+12>>2])<<2);H[c+44>>2]=a;if(!a){if(b){while(1){Ca(H[(N(b,52)+f|0)-8>>2]);b=b-1|0;if(b){continue}break}}Ca(f);q=0;Ba(d,1,13825,0);break a}H[c+24>>2]=I[b+j|0];H[c+32>>2]=I[b+h|0];b=b+1|0;if((i|0)!=(b|0)){continue}break}j=J[H[o+120>>2]+16>>1]-1|0;while(1){a=N(p,52)+f|0;c=N(H[a+12>>2],H[a+8>>2]);b=l+(p<<2)|0;e=H[(m+N(J[b>>1],52)|0)+44>>2];o:{if(!I[b+2|0]){if(!c){break o}h=H[a+44>>2];r=c&3;a=0;b=0;if(c>>>0>=4){t=c&-4;k=0;while(1){c=b<<2;H[c+h>>2]=H[c+e>>2];s=c|4;H[s+h>>2]=H[e+s>>2];s=c|8;H[s+h>>2]=H[e+s>>2];c=c|12;H[c+h>>2]=H[c+e>>2];b=b+4|0;k=k+4|0;if((t|0)!=(k|0)){continue}break}if(!r){break o}}while(1){c=b<<2;H[c+h>>2]=H[c+e>>2];b=b+1|0;a=a+1|0;if((r|0)!=(a|0)){continue}break}break o}if(!c){break o}b=I[b+3|0];a=(b<<2)+u|0;r=H[(N(b,52)+f|0)+44>>2];b=0;if((c|0)!=1){t=c&1;s=c&-2;h=0;while(1){k=b<<2;c=H[k+e>>2];H[k+r>>2]=H[a+(N(i,(c|0)>=0?(c|0)<(j|0)?c:j:0)<<2)>>2];k=k|4;c=H[k+e>>2];H[k+r>>2]=H[a+(N(i,(c|0)>=0?(c|0)<(j|0)?c:j:0)<<2)>>2];b=b+2|0;h=h+2|0;if((s|0)!=(h|0)){continue}break}if(!t){break o}}c=b<<2;b=H[c+e>>2];H[c+r>>2]=H[a+(N(i,(b|0)>=0?(b|0)<(j|0)?b:j:0)<<2)>>2]}p=p+1|0;if((i|0)!=(p|0)){continue}break}break l}f=Fa(N(i,52));if(f){break l}}q=0;Ba(d,1,13825,0);break a}a=H[n+16>>2];if(a){e=0;while(1){b=H[(m+N(e,52)|0)+44>>2];if(b){Ca(b)}e=e+1|0;if((a|0)!=(e|0)){continue}break}}Ca(m);H[n+16>>2]=i;H[n+24>>2]=f}e=H[o+116>>2];if(!e){break a}i=H[e>>2];m=J[e+4>>1];if(m){e=0;r=m-2&65535;h=1;while(1){a=H[n+16>>2];p=N(e,6)+i|0;b=J[p>>1];p:{if(a>>>0<=b>>>0){H[g+20>>2]=a;H[g+16>>2]=b;Ba(d,2,7334,g+16|0);break p}c=J[p+4>>1];if((c+1&65535)>>>0<=1){G[(H[n+24>>2]+N(b,52)|0)+48>>1]=J[p+2>>1];break p}c=c-1|0;j=c&65535;if(j>>>0>=a>>>0){H[g+4>>2]=a;H[g>>2]=j;Ba(d,2,7293,g);break p}q:{if(J[p+2>>1]|(b|0)==(j|0)){break q}f=H[n+24>>2];a=f+N(b,52)|0;H[g+232>>2]=H[a+48>>2];l=H[a+44>>2];H[g+224>>2]=H[a+40>>2];H[g+228>>2]=l;l=H[a+36>>2];H[g+216>>2]=H[a+32>>2];H[g+220>>2]=l;l=H[a+28>>2];H[g+208>>2]=H[a+24>>2];H[g+212>>2]=l;l=H[a+20>>2];H[g+200>>2]=H[a+16>>2];H[g+204>>2]=l;l=H[a+12>>2];H[g+192>>2]=H[a+8>>2];H[g+196>>2]=l;l=H[a+4>>2];H[g+184>>2]=H[a>>2];H[g+188>>2]=l;l=N(j,52);f=l+f|0;H[a+48>>2]=H[f+48>>2];k=H[f+44>>2];H[a+40>>2]=H[f+40>>2];H[a+44>>2]=k;k=H[f+36>>2];H[a+32>>2]=H[f+32>>2];H[a+36>>2]=k;k=H[f+28>>2];H[a+24>>2]=H[f+24>>2];H[a+28>>2]=k;k=H[f+20>>2];H[a+16>>2]=H[f+16>>2];H[a+20>>2]=k;k=H[f+12>>2];H[a+8>>2]=H[f+8>>2];H[a+12>>2]=k;k=H[f+4>>2];H[a>>2]=H[f>>2];H[a+4>>2]=k;a=l+H[n+24>>2]|0;H[a+48>>2]=H[g+232>>2];f=H[g+228>>2];H[a+40>>2]=H[g+224>>2];H[a+44>>2]=f;f=H[g+220>>2];H[a+32>>2]=H[g+216>>2];H[a+36>>2]=f;f=H[g+212>>2];H[a+24>>2]=H[g+208>>2];H[a+28>>2]=f;f=H[g+204>>2];H[a+16>>2]=H[g+200>>2];H[a+20>>2]=f;f=H[g+196>>2];H[a+8>>2]=H[g+192>>2];H[a+12>>2]=f;f=H[g+188>>2];H[a>>2]=H[g+184>>2];H[a+4>>2]=f;if(m>>>0<=e+1>>>0){break q}f=h;if(!(e-m&1)){a=c;f=N(f,6)+i|0;l=J[f>>1];r:{if((l|0)!=(b|0)){a=b;if((j|0)!=(l|0)){break r}}G[f>>1]=a}f=h+1|0}if((r|0)==(e&65535)){break q}while(1){a=c;l=N(f,6)+i|0;k=J[l>>1];s:{if((k|0)!=(b|0)){a=b;if((j|0)!=(k|0)){break s}}G[l>>1]=a}a=c;k=J[l+6>>1];t:{if((k|0)!=(b|0)){a=b;if((j|0)!=(k|0)){break t}}G[l+6>>1]=a}f=f+2|0;if((m|0)!=(f&65535)){continue}break}}G[(H[n+24>>2]+N(b,52)|0)+48>>1]=J[p+2>>1]}h=h+1|0;e=e+1|0;if((m|0)!=(e|0)){continue}break}e=H[o+116>>2];i=H[e>>2]}if(i){Ca(i);e=H[o+116>>2]}Ca(e);H[o+116>>2]=0;break a}q=0;Ba(d,1,9499,0)}na=g+240|0;return q|0}function Mc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=na-48|0;na=k;H[a+8>>2]=1;a:{b:{d=k+40|0;c:{if((Ja(b,d,2,c)|0)!=2){break c}Da(d,k+44|0,2);if(H[k+44>>2]!=65359){break c}H[a+8>>2]=2;d=H[b+56>>2];e=d-2|0;d=H[b+60>>2]-(d>>>0<2)|0;g=H[a+224>>2];H[g>>2]=e;H[g+4>>2]=d;H[k+16>>2]=e;H[k+20>>2]=d;Ba(c,4,12769,k+16|0);i=H[a+224>>2];f=H[i>>2];e=H[i+24>>2];d=e+1|0;g=H[i+32>>2];if(d>>>0<=g>>>0){g=H[i+28>>2];break b}d=~~O(O(g>>>0)+O(100))>>>0;H[i+32>>2]=d;g=Ga(H[i+28>>2],N(d,24));if(g){H[i+28>>2]=g;e=H[i+24>>2];d=e+1|0;break b}Ca(H[i+28>>2]);H[i+32>>2]=0;H[i+24>>2]=0;H[i+28>>2]=0;Ba(c,1,3899,0)}Ba(c,1,15656,0);a=0;break a}e=N(e,24)+g|0;H[e+16>>2]=2;H[e+8>>2]=f;H[e+12>>2]=f>>31;G[e>>1]=65359;H[i+24>>2]=d;if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}Da(H[a+16>>2],k+40|0,2);d:{e:{g=H[k+40>>2];if((g|0)!=65424){while(1){e=24912;if(g>>>0<=65279){H[k>>2]=g;Ba(c,1,2268,k);a=0;break a}while(1){d=e;f=H[d>>2];if(f){e=d+12|0;if((f|0)!=(g|0)){continue}}break}f:{g:{if(f){break g}i=2;Ba(c,2,3847,0);e=2472;h:{i:{if((Ja(b,H[a+16>>2],2,c)|0)!=2){break i}while(1){Da(H[a+16>>2],k+44|0,2);f=24912;g=H[k+44>>2];if(g>>>0>=65280){while(1){d=f;j=H[d>>2];if(j){f=d+12|0;if((g|0)!=(j|0)){continue}}break}if(!(H[d+4>>2]&H[a+8>>2])){e=5397;break i}if(j){if((j|0)==65424){H[k+40>>2]=65424;break f}f=H[b+56>>2];h=H[a+224>>2];g=H[h+24>>2];e=g+1|0;d=H[h+32>>2];if(e>>>0<=d>>>0){d=H[h+28>>2];break h}d=~~O(O(d>>>0)+O(100))>>>0;H[h+32>>2]=d;d=Ga(H[h+28>>2],N(d,24));if(d){H[h+28>>2]=d;g=H[h+24>>2];e=g+1|0;break h}Ca(H[h+28>>2]);H[h+32>>2]=0;H[h+24>>2]=0;H[h+28>>2]=0;e=3899;break i}i=i+2|0}if((Ja(b,H[a+16>>2],2,c)|0)==2){continue}break}}Ba(c,1,e,0);Ba(c,1,9847,0);a=0;break a}d=N(g,24)+d|0;H[d+16>>2]=i;g=f-i|0;H[d+8>>2]=g;H[d+12>>2]=g>>31;G[d>>1]=0;H[h+24>>2]=e;H[k+40>>2]=j;g=24912;while(1){d=g;f=H[d>>2];if(!f){break g}g=d+12|0;if((f|0)!=(j|0)){continue}break}}if(!(H[d+4>>2]&H[a+8>>2])){Ba(c,1,5397,0);a=0;break a}if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}Da(H[a+16>>2],k+36|0,2);e=H[k+36>>2];if(e>>>0<=1){Ba(c,1,6074,0);a=0;break a}e=e-2|0;H[k+36>>2]=e;g=H[a+16>>2];if(K[a+20>>2]>>0){g=Ga(g,e);if(!g){Ca(H[a+16>>2]);H[a+16>>2]=0;H[a+20>>2]=0;Ba(c,1,4973,0);a=0;break a}H[a+16>>2]=g;e=H[k+36>>2];H[a+20>>2]=e}e=Ja(b,g,e,c);if((e|0)!=H[k+36>>2]){Ba(c,1,2472,0);a=0;break a}if(!(ra[H[d+8>>2]](a,H[a+16>>2],e,c)|0)){Ba(c,1,2490,0);a=0;break a}d=H[b+56>>2];j=H[k+36>>2];h=H[a+224>>2];g=H[h+24>>2];i=g+1|0;e=H[h+32>>2];j:{if(i>>>0<=e>>>0){e=H[h+28>>2];break j}e=~~O(O(e>>>0)+O(100))>>>0;H[h+32>>2]=e;e=Ga(H[h+28>>2],N(e,24));if(!e){break d}H[h+28>>2]=e;g=H[h+24>>2];i=g+1|0}e=N(g,24)+e|0;H[e+16>>2]=j+4;d=(d-j|0)-4|0;H[e+8>>2]=d;H[e+12>>2]=d>>31;G[e>>1]=f;H[h+24>>2]=i;if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}n=(f|0)==65372?1:n;l=(f|0)==65362?1:l;m=(f|0)==65361?1:m;Da(H[a+16>>2],k+40|0,2);g=H[k+40>>2];if((g|0)!=65424){continue}}break}if(m){break e}}Ba(c,1,4785,0);a=0;break a}if(!l){Ba(c,1,4831,0);a=0;break a}if(!n){Ba(c,1,4877,0);a=0;break a}d=0;e=0;l=0;i=na-16|0;na=i;n=1;k:{if(!(F[a+212|0]&1)){break k}l:{f=H[a+136>>2];if(!f){break l}m:{while(1){j=H[a+140>>2]+(l<<3)|0;if(H[j>>2]){h=H[j+4>>2];g=d-h|0;g=d>>>0>=g>>>0?g:0;if(d>>>0>>0){h=h-d|0;f=H[j>>2]+d|0;while(1){if(h>>>0<4){d=5671;break m}Da(f,i+12|0,4);d=H[i+12>>2];if((d^-1)>>>0>>0){d=5645;break m}j=h-4|0;m=j>>>0>>0;g=m?d-j|0:g;e=d+e|0;h=j-d|0;f=((m?0:d)+f|0)+4|0;if(d>>>0>>0){continue}break}f=H[a+136>>2]}d=g}l=l+1|0;if(l>>>0>>0){continue}break}if(!d){break l}n=0;Ba(c,1,3067,0);break k}n=0;Ba(c,1,d,0);break k}d=Fa(e);H[a+160>>2]=d;if(!d){n=0;Ba(c,1,4337,0);break k}H[a+148>>2]=e;h=H[a+140>>2];f=H[a+136>>2];if(f){e=0;d=0;g=0;while(1){l=g<<3;j=l+h|0;if(H[j>>2]){m=H[a+160>>2]+d|0;h=H[j>>2];f=H[j+4>>2];n:{if(f>>>0<=e>>>0){if(f){B(m,h,f)}d=d+f|0;e=e-f|0;break n}if(e){B(m,h,e)}d=d+e|0;f=f-e|0;e=e+h|0;while(1){if(f>>>0<=3){n=0;Ba(c,1,5671,0);break k}Da(e,i+8|0,4);e=e+4|0;h=H[a+160>>2]+d|0;f=f-4|0;j=H[i+8>>2];if(f>>>0>>0){if(f){B(h,e,f)}d=d+f|0;e=H[i+8>>2]-f|0;break n}if(j){B(h,e,j)}h=H[i+8>>2];d=h+d|0;e=e+h|0;f=f-h|0;if(f){continue}break}e=0}Ca(H[l+H[a+140>>2]>>2]);h=H[a+140>>2];f=l+h|0;H[f>>2]=0;H[f+4>>2]=0;f=H[a+136>>2]}g=g+1|0;if(g>>>0>>0){continue}break}e=H[a+148>>2];d=H[a+160>>2]}H[a+168>>2]=e;H[a+144>>2]=d;H[a+136>>2]=0;Ca(h);H[a+140>>2]=0}na=i+16|0;if(!n){Ba(c,1,8085,0);a=0;break a}Ba(c,4,11754,0);d=H[a+224>>2];e=H[b+56>>2];e=e-2|0;H[d+8>>2]=e;H[d+12>>2]=0;b=0;i=0;n=na-16|0;na=n;g=H[a+68>>2];o:{if(!g){H[a+76>>2]=1;break o}if(H[a+76>>2]){break o}d=H[a+72>>2];h=H[a+224>>2];e=H[h+40>>2];p:{if((g|0)!=1){l=g&1;j=g&-2;while(1){m=(b<<3)+d|0;o=J[m>>1];f=e+N(o,40)|0;H[f>>2]=o;H[f+8>>2]=H[f+8>>2]+1;m=J[m+8>>1];f=e+N(m,40)|0;H[f>>2]=m;H[f+8>>2]=H[f+8>>2]+1;b=b+2|0;i=i+2|0;if((j|0)!=(i|0)){continue}break}if(!l){break p}}f=J[(b<<3)+d>>1];b=e+N(f,40)|0;H[b>>2]=f;H[b+8>>2]=H[b+8>>2]+1}f=H[h+36>>2];q:{if(f){b=0;while(1){if(!H[(e+N(b,40)|0)+8>>2]){H[n>>2]=b;Ba(c,1,9304,n);break q}b=b+1|0;if((f|0)!=(b|0)){continue}break}}f=H[h+8>>2];b=H[h+12>>2];e=0;while(1){r:{l=e<<3;h=H[H[a+224>>2]+40>>2]+N(J[l+d>>1],40)|0;i=H[h+16>>2];if(!i){i=Ea(H[h+8>>2],24);H[h+16>>2]=i;if(!i){break r}g=H[a+68>>2];d=H[a+72>>2]}j=H[h+4>>2];i=N(j,24)+i|0;H[i>>2]=f;H[i+4>>2]=b;l=H[(d+l|0)+4>>2];f=l+f|0;H[i+16>>2]=f;b=f>>>0>>0?b+1|0:b;H[i+20>>2]=b;H[h+4>>2]=j+1;e=e+1|0;if(g>>>0>e>>>0){continue}break o}break}Ba(c,1,6882,0)}H[a+76>>2]=1;if(!H[a+68>>2]){break o}d=H[H[a+224>>2]+40>>2];b=0;while(1){c=N(J[H[a+72>>2]+(b<<3)>>1],40);d=c+d|0;H[d+8>>2]=0;Ca(H[d+16>>2]);d=H[H[a+224>>2]+40>>2];H[(c+d|0)+16>>2]=0;b=b+1|0;if(b>>>0>2]){continue}break}}na=n+16|0;H[a+8>>2]=8;a=1;break a}Ca(H[h+28>>2]);H[h+32>>2]=0;H[h+24>>2]=0;H[h+28>>2]=0;Ba(c,1,3899,0);a=0}na=k+48|0;return a|0}function Sd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;g=na-160|0;na=g;a:{if(c>>>0<=35){b=0;Ba(d,1,6095,0);break a}h=H[a+96>>2];c=c-36|0;f=(c>>>0)/3|0;if((N(f,3)|0)!=(c|0)){b=0;Ba(d,1,6095,0);break a}c=g+156|0;Da(b,c,2);G[a+104>>1]=H[g+156>>2];Da(b+2|0,h+8|0,4);Da(b+6|0,h+12|0,4);Da(b+10|0,h,4);Da(b+14|0,h+4|0,4);Da(b+18|0,a+116|0,4);Da(b+22|0,a+120|0,4);Da(b+26|0,a+108|0,4);Da(b+30|0,a+112|0,4);Da(b+34|0,c,2);b:{c:{d:{e:{c=H[g+156>>2];if(c>>>0<=16384){H[h+16>>2]=c;if((c|0)!=(f|0)){H[g+132>>2]=f;H[g+128>>2]=c;Ba(d,1,14980,g+128|0);break b}c=H[h+4>>2];j=H[h+12>>2];k=H[h+8>>2];e=H[h>>2];if(!(c>>>0>>0&k>>>0>e>>>0)){H[g+120>>2]=j-c;H[g+124>>2]=0-(c>>>0>j>>>0);H[g+112>>2]=k-e;H[g+116>>2]=0-(e>>>0>k>>>0);Ba(d,1,14542,g+112|0);break b}i=H[a+116>>2];l=H[a+120>>2];if(!(l?i:0)){H[g+4>>2]=l;H[g>>2]=i;Ba(d,1,15094,g);break b}f:{o=H[a+108>>2];g:{if(o>>>0>e>>>0){break g}i=i+o|0;if(e>>>0>=(i>>>0>>0?-1:i)>>>0){break g}i=H[a+112>>2];if(i>>>0>c>>>0){break g}l=i+l|0;if(c>>>0<(i>>>0>l>>>0?-1:l)>>>0){break f}}b=0;Ba(d,1,2792,0);break a}h:{if(H[a+248>>2]){break h}i=H[a+240>>2];if(!i){break h}l=H[a+244>>2];if(!l){break h}e=k-e|0;c=j-c|0;if((e|0)==(i|0)&(c|0)==(l|0)){break h}H[g+108>>2]=c;H[g+104>>2]=e;H[g+100>>2]=l;H[g+96>>2]=i;Ba(d,1,14006,g+96|0);break b}c=Ea(f,52);H[h+24>>2]=c;if(!c){break e}i:{if(!H[h+16>>2]){break i}e=g+152|0;Da(b+36|0,e,1);f=H[g+152>>2];i=f>>>7|0;H[c+32>>2]=i;l=(f&127)+1|0;H[c+24>>2]=l;j=H[a+248>>2];Da(b+37|0,e,1);H[c>>2]=H[g+152>>2];Da(b+38|0,e,1);f=H[g+152>>2];H[c+4>>2]=f;e=0;k=H[c>>2];if(k-256>>>0<4294967041|f-256>>>0<4294967041){break c}f=H[c+24>>2];if(f>>>0>31){break d}H[c+36>>2]=0;H[c+40>>2]=H[a+184>>2];e=1;if(K[h+16>>2]<=1){break i}i=j?0:i;j=j?0:l;b=b+39|0;while(1){Da(b,g+152|0,1);k=H[g+152>>2];f=k>>>7|0;H[c+84>>2]=f;k=(k&127)+1|0;H[c+76>>2]=k;if(!(H[a+248>>2]|(I[a+212|0]&4|(j|0)==(k|0)&(f|0)==(i|0)))){H[g+84>>2]=f;H[g+80>>2]=k;H[g+76>>2]=e;H[g+72>>2]=i;H[g+68>>2]=j;H[g+64>>2]=e;Ba(d,2,14778,g- -64|0)}f=g+152|0;Da(b+1|0,f,1);H[c+52>>2]=H[g+152>>2];Da(b+2|0,f,1);f=H[g+152>>2];H[c+56>>2]=f;k=H[c+52>>2];if(!(f-256>>>0>4294967040&k-256>>>0>=4294967041)){break c}f=H[c+76>>2];if(f>>>0>=32){break d}b=b+3|0;H[c+88>>2]=0;H[c+92>>2]=H[a+184>>2];c=c+52|0;e=e+1|0;if(e>>>0>2]){continue}break}}b=0;f=H[a+116>>2];if(!f){break a}j=H[a+120>>2];if(!j){break a}k=0;c=k;p=H[a+108>>2];l=H[h+8>>2]-p|0;i=f-1|0;e=l+i|0;c=l>>>0>e>>>0?c+1|0:c;e=fe(e,c,f,0);H[a+128>>2]=e;o=0;c=o;q=H[a+112>>2];n=H[h+12>>2]-q|0;m=n;l=j-1|0;n=n+l|0;c=m>>>0>n>>>0?c+1|0:c;c=fe(n,c,j,0);H[a+132>>2]=c;j:{if(!(!e|!c)){if(e>>>0<=65535/(c>>>0)>>>0){break j}}H[g+20>>2]=c;H[g+16>>2]=e;Ba(d,1,14120,g+16|0);break a}n=N(c,e);k:{if(I[a+92|0]&2){H[a+28>>2]=(H[a+28>>2]-p>>>0)/(f>>>0);H[a+32>>2]=(H[a+32>>2]-q>>>0)/(j>>>0);c=k;e=H[a+36>>2]-p|0;m=e;e=e+i|0;c=m>>>0>e>>>0?c+1|0:c;v=a,w=fe(e,c,f,0),H[v+36>>2]=w;c=o;e=H[a+40>>2]-q|0;m=e;e=e+l|0;c=m>>>0>e>>>0?c+1|0:c;v=a,w=fe(e,c,j,0),H[v+40>>2]=w;break k}H[a+40>>2]=c;H[a+36>>2]=e;H[a+28>>2]=0;H[a+32>>2]=0}c=Ea(n,5644);H[a+180>>2]=c;if(!c){Ba(d,1,3935,0);break a}c=Ea(H[h+16>>2],1080);H[H[a+12>>2]+5584>>2]=c;if(!H[H[a+12>>2]+5584>>2]){Ba(d,1,3935,0);break a}c=Ea(10,20);H[H[a+12>>2]+5616>>2]=c;c=H[a+12>>2];if(!H[c+5616>>2]){Ba(d,1,3935,0);break a}H[c+5624>>2]=10;c=Ea(10,20);H[H[a+12>>2]+5628>>2]=c;c=H[a+12>>2];if(!H[c+5628>>2]){Ba(d,1,3935,0);break a}H[c+5636>>2]=10;e=H[h+16>>2];l:{if(!e){break l}f=H[h+24>>2];c=0;if((e|0)!=1){j=e&1;k=e&-2;e=0;while(1){i=f+N(c,52)|0;if(!H[i+32>>2]){H[(H[H[a+12>>2]+5584>>2]+N(c,1080)|0)+1076>>2]=1<>2]-1}i=c|1;l=f+N(i,52)|0;if(!H[l+32>>2]){H[(H[H[a+12>>2]+5584>>2]+N(i,1080)|0)+1076>>2]=1<>2]-1}c=c+2|0;e=e+2|0;if((k|0)!=(e|0)){continue}break}if(!j){break l}}e=f+N(c,52)|0;if(H[e+32>>2]){break l}H[(H[H[a+12>>2]+5584>>2]+N(c,1080)|0)+1076>>2]=1<>2]-1}if(n){c=H[a+180>>2];e=0;while(1){f=Ea(H[h+16>>2],1080);H[c+5584>>2]=f;if(!f){Ba(d,1,3935,0);break a}c=c+5644|0;e=e+1|0;if(n>>>0>e>>>0){continue}break}}c=N(H[a+132>>2],H[a+128>>2]);H[H[a+224>>2]+36>>2]=c;c=Ea(c,40);d=H[a+224>>2];H[d+40>>2]=c;e=0;m:{if(!c){break m}e=1;if(!H[d+36>>2]){break m}d=0;while(1){n:{e=0;j=N(d,40);c=j+c|0;H[c+20>>2]=0;H[c+28>>2]=100;f=Ea(100,24);k=H[a+224>>2];c=H[k+40>>2];H[(j+c|0)+24>>2]=f;if(!f){break n}e=1;d=d+1|0;if(d>>>0>2]){continue}}break}}if(!e){break a}H[a+8>>2]=4;r=H[h+16>>2];if(r){b=H[a+112>>2];d=H[a+120>>2];c=b+N(d,H[a+132>>2]-1|0)|0;d=c+d|0;c=c>>>0>d>>>0?-1:d;d=H[h+12>>2];c=c>>>0>>0?c:d;k=c-1|0;l=0-!c|0;c=H[a+108>>2];d=H[a+116>>2];a=c+N(d,H[a+128>>2]-1|0)|0;d=a+d|0;a=a>>>0>d>>>0?-1:d;d=H[h+8>>2];a=a>>>0>>0?a:d;i=a-1|0;o=0-!a|0;a=H[h+4>>2];b=a>>>0>>0?b:a;n=b-1|0;p=0-!b|0;a=H[h>>2];b=a>>>0>>0?c:a;q=b-1|0;u=0-!b|0;a=H[h+24>>2];b=0;while(1){c=p;e=H[a+4>>2];d=e+n|0;h=fe(d,d>>>0>>0?c+1|0:c,e,0);H[a+20>>2]=h;c=u;f=H[a>>2];d=f+q|0;s=fe(d,d>>>0>>0?c+1|0:c,f,0);H[a+16>>2]=s;d=H[a+40>>2];j=d&31;if((d&63)>>>0>=32){c=-1<>>32-j}j=m^-1;c=c^-1;m=c;c=l;t=e+k|0;c=t>>>0>>0?c+1|0:c;e=fe(t,c,e,0)-h|0;c=m;h=e;e=e+j|0;c=h>>>0>e>>>0?c+1|0:c;h=e;e=d&31;if((d&63)>>>0>=32){c=c>>>e|0}else{c=((1<>>e}H[a+12>>2]=c;c=o;e=f+i|0;c=e>>>0>>0?c+1|0:c;e=fe(e,c,f,0)-s|0;c=m;e=e+j|0;c=e>>>0>>0?c+1|0:c;f=e;e=d&31;if((d&63)>>>0>=32){c=c>>>e|0}else{c=((1<>>e}H[a+8>>2]=c;a=a+52|0;b=b+1|0;if((r|0)!=(b|0)){continue}break}}b=1;break a}H[g+144>>2]=c;Ba(d,1,7932,g+144|0);break b}b=0;H[h+16>>2]=0;Ba(d,1,3935,0);break a}H[g+52>>2]=f;H[g+48>>2]=e;Ba(d,1,15402,g+48|0);break b}H[g+40>>2]=f;H[g+36>>2]=k;H[g+32>>2]=e;Ba(d,1,14340,g+32|0)}b=0}na=g+160|0;return b|0}function nc(a,b,c,d,e,f,g){var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;j=na+-64|0;na=j;H[j+60>>2]=b;y=j+41|0;z=j+39|0;u=j+40|0;a:{b:{c:{d:{e:while(1){b=0;f:while(1){if((o^2147483647)<(b|0)){break d}o=b+o|0;g:{h:{i:{j:{k=H[j+60>>2];b=k;i=I[b|0];if(i){while(1){k:{h=i&255;l:{if(!h){n=b;break l}if((h|0)!=37){break k}i=b;while(1){if(I[i+1|0]!=37){n=i;break l}b=b+1|0;h=I[i+2|0];n=i+2|0;i=n;if((h|0)==37){continue}break}}H[j+60>>2]=n;b=b-k|0;x=o^2147483647;if((b|0)>(x|0)){break d}if(a){La(a,k,b)}if(b){continue f}b=n+1|0;s=-1;h=F[n+1|0]-48|0;if(!(I[n+2|0]!=36|h>>>0>9)){v=1;s=h;b=n+3|0}H[j+60>>2]=b;p=0;i=F[b|0];m=i-32|0;m:{if(m>>>0>31){h=b;break m}h=b;n=1<>2]=h;p=n|p;i=F[b+1|0];m=i-32|0;if(m>>>0>=32){break m}b=h;n=1<>>0>9)){p:{if(!a){H[(b<<2)+e>>2]=10;q=0;break p}q=H[(b<<3)+d>>2]}b=h+3|0;v=1;break o}if(v){break j}b=h+1|0;if(!a){H[j+60>>2]=b;v=0;q=0;break n}h=H[c>>2];H[c>>2]=h+4;q=H[h>>2];v=0}H[j+60>>2]=b;if((q|0)>=0){break n}q=0-q|0;p=p|8192;break n}q=mc(j+60|0);if((q|0)<0){break d}b=H[j+60>>2]}h=0;l=-1;i=0;q:{if(I[b|0]!=46){break q}if(I[b+1|0]==42){i=F[b+2|0]-48|0;r:{if(!(I[b+3|0]!=36|i>>>0>9)){b=b+4|0;s:{if(!a){H[(i<<2)+e>>2]=10;l=0;break s}l=H[(i<<3)+d>>2]}break r}if(v){break j}b=b+2|0;l=0;if(!a){break r}i=H[c>>2];H[c>>2]=i+4;l=H[i>>2]}H[j+60>>2]=b;i=(l|0)>=0;break q}H[j+60>>2]=b+1;l=mc(j+60|0);b=H[j+60>>2];i=1}w=i;while(1){m=h;n=28;r=b;i=F[b|0];if(i-123>>>0<4294967238){break c}b=b+1|0;h=I[(i+N(h,58)|0)+25551|0];if((h-1&255)>>>0<8){continue}break}H[j+60>>2]=b;t:{if((h|0)!=27){if(!h){break c}if((s|0)>=0){if(!a){H[(s<<2)+e>>2]=h;continue e}b=(s<<3)+d|0;h=H[b+4>>2];H[j+48>>2]=H[b>>2];H[j+52>>2]=h;break t}if(!a){break g}lc(j+48|0,h,c,g);break t}if((s|0)>=0){break c}b=0;if(!a){continue f}}if(I[a|0]&32){break b}i=p&-65537;h=p&8192?i:p;p=0;s=1072;n=u;u:{v:{w:{x:{y:{z:{A:{B:{C:{D:{E:{F:{G:{H:{I:{J:{K:{r=I[r|0];b=r<<24>>24;b=m?(r&15)==3?b&-45:b:b;switch(b-88|0){case 0:case 32:break G;case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 10:case 16:case 18:case 19:case 20:case 21:case 25:case 26:case 28:case 30:case 31:break h;case 9:case 13:case 14:case 15:break u;case 11:break B;case 12:case 17:break E;case 22:break I;case 23:break F;case 24:break H;case 27:break A;case 29:break J;default:break K}}L:{switch(b-65|0){case 1:case 3:break h;case 0:case 4:case 5:case 6:break u;case 2:break z;default:break L}}if((b|0)==83){break y}break h}m=H[j+48>>2];r=H[j+52>>2];s=1072;break D}b=0;M:{switch(m|0){case 0:H[H[j+48>>2]>>2]=o;continue f;case 1:H[H[j+48>>2]>>2]=o;continue f;case 2:h=H[j+48>>2];H[h>>2]=o;H[h+4>>2]=o>>31;continue f;case 3:G[H[j+48>>2]>>1]=o;continue f;case 4:F[H[j+48>>2]]=o;continue f;case 6:H[H[j+48>>2]>>2]=o;continue f;case 7:break M;default:continue f}}h=H[j+48>>2];H[h>>2]=o;H[h+4>>2]=o>>31;continue f}l=l>>>0<=8?8:l;h=h|8;b=120}i=u;k=H[j+52>>2];r=k;m=H[j+48>>2];t=m;if(k|m){A=b&32;while(1){i=i-1|0;F[i|0]=A|I[(t&15)+26080|0];t=(k&15)<<28|t>>>4;k=k>>>4|0;if(t|k){continue}break}}k=i;if(!(h&8)|!(m|r)){break C}s=(b>>>4|0)+1072|0;p=2;break C}b=u;k=H[j+52>>2];r=k;m=H[j+48>>2];t=m;if(k|m){while(1){b=b-1|0;F[b|0]=t&7|48;t=(k&7)<<29|t>>>3;k=k>>>3|0;if(t|k){continue}break}}k=b;if(!(h&8)){break C}b=y-b|0;l=(b|0)<(l|0)?l:b;break C}m=H[j+48>>2];b=H[j+52>>2];r=b;if((b|0)<0){k=0-(b+((m|0)!=0)|0)|0;r=k;m=0-m|0;H[j+48>>2]=m;H[j+52>>2]=k;p=1;s=1072;break D}if(h&2048){p=1;s=1073;break D}p=h&1;s=p?1074:1072}k=_a(m,r,u)}if((l|0)<0&w){break d}h=w?h&-65537:h;if(!((m|r)!=0|l)){k=u;l=0;break h}b=!(m|r)+(u-k|0)|0;l=(b|0)<(l|0)?l:b;break h}b=I[j+48|0];break i}m=l>>>0>=2147483647?2147483647:l;h=m;n=(h|0)!=0;b=H[j+48>>2];k=b?b:1686;b=k;N:{O:{P:{Q:{if(!(b&3)|!h){break Q}while(1){if(!I[b|0]){break P}h=h-1|0;n=(h|0)!=0;b=b+1|0;if(!(b&3)){break Q}if(h){continue}break}}if(!n){break O}if(!(!I[b|0]|h>>>0<4)){while(1){n=H[b>>2];if(((16843008-n|n)&-2139062144)!=-2139062144){break P}b=b+4|0;h=h-4|0;if(h>>>0>3){continue}break}}if(!h){break O}}while(1){if(!I[b|0]){break N}b=b+1|0;h=h-1|0;if(h){continue}break}}b=0}b=b?b-k|0:m;n=b+k|0;if((l|0)>=0){h=i;l=b;break h}h=i;l=b;if(I[n|0]){break d}break h}b=H[j+48>>2];if(b|H[j+52>>2]){break x}b=0;break i}if(l){i=H[j+48>>2];break w}b=0;Na(a,32,q,0,h);break v}H[j+12>>2]=0;H[j+8>>2]=b;i=j+8|0;H[j+48>>2]=i;l=-1}b=0;while(1){R:{k=H[i>>2];if(!k){break R}k=jc(j+4|0,k);if((k|0)<0){break b}if(k>>>0>l-b>>>0){break R}i=i+4|0;b=b+k|0;if(l>>>0>b>>>0){continue}}break}n=61;if((b|0)<0){break c}Na(a,32,q,b,h);if(!b){b=0;break v}n=0;i=H[j+48>>2];while(1){k=H[i>>2];if(!k){break v}m=j+4|0;k=jc(m,k);n=k+n|0;if(n>>>0>b>>>0){break v}La(a,m,k);i=i+4|0;if(b>>>0>n>>>0){continue}break}}Na(a,32,q,b,h^8192);b=(b|0)<(q|0)?q:b;continue f}if((l|0)<0&w){break d}n=61;b=ra[f|0](a,M[j+48>>3],q,l,h,b,m)|0;if((b|0)>=0){continue f}break c}i=I[b+1|0];b=b+1|0;continue}}if(a){break a}if(!v){break g}b=1;while(1){a=H[(b<<2)+e>>2];if(a){lc((b<<3)+d|0,a,c,g);o=1;b=b+1|0;if((b|0)!=10){continue}break a}break}while(1){if(H[(b<<2)+e>>2]){break j}o=1;b=b+1|0;if((b|0)!=10){continue}break}break a}n=28;break c}F[j+39|0]=b;l=1;k=z;h=i}m=n-k|0;l=(l|0)>(m|0)?l:m;if((l|0)>(p^2147483647)){break d}n=61;i=l+p|0;b=(i|0)<(q|0)?q:i;if(x>>>0>>0){break c}Na(a,32,b,i,h);La(a,s,p);Na(a,48,b,i,h^65536);Na(a,48,l,m,0);La(a,k,m);Na(a,32,b,i,h^8192);continue}break}break}o=0;break a}n=61}H[6605]=n}o=-1}na=j- -64|0;return o}function _c(a,b,c,d,e,f,g){a=a|0;b=+b;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,t=0,u=0,v=0,w=0,y=0,z=0,A=0,B=0,C=0;m=na-560|0;na=m;H[m+556>>2]=0;x(+b);i=s(1)|0;s(0)|0;a:{if((i|0)<0){q=1;z=1082;b=-b;x(+b);i=s(1)|0;s(0)|0;break a}if(e&2048){q=1;z=1085;break a}q=e&1;z=q?1088:1083;A=!q}b:{if((i&2146435072)==2146435072){g=q+3|0;Na(a,32,c,g,e&-65537);La(a,z,q);d=f&32;La(a,b!=b?d?1207:1435:d?1312:1476,3);Na(a,32,c,g,e^8192);k=(c|0)>(g|0)?c:g;break b}w=m+528|0;c:{d:{e:{b=kc(b,m+556|0);b=b+b;if(b!=0){g=H[m+556>>2];H[m+556>>2]=g-1;y=f|32;if((y|0)!=97){break e}break c}y=f|32;if((y|0)==97){break c}k=H[m+556>>2];break d}k=g-29|0;H[m+556>>2]=k;b=b*268435456}l=(d|0)<0?6:d;o=((k|0)>=0?488:0)+m|0;h=o;while(1){d=~~b>>>0;H[h>>2]=d;h=h+4|0;b=(b-+(d>>>0))*1e9;if(b!=0){continue}break}f:{if((k|0)<=0){i=k;j=o;g=h;break f}j=o;i=k;while(1){u=i>>>0>=29?29:i;g=h-4|0;g:{if(j>>>0>g>>>0){break g}d=0;i=0;while(1){p=H[g>>2];v=u&31;if((u&63)>>>0>=32){n=p<>>32-v;p=p<>>0>p>>>0?i+1|0:i;d=fe(p,n,1e9,0);i=qa;B=g,C=p+be(d,i,-1e9)|0,H[B>>2]=C;g=g-4|0;if(j>>>0<=g>>>0){continue}break}if(!n&p>>>0<1e9){break g}j=j-4|0;H[j>>2]=d}while(1){g=h;if(j>>>0>>0){h=g-4|0;if(!H[h>>2]){continue}}break}i=H[m+556>>2]-u|0;H[m+556>>2]=i;h=g;if((i|0)>0){continue}break}}if((i|0)<0){r=((l+25>>>0)/9|0)+1|0;u=(y|0)==102;while(1){d=0-i|0;v=d>>>0>=9?9:d;h:{if(g>>>0<=j>>>0){h=H[j>>2]?0:4;break h}p=1e9>>>v|0;n=-1<>2];H[h>>2]=i+(d>>>v|0);i=N(p,d&n);h=h+4|0;if(h>>>0>>0){continue}break}h=H[j>>2]?0:4;if(!i){break h}H[g>>2]=i;g=g+4|0}i=H[m+556>>2]+v|0;H[m+556>>2]=i;j=h+j|0;d=u?o:j;g=g-d>>2>(r|0)?d+(r<<2)|0:g;if((i|0)<0){continue}break}}i=0;i:{if(g>>>0<=j>>>0){break i}i=N(o-j>>2,9);h=10;d=H[j>>2];if(d>>>0<10){break i}while(1){i=i+1|0;h=N(h,10);if(d>>>0>=h>>>0){continue}break}}d=(l-((y|0)!=102?i:0)|0)-((y|0)==103&(l|0)!=0)|0;if((d|0)<(N(g-o>>2,9)-9|0)){n=d+9216|0;d=(n|0)/9|0;k=(((k|0)<0?-4092:-3604)+m|0)+(d<<2)|0;h=10;d=n+N(d,-9)|0;if((d|0)<=7){while(1){h=N(h,10);d=d+1|0;if((d|0)!=8){continue}break}}n=H[k>>2];r=(n>>>0)/(h>>>0)|0;p=N(r,h);d=k+4|0;j:{if((n|0)==(p|0)&(d|0)==(g|0)){break j}n=n-p|0;k:{if(!(r&1)){b=9007199254740992;if(!(F[k-4|0]&1)|((h|0)!=1e9|j>>>0>=k>>>0)){break k}}b=9007199254740994}t=(d|0)==(g|0)?1:1.5;d=h>>>1|0;t=d>>>0>n>>>0?.5:(d|0)==(n|0)?t:1.5;if(!(I[z|0]!=45|A)){t=-t;b=-b}H[k>>2]=p;if(b+t==b){break j}d=h+p|0;H[k>>2]=d;if(d>>>0>=1e9){while(1){H[k>>2]=0;k=k-4|0;if(k>>>0>>0){j=j-4|0;H[j>>2]=0}d=H[k>>2]+1|0;H[k>>2]=d;if(d>>>0>999999999){continue}break}}i=N(o-j>>2,9);h=10;d=H[j>>2];if(d>>>0<10){break j}while(1){i=i+1|0;h=N(h,10);if(d>>>0>=h>>>0){continue}break}}d=k+4|0;g=d>>>0>>0?d:g}while(1){n=g;h=g>>>0<=j>>>0;if(!h){g=g-4|0;if(!H[g>>2]){continue}}break}l:{if((y|0)!=103){d=e&8;break l}g=l?l:1;d=(g|0)>(i|0)&(i|0)>-5;l=(d?i^-1:-1)+g|0;f=(d?-1:-2)+f|0;d=e&8;if(d){break l}g=-9;m:{if(h){break m}k=H[n-4>>2];if(!k){break m}d=10;g=0;if((k>>>0)%10|0){break m}while(1){h=g;g=g+1|0;d=N(d,10);if(!((k>>>0)%(d>>>0)|0)){continue}break}g=h^-1}h=N(n-o>>2,9);if((f&-33)==70){d=0;g=(g+h|0)-9|0;g=(g|0)>0?g:0;l=(g|0)>(l|0)?l:g;break l}d=0;g=((h+i|0)+g|0)-9|0;g=(g|0)>0?g:0;l=(g|0)>(l|0)?l:g}k=-1;p=d|l;if(((p?2147483645:2147483646)|0)<(l|0)){break b}u=(((p|0)!=0)+l|0)+1|0;h=f&-33;n:{if((h|0)==70){if((u^2147483647)<(i|0)){break b}g=(i|0)>0?i:0;break n}g=i>>31;g=_a((g^i)-g|0,0,w);if((w-g|0)<=1){while(1){g=g-1|0;F[g|0]=48;if((w-g|0)<2){continue}break}}r=g-2|0;F[r|0]=f;F[g-1|0]=(i|0)<0?45:43;g=w-r|0;if((g|0)>(u^2147483647)){break b}}f=g+u|0;if((f|0)>(q^2147483647)){break b}k=f+q|0;Na(a,32,c,k,e);La(a,z,q);Na(a,48,c,k,e^65536);o:{p:{q:{if((h|0)==70){f=m+528|9;d=j>>>0>o>>>0?o:j;j=d;while(1){g=_a(H[j>>2],0,f);r:{if((d|0)!=(j|0)){if(m+528>>>0>=g>>>0){break r}while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}break r}if((f|0)!=(g|0)){break r}g=g-1|0;F[g|0]=48}La(a,g,f-g|0);j=j+4|0;if(o>>>0>=j>>>0){continue}break}if(p){La(a,1684,1)}if((l|0)<=0|j>>>0>=n>>>0){break q}while(1){g=_a(H[j>>2],0,f);if(g>>>0>m+528>>>0){while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}}La(a,g,(l|0)>=9?9:l);g=l-9|0;j=j+4|0;if(n>>>0<=j>>>0){break p}d=(l|0)>9;l=g;if(d){continue}break}break p}s:{if((l|0)<0){break s}f=j>>>0>>0?n:j+4|0;o=m+528|9;h=j;while(1){g=_a(H[h>>2],0,o);if((o|0)==(g|0)){g=g-1|0;F[g|0]=48}t:{if((h|0)!=(j|0)){if(m+528>>>0>=g>>>0){break t}while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}break t}La(a,g,1);g=g+1|0;if(!(d|l)){break t}La(a,1684,1)}i=g;g=o-g|0;La(a,i,(g|0)<(l|0)?g:l);l=l-g|0;h=h+4|0;if(f>>>0<=h>>>0){break s}if((l|0)>=0){continue}break}}Na(a,48,l+18|0,18,0);La(a,r,w-r|0);break o}g=l}Na(a,48,g+9|0,9,0)}Na(a,32,c,k,e^8192);k=(c|0)>(k|0)?c:k;break b}i=(f<<26>>31&9)+z|0;u:{if(d>>>0>12){break u}g=I[i|0];t=tc(52-(d<<2)|0);if((g|0)==45){b=-(t+(-b-t));break u}b=b+t-t}h=H[m+556>>2];g=h>>31;g=_a((g^h)-g|0,0,w);if((w|0)==(g|0)){g=g-1|0;F[g|0]=48;h=H[m+556>>2]}o=f&32;l=g-2|0;F[l|0]=f+15;F[g-1|0]=(h|0)<0?45:43;j=!(e&8)&(d|0)<=0;h=m+528|0;while(1){f=h;g=~~b;F[h|0]=o|I[g+26080|0];b=(b-+(g|0))*16;h=h+1|0;if(!(j&b==0|(h-(m+528|0)|0)!=1)){F[f+1|0]=46;h=f+2|0}if(b!=0){continue}break}k=-1;j=w-l|0;if((2147483643-(j+q|0)|0)<(d|0)){break b}g=m+528|0;o=h-g|0;f=d?(o-2|0)<(d|0)?d+2|0:o:o;d=q|2;h=f+(d+j|0)|0;Na(a,32,c,h,e);La(a,i,d);Na(a,48,c,h,e^65536);La(a,g,o);Na(a,48,f-o|0,0,0);La(a,l,j);Na(a,32,c,h,e^8192);k=(c|0)>(h|0)?c:h}na=m+560|0;return k|0}function Hc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;i=na-80|0;na=i;H[i+76>>2]=1;a:{b:{if(H[a+128>>2]!=1|H[a+132>>2]!=1|(H[a+108>>2]|H[a+112>>2])){break b}j=H[a+100>>2];if(H[j>>2]|H[j+4>>2]|(H[j+8>>2]!=H[a+116>>2]|H[j+12>>2]!=H[a+120>>2])){break b}if(!bb(a,i+72|0,0,i+68|0,i- -64|0,i+60|0,i+56|0,i+52|0,i+76|0,b,c)){break a}c:{d:{if(!H[i+76>>2]){break d}if(!hb(a,H[i+72>>2],0,0,b,c)){break d}b=H[a+100>>2];if(H[b+16>>2]){break c}e=1;break a}Ba(c,1,8776,0);break a}f=H[b+24>>2];while(1){b=N(h,52);Ca(H[(b+f|0)+44>>2]);c=H[a+100>>2];f=H[c+24>>2];j=b+f|0;l=H[a+232>>2];d=H[H[H[l+20>>2]>>2]+20>>2]+N(h,76)|0;H[j+44>>2]=H[d+36>>2];H[j+36>>2]=H[(b+H[H[l+24>>2]+24>>2]|0)+36>>2];H[d+36>>2]=0;e=1;h=h+1|0;if(h>>>0>2]){continue}break}break a}H[a+80>>2]=0;H[a+84>>2]=0;Ca(H[a+88>>2]);H[a+88>>2]=0;e:{if(!(H[a+28>>2]|H[a+32>>2]|H[a+36>>2]!=H[a+128>>2])){j=2;if(H[a+40>>2]==H[a+132>>2]){break e}}j=2;if(H[a+76>>2]){break e}if(!zb(b)){break e}q=H[a+128>>2];j=N(q,H[a+132>>2]);if(j){e=H[H[a+224>>2]+40>>2];f:{g:{if((j|0)==1){j=0;break g}k=j&1;m=j&-2;j=0;while(1){d=e+N(h,40)|0;g=H[d+4>>2];if(g){n=(H[d+16>>2]+N(g,24)|0)-8|0;g=H[n>>2];o=g;p=g>>>0>j>>>0;g=H[n+4>>2];n=p&(g|0)>=(l|0)|(g|0)>(l|0);j=n?o:j;l=n?g:l}g=H[d+44>>2];if(g){g=(H[d+56>>2]+N(g,24)|0)-8|0;d=H[g>>2];o=d;n=d>>>0>j>>>0;d=H[g+4>>2];g=n&(d|0)>=(l|0)|(d|0)>(l|0);j=g?o:j;l=g?d:l}h=h+2|0;f=f+2|0;if((m|0)!=(f|0)){continue}break}if(!k){break f}}d=e+N(h,40)|0;e=H[d+4>>2];if(!e){break f}e=(H[d+16>>2]+N(e,24)|0)-8|0;d=H[e>>2];g=d;o=d>>>0>j>>>0;d=H[e+4>>2];e=o&(d|0)>=(l|0)|(d|0)>(l|0);j=e?g:j;l=e?d:l}j=j+2|0;l=j>>>0<2?l+1|0:l}else{j=2;l=0}h=0;m=H[a+32>>2];p=H[a+40>>2];h:{if(m>>>0>=p>>>0){break h}d=H[a+28>>2];g=H[a+36>>2];if(d>>>0>=g>>>0){break h}k=g-d&3;s=H[H[a+224>>2]+40>>2];t=d-g>>>0>4294967292;while(1){n=s+N(N(m,q),40)|0;e=d;f=0;if(k){while(1){h=H[(n+N(e,40)|0)+4>>2]+h|0;e=e+1|0;f=f+1|0;if((k|0)!=(f|0)){continue}break}}if(!t){while(1){f=n+N(e,40)|0;h=H[f+124>>2]+(H[f+84>>2]+(H[f+44>>2]+(H[f+4>>2]+h|0)|0)|0)|0;e=e+4|0;if((g|0)!=(e|0)){continue}break}}m=m+1|0;if((p|0)!=(m|0)){continue}break}}g=Fa(h<<3);H[a+88>>2]=g;if(!h|!g){break e}h=0;e=H[a+40>>2];n=H[a+32>>2];i:{if(e>>>0<=n>>>0){break i}f=H[a+36>>2];if(f>>>0<=K[a+28>>2]){break i}while(1){g=H[a+28>>2];if(f>>>0>g>>>0){s=H[H[a+224>>2]+40>>2]+N(N(H[a+128>>2],n),40)|0;while(1){e=s+N(g,40)|0;d=H[e+4>>2];if(d){q=d&3;p=H[e+16>>2];k=0;j:{k:{if(d>>>0<4){e=0;break k}t=d&-4;e=0;m=0;while(1){d=p+N(e,24)|0;r=H[d+4>>2];f=h<<3;o=f+H[a+88>>2]|0;H[o>>2]=H[d>>2];H[o+4>>2]=r;r=H[d+28>>2];o=f+H[a+88>>2]|0;H[o+8>>2]=H[d+24>>2];H[o+12>>2]=r;r=H[d+52>>2];o=f+H[a+88>>2]|0;H[o+16>>2]=H[d+48>>2];H[o+20>>2]=r;o=H[d+76>>2];f=f+H[a+88>>2]|0;H[f+24>>2]=H[d+72>>2];H[f+28>>2]=o;e=e+4|0;h=h+4|0;m=m+4|0;if((t|0)!=(m|0)){continue}break}if(!q){break j}}while(1){f=p+N(e,24)|0;m=H[f+4>>2];d=H[a+88>>2]+(h<<3)|0;H[d>>2]=H[f>>2];H[d+4>>2]=m;e=e+1|0;h=h+1|0;k=k+1|0;if((q|0)!=(k|0)){continue}break}}f=H[a+36>>2]}g=g+1|0;if(f>>>0>g>>>0){continue}break}e=H[a+40>>2]}n=n+1|0;if(n>>>0>>0){continue}break}g=H[a+88>>2]}H[a+84>>2]=h;f=na-208|0;na=f;H[f+8>>2]=1;H[f+12>>2]=0;e=h<<3;l:{if(!e){break l}H[f+16>>2]=8;H[f+20>>2]=8;d=2;while(1){h=(f+16|0)+(d<<2)|0;k=H[h-4>>2]+(H[h-8>>2]+8|0)|0;H[h>>2]=k;d=d+1|0;if(e>>>0>k>>>0){continue}break}d=e-8|0;m:{if((d|0)<=0){k=1;d=1;e=0;break m}e=d+g|0;k=1;d=1;while(1){n:{if((k&3)==3){Ab(g,d,f+16|0);tb(f+8|0,2);d=d+2|0;break n}m=f+16|0;h=d-1|0;o:{if(K[m+(h<<2)>>2]>=e-g>>>0){sb(g,k,H[f+12>>2],d,0,m);break o}Ab(g,d,f+16|0)}if((d|0)==1){rb(f+8|0,1);d=0;break n}rb(f+8|0,h);d=1}k=H[f+8>>2]|1;H[f+8>>2]=k;g=g+8|0;if(e>>>0>g>>>0){continue}break}e=H[f+12>>2]}sb(g,k,e,d,0,f+16|0);e=H[f+12>>2];k=H[f+8>>2];if(!(e|((d|0)!=1|(k|0)!=1))){break l}while(1){p:{if((d|0)<=1){e=qc(k,e);tb(f+8|0,e);d=d+e|0;break p}h=f+8|0;rb(h,2);H[f+8>>2]=H[f+8>>2]^7;tb(h,1);m=g-8|0;k=f+16|0;e=d-2|0;sb(m-H[k+(e<<2)>>2]|0,H[f+8>>2],H[f+12>>2],d-1|0,1,k);rb(h,1);d=H[f+8>>2]|1;H[f+8>>2]=d;sb(m,d,H[f+12>>2],e,1,k);d=e}g=g-8|0;e=H[f+12>>2];k=H[f+8>>2];if(e|((d|0)!=1|(k|0)!=1)){continue}break}}na=f+208|0}e=H[a+128>>2];f=0;q:{while(1){r:{if(!(!H[H[a+180>>2]+5596>>2]|((e|0)!=1|H[a+132>>2]!=1))){H[i+72>>2]=0;H[a+228>>2]=0;H[a+8>>2]=H[a+8>>2]|128;d=0;break r}e=0;if(!bb(a,i+72|0,0,i+68|0,i- -64|0,i+60|0,i+56|0,i+52|0,i+76|0,b,c)){break a}if(!H[i+76>>2]){break q}d=H[i+72>>2]}e=d+1|0;g=hb(a,d,0,0,b,c);h=N(H[a+128>>2],H[a+132>>2]);if(!g){H[i+4>>2]=h;H[i>>2]=e;Ba(c,1,7537,i);e=0;break a}H[i+36>>2]=h;H[i+32>>2]=e;Ba(c,4,11795,i+32|0);if(!Ac(H[a+232>>2],H[H[a+100>>2]+24>>2])){e=0;break a}s:{if(!(H[a+128>>2]!=1|H[a+132>>2]!=1)){h=H[a+100>>2];g=H[a+96>>2];if(H[h>>2]!=H[g>>2]|H[h+4>>2]!=H[g+4>>2]|(H[h+8>>2]!=H[g+8>>2]|H[h+12>>2]!=H[g+12>>2])){break s}}d=H[a+180>>2]+N(d,5644)|0;h=H[d+5596>>2];if(!h){break s}Ca(h);H[d+5596>>2]=0;H[d+5600>>2]=0}H[i+16>>2]=e;Ba(c,4,16601,i+16|0);if(!(Pa(b)|qa)&H[a+8>>2]==64){break q}f=f+1|0;e=H[a+128>>2];if((f|0)==(N(e,H[a+132>>2])|0)){break q}d=H[a+84>>2];if(!d|(d|0)!=H[a+80>>2]){continue}break}hc(b,j,l,c)}e=zc(a,c)}na=i+80|0;return e|0}function Xa(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;d=H[a+32>>2];a:{if(d){break a}b:{h=H[a+16>>2];if((h|0)>=6){b=H[a+8>>2];f=H[a+12>>2];d=h;break b}b=H[a+20>>2];c:{d:{if((b|0)>=5){c=H[a>>2];d=H[c>>2];H[a>>2]=c+4;g=b-4|0;break d}if((b|0)<=0){d=-1;break c}c=H[a>>2];e:{if((b|0)==1){e=-1;b=0;break e}d=0;f:{g:{if((b|0)==2){e=-1;i=b;break g}f=b-1|0;k=f&1;j=f&-2;e=-1;i=b;while(1){f=c;H[a>>2]=c+1;l=I[c|0];c=c+2|0;H[a>>2]=c;H[a+20>>2]=i-1;f=I[f+1|0];i=i-2|0;H[a+20>>2]=i;e=((255<>2]=f;c=I[c|0];H[a+20>>2]=i-1;e=(255<>2]=c+1;d=(255<>2]=g}b=H[a+24>>2];c=d>>>24|0;H[a+24>>2]=(c|0)==255;g=d>>>16&255;k=(g|0)==255;f=d&255;e=(f|0)==255;j=b+e|0;b=d>>>8&255;i=(b|0)==255;j=k+(j+i|0)|0;d=(h-j|0)+32|0;H[a+16>>2]=d;l=H[a+12>>2];b=c|(g|(b|f<<(e?7:8))<<(i?7:8))<<(k?7:8);f=(j-h|0)+32|0;c=f&31;if((f&63)>>>0>=32){i=b<>>32-c;g=b<>2];c=i|l;f=c;H[a+8>>2]=b;H[a+12>>2]=c;if((d|0)>=6){break b}d=0;break a}e=H[a+28>>2];i=H[(e<<2)+20752>>2];h:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break h}g=b;h=63-i|0;c=h&31;if((h&63)>>>0>=32){g=f>>>c|0}else{g=((1<>>c}c=(g&(-1<>2]=d;H[a+28>>2]=e;g=b;h=i&31;if((i&63)>>>0>=32){b=b<>>32-h|f<>2]=g;H[a+12>>2]=b;i=H[a+44>>2]|c>>31;j=H[a+40>>2]&-64|c;H[a+40>>2]=j;H[a+44>>2]=i;if((d|0)<6){d=1;break a}b=H[(e<<2)+20752>>2];i:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break i}k=g;h=63-b|0;c=h&31;if((h&63)>>>0>=32){k=f>>>c|0}else{k=((1<>>c}c=(k&(-1<>2]=d;H[a+28>>2]=e;k=g;h=b&31;if((b&63)>>>0>=32){b=g<>>32-h|f<>2]=k;H[a+12>>2]=b;b=c>>31<<7|c>>>25|i;h=b;j=j&-8065|c<<7;H[a+40>>2]=j;H[a+44>>2]=b;if((d|0)<6){d=2;break a}b=H[(e<<2)+20752>>2];j:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break j}g=k;i=63-b|0;c=i&31;if((i&63)>>>0>=32){g=f>>>c|0}else{g=((1<>>c}c=(g&(-1<>2]=d;H[a+28>>2]=e;l=k;g=b&31;if((b&63)>>>0>=32){i=k<>>32-g|f<>2]=g;f=i;H[a+12>>2]=f;b=c>>31<<14|c>>>18|h;i=b;k=j&-1032193|c<<14;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=3;break a}b=H[(e<<2)+20752>>2];k:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break k}j=g;h=63-b|0;c=h&31;if((h&63)>>>0>=32){j=f>>>c|0}else{j=((1<>>c}c=(j&(-1<>2]=d;H[a+28>>2]=e;j=g;h=b&31;if((b&63)>>>0>=32){b=g<>>32-h|f<>2]=g;H[a+12>>2]=b;b=c>>31<<21|c>>>11|i;j=b;k=k&-132120577|c<<21;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=4;break a}b=H[(e<<2)+20752>>2];l:{if((f|0)<0){c=(-1<=11?11:e)+1|0;d=d-1|0;break l}h=g;i=63-b|0;c=i&31;if((i&63)>>>0>=32){i=f>>>c|0}else{i=((1<>>c}c=(i&(-1<>2]=d;H[a+28>>2]=h;i=g;e=b&31;if((b&63)>>>0>=32){b=g<>>32-e|f<>2]=g;f=b;H[a+12>>2]=b;b=j&-4|(c>>31<<28|c>>>4);j=b;k=k&268435455|c<<28;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=5;break a}b=H[(h<<2)+20752>>2];m:{if((f|0)<0){e=(-1<=11?11:h)+1|0;i=d-1|0;break m}i=g;e=63-b|0;c=e&31;if((e&63)>>>0>=32){i=f>>>c|0}else{i=((1<>>c}e=(i&(-1<>2]=i;H[a+28>>2]=h;d=g;c=b&31;if((b&63)>>>0>=32){b=d<>>32-c|f<>2]=g;H[a+12>>2]=b;b=j&-505|e<<3;l=b;H[a+40>>2]=k;H[a+44>>2]=b;d=6;if((i|0)<6){break a}b=H[(h<<2)+20752>>2];n:{if((c|0)<0){e=(-1<=11?11:h)+1|0;d=i-1|0;break n}d=g;e=63-b|0;f=e&31;if((e&63)>>>0>=32){f=c>>>f|0}else{f=((1<>>f}e=(f&(-1<>2]=d;H[a+28>>2]=h;j=g;f=b&31;if((b&63)>>>0>=32){i=g<>>32-f|c<>2]=g;f=i;H[a+12>>2]=f;i=k;b=l&-64513|e<<10;k=b;H[a+40>>2]=i;H[a+44>>2]=b;if((d|0)<6){d=7;break a}b=H[(h<<2)+20752>>2];o:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:h)+1|0;break o}j=g;e=63-b|0;c=e&31;if((e&63)>>>0>=32){j=f>>>c|0}else{j=((1<>>c}c=(j&(-1<>2]=d;H[a+28>>2]=e;d=g;e=b&31;if((b&63)>>>0>=32){b=d<>>32-e|f<>2]=g;H[a+12>>2]=b;H[a+40>>2]=i;H[a+44>>2]=k&-8257537|c<<17;d=8}H[a+32>>2]=d-1;f=H[a+44>>2];b=f>>>7|0;c=H[a+40>>2];H[a+40>>2]=(f&127)<<25|c>>>7;H[a+44>>2]=b;return c&127}function Rb(a,b,c,d,e,f,g,h,i){var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0;p=na-32|0;na=p;H[p+24>>2]=f;r=H[(N(H[d+28>>2],76)+b|0)+28>>2]+N(H[d+32>>2],152)|0;a:{if(!(H[d+40>>2]|!H[r+24>>2])){k=r+28|0;while(1){b:{if(Nb(k)){break b}b=H[d+36>>2];if(b>>>0>=K[k+24>>2]/40>>>0){Ba(i,1,2836,0);break a}b=H[k+20>>2]+N(b,40)|0;cc(H[b+32>>2]);cc(H[b+36>>2]);o=N(H[b+20>>2],H[b+16>>2]);if(!o){break b}q=o&7;b=H[b+24>>2];if(o>>>0>=8){o=o&-8;j=0;while(1){H[b+516>>2]=0;H[b+520>>2]=0;H[b+448>>2]=0;H[b+452>>2]=0;H[b+380>>2]=0;H[b+384>>2]=0;H[b+312>>2]=0;H[b+316>>2]=0;H[b+244>>2]=0;H[b+248>>2]=0;H[b+176>>2]=0;H[b+180>>2]=0;H[b+108>>2]=0;H[b+112>>2]=0;H[b+40>>2]=0;H[b+44>>2]=0;b=b+544|0;j=j+8|0;if((o|0)!=(j|0)){continue}break}if(!q){break b}}j=0;while(1){H[b+40>>2]=0;H[b+44>>2]=0;b=b+68|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}k=k+36|0;n=n+1|0;if(n>>>0>2]){continue}break}}q=f;c:{if(!(I[c|0]&2)){break c}if(h>>>0<=5){Ba(i,2,4196,0);break c}if(!(I[f|0]==255&I[f+1|0]==145)){Ba(i,2,4238,0);break c}q=f+6|0;H[p+24>>2]=q}l=Fa(20);if(!l){break a}d:{if(F[a+108|0]&1){q=H[a+40>>2];o=a+44|0;h=a+40|0;break d}if(I[c+5640|0]&2){q=H[c+5168>>2];o=c+5180|0;h=c+5168|0;break d}H[p+28>>2]=(f+h|0)-q;o=p+28|0;h=p+24|0}a=H[o>>2];H[l+12>>2]=0;H[l+16>>2]=0;H[l+8>>2]=q;H[l>>2]=q;H[l+4>>2]=a+q;if(!Qa(l,1)){ec(l);a=fc(l);cb(l);a=a+q|0;b=H[h>>2];d=H[o>>2];if(I[c|0]&4){if(b+(d-a|0)>>>0<=1){Ba(i,1,4422,0);break a}if(!(I[a|0]==255&I[a+1|0]==146)){Ba(i,1,4401,0);break a}a=a+2|0}a=a-b|0;H[o>>2]=d-a;H[h>>2]=a+b;H[e>>2]=0;H[g>>2]=H[p+24>>2]-f;x=1;break a}if(H[r+24>>2]){t=r+28|0;while(1){a=H[d+36>>2];b=H[t+20>>2];e:{if(Nb(t)){break e}u=b+N(a,40)|0;z=N(H[u+20>>2],H[u+16>>2]);if(!z){break e}k=H[u+24>>2];v=0;while(1){f:{g:{if(!H[k+40>>2]){a=ac(l,H[u+32>>2],v,H[d+40>>2]+1|0);break g}a=Qa(l,1)}if(!a){H[k+36>>2]=0;break f}if(!H[k+40>>2]){b=0;while(1){a=b;b=b+1|0;if(!ac(l,H[u+36>>2],v,a)){continue}break}b=H[t+28>>2];H[k+32>>2]=3;H[k+24>>2]=b;H[k+28>>2]=(b-a|0)+1}a=1;h:{if(!Qa(l,1)){break h}a=2;if(!Qa(l,1)){break h}a=Qa(l,2);if((a|0)!=3){a=a+3|0;break h}a=Qa(l,5);if((a|0)!=31){a=a+6|0;break h}a=Qa(l,7)+37|0}H[k+36>>2]=a;b=0;while(1){a=b;b=b+1|0;if(Qa(l,1)){continue}break}H[k+32>>2]=a+H[k+32>>2];i:{a=H[k+40>>2];j:{k:{if(!a){a=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];if(!H[k+48>>2]){b=Ga(H[k>>2],240);if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=10}j=H[k>>2];jb(j);b=a&4?1:a&1?10:109;a=0;break k}b=H[k>>2];n=a-1|0;j=b+N(n,24)|0;if(H[j+4>>2]!=H[j+12>>2]){break j}n=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];j=H[k+48>>2];if(j>>>0>>0){j=j+10|0;b=Ga(b,N(j,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=j;b=H[k>>2]}j=N(a,24)+b|0;jb(j);b=1;l:{if(n&4){break l}b=109;if(!(n&1)){break l}b=H[j-12>>2];b=(b|0)==1?2:(b|0)==10?2:1}}n=a;H[j+12>>2]=b}a=H[k+36>>2];if(I[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16|0]&64){while(1){m=N(n,24);s=n?a:1;H[(m+H[k>>2]|0)+16>>2]=s;w=H[k+32>>2];j=0;b=a;if(s>>>0>=2){while(1){j=j+1|0;s=b>>>0>3;b=b>>>1|0;if(s){continue}break}}b=j+w|0;if(b>>>0>=33){H[p+16>>2]=b;Ba(i,1,15535,p+16|0);break i}j=Qa(l,b);b=H[k>>2];m=m+b|0;H[m+20>>2]=j;a=a-H[m+16>>2]|0;if((a|0)<=0){break f}j=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];m=H[k+48>>2];if(m>>>0>>0){m=m+10|0;b=Ga(b,N(m,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=m;b=H[k>>2]}n=n+1|0;b=b+N(n,24)|0;jb(b);if(j&4){H[b+12>>2]=1;continue}if(j&1){j=b;b=H[b-12>>2];H[j+12>>2]=(b|0)==1?2:(b|0)==10?2:1}else{H[b+12>>2]=109}continue}}while(1){m=N(n,24);j=m+H[k>>2]|0;b=H[j+12>>2]-H[j+4>>2]|0;b=(a|0)>(b|0)?b:a;H[j+16>>2]=b;s=H[k+32>>2];j=0;if(b>>>0>=2){while(1){j=j+1|0;w=b>>>0>3;b=b>>>1|0;if(w){continue}break}}b=j+s|0;if(b>>>0>=33){H[p>>2]=b;Ba(i,1,15535,p);break i}j=Qa(l,b);b=H[k>>2];m=m+b|0;H[m+20>>2]=j;a=a-H[m+16>>2]|0;if((a|0)<=0){break f}j=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];m=H[k+48>>2];if(m>>>0>>0){m=m+10|0;b=Ga(b,N(m,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=m;b=H[k>>2]}n=n+1|0;b=b+N(n,24)|0;jb(b);if(j&4){H[b+12>>2]=1;continue}if(j&1){j=b;b=H[b-12>>2];H[j+12>>2]=(b|0)==1?2:(b|0)==10?2:1}else{H[b+12>>2]=109}continue}}cb(l);break a}k=k+68|0;v=v+1|0;if((z|0)!=(v|0)){continue}break}}t=t+36|0;A=A+1|0;if(A>>>0>2]){continue}break}}if(!ec(l)){cb(l);break a}a=fc(l);cb(l);b=a+q|0;a=H[h>>2];if(I[c|0]&4){if(a+(H[o>>2]-b|0)>>>0<=1){Ba(i,1,4422,0);break a}if(!(I[b|0]==255&I[b+1|0]==146)){Ba(i,1,4401,0);break a}b=b+2|0}if((a|0)==(b|0)){break a}H[o>>2]=H[o>>2]+(a-b|0);H[h>>2]=b;x=1;H[e>>2]=1;H[g>>2]=H[p+24>>2]-f}na=p+32|0;return x}function Fb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;a:{if(!c){break a}b:{e=H[a+184>>2];if(!e){break b}f=H[a+96>>2];if(!f|!H[f+16>>2]|(e|0)!=H[H[f+24>>2]+40>>2]){break b}f=H[c+16>>2];if(!f){break b}n=H[c+24>>2];if(H[n+40>>2]|H[n+44>>2]){break b}i=f&7;c:{if(f>>>0>=8){p=f&-8;while(1){f=N(h,52)+n|0;H[f+404>>2]=e;H[f+352>>2]=e;H[f+300>>2]=e;H[f+248>>2]=e;H[f+196>>2]=e;H[f+144>>2]=e;H[f+92>>2]=e;H[f+40>>2]=e;h=h+8|0;g=g+8|0;if((g|0)!=(p|0)){continue}break}if(!i){break c}}while(1){H[(N(h,52)+n|0)+40>>2]=e;h=h+1|0;j=j+1|0;if((i|0)!=(j|0)){continue}break}}e=0;p=0;n=na-32|0;na=n;v=H[c+16>>2];d:{if(!v){e=1;break d}h=H[c>>2];f=h>>31;q=f;e:{if((f|0)<0){break e}i=H[c+4>>2];f=i>>31;m=f;if((f|0)<0){break e}g=H[c+8>>2];f=g>>31;s=f;if((f|0)<0){break e}j=H[c+12>>2];r=j>>31;if((r|0)<0){break e}f=H[c+24>>2];y=h-1|0;w=q-!h|0;z=i-1|0;x=m-!i|0;A=g-1|0;B=s-!g|0;C=j-1|0;D=r-!j|0;while(1){e=H[f>>2];h=e+y|0;i=e>>>0>h>>>0?w+1|0:w;j=fe(h,i,e,0);H[f+16>>2]=j;i=H[f+4>>2];h=i+z|0;g=i>>>0>h>>>0?x+1|0:x;r=fe(h,g,i,0);H[f+20>>2]=r;h=H[f+40>>2];g=h&31;if((h&63)>>>0>=32){q=1<>>32-g}s=m-1|0;g=q-!m|0;u=g;t=e>>31;l=t+B|0;o=l+1|0;g=l;l=e+A|0;g=ee(l,l>>>0>>0?o:g,e,t);e=u+(g>>31)|0;o=e+1|0;l=e;e=g+s|0;t=g>>>0>e>>>0?o:l;g=h&31;l=(j>>31)+u|0;E=l+1|0;o=l;l=j+s|0;j=j>>>0>l>>>0?E:o;if((h&63)>>>0>=32){g=t>>g}else{g=((1<>>g}e=h&31;if((h&63)>>>0>=32){e=j>>e}else{e=((1<>>e}e=g-e|0;if((e|0)<0){H[n+4>>2]=e;H[n>>2]=p;Ba(d,1,13510,n);e=0;break d}H[f+8>>2]=e;e=i>>31;g=e+D|0;o=g+1|0;l=g;g=i+C|0;j=g>>>0>>0?o:l;i=ee(g,j,i,e);e=(i>>31)+u|0;g=e+1|0;l=e;e=i+s|0;g=i>>>0>e>>>0?g:l;i=h&31;j=q+(r>>31)|0;o=j+1|0;l=j;j=m+r|0;q=j>>>0>>0?o:l;m=j-1|0;if((h&63)>>>0>=32){l=g>>i}else{l=((1<>>i}i=q-!j|0;e=h&31;if((h&63)>>>0>=32){m=i>>e}else{m=((1<>>e}e=l-m|0;if((e|0)<0){H[n+20>>2]=e;H[n+16>>2]=p;Ba(d,1,13579,n+16|0);e=0;break d}H[f+12>>2]=e;f=f+52|0;e=1;p=p+1|0;if((v|0)!=(p|0)){continue}break}break d}Ba(d,1,6720,0)}na=n+32|0;if(e){break b}return 0}e=H[a+100>>2];if(!e){e=Db();H[a+100>>2]=e;if(!e){break a}}wc(c,e);if(!Va(H[a+216>>2],22,d)){break a}h=H[a+216>>2];f=H[h>>2];e=H[h+8>>2];f:{if(f){k=1;g:{if((f|0)!=1){n=f&1;i=f&-2;f=0;while(1){m=0;h:{if(!k){break h}m=0;if(!(ra[H[e>>2]](a,b,d)|0)){break h}m=(ra[H[e+4>>2]](a,b,d)|0)!=0}k=m;e=e+8|0;f=f+2|0;if((i|0)!=(f|0)){continue}break}if(!n){break g}}if(!k){k=0;break g}k=(ra[H[e>>2]](a,b,d)|0)!=0}Oa(h);if(k){break f}Ta(H[a+96>>2]);H[a+96>>2]=0;return 0}Oa(h)}f=0;b=0;d=0;i:{j:{k:{e=H[a+60>>2];if(!e){if(H[c+16>>2]){break k}k=1;break i}h=Fa(N(e,52));if(!h){break j}e=0;if(H[c+16>>2]){b=H[c+24>>2];while(1){e=N(d,52);Ca(H[(e+b|0)+44>>2]);b=H[c+24>>2];H[(e+b|0)+44>>2]=0;d=d+1|0;e=H[c+16>>2];if(d>>>0>>0){continue}break}}if(H[a+60>>2]){d=H[H[a+100>>2]+24>>2];e=0;while(1){b=h+N(e,52)|0;f=N(H[H[a+64>>2]+(e<<2)>>2],52);d=f+d|0;H[b+48>>2]=H[d+48>>2];k=H[d+44>>2];H[b+40>>2]=H[d+40>>2];H[b+44>>2]=k;k=H[d+36>>2];H[b+32>>2]=H[d+32>>2];H[b+36>>2]=k;k=H[d+28>>2];H[b+24>>2]=H[d+24>>2];H[b+28>>2]=k;k=H[d+20>>2];H[b+16>>2]=H[d+16>>2];H[b+20>>2]=k;k=H[d+12>>2];H[b+8>>2]=H[d+8>>2];H[b+12>>2]=k;k=H[d+4>>2];H[b>>2]=H[d>>2];H[b+4>>2]=k;d=H[H[a+100>>2]+24>>2];f=f+d|0;H[b+36>>2]=H[f+36>>2];H[b+44>>2]=H[f+44>>2];H[f+44>>2]=0;e=e+1|0;f=H[a+60>>2];if(e>>>0>>0){continue}break}e=H[c+16>>2]}if(e){b=H[H[a+100>>2]+24>>2];d=0;while(1){e=N(d,52);Ca(H[(e+b|0)+44>>2]);b=H[H[a+100>>2]+24>>2];H[(e+b|0)+44>>2]=0;d=d+1|0;if(d>>>0>2]){continue}break}f=H[a+60>>2]}H[c+16>>2]=f;Ca(H[c+24>>2]);H[c+24>>2]=h;k=1;break i}e=H[c+24>>2];d=H[H[a+100>>2]+24>>2];while(1){f=N(b,52);e=f+e|0;H[e+36>>2]=H[(d+f|0)+36>>2];Ca(H[e+44>>2]);e=H[c+24>>2];m=f+e|0;d=H[H[a+100>>2]+24>>2];f=f+d|0;H[m+44>>2]=H[f+44>>2];H[f+44>>2]=0;b=b+1|0;if(b>>>0>2]){continue}break}k=1;break i}Ta(H[a+96>>2]);H[a+96>>2]=0;k=0}}return k|0}function yb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;j=(c<<2)+b|0;e=H[a>>2];f=H[a+12>>2]<<5;i=e+f|0;l=e-f|0;e=H[a+16>>2];k=H[a+28>>2];h=H[a+20>>2];m=H[a+8>>2];a:{b:{if(i&15|(b&15|d>>>0<8)){if(e>>>0>=h>>>0){break a}c:{switch(d-1|0){case 1:f=e+1|0;if(h-e&1){j=i+(e<<6)|0;e=(e<<2)+b|0;L[j>>2]=L[e>>2];L[j+4>>2]=L[e+(c<<2)>>2];e=f}if((f|0)==(h|0)){break a}while(1){f=i+(e<<6)|0;j=(e<<2)+b|0;L[f>>2]=L[j>>2];g=f;f=c<<2;L[g+4>>2]=L[f+j>>2];j=e+1|0;g=i+(j<<6)|0;j=(j<<2)+b|0;L[g>>2]=L[j>>2];L[g+4>>2]=L[f+j>>2];e=e+2|0;if((h|0)!=(e|0)){continue}break};break a;case 0:break c;default:break b}}f=e;g=h-e&3;if(g){j=0;while(1){L[i+(f<<6)>>2]=L[(f<<2)+b>>2];f=f+1|0;j=j+1|0;if((g|0)!=(j|0)){continue}break}}if(e-h>>>0>4294967292){break a}while(1){L[i+(f<<6)>>2]=L[(f<<2)+b>>2];e=f+1|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];e=f+2|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];e=f+3|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];f=f+4|0;if((h|0)!=(f|0)){continue}break}break a}if(e>>>0>=h>>>0){break a}while(1){f=i+(e<<6)|0;L[f>>2]=L[(e<<2)+b>>2];g=c+e|0;L[f+4>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+8>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+12>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+16>>2]=L[(g<<2)+b>>2];g=c+g<<2;L[f+20>>2]=L[g+b>>2];g=g+j|0;L[f+24>>2]=L[g>>2];L[f+28>>2]=L[g+(c<<2)>>2];e=e+1|0;if((h|0)!=(e|0)){continue}break}break a}while(1){f=i+(e<<6)|0;L[f>>2]=L[(e<<2)+b>>2];g=c+e|0;L[f+4>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+8>>2]=L[(g<<2)+b>>2];d:{if((d|0)==3){break d}g=c+g|0;L[f+12>>2]=L[(g<<2)+b>>2];if((d|0)==4){break d}g=c+g|0;L[f+16>>2]=L[(g<<2)+b>>2];if((d|0)==5){break d}g=c+g|0;L[f+20>>2]=L[(g<<2)+b>>2];if((d|0)==6){break d}g=j+(g<<2)|0;L[f+24>>2]=L[g>>2];if((d|0)==7){break d}L[f+28>>2]=L[g+(c<<2)>>2]}e=e+1|0;if((h|0)!=(e|0)){continue}break}}b=(m<<2)+b|0;f=b+(c<<2)|0;e=H[a+24>>2];i=l+32|0;e:{if(i&15|(b&15|d>>>0<8)){if(e>>>0>=k>>>0){break e}f:{switch(d-1|0){case 1:a=e+1|0;if(k-e&1){d=i+(e<<6)|0;e=b+(e<<2)|0;L[d>>2]=L[e>>2];L[d+4>>2]=L[e+(c<<2)>>2];e=a}if((a|0)==(k|0)){break e}while(1){a=i+(e<<6)|0;d=b+(e<<2)|0;L[a>>2]=L[d>>2];f=a;a=c<<2;L[f+4>>2]=L[a+d>>2];d=e+1|0;f=i+(d<<6)|0;d=b+(d<<2)|0;L[f>>2]=L[d>>2];L[f+4>>2]=L[a+d>>2];e=e+2|0;if((k|0)!=(e|0)){continue}break};break e;case 0:c=e;a=k-e&3;if(a){f=0;while(1){L[i+(c<<6)>>2]=L[b+(c<<2)>>2];c=c+1|0;f=f+1|0;if((a|0)!=(f|0)){continue}break}}if(e-k>>>0>4294967292){break e}while(1){L[i+(c<<6)>>2]=L[b+(c<<2)>>2];a=c+1|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];a=c+2|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];a=c+3|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];c=c+4|0;if((k|0)!=(c|0)){continue}break};break e;default:break f}}while(1){a=i+(e<<6)|0;L[a>>2]=L[b+(e<<2)>>2];h=c+e|0;L[a+4>>2]=L[b+(h<<2)>>2];h=c+h|0;L[a+8>>2]=L[b+(h<<2)>>2];g:{if((d|0)==3){break g}h=c+h|0;L[a+12>>2]=L[b+(h<<2)>>2];if((d|0)==4){break g}h=c+h|0;L[a+16>>2]=L[b+(h<<2)>>2];if((d|0)==5){break g}h=c+h|0;L[a+20>>2]=L[b+(h<<2)>>2];if((d|0)==6){break g}h=f+(h<<2)|0;L[a+24>>2]=L[h>>2];if((d|0)==7){break g}L[a+28>>2]=L[h+(c<<2)>>2]}e=e+1|0;if((k|0)!=(e|0)){continue}break}break e}if(e>>>0>=k>>>0){break e}while(1){a=i+(e<<6)|0;L[a>>2]=L[b+(e<<2)>>2];d=c+e|0;L[a+4>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+8>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+12>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+16>>2]=L[b+(d<<2)>>2];d=c+d<<2;L[a+20>>2]=L[d+b>>2];d=d+f|0;L[a+24>>2]=L[d>>2];L[a+28>>2]=L[d+(c<<2)>>2];e=e+1|0;if((k|0)!=(e|0)){continue}break}}}function Hd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=na-16|0;na=i;if(H[a+8>>2]==16){k=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{k=H[a+12>>2]}a:{if(!c){Ba(d,1,4259,0);break a}j=H[a+96>>2];e=1;Da(b,i,1);g=H[i>>2];if(g>>>0>=2){Ba(d,2,9792,0);break a}if((g+1|0)!=(c|0)){e=0;Ba(d,2,4259,0);break a}b:{if((g|0)!=1){break b}d=H[j+16>>2];if(!d){break b}j=d&7;c=H[k+5584>>2];if(d>>>0>=8){d=d&-8;e=0;while(1){H[c+8636>>2]=0;H[c+7556>>2]=0;H[c+6476>>2]=0;H[c+5396>>2]=0;H[c+4316>>2]=0;H[c+3236>>2]=0;H[c+2156>>2]=0;H[c+1076>>2]=0;c=c+8640|0;e=e+8|0;if((d|0)!=(e|0)){continue}break}if(!j){break b}}e=0;while(1){H[c+1076>>2]=0;c=c+1080|0;e=e+1|0;if((j|0)!=(e|0)){continue}break}}c=H[k+5608>>2];if(c){Ca(c);H[k+5608>>2]=0;g=H[i>>2]}if(!g){e=1;break a}while(1){b=b+1|0;Da(b,i+4|0,1);c:{if(!H[k+5632>>2]){break c}j=H[k+5628>>2];if(H[j>>2]!=H[i+4>>2]){break c}c=H[j+4>>2];d=H[a+96>>2];if((c|0)!=H[d+16>>2]){break c}g=H[j+8>>2];d:{if(!g){break d}e=0;h=N(c,c);if(H[g+16>>2]!=(N(h,H[(H[g>>2]<<2)+24896>>2])|0)){break a}c=Fa(h<<2);H[k+5608>>2]=c;if(!c){break a}e=H[g+12>>2];e:{switch(H[g>>2]-1|0){default:f=0;g=na-16|0;na=g;if(h){while(1){Da(e,g+12|0,2);L[c>>2]=K[g+12>>2];c=c+4|0;e=e+2|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}na=g+16|0;break d;case 0:f=0;g=na-16|0;na=g;if(h){while(1){Da(e,g+12|0,4);L[c>>2]=K[g+12>>2];c=c+4|0;e=e+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}na=g+16|0;break d;case 1:f=0;if(!h){break d}while(1){Fc(e,i+8|0);L[c>>2]=L[i+8>>2];c=c+4|0;e=e+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break d;case 2:break e}}f=0;if(!h){break d}while(1){Hb(e,i+8|0);L[c>>2]=M[i+8>>3];c=c+4|0;e=e+8|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}j=H[j+12>>2];if(!j){break c}e=0;h=H[d+16>>2];if(H[j+16>>2]!=(N(h,H[(H[j>>2]<<2)+24896>>2])|0)){break a}g=Fa(h<<2);if(!g){break a}c=H[j+12>>2];f:{g:{switch(H[j>>2]-1|0){default:f=0;e=g;if(!h){break f}while(1){Da(c,i+8|0,2);H[e>>2]=H[i+8>>2];e=e+4|0;c=c+2|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 0:f=0;e=g;if(!h){break f}while(1){Da(c,i+8|0,4);H[e>>2]=H[i+8>>2];e=e+4|0;c=c+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 1:f=0;e=g;if(!h){break f}while(1){Fc(c,i+8|0);H[e>>2]=~~L[i+8>>2];e=e+4|0;c=c+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 2:break g}}f=0;e=g;if(!h){break f}while(1){Hb(c,i+8|0);H[e>>2]=~~M[i+8>>3];e=e+4|0;c=c+8|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}d=H[d+16>>2];h:{if(!d){break h}h=d&7;c=H[k+5584>>2];i:{if(d>>>0<8){e=g;break i}j=d&-8;d=0;e=g;while(1){H[c+1076>>2]=H[e>>2];H[c+2156>>2]=H[e+4>>2];H[c+3236>>2]=H[e+8>>2];H[c+4316>>2]=H[e+12>>2];H[c+5396>>2]=H[e+16>>2];H[c+6476>>2]=H[e+20>>2];H[c+7556>>2]=H[e+24>>2];H[c+8636>>2]=H[e+28>>2];c=c+8640|0;e=e+32|0;d=d+8|0;if((j|0)!=(d|0)){continue}break}if(!h){break h}}d=0;while(1){H[c+1076>>2]=H[e>>2];c=c+1080|0;e=e+4|0;d=d+1|0;if((h|0)!=(d|0)){continue}break}}Ca(g)}e=1;l=l+1|0;if(l>>>0>2]){continue}break}}na=i+16|0;return e|0}function _d(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=na-128|0;na=e;H[e+120>>2]=0;a:{if((c|0)!=8){Ba(d,1,4047,0);Ba(d,1,4047,0);break a}Da(b,a+228|0,2);Da(b+2|0,e+124|0,4);Da(b+6|0,e+116|0,1);Da(b+7|0,e+120|0,1);c=H[a+228>>2];i=H[a+128>>2];if(c>>>0>=N(i,H[a+132>>2])>>>0){H[e+112>>2]=c;Ba(d,1,7843,e+112|0);break a}f=H[a+180>>2]+N(c,5644)|0;h=(c>>>0)/(i>>>0)|0;b=H[e+116>>2];b:{g=H[a+44>>2];if((g|0)>=0&(c|0)!=(g|0)){break b}g=H[f+5588>>2]+1|0;if((g|0)==(b|0)){break b}H[e+104>>2]=g;H[e+100>>2]=b;H[e+96>>2]=c;Ba(d,1,7867,e+96|0);f=0;break a}H[f+5588>>2]=b;c:{b=H[e+124>>2];if(b-1>>>0<=12){if((b|0)!=12){break c}H[e+64>>2]=12;Ba(d,2,11864,e- -64|0);b=H[e+124>>2]}if(!b){Ba(d,4,10695,0);H[a+56>>2]=1}d:{e:{f:{g:{b=H[f+5592>>2];if(b){g=H[e+116>>2];if(g>>>0>>0){break g}H[e+52>>2]=b;H[e+48>>2]=g;Ba(d,1,5150,e+48|0);H[a+56>>2]=1;f=0;break a}g=H[e+120>>2];if(g){break f}break d}g=H[e+120>>2];if(!g){break e}}b=(I[a+92|0]>>>4&1)+g|0;H[e+120>>2]=b;g=H[e+116>>2];j=H[f+5592>>2];if(g>>>0>j-1>>>0){H[e+20>>2]=j;H[e+16>>2]=g;Ba(d,1,5051,e+16|0);H[a+56>>2]=1;f=0;break a}if(b>>>0<=g>>>0){H[e+36>>2]=b;H[e+32>>2]=g;Ba(d,1,5250,e+32|0);H[a+56>>2]=1;f=0;break a}H[f+5592>>2]=b}if((H[e+116>>2]+1|0)!=(b|0)){break d}F[a+92|0]=I[a+92|0]|1}b=H[e+124>>2];H[a+8>>2]=16;H[a+24>>2]=H[a+56>>2]?0:b-12|0;f=H[a+44>>2];h:{if((f|0)==-1){f=4;b=c-N(h,i)|0;if(!(b>>>0>2]|b>>>0>=K[a+36>>2]|h>>>0>2])){f=h>>>0>=K[a+40>>2]?4:0}F[a+92|0]=I[a+92|0]&251|f;b=H[a+228>>2];break h}b=H[a+228>>2];F[a+92|0]=I[a+92|0]&251|((f|0)!=(b|0)?4:0)}c=H[H[a+224>>2]+40>>2]+N(b,40)|0;H[c>>2]=b;H[c+12>>2]=H[e+116>>2];f=H[e+120>>2];if(!H[a+76>>2]){if(K[c+4>>2]>=f>>>0){f=1;break a}H[e>>2]=b;Ba(d,2,1612,e);H[a+76>>2]=1;f=H[e+120>>2]}c=H[H[a+224>>2]+40>>2];b=H[a+228>>2];h=c+N(b,40)|0;if(f){H[h+4>>2]=f;b=H[e+120>>2];H[h+8>>2]=b;c=H[h+16>>2];if(!c){b=Ea(b,24);H[(H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0)+16>>2]=b;if(b){f=1;break a}f=0;Ba(d,1,6947,0);break a}b=Ga(c,N(b,24));c=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;if(!b){Ca(H[c+16>>2]);f=0;H[(H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0)+16>>2]=0;Ba(d,1,6947,0);break a}H[c+16>>2]=b;f=1;break a}i:{g=H[h+16>>2];if(g){break i}H[h+8>>2]=10;g=Ea(10,24);c=H[H[a+224>>2]+40>>2];b=H[a+228>>2];h=c+N(b,40)|0;H[h+16>>2]=g;if(g){break i}f=0;H[h+8>>2]=0;Ba(d,1,6947,0);break a}f=1;b=N(b,40)+c|0;c=H[e+116>>2];if(K[b+8>>2]>c>>>0){break a}h=b;b=c+1|0;H[h+8>>2]=b;b=Ga(g,N(b,24));c=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;if(!b){Ca(H[c+16>>2]);f=0;a=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;H[a+8>>2]=0;H[a+16>>2]=0;Ba(d,1,6947,0);break a}H[c+16>>2]=b;break a}H[e+80>>2]=b;Ba(d,1,12133,e+80|0);f=0}na=e+128|0;return f|0}function mb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;f=H[a+8>>2];e=f+H[a+4>>2]|0;a:{if(!H[a+12>>2]){if((e|0)<2|(d|0)<=0){break a}q=e&2147483644;m=e&3;r=e&1;s=f+1|0;g=H[a>>2];o=g+(e<<2)|0;t=e-4>>>1|0;a=e-1|0;u=g+(a<<2)|0;v=N(c,f)<<2;l=e>>>0<4;w=N(a>>>1|0,c)<<2;while(1){f=H[b+v>>2];e=H[b>>2]-(f+1>>1)|0;a=0;h=0;if(!l){while(1){j=a+1|0;x=H[(N(j,c)<<2)+b>>2];i=H[(N(a+s|0,c)<<2)+b>>2];p=g+(h<<2)|0;H[p>>2]=e;k=e;e=x-((f+i|0)+2>>2)|0;H[p+4>>2]=(k+e>>1)+f;h=h+2|0;k=(a|0)!=(t|0);f=i;a=j;if(k){continue}break}}H[g+(h<<2)>>2]=e;if(r){a=H[b+w>>2]-(f+1>>1)|0;H[u>>2]=a;e=a+e>>1;a=-8}else{a=-4}H[a+o>>2]=e+f;f=0;a=0;h=0;e=0;b:{if(!l){while(1){H[(N(a,c)<<2)+b>>2]=H[g+(a<<2)>>2];e=a|1;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|2;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|3;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];a=a+4|0;h=h+4|0;if((q|0)!=(h|0)){continue}break}e=a;if(!m){break b}}while(1){H[(N(c,e)<<2)+b>>2]=H[g+(e<<2)>>2];e=e+1|0;f=f+1|0;if((m|0)!=(f|0)){continue}break}}b=b+4|0;n=n+1|0;if((n|0)!=(d|0)){continue}break}break a}c:{switch(e-1|0){case 0:if((d|0)<=0){break a}a=d&3;if(d>>>0>=4){d=d&2147483644;c=0;while(1){H[b>>2]=H[b>>2]/2;H[b+4>>2]=H[b+4>>2]/2;H[b+8>>2]=H[b+8>>2]/2;H[b+12>>2]=H[b+12>>2]/2;b=b+16|0;c=c+4|0;if((d|0)!=(c|0)){continue}break}if(!a){break a}}c=0;while(1){H[b>>2]=H[b>>2]/2;b=b+4|0;c=c+1|0;if((a|0)!=(c|0)){continue}break};break a;case 1:if((d|0)<=0){break a}a=H[a>>2];e=0;f=N(c,f)<<2;while(1){i=b+f|0;j=H[b>>2]-(H[i>>2]+1>>1)|0;H[a+4>>2]=j;i=j+H[i>>2]|0;H[a>>2]=i;H[b>>2]=i;H[(c<<2)+b>>2]=H[a+4>>2];b=b+4|0;e=e+1|0;if((e|0)!=(d|0)){continue}break};break a;default:break c}}if((e|0)<3|(d|0)<=0){break a}q=e&2147483644;m=e&3;g=H[a>>2];r=(g+(e<<2)|0)-4|0;a=e-2|0;s=g+(a<<2)|0;o=e&1;i=!o;t=((e-i|0)-4>>>1|0)+1|0;u=N(c,f)<<2;v=a-i>>>0<2;w=N((e>>>1|0)-1|0,c)<<2;x=e-1>>>0<3;while(1){l=b+u|0;f=H[l+(c<<2)>>2];a=H[l>>2];e=H[b>>2]-((f+a|0)+2>>2)|0;H[g>>2]=e+a;a=1;h=1;if(!v){while(1){p=H[(N(a,c)<<2)+b>>2];j=a+1|0;i=H[l+(N(j,c)<<2)>>2];y=g+(h<<2)|0;H[y>>2]=e;k=e;e=p-((f+i|0)+2>>2)|0;H[y+4>>2]=(k+e>>1)+f;h=h+2|0;k=(a|0)!=(t|0);f=i;a=j;if(k){continue}break}}H[g+(h<<2)>>2]=e;d:{if(!o){a=H[b+w>>2]-(f+1>>1)|0;H[s>>2]=(e+a>>1)+f;break d}a=e+f|0}H[r>>2]=a;f=0;a=0;h=0;e=0;e:{if(!x){while(1){H[(N(a,c)<<2)+b>>2]=H[g+(a<<2)>>2];e=a|1;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|2;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|3;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];a=a+4|0;h=h+4|0;if((q|0)!=(h|0)){continue}break}e=a;if(!m){break e}}while(1){H[(N(c,e)<<2)+b>>2]=H[g+(e<<2)>>2];e=e+1|0;f=f+1|0;if((m|0)!=(f|0)){continue}break}}b=b+4|0;n=n+1|0;if((n|0)!=(d|0)){continue}break}}}function Ub(a,b,c,d,e,f,g){var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;a:{n=N(e,3);h=H[b>>2]>>>n|0;if(h&2097168){break a}h=h&495;if(!h){break a}o=a+28|0;l=o+(I[h+H[a+108>>2]|0]<<2)|0;H[a+104>>2]=l;k=H[l>>2];i=H[k>>2];h=H[a+4>>2]-i|0;H[a+4>>2]=h;j=H[a>>2];b:{if(j>>>16>>>0>>0){m=H[k+4>>2];H[a+4>>2]=i;h=h>>>0>>0;H[l>>2]=H[k+(h?8:12)>>2];k=h?m:!m;h=H[a+8>>2];while(1){c:{if(h){break c}h=H[a+16>>2];m=h+1|0;l=I[h+1|0];if(I[h|0]==255){if(l>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;h=8;break c}H[a+16>>2]=m;j=(l<<9)+j|0;h=7;break c}H[a+16>>2]=m;h=8;j=(l<<8)+j|0}h=h-1|0;H[a+8>>2]=h;j=j<<1;H[a>>2]=j;i=i<<1;H[a+4>>2]=i;if(i>>>0<32768){continue}break}h=i;break b}j=j-(i<<16)|0;H[a>>2]=j;if(!(h&32768)){m=H[k+4>>2];i=h>>>0>>0;H[l>>2]=H[k+(i?12:8)>>2];k=i?!m:m;i=H[a+8>>2];while(1){d:{if(i){break d}i=H[a+16>>2];m=i+1|0;l=I[i+1|0];if(I[i|0]==255){if(l>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;i=8;break d}H[a+16>>2]=m;j=(l<<9)+j|0;i=7;break d}H[a+16>>2]=m;i=8;j=(l<<8)+j|0}i=i-1|0;H[a+8>>2]=i;j=j<<1;H[a>>2]=j;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break b}k=H[k+4>>2]}e:{if(!k){break e}p=b-4|0;i=H[b>>2];k=H[b+4>>2]>>>n+17&4|(H[p>>2]>>>n+19&1|(i>>>n+16&64|i>>>n&170|i>>>(e?n+12|0:14)&16));m=o+(I[k+24384|0]<<2)|0;H[a+104>>2]=m;l=H[m>>2];i=H[l>>2];h=h-i|0;H[a+4>>2]=h;o=I[k+24640|0];f:{if(j>>>16>>>0>>0){k=H[l+4>>2];H[a+4>>2]=i;h=h>>>0>>0;H[m>>2]=H[l+(h?8:12)>>2];l=h?k:!k;h=H[a+8>>2];while(1){g:{if(h){break g}h=H[a+16>>2];m=h+1|0;k=I[h+1|0];if(I[h|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;h=8;break g}H[a+16>>2]=m;j=(k<<9)+j|0;h=7;break g}H[a+16>>2]=m;h=8;j=(k<<8)+j|0}h=h-1|0;H[a+8>>2]=h;j=j<<1;H[a>>2]=j;i=i<<1;H[a+4>>2]=i;if(i>>>0<32768){continue}break}break f}k=j-(i<<16)|0;H[a>>2]=k;if(!(h&32768)){j=H[l+4>>2];i=h>>>0>>0;H[m>>2]=H[l+(i?12:8)>>2];l=i?!j:j;j=H[a+8>>2];while(1){h:{if(j){break h}j=H[a+16>>2];m=j+1|0;i=I[j+1|0];if(I[j|0]==255){if(i>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;k=k+65280|0;j=8;break h}H[a+16>>2]=m;k=(i<<9)+k|0;j=7;break h}H[a+16>>2]=m;j=8;k=(i<<8)+k|0}j=j-1|0;H[a+8>>2]=j;k=k<<1;H[a>>2]=k;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break f}l=H[l+4>>2]}H[c>>2]=(l|0)==(o|0)?d:0-d|0;H[p>>2]=H[p>>2]|32<>2]=H[b>>2]|(c<<19|16)<>2]=H[b+4>>2]|8<>2]=H[a+4>>2]|32768;H[a>>2]=H[a>>2]|c<<31|65536;a=a-4|0;H[a>>2]=H[a>>2]|131072}if((e|0)!=3){break e}a=(f<<2)+b|0;H[a+4>>2]=H[a+4>>2]|1;H[a>>2]=H[a>>2]|c<<18|2;a=a-4|0;H[a>>2]=H[a>>2]|4}H[b>>2]=H[b>>2]|2097152<>2]>>>m|0;if(g&2097168){break a}n=a+28|0;k=n+(I[H[a+108>>2]+(g&495)|0]<<2)|0;H[a+104>>2]=k;j=H[k>>2];h=H[j>>2];g=H[a+4>>2]-h|0;H[a+4>>2]=g;i=H[a>>2];b:{if(i>>>16>>>0>>0){l=H[j+4>>2];H[a+4>>2]=h;g=g>>>0>>0;H[k>>2]=H[j+(g?8:12)>>2];j=g?l:!l;g=H[a+8>>2];while(1){c:{if(g){break c}g=H[a+16>>2];l=g+1|0;k=I[g+1|0];if(I[g|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;g=8;break c}H[a+16>>2]=l;i=(k<<9)+i|0;g=7;break c}H[a+16>>2]=l;g=8;i=(k<<8)+i|0}g=g-1|0;H[a+8>>2]=g;i=i<<1;H[a>>2]=i;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}g=h;break b}i=i-(h<<16)|0;H[a>>2]=i;if(!(g&32768)){l=H[j+4>>2];h=g>>>0>>0;H[k>>2]=H[j+(h?12:8)>>2];j=h?!l:l;h=H[a+8>>2];while(1){d:{if(h){break d}h=H[a+16>>2];l=h+1|0;k=I[h+1|0];if(I[h|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;h=8;break d}H[a+16>>2]=l;i=(k<<9)+i|0;h=7;break d}H[a+16>>2]=l;h=8;i=(k<<8)+i|0}h=h-1|0;H[a+8>>2]=h;i=i<<1;H[a>>2]=i;g=g<<1;H[a+4>>2]=g;if(g>>>0<32768){continue}break}break b}j=H[j+4>>2]}if(!j){break a}j=n;n=b-4|0;h=H[b>>2];o=H[b+4>>2]>>>m+17&4|(H[n>>2]>>>m+19&1|(h>>>m+16&64|h>>>m&170|h>>>(e?m+12|0:14)&16));l=j+(I[o+24384|0]<<2)|0;H[a+104>>2]=l;k=H[l>>2];h=H[k>>2];g=g-h|0;H[a+4>>2]=g;e:{if(i>>>16>>>0>>0){j=H[k+4>>2];H[a+4>>2]=h;g=g>>>0>>0;H[l>>2]=H[k+(g?8:12)>>2];k=g?j:!j;g=H[a+8>>2];while(1){f:{if(g){break f}g=H[a+16>>2];l=g+1|0;j=I[g+1|0];if(I[g|0]==255){if(j>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;g=8;break f}H[a+16>>2]=l;i=(j<<9)+i|0;g=7;break f}H[a+16>>2]=l;g=8;i=(j<<8)+i|0}g=g-1|0;H[a+8>>2]=g;i=i<<1;H[a>>2]=i;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break e}j=i-(h<<16)|0;H[a>>2]=j;if(!(g&32768)){i=H[k+4>>2];h=g>>>0>>0;H[l>>2]=H[k+(h?12:8)>>2];k=h?!i:i;i=H[a+8>>2];while(1){g:{if(i){break g}i=H[a+16>>2];l=i+1|0;h=I[i+1|0];if(I[i|0]==255){if(h>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;i=8;break g}H[a+16>>2]=l;j=(h<<9)+j|0;i=7;break g}H[a+16>>2]=l;i=8;j=(h<<8)+j|0}i=i-1|0;H[a+8>>2]=i;j=j<<1;H[a>>2]=j;g=g<<1;H[a+4>>2]=g;if(g>>>0<32768){continue}break}break e}k=H[k+4>>2]}g=c;c=I[o+24640|0];H[g>>2]=(c|0)==(k|0)?d:0-d|0;H[n>>2]=H[n>>2]|32<>2]=H[b>>2]|(d<<19|16)<>2]=H[b+4>>2]|8<>2]<<2)+b|0;H[c+4>>2]=H[c+4>>2]|32768;H[c>>2]=H[c>>2]|d<<31|65536;c=c-4|0;H[c>>2]=H[c>>2]|131072}if((e|0)!=3){break a}a=(H[a+124>>2]<<2)+b|0;H[a+4>>2]=H[a+4>>2]|4;H[a+12>>2]=H[a+12>>2]|1;H[a+8>>2]=H[a+8>>2]|d<<18|2}}function Cd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;e=na-112|0;na=e;j=1024;a:{b:{h=Ea(1,1024);if(h){l=e+92|0;k=e+108|0;while(1){c:{d:{d=e+104|0;e:{if((Ja(b,d,8,c)|0)!=8){break e}Da(d,e+88|0,4);Da(k,l,4);f=8;f:{g:{h:{i:{switch(H[e+88>>2]){case 0:d=Pa(b);g=qa;if((g|0)<0){g=1}else{g=d>>>0<4294967288&(g|0)<=0}if(g){break h}Ba(c,1,8449,0);break e;case 1:break i;default:break f}}d=e+104|0;if((Ja(b,d,8,c)|0)!=8){break e}Da(d,e+100|0,4);if(!H[e+100>>2]){break g}Ba(c,1,8449,0);break e}H[e+88>>2]=d+8;break f}Da(k,e+88|0,4);f=16}d=H[e+92>>2];if((d|0)==1785737827){b=H[a+100>>2];if(b&4){H[a+100>>2]=b|8;break e}Ba(c,1,5702,0);Ca(h);a=0;break a}i=H[e+88>>2];if(!i){Ba(c,1,3268,0);Ca(h);a=0;break a}if(f>>>0>i>>>0){H[e+4>>2]=d;H[e>>2]=i;Ba(c,1,13933,e);break b}j:{k:{l:{m:{n:{o:{p:{q:{r:{s:{if((d|0)<=1668246641){if((d|0)==1651532643){break r}if((d|0)==1667523942){break p}if((d|0)!=1668112752){break s}g=25264;break n}if((d|0)<=1783635999){if((d|0)==1668246642){break o}g=25232;if((d|0)==1768449138){break n}if((d|0)!=1718909296){break s}g=25208;break l}if((d|0)==1885564018){break q}if((d|0)==1783636e3){break m}g=25216;if((d|0)==1785737832){break l}}d=H[a+100>>2];if(d&1){break j}Ba(c,1,2062,0);Ca(h);a=0;break a}g=25248;break n}g=25256;break n}g=25272;break n}g=25240}H[e+76>>2]=d&255;H[e+64>>2]=d>>>24;H[e+72>>2]=d>>>8&255;H[e+68>>2]=d>>>16&255;Ba(c,2,2011,e- -64|0);f=i-f|0;if(I[a+100|0]&4){break k}d=H[e+92>>2];H[e+48>>2]=d>>>24;H[e+60>>2]=d&255;H[e+52>>2]=d>>>16&255;H[e+56>>2]=d>>>8&255;Ba(c,2,6771,e+48|0);H[a+100>>2]=H[a+100>>2]|2147483647;d=qb(b,f,c);if(!qa&(d|0)==(f|0)){continue}Ba(c,1,3748,0);Ca(h);a=0;break a}g=25200}f=i-f|0}d=f;f=Pa(b);i=qa;if((i|0)<0){f=1}else{f=(i|0)<=0&d>>>0>f>>>0}if(f){f=H[e+88>>2];a=H[e+92>>2];m=e,n=Pa(b),H[m+40>>2]=n;H[e+36>>2]=d;H[e+32>>2]=a&255;H[e+20>>2]=a>>>24;H[e+16>>2]=f;H[e+28>>2]=a>>>8&255;H[e+24>>2]=a>>>16&255;Ba(c,1,15680,e+16|0);break b}if(d>>>0<=j>>>0){f=h;break c}j=d;f=Ga(h,d);if(f){break c}Ca(h);Ba(c,1,2193,0);a=0;break a}if(!(d&2)){Ba(c,1,2132,0);Ca(h);a=0;break a}H[a+100>>2]=d|2147483647;d=i-f|0;f=qb(b,d,c);if(!qa&(d|0)==(f|0)){continue}if(!(I[a+100|0]&8)){break d}Ba(c,2,3748,0)}Ca(h);a=1;break a}Ba(c,1,3748,0);Ca(h);a=0;break a}if((Ja(b,f,d,c)|0)!=(d|0)){Ba(c,1,3798,0);Ca(f);a=0;break a}h=f;if(ra[H[g+4>>2]](a,f,d,c)|0){continue}break}Ca(f);a=0;break a}Ba(c,1,4923,0);a=0;break a}Ca(h);a=0}na=e+112|0;return a|0}function Id(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=na-16|0;na=f;if(H[a+8>>2]==16){i=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{i=H[a+12>>2]}a:{if(c>>>0<=1){Ba(d,1,4721,0);a=0;break a}Da(b,f+12|0,2);if(H[f+12>>2]){Ba(d,2,5897,0);a=1;break a}if(c>>>0<=6){Ba(d,1,4721,0);a=0;break a}Da(b+2|0,f+8|0,1);e=H[i+5628>>2];h=H[i+5632>>2];b:{c:{if(!h){break c}j=H[f+8>>2];a=e;while(1){if((j|0)!=H[a>>2]){a=a+20|0;g=g+1|0;if((h|0)!=(g|0)){continue}break c}break}j=0;if((g|0)!=(h|0)){break b}}if(H[i+5636>>2]==(h|0)){a=h+10|0;H[i+5636>>2]=a;a=Ga(e,N(a,20));if(!a){Ca(H[i+5628>>2]);H[i+5636>>2]=0;H[i+5628>>2]=0;H[i+5632>>2]=0;Ba(d,1,4747,0);a=0;break a}H[i+5628>>2]=a;e=H[i+5632>>2];j=N(H[i+5636>>2]-e|0,20);if(j){y(a+N(e,20)|0,0,j)}h=H[i+5632>>2];e=H[i+5628>>2]}a=e+N(h,20)|0;j=1}H[a>>2]=H[f+8>>2];Da(b+3|0,f+12|0,2);if(H[f+12>>2]){Ba(d,2,5897,0);a=1;break a}Da(b+5|0,f+4|0,2);e=H[f+4>>2];if(e>>>0>=2){Ba(d,2,3130,0);a=1;break a}h=c-7|0;if(e){c=b+7|0;while(1){if(h>>>0<=2){Ba(d,1,4721,0);a=0;break a}Da(c,f+12|0,1);if(H[f+12>>2]!=1){Ba(d,2,5579,0);a=1;break a}Da(c+1|0,f,2);e=H[f>>2];b=e&32767;H[a+4>>2]=b;h=h-3|0;e=(e>>>15|0)+1|0;k=N(e,b)+2|0;if(h>>>0>>0){Ba(d,1,4721,0);a=0;break a}c=c+3|0;g=0;if(b){while(1){Da(c,f+12|0,e);if(H[f+12>>2]!=(g|0)){Ba(d,2,6259,0);a=1;break a}c=c+e|0;g=g+1|0;if(g>>>0>2]){continue}break}}Da(c,f,2);e=H[f>>2];b=e&32767;H[f>>2]=b;if((b|0)!=H[a+4>>2]){Ba(d,2,3306,0);a=1;break a}e=(e>>>15|0)+1|0;l=N(e,b)+3|0;k=h-k|0;if(l>>>0>k>>>0){Ba(d,1,4721,0);a=0;break a}c=c+2|0;g=0;if(b){while(1){Da(c,f+12|0,e);if(H[f+12>>2]!=(g|0)){Ba(d,2,6259,0);a=1;break a}c=c+e|0;g=g+1|0;if(g>>>0>2]){continue}break}}Da(c,f+12|0,3);e=H[f+12>>2];H[a+8>>2]=0;H[a+12>>2]=0;F[a+16|0]=!(e&65536)|I[a+16|0]&254;h=e&255;H[f+8>>2]=h;d:{if(!h){break d}m=H[i+5620>>2];if(m){g=H[i+5616>>2];b=0;while(1){if((h|0)==H[g+8>>2]){H[a+8>>2]=g;break d}g=g+20|0;b=b+1|0;if((m|0)!=(b|0)){continue}break}}Ba(d,1,4721,0);a=0;break a}e=e>>>8&255;H[f+8>>2]=e;e:{if(!e){break e}h=H[i+5620>>2];if(h){g=H[i+5616>>2];b=0;while(1){if((e|0)==H[g+8>>2]){H[a+12>>2]=g;break e}g=g+20|0;b=b+1|0;if((h|0)!=(b|0)){continue}break}}Ba(d,1,4721,0);a=0;break a}h=k-l|0;c=c+3|0;n=n+1|0;if(n>>>0>2]){continue}break}}if(h){Ba(d,1,4721,0);a=0;break a}a=1;if(!j){break a}H[i+5632>>2]=H[i+5632>>2]+1;a=1}na=f+16|0;return a|0}function Tc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;if(K[a+44>>2]>=8){i=H[a+40>>2];l=8;while(1){k=H[a+12>>2]<<5;e=H[a>>2];g=H[a+36>>2];b=H[a+16>>2];h=H[a+20>>2];a:{if(b>>>0>=h>>>0){break a}j=e+k|0;d=b+1|0;if(h-b&1){c=j+(b<<6)|0;b=(N(b,g)<<2)+i|0;f=H[b+28>>2];H[c+24>>2]=H[b+24>>2];H[c+28>>2]=f;f=H[b+20>>2];H[c+16>>2]=H[b+16>>2];H[c+20>>2]=f;f=H[b+12>>2];H[c+8>>2]=H[b+8>>2];H[c+12>>2]=f;f=H[b+4>>2];H[c>>2]=H[b>>2];H[c+4>>2]=f;b=d}if((d|0)==(h|0)){break a}while(1){d=(N(b,g)<<2)+i|0;f=H[d+28>>2];c=j+(b<<6)|0;H[c+24>>2]=H[d+24>>2];H[c+28>>2]=f;f=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=f;f=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=f;f=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=f;d=b+1|0;c=j+(d<<6)|0;d=(N(d,g)<<2)+i|0;f=H[d+28>>2];H[c+24>>2]=H[d+24>>2];H[c+28>>2]=f;f=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=f;f=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=f;f=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=f;b=b+2|0;if((h|0)!=(b|0)){continue}break}}b=H[a+24>>2];h=H[a+28>>2];b:{if(b>>>0>=h>>>0){break b}j=(e-k|0)+32|0;k=(N(g,H[a+8>>2])<<2)+i|0;d=b+1|0;if(h-b&1){c=j+(b<<6)|0;b=k+(N(b,g)<<2)|0;e=H[b+28>>2];H[c+24>>2]=H[b+24>>2];H[c+28>>2]=e;e=H[b+20>>2];H[c+16>>2]=H[b+16>>2];H[c+20>>2]=e;e=H[b+12>>2];H[c+8>>2]=H[b+8>>2];H[c+12>>2]=e;e=H[b+4>>2];H[c>>2]=H[b>>2];H[c+4>>2]=e;b=d}if((d|0)==(h|0)){break b}while(1){d=k+(N(b,g)<<2)|0;e=H[d+28>>2];c=j+(b<<6)|0;H[c+24>>2]=H[d+24>>2];H[c+28>>2]=e;e=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=e;e=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=e;e=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=e;d=b+1|0;c=j+(d<<6)|0;d=k+(N(d,g)<<2)|0;e=H[d+28>>2];H[c+24>>2]=H[d+24>>2];H[c+28>>2]=e;e=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=e;e=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=e;e=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=e;b=b+2|0;if((h|0)!=(b|0)){continue}break}}Sa(a);b=0;if(H[a+32>>2]){while(1){d=H[a>>2]+(b<<5)|0;c=H[d+28>>2];g=(N(H[a+36>>2],b)<<2)+i|0;H[g+24>>2]=H[d+24>>2];H[g+28>>2]=c;c=H[d+20>>2];H[g+16>>2]=H[d+16>>2];H[g+20>>2]=c;c=H[d+12>>2];H[g+8>>2]=H[d+8>>2];H[g+12>>2]=c;c=H[d+4>>2];H[g>>2]=H[d>>2];H[g+4>>2]=c;b=b+1|0;if(b>>>0>2]){continue}break}}i=i+32|0;l=l+8|0;if(l>>>0<=K[a+44>>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function Zc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;d=b;b=H[b>>2]+7&-8;H[d>>2]=b+16;o=a;f=H[b>>2];a=H[b+4>>2];d=H[b+8>>2];c=H[b+12>>2];p=c;h=na-32|0;na=h;b=c&65535;e=c;c=d;d=e>>>16&32767;g=d;a:{if(d-15361>>>0<=2045){b=b<<4|c>>>28;d=c<<4|a>>>28;e=g-15360|0;a=a&268435455;b:{if((a|0)==134217728&(f|0)!=0|a>>>0>134217728){c=b;d=d+1|0;b=d?c:c+1|0;break b}if(f|(a|0)!=134217728){break b}a=d;d=d+(d&1)|0;b=a>>>0>d>>>0?b+1|0:b}c=b>>>0>1048575;f=c?0:d;a=c?0:b;b=0;c=c+e|0;b=c>>>0>>0?1:b;break a}if(!(!(c|f|(a|b))|(d|0)!=32767)){e=b<<4|c>>>28;f=c<<4|a>>>28;a=e|524288;c=2047;b=0;break a}if(g>>>0>17406){f=0;a=0;c=2047;b=0;break a}e=!(d|k);l=e?15360:15361;k=l-g|0;if((k|0)>112){f=0;a=0;c=0;b=0;break a}d=c;b=e?b:b|65536;if((g|0)!=(l|0)){m=f;c=a;i=d;e=b;l=128-k|0;c:{if(l&64){g=f;e=l+-64|0;c=e&31;if((e&63)>>>0>=32){e=f<>>32-c|a<>>0>=32){g=i<>>32-j|e<>>0>=32){e=0;i=c>>>i|0}else{e=c>>>i|0;i=((1<>>i}i=n|i;e=e|g;n=m;j=l&31;if((l&63)>>>0>=32){g=m<>>32-j|c<>2]=m;H[h+20>>2]=c;H[h+24>>2]=i;H[h+28>>2]=e;m=(H[h+16>>2]|H[h+24>>2]|(H[h+20>>2]|H[h+28>>2]))!=0}d:{if(k&64){c=d;f=k+-64|0;a=f&31;if((f&63)>>>0>=32){e=0;f=b>>>a|0}else{e=b>>>a|0;f=((1<>>a}a=e;d=0;b=0;break d}if(!k){break d}g=d;c=64-k|0;e=c&31;if((c&63)>>>0>=32){c=d<>>32-e|b<>>0>=32){g=0;a=a>>>f|0}else{g=a>>>f|0;a=((1<>>f}f=i|a;a=c|g;e=d;d=k&31;if((k&63)>>>0>=32){c=0;d=b>>>d|0}else{c=b>>>d|0;d=((1<>>d}b=c}H[h>>2]=f;H[h+4>>2]=a;H[h+8>>2]=d;H[h+12>>2]=b;a=H[h+8>>2];d=H[h+4>>2];f=a<<4|d>>>28;a=H[h+12>>2]<<4|a>>>28;c=d&268435455;b=H[h>>2]|m;e:{if((c|0)==134217728&(b|0)!=0|c>>>0>134217728){f=f+1|0;a=f?a:a+1|0;break e}if(b|(c|0)!=134217728){break e}b=a;a=f;f=f+(f&1)|0;a=a>>>0>f>>>0?b+1|0:b}c=a>>>0>1048575;a=c?a^1048576:a;b=0}na=h+32|0;u(0,f|0);u(1,a|(p&-2147483648|c<<20));q=o,r=+w(),M[q>>3]=r}function Ac(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;q=H[a+24>>2];if(!H[q+16>>2]){return 1}r=H[q+24>>2];o=H[H[H[a+20>>2]>>2]+20>>2];while(1){d=H[r+36>>2];H[b+36>>2]=d;f=H[o+28>>2];c=f+N(d,152)|0;t=H[a+64>>2];a:{if(t){d=f+N(H[o+24>>2],152)|0;p=H[d-144>>2]-H[d-152>>2]|0;f=c+12|0;e=c+4|0;d=H[c+8>>2];g=H[c>>2];c=36;break a}f=c+148|0;e=c+140|0;d=H[c+144>>2];g=H[c+136>>2];p=d-g|0;c=52}u=H[c+o>>2];b:{c:{if(!u){break c}k=H[e>>2];l=H[f>>2];f=d-g|0;e=H[b+40>>2];c=e&31;if((e&63)>>>0>=32){c=-1<>>32-c}m=h^-1;j=H[b+20>>2];h=m+j|0;i=c^-1;c=i;j=h>>>0>>0?c+1|0:c;c=e&31;if((e&63)>>>0>=32){h=j>>>c|0}else{h=((1<>>c}c=H[b+16>>2];j=c+m|0;m=c>>>0>j>>>0?i+1|0:i;i=e&31;c=H[b+8>>2];if((e&63)>>>0>=32){i=m>>>i|0}else{i=((1<>>i}e=c+i|0;d:{if(g>>>0>i>>>0){m=g-i|0;i=0;if(e>>>0>=d>>>0){d=f;j=0;break d}d=e-g|0;j=f-d|0;break d}i=i-g|0;if(e>>>0>=d>>>0){d=f-i|0;j=0;m=0;break d}j=d-e|0;m=0;d=c}f=l-k|0;e=H[b+12>>2];g=e+h|0;e:{if(h>>>0>>0){s=k-h|0;h=0;n=0;if(g>>>0>=l>>>0){break e}n=f;f=g-k|0;n=n-f|0;break e}h=h-k|0;if(g>>>0>=l>>>0){f=f-h|0;s=0;n=0;break e}s=0;f=e;n=l-g|0}k=n;g=0;if((h|i|(j|k)|(d|f))<0){break b}k=N(h,p)+i|0;l=H[b+44>>2];h=N(c,s)+m|0;f:{g:{if(!(k|l|(h|(c|0)!=(p|0))|(c|0)!=(d|0))){if((e|0)!=(f|0)){break g}d=(t?36:52)+o|0;H[b+44>>2]=H[d>>2];H[d>>2]=0;break c}if(l){break f}}be(e,0,c);if(qa|!e){break b}c=N(c,e);if(c>>>0>1073741823){break b}c=Ia(c<<2);H[b+44>>2]=c;if(!c){break b}e=H[b+8>>2];g=H[b+12>>2];if((e|0)==(d|0)&(g|0)==(f|0)){break f}e=N(e,g)<<2;if(!e){break f}y(c,0,e)}if(!f){break c}d=d<<2;g=H[b+44>>2]+(h<<2)|0;c=(k<<2)+u|0;if((f|0)!=1){k=f&1;l=f&2147483646;f=0;while(1){h=!d;if(!h){B(g,c,d)}i=p<<2;c=i+c|0;e=(H[b+8>>2]<<2)+g|0;if(!h){B(e,c,d)}c=c+i|0;g=e+(H[b+8>>2]<<2)|0;f=f+2|0;if((l|0)!=(f|0)){continue}break}if(!k){break c}}if(!d){break c}B(g,c,d)}o=o+76|0;r=r+52|0;b=b+52|0;g=1;v=v+1|0;if(v>>>0>2]){continue}}break}return g}function vb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0;if(a){a:{if(H[a>>2]){b=H[a+12>>2];if(b){ib(b);Ca(H[a+12>>2]);H[a+12>>2]=0}b=H[a+16>>2];if(b){Ca(b);H[a+16>>2]=0;H[a+20>>2]=0}Ca(H[a+64>>2]);H[a+60>>2]=0;H[a+64>>2]=0;Ca(H[a+72>>2]);H[a+72>>2]=0;Ca(H[a+88>>2]);H[a+88>>2]=0;break a}b=H[a+44>>2];if(b){Ca(b);H[a+44>>2]=0}b=H[a+32>>2];if(b){Ca(b);H[a+32>>2]=0;H[a+36>>2]=0}b=H[a+52>>2];if(!b){break a}Ca(b);H[a+52>>2]=0;H[a+56>>2]=0}Qb(H[a+232>>2]);b=H[a+180>>2];if(b){e=N(H[a+128>>2],H[a+132>>2]);if(e){while(1){ib(b);b=b+5644|0;c=c+1|0;if((e|0)!=(c|0)){continue}break}b=H[a+180>>2]}Ca(b);H[a+180>>2]=0}b=H[a+140>>2];if(b){c=H[a+136>>2];if(c){b=0;while(1){e=H[H[a+140>>2]+(b<<3)>>2];if(e){Ca(e);c=H[a+136>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+140>>2]}H[a+136>>2]=0;Ca(b);H[a+140>>2]=0}Ca(H[a+160>>2]);H[a+144>>2]=0;H[a+160>>2]=0;Ca(H[a+124>>2]);H[a+124>>2]=0;if(!(I[a+212|0]&2)){Ca(H[a+192>>2])}y(a+104|0,0,112);ob(H[a+216>>2]);H[a+216>>2]=0;ob(H[a+220>>2]);H[a+216>>2]=0;d=H[a+224>>2];if(d){b=H[d+28>>2];if(b){Ca(b);H[d+28>>2]=0}c=H[d+40>>2];if(c){if(H[d+36>>2]){while(1){e=N(g,40);b=H[(e+c|0)+36>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+36>>2]=0}b=H[(c+e|0)+16>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+16>>2]=0}b=H[(c+e|0)+24>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+24>>2]=0}g=g+1|0;if(g>>>0>2]){continue}break}}Ca(c);H[d+40>>2]=0}Ca(d)}H[a+224>>2]=0;Ta(H[a+96>>2]);H[a+96>>2]=0;Ta(H[a+100>>2]);H[a+100>>2]=0;f=H[a+236>>2];if(f){b:{if(!H[f+8>>2]){break b}if(H[f+12>>2]){H[f+40>>2]=0;while(1){if(H[f+24>>2]>0){continue}break}}H[f+16>>2]=1;Ca(H[f>>2]);c=H[f+28>>2];if(!c){break b}while(1){b=H[c+4>>2];Ca(c);H[f+28>>2]=b;c=b;if(b){continue}break}}d=H[f+36>>2];if(d){g=H[d+4>>2];if((g|0)>0){b=0;while(1){e=H[d>>2]+N(b,12)|0;c=H[e+8>>2];if(c){ra[c|0](H[e+4>>2]);g=H[d+4>>2]}b=b+1|0;if((g|0)>(b|0)){continue}break}}Ca(H[d>>2]);Ca(d)}Ca(f)}H[a+236>>2]=0;Ca(a)}}function bc(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=na-256|0;na=k;a:{if(!a){a=0;break a}if(!(H[a>>2]==(b|0)&H[a+4>>2]==(c|0))){H[a+4>>2]=c;H[a>>2]=b;H[k>>2]=c;H[k+128>>2]=b;e=c;g=b;while(1){o=h;h=h+1|0;j=h<<2;l=(e+1|0)/2|0;H[j+k>>2]=l;n=j+(k+128|0)|0;j=(g+1|0)/2|0;H[n>>2]=j;i=N(e,g);f=i+f|0;e=l;g=j;if(i>>>0>1){continue}break}H[a+8>>2]=f;b:{c:{d:{if(!f){b=H[a+12>>2];if(!b){break d}Ca(b);H[a+12>>2]=0;break d}f=f<<4;if(f>>>0<=K[a+16>>2]){break b}b=Ga(H[a+12>>2],f);if(b){break c}Ba(d,1,6451,0);b=H[a+12>>2];if(!b){break d}Ca(b);H[a+12>>2]=0}Ca(a);a=0;break a}H[a+12>>2]=b;c=H[a+16>>2];d=f-c|0;if(d){y(b+c|0,0,d)}H[a+16>>2]=f;c=H[a+4>>2];b=H[a>>2]}g=H[a+12>>2];if(o){l=0;f=(N(b,c)<<4)+g|0;e=f;while(1){b=l<<2;h=H[b+k>>2];e:{if((h|0)<=0){break e}j=h-1|0;d=0;f:{g:{b=H[b+(k+128|0)>>2];if((b|0)<=0){i=h&3;if(h>>>0>=4){break g}h=0;break f}while(1){c=f;f=b;while(1){h:{H[g>>2]=e;if((f|0)==1){g=g+16|0;e=e+16|0;break h}H[g+16>>2]=e;e=e+16|0;g=g+32|0;i=(f|0)>2;f=f-2|0;if(i){continue}}break}i=((d|0)==(j|0)|d)&1;f=i?e:c+(b<<4)|0;e=i?e:c;d=d+1|0;if((h|0)!=(d|0)){continue}break}break e}m=h&2147483644;h=0;c=0;while(1){n=(h|0)==(j|0);h=h+4|0;f=n?e:f;e=f;c=c+4|0;if((m|0)!=(c|0)){continue}break}if(i){break f}break e}while(1){c=f;m=((h|0)==(j|0)|h)&1;f=m?e:(b<<4)+f|0;e=m?e:c;h=h+1|0;d=d+1|0;if((i|0)!=(d|0)){continue}break}}l=l+1|0;if((o|0)!=(l|0)){continue}break}}H[g>>2]=0}b=H[a+8>>2];if(!b){break a}c=b&3;e=H[a+12>>2];if(b>>>0>=4){b=b&-4;g=0;while(1){H[e+60>>2]=0;H[e+52>>2]=999;H[e+56>>2]=0;H[e+44>>2]=0;H[e+36>>2]=999;H[e+40>>2]=0;H[e+28>>2]=0;H[e+20>>2]=999;H[e+24>>2]=0;H[e+12>>2]=0;H[e+4>>2]=999;H[e+8>>2]=0;e=e- -64|0;g=g+4|0;if((b|0)!=(g|0)){continue}break}if(!c){break a}}g=0;while(1){H[e+12>>2]=0;H[e+4>>2]=999;H[e+8>>2]=0;e=e+16|0;g=g+1|0;if((c|0)!=(g|0)){continue}break}}na=k+256|0;return a}function fb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;i=a>>>0<=8?8:a;l=i+48|0;a:{b:{while(1){if(a-1&a|b>>>0>4294967239){break b}c=H[6867];d=c;g=H[6866];b=b>>>0<=8?8:b+3&-4;c:{if(b>>>0<=127){e=(b>>>3|0)-1|0;break c}f=Q(b);e=((b>>>29-f^4)-(f<<2)|0)+110|0;if(b>>>0<=4095){break c}f=((b>>>30-f^2)-(f<<1)|0)+71|0;e=f>>>0>=63?63:f}h=e;e=h&31;if((h&63)>>>0>=32){f=0;c=c>>>e|0}else{f=c>>>e|0;c=((1<>>e}if(c|f){while(1){e=f;d:{if(e|c){d=e-1|0;f=d+1|0;g=d;d=c-1|0;g=(d|0)!=-1?f:g;f=Q(e^g);f=(f|0)==32?Q(c^d)+32|0:f;d=63-f|0;qa=0-(f>>>0>63)|0;break d}qa=0;d=64}g=d;d=g&31;if((g&63)>>>0>=32){f=0;j=e>>>d|0}else{f=e>>>d|0;j=((1<>>d}h=g+h|0;c=h<<4;e=H[c+26440>>2];d=c+26432|0;e:{if((e|0)!=(d|0)){c=Cb(e,i,b);if(c){break a}c=H[e+4>>2];g=H[e+8>>2];H[c+8>>2]=g;H[g+4>>2]=c;H[e+8>>2]=d;H[e+4>>2]=H[d+4>>2];H[d+4>>2]=e;H[H[e+4>>2]+8>>2]=e;h=h+1|0;c=(f&1)<<31|j>>>1;f=f>>>1|0;break e}m=H[6867];g=h&63;c=g;e=c&31;if(c>>>0>=32){c=0;d=-1>>>e|0}else{c=-1>>>e|0;d=c|(1<>>0>=32){c=d<>>32-e|c<>>0>=32){c=-1<>>32-c}n=d&-2;d=g&31;if(g>>>0>=32){g=0;d=c>>>d|0}else{g=c>>>d|0;d=((1<>>d}c=d|k;qa=e|g;H[6866]=H[6866]&c;H[6867]=qa&m;c=j^1}if(c|f){continue}break}g=H[6866];d=H[6867]}f=Q(d);e=63-((f|0)==32?Q(g)+32|0:f)|0;f:{if(!(d|g)){h=0;break f}c=e<<4;h=H[c+26440>>2];if(!d&g>>>0<1073741824){break f}f=98;d=c+26432|0;if((d|0)==(h|0)){break f}while(1){c=Cb(h,i,b);if(c){break a}h=H[h+8>>2];if((d|0)==(h|0)){break f}c=f;f=c-1|0;if(c){continue}break}}f=a>>>0>8;a=i;if(sc((f?l:48)+b|0)){continue}break}if(!h){break b}a=(e<<4)+26432|0;if((a|0)==(h|0)){break b}while(1){c=Cb(h,i,b);if(c){break a}h=H[h+8>>2];if((a|0)!=(h|0)){continue}break}}c=0}return c}function wb(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;m=H[H[a+24>>2]+24>>2]+N(b,52)|0;l=H[m+4>>2];k=l-1|0;o=H[a+60>>2];j=k+o|0;p=0-!l|0;i=p;r=H[H[H[a+20>>2]>>2]+20>>2]+N(b,76)|0;n=H[r+12>>2];i=fe(j,j>>>0>>0?i+1|0:i,l,0);q=i>>>0>n>>>0?n:i;j=H[m>>2];m=j-1|0;s=H[a+56>>2];n=m+s|0;o=0-!j|0;i=o;t=H[r+8>>2];i=fe(n,n>>>0>>0?i+1|0:i,j,0);n=i>>>0>t>>>0?t:i;i=p;t=H[r+4>>2];s=H[a+52>>2];k=s+k|0;i=fe(k,k>>>0>>0?i+1|0:i,l,0);k=i>>>0>>0?t:i;i=o;p=H[r>>2];l=m;m=H[a+48>>2];l=l+m|0;i=fe(l,l>>>0>>0?i+1|0:i,j,0);i=i>>>0

      >>0?p:i;l=0;p=H[(H[H[a+32>>2]+5584>>2]+N(b,1080)|0)+20>>2];c=H[r+20>>2]+(c?0-c|0:-1)|0;a:{if(!c){a=n;l=i;b=k;break a}m=c-1|0;j=(d&1)<>>0>>0){a=c&31;l=i-j|0;if((c&63)>>>0>=32){i=-1<>>32-a;a=-1<>>0>>0?i+1|0:i;b=a;a=c&31;if((c&63)>>>0>=32){l=i>>>a|0}else{l=((1<>>a}}a=0;b=0;d=d>>>1<>>0>>0){b=c&31;o=k-d|0;if((c&63)>>>0>=32){i=-1<>>32-b;b=-1<>>0>>0?i+1|0:i;k=b;b=c&31;if((c&63)>>>0>=32){b=i>>>b|0}else{b=((1<>>b}}if(j>>>0>>0){a=c&31;k=n-j|0;if((c&63)>>>0>=32){i=-1<>>32-a;a=-1<>>0>>0?i+1|0:i;j=a;a=c&31;if((c&63)>>>0>=32){a=i>>>a|0}else{a=((1<>>a}}if(d>>>0>=q>>>0){q=0;break a}k=q-d|0;d=c&31;if((c&63)>>>0>=32){i=-1<>>32-d;d=-1<>>0>>0?i+1|0:i;j=d;d=c&31;if((c&63)>>>0>=32){q=i>>>d|0}else{q=((1<>>d}}c=(p|0)==1?2:3;d=c+a|0;d=(a>>>0>d>>>0?-1:d)>>>0>e>>>0;a=c+q|0;d=d&(a>>>0>>0?-1:a)>>>0>f>>>0;a=l-c|0;d=d&(a>>>0<=l>>>0?a:0)>>>0>>0;a=b-c|0;return d&(a>>>0<=b>>>0?a:0)>>>0>>0}function kb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;g=H[a+16>>2];if(g>>>0>=32){return H[a+8>>2]}b=H[a+20>>2];a:{if((b|0)>=4){d=H[a>>2];c=H[d-3>>2];e=b-4|0;H[a+20>>2]=e;H[a>>2]=d-4;break a}if((b|0)<=0){e=b;break a}f=H[a>>2];d=24;b:{if((b|0)==1){break b}l=b&1;k=b&2147483646;while(1){h=f-1|0;H[a>>2]=h;i=I[f|0];f=f-2|0;H[a>>2]=f;H[a+20>>2]=b-1;h=I[h|0];b=b-2|0;H[a+20>>2]=b;c=i<>2]=f-1;f=I[f|0];H[a+20>>2]=b-1;c=f<>2];k=c&255;H[a+24>>2]=k>>>0>143;b=b?(c&2130706432)==2130706432?7:8:8;h=b+(c>>>0<=2415919103?8:(c&8323072)==8323072?7:8)|0;f=c>>>16&255;i=h+(f>>>0<=143?8:(c&32512)==32512?7:8)|0;d=c>>>8&255;l=i+(g+(d>>>0<=143?8:(c&127)==127?7:8)|0)|0;H[a+16>>2]=l;j=H[a+12>>2];b=f<>>24|d<>>0>=32){d=b<>>32-c;b=b<>2];b=d|j;h=b;H[a+8>>2]=g;H[a+12>>2]=b;if(l>>>0<=31){c:{if((e|0)>=4){b=H[a>>2];c=H[b-3>>2];H[a+20>>2]=e-4;H[a>>2]=b-4;break c}if((e|0)<=0){c=0;break c}b=H[a>>2];d:{if((e|0)==1){d=24;c=0;break d}i=e&1;j=e&2147483646;d=24;c=0;f=0;while(1){m=b-1|0;H[a>>2]=m;n=I[b|0];b=b-2|0;H[a>>2]=b;H[a+20>>2]=e-1;m=I[m|0];e=e-2|0;H[a+20>>2]=e;c=n<>2]=b-1;b=I[b|0];H[a+20>>2]=e-1;c=b<>2]=e>>>0>143;k=k>>>0<=143?8:(c&2130706432)==2130706432?7:8;i=k+(c>>>0<=2415919103?8:(c&8323072)==8323072?7:8)|0;f=c>>>16&255;j=i+(f>>>0<=143?8:(c&32512)==32512?7:8)|0;d=c>>>8&255;H[a+16>>2]=j+(l+(d>>>0<=143?8:(c&127)==127?7:8)|0);b=a;a=f<>>24|d<>>0>=32){c=a<>>32-e;a=a<>2]=g;H[b+12>>2]=c|h}return g}function Lc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;j=H[a+96>>2];l=N(H[a+128>>2],H[a+132>>2]);a:{if(l){b=H[j+16>>2];m=N(b,1080);k=N(b,b)<<2;e=H[a+12>>2];b=H[a+180>>2];while(1){n=H[b+5584>>2];B(b,e,5644);H[b+5608>>2]=0;H[b+5588>>2]=-1;H[b+5168>>2]=0;H[b+5636>>2]=0;H[b+5616>>2]=0;H[b+5624>>2]=0;H[b+5628>>2]=0;H[b+5584>>2]=n;F[b+5640|0]=I[b+5640|0]&252;b:{if(!H[e+5608>>2]){break b}d=Fa(k);H[b+5608>>2]=d;if(!d){return 0}if(!k){break b}B(d,H[e+5608>>2],k)}d=N(H[e+5624>>2],20);f=Fa(d);H[b+5616>>2]=f;i=0;if(!f){break a}if(d){B(f,H[e+5616>>2],d)}g=H[e+5620>>2];if(g){d=H[e+5616>>2];f=H[b+5616>>2];h=0;while(1){if(H[d+12>>2]){g=Fa(H[d+16>>2]);H[f+12>>2]=g;if(!g){return 0}o=H[d+16>>2];if(o){B(g,H[d+12>>2],o)}g=H[e+5620>>2]}H[b+5624>>2]=H[b+5624>>2]+1;f=f+20|0;d=d+20|0;h=h+1|0;if(h>>>0>>0){continue}break}}d=N(H[e+5636>>2],20);f=Fa(d);H[b+5628>>2]=f;if(!f){break a}if(d){B(f,H[e+5628>>2],d)}i=H[e+5636>>2];H[b+5636>>2]=i;if(i){d=H[e+5628>>2];f=H[b+5628>>2];h=0;while(1){g=H[d+8>>2];if(g){H[f+8>>2]=H[b+5616>>2]+(g-H[e+5616>>2]|0)}g=H[d+12>>2];if(g){H[f+12>>2]=H[b+5616>>2]+(g-H[e+5616>>2]|0)}f=f+20|0;d=d+20|0;h=h+1|0;if((i|0)!=(h|0)){continue}break}}if(m){B(n,H[e+5584>>2],m)}b=b+5644|0;p=p+1|0;if((p|0)!=(l|0)){continue}break}}i=1;e=Ea(1,72);b=0;c:{if(!e){break c}F[e+40|0]=I[e+40|0]&254|1;d=Ea(1,4);H[e+20>>2]=d;b=e;if(d){break c}Ca(b);b=0}H[a+232>>2]=b;if(!b){return 0}f=H[a+236>>2];e=0;H[b+28>>2]=a+104;H[b+24>>2]=j;d=Ea(1,848);H[H[b+20>>2]>>2]=d;d:{if(!d){break d}d=Ea(H[j+16>>2],76);h=H[H[b+20>>2]>>2];H[h+20>>2]=d;if(!d){break d}H[h+16>>2]=H[j+16>>2];e=H[a+188>>2];H[b+44>>2]=f;H[b>>2]=e;e=1}if(e){break a}Qb(H[a+232>>2]);i=0;H[a+232>>2]=0;Ba(c,1,3668,0)}return i|0}function $d(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;f=na-80|0;na=f;H[f+76>>2]=1;j=H[a+44>>2];d=H[a+224>>2];e=H[d+40>>2];a:{b:{if(!e|!H[e+16>>2]){break b}c:{e=e+N(j,40)|0;if(!H[e+4>>2]){e=H[d+8>>2];d=H[d+12>>2];e=e+2|0;d=e>>>0<2?d+1|0:d;if(ab(b,e,d,c)){break c}d=0;Ba(c,1,5440,0);break a}d=H[e+16>>2];if(!ab(b,H[d>>2],H[d+4>>2],c)){Ba(c,1,5440,0);d=0;break a}if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);d=0;break a}Da(H[a+16>>2],f+72|0,2);if(H[f+72>>2]==65424){break c}Ba(c,1,4073,0);d=0;break a}if(H[a+8>>2]!=256){break b}H[a+8>>2]=8}e=N(H[a+132>>2],H[a+128>>2]);d:{if(!e){break d}i=e&7;h=H[a+180>>2];d=0;if(e>>>0>=8){k=e&-8;while(1){e=h+N(g,5644)|0;H[e+45096>>2]=-1;H[e+39452>>2]=-1;H[e+33808>>2]=-1;H[e+28164>>2]=-1;H[e+22520>>2]=-1;H[e+16876>>2]=-1;H[e+11232>>2]=-1;H[e+5588>>2]=-1;g=g+8|0;l=l+8|0;if((k|0)!=(l|0)){continue}break}if(!i){break d}}while(1){H[(h+N(g,5644)|0)+5588>>2]=-1;g=g+1|0;d=d+1|0;if((i|0)!=(d|0)){continue}break}}d=0;if(!bb(a,f+72|0,0,f+68|0,f- -64|0,f+60|0,f+56|0,f+52|0,f+76|0,b,c)){break a}i=j+1|0;while(1){e:{if(!H[f+76>>2]){break e}e=H[f+72>>2];if(!hb(a,e,0,0,b,c)){break a}h=H[a+128>>2];k=H[a+132>>2];g=e+1|0;H[f+32>>2]=g;H[f+36>>2]=N(h,k);Ba(c,4,11795,f+32|0);if(!Ac(H[a+232>>2],H[H[a+100>>2]+24>>2])){break a}d=H[a+180>>2]+N(e,5644)|0;h=H[d+5596>>2];if(h){Ca(h);H[d+5596>>2]=0;H[d+5600>>2]=0}H[f+16>>2]=g;Ba(c,4,16601,f+16|0);if((e|0)==(j|0)){d=H[a+224>>2];e=H[d+8>>2];d=H[d+12>>2];e=e+2|0;d=e>>>0<2?d+1|0:d;if(ab(b,e,d,c)){break e}d=0;Ba(c,1,5440,0);break a}H[f+4>>2]=i;H[f>>2]=g;Ba(c,2,13648,f);d=0;if(bb(a,f+72|0,0,f+68|0,f- -64|0,f+60|0,f+56|0,f+52|0,f+76|0,b,c)){continue}break a}break}d=zc(a,c)}na=f+80|0;return d|0}function Ma(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=H[a+16>>2];if(h>>>0>=32){return H[a+8>>2]}c=H[a+24>>2];a:{if((c|0)>=4){b=H[a>>2];d=H[b>>2];e=c-4|0;H[a+24>>2]=e;H[a>>2]=b+4;break a}d=H[a+28>>2]?-1:0;if((c|0)<=0){e=c;break a}b=H[a>>2];if((c|0)!=1){j=c&1;i=c&2147483646;while(1){e=b;H[a>>2]=b+1;k=I[b|0];b=b+2|0;H[a>>2]=b;H[a+24>>2]=c-1;e=I[e+1|0];c=c-2|0;H[a+24>>2]=c;d=((255<>2]=b+1;b=I[b|0];H[a+24>>2]=c-1;d=(255<>2];i=d>>>24|0;H[a+20>>2]=(i|0)==255;c=d>>>16&255;f=d>>>8&255;b=b?7:8;d=d&255;g=b+((d|0)==255?7:8)|0;k=((f|0)==255?7:8)+g|0;j=(h+((c|0)==255?7:8)|0)+k|0;H[a+16>>2]=j;l=H[a+12>>2];b=d|(f<>>0>=32){d=b<>>32-c;b=b<>2];b=d|l;k=b;H[a+8>>2]=h;H[a+12>>2]=b;if(j>>>0<=31){b:{if((e|0)>=4){b=H[a>>2];d=H[b>>2];H[a+24>>2]=e-4;H[a>>2]=b+4;break b}f=0;d=H[a+28>>2]?-1:0;if((e|0)<=0){break b}b=H[a>>2];if((e|0)!=1){l=e&1;m=e&2147483646;g=0;while(1){c=b;H[a>>2]=b+1;n=I[b|0];b=b+2|0;H[a>>2]=b;H[a+24>>2]=e-1;c=I[c+1|0];e=e-2|0;H[a+24>>2]=e;d=((255<>2]=b+1;b=I[b|0];H[a+24>>2]=e-1;d=(255<>>24|0;H[a+20>>2]=(e|0)==255;c=d>>>16&255;f=d>>>8&255;g=(i|0)==255?7:8;d=d&255;i=g+((d|0)==255?7:8)|0;l=((f|0)==255?7:8)+i|0;H[a+16>>2]=(((c|0)==255?7:8)+j|0)+l;b=a;a=d|(f<>>0>=32){c=a<>>32-e;a=a<>2]=h;H[b+12>>2]=c|k}return h}function Dc(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0;h=na-32|0;na=h;if(H[a+8>>2]==16){f=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{f=H[a+12>>2]}a:{if(K[d>>2]<=4){a=0;Ba(e,1,2607,0);break a}f=H[f+5584>>2]+N(b,1080)|0;Da(c,f+4|0,1);g=H[f+4>>2]+1|0;H[f+4>>2]=g;if(g>>>0>=34){H[h+4>>2]=33;H[h>>2]=g;Ba(e,1,7635,h);a=0;break a}j=H[a+184>>2];if(j>>>0>=g>>>0){H[h+24>>2]=g;H[h+20>>2]=j;H[h+16>>2]=b;Ba(e,1,16423,h+16|0);H[a+8>>2]=H[a+8>>2]|32768;a=0;break a}Da(c+1|0,f+8|0,1);H[f+8>>2]=H[f+8>>2]+2;Da(c+2|0,f+12|0,1);a=H[f+12>>2]+2|0;H[f+12>>2]=a;b=H[f+8>>2];if(!(!(b>>>0>10|a>>>0>10)&a+b>>>0<13)){a=0;Ba(e,1,5468,0);break a}Da(c+3|0,f+16|0,1);if(I[f+16|0]&128){a=0;Ba(e,1,6564,0);break a}Da(c+4|0,f+20|0,1);if(K[f+20>>2]>=2){a=0;Ba(e,1,6499,0);break a}b=H[d>>2]-5|0;H[d>>2]=b;a=1;g=H[f+4>>2];if(!(F[f|0]&1)){if(!g){break a}j=g&3;d=f+944|0;e=f+812|0;b=0;c=0;if(g>>>0>=4){k=g&-4;g=0;while(1){f=c<<2;H[f+e>>2]=15;H[d+f>>2]=15;i=f|4;H[i+e>>2]=15;H[d+i>>2]=15;i=f|8;H[i+e>>2]=15;H[d+i>>2]=15;f=f|12;H[f+e>>2]=15;H[d+f>>2]=15;c=c+4|0;g=g+4|0;if((k|0)!=(g|0)){continue}break}if(!j){break a}}while(1){a=c<<2;H[a+e>>2]=15;H[a+d>>2]=15;a=1;c=c+1|0;b=b+1|0;if((j|0)!=(b|0)){continue}break}break a}if(b>>>0>=g>>>0){b:{if(!g){g=0;break b}Da(c+5|0,h+28|0,1);a=H[h+28>>2];H[f+944>>2]=a>>>4;H[f+812>>2]=a&15;g=H[f+4>>2];if(g>>>0>=2){j=f+944|0;k=f+812|0;a=c+6|0;c=1;while(1){Da(a,h+28|0,1);c:{b=H[h+28>>2];if(b>>>0>=16){g=b&15;if(g){break c}}a=0;Ba(e,1,6025,0);break a}i=c<<2;H[i+k>>2]=g;H[j+i>>2]=b>>>4;a=a+1|0;c=c+1|0;g=H[f+4>>2];if(c>>>0>>0){continue}break}}b=H[d>>2]}H[d>>2]=b-g;a=1;break a}a=0;Ba(e,1,2607,0)}na=h+32|0;return a}function dc(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;j=na-256|0;na=j;g=Ea(1,20);a:{if(!g){Ba(c,1,6413,0);g=0;break a}H[g+4>>2]=b;H[g>>2]=a;H[j>>2]=b;H[j+128>>2]=a;while(1){p=h;h=h+1|0;e=h<<2;d=(b+1|0)/2|0;H[e+j>>2]=d;i=e+(j+128|0)|0;e=(a+1|0)/2|0;H[i>>2]=e;i=N(a,b);f=i+f|0;b=d;a=e;if(i>>>0>1){continue}break}H[g+8>>2]=f;if(!f){Ca(g);g=0;break a}d=Ea(f,16);H[g+12>>2]=d;if(!d){Ba(c,1,3564,0);Ca(g);g=0;break a}l=H[g+8>>2];H[g+16>>2]=l<<4;a=d;if(p){f=(N(H[g+4>>2],H[g>>2])<<4)+d|0;b=f;while(1){c=n<<2;e=H[c+j>>2];b:{if((e|0)<=0){break b}o=e-1|0;i=0;c:{d:{c=H[c+(j+128|0)>>2];if((c|0)<=0){k=e&3;if(e>>>0>=4){break d}h=0;break c}while(1){h=f;f=c;while(1){e:{H[a>>2]=b;if((f|0)==1){a=a+16|0;b=b+16|0;break e}H[a+16>>2]=b;b=b+16|0;a=a+32|0;k=(f|0)>2;f=f-2|0;if(k){continue}}break}k=((i|0)==(o|0)|i)&1;f=k?b:h+(c<<4)|0;b=k?b:h;i=i+1|0;if((e|0)!=(i|0)){continue}break}break b}m=e&2147483644;h=0;e=0;while(1){q=(h|0)==(o|0);h=h+4|0;f=q?b:f;b=f;e=e+4|0;if((m|0)!=(e|0)){continue}break}if(k){break c}break b}while(1){e=f;m=((h|0)==(o|0)|h)&1;f=m?b:e+(c<<4)|0;b=m?b:e;h=h+1|0;i=i+1|0;if((k|0)!=(i|0)){continue}break}}n=n+1|0;if((n|0)!=(p|0)){continue}break}}H[a>>2]=0;f:{if(!l){break f}a=l&3;if(l>>>0>=4){c=l&-4;b=0;while(1){H[d+60>>2]=0;H[d+52>>2]=999;H[d+56>>2]=0;H[d+44>>2]=0;H[d+36>>2]=999;H[d+40>>2]=0;H[d+28>>2]=0;H[d+20>>2]=999;H[d+24>>2]=0;H[d+12>>2]=0;H[d+4>>2]=999;H[d+8>>2]=0;d=d- -64|0;b=b+4|0;if((c|0)!=(b|0)){continue}break}if(!a){break f}}b=0;while(1){H[d+12>>2]=0;H[d+4>>2]=999;H[d+8>>2]=0;d=d+16|0;b=b+1|0;if((a|0)!=(b|0)){continue}break}}}na=j+256|0;return g}function Wb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;H[a+8>>2]=0;H[a+12>>2]=0;H[a>>2]=b;H[a+16>>2]=0;H[a+20>>2]=0;H[a+28>>2]=d;h=c-1|0;H[a+24>>2]=h;n=b&3;a:{if((c|0)<=0){e=b;b=d;break a}e=b+1|0;H[a>>2]=e;b=I[b|0]}g=b;i=8;H[a+16>>2]=8;j=(g|0)==255;H[a+20>>2]=j;H[a+8>>2]=g;H[a+12>>2]=0;b:{if((n|0)==3){break b}k=c-2|0;H[a+24>>2]=k;c:{if((c|0)<2){b=e;e=d;break c}b=e+1|0;H[a>>2]=b;e=I[e|0]}j=(e|0)==255;H[a+20>>2]=j;i=(g|0)==255?15:16;H[a+16>>2]=i;g=g|e<<8;H[a+8>>2]=g;H[a+12>>2]=0;if((n|0)==2){e=b;c=h;h=k;break b}o=c-3|0;H[a+24>>2]=o;d:{if((c|0)<3){f=b;b=d;break d}f=b+1|0;H[a>>2]=f;b=I[b|0]}j=(b|0)==255;H[a+20>>2]=j;l=((e|0)==255?7:8)+i|0;H[a+16>>2]=l;e=i&31;if((i&63)>>>0>=32){m=b<>>32-e;e=b<>2]=g;H[a+12>>2]=m;if((n|0)==1){e=f;i=l;c=k;h=o;break b}h=c-4|0;H[a+24>>2]=h;e:{if((c|0)<4){e=f;c=d;break e}e=f+1|0;H[a>>2]=e;c=I[f|0]}j=(c|0)==255;H[a+20>>2]=j;i=l+((b|0)==255?7:8)|0;H[a+16>>2]=i;b=l&31;if((l&63)>>>0>=32){f=c<>>32-b;b=c<>2]=g;H[a+12>>2]=b;c=o}f:{if((c|0)>=5){d=H[e>>2];H[a+24>>2]=c-5;H[a>>2]=e+4;break f}b=0;d=d?-1:0;if((c|0)<2){break f}while(1){c=e+1|0;H[a>>2]=c;e=I[e|0];f=h-1|0;H[a+24>>2]=f;d=(255<>>0>1;e=c;h=f;if(k){continue}break}}b=d>>>24|0;H[a+20>>2]=(b|0)==255;c=d>>>16&255;e=d>>>8&255;h=j?7:8;d=d&255;f=h+((d|0)==255?7:8)|0;k=((e|0)==255?7:8)+f|0;H[a+16>>2]=(((c|0)==255?7:8)+i|0)+k;b=d|(e<>>0>=32){d=a<>>32-b;a=a<>2]=a|g;H[c+12>>2]=d|m}function Ga(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;if(!b){return 0}a:{if(!a){a=fb(8,b);break a}b:{if(b>>>0>4294967239){break b}h=b>>>0<=8?8:b+3&-4;d=h+8|0;j=a-4|0;f=j;e=H[f>>2];b=e+f|0;c=H[b>>2];c:{d:{e:{if((c|0)!=H[(b+c|0)-4>>2]){c=c+e|0;if(c>>>0>=d+16>>>0){e=H[b+4>>2];b=H[b+8>>2];H[e+8>>2]=b;H[b+4>>2]=e;b=d+f|0;c=c-d|0;H[b>>2]=c;H[(b+(c&-4)|0)-4>>2]=c|1;e=H[b>>2]-8|0;f:{if(e>>>0<=127){g=(e>>>3|0)-1|0;break f}c=Q(e);g=((e>>>29-c^4)-(c<<2)|0)+110|0;if(e>>>0<=4095){break f}c=((e>>>30-c^2)-(c<<1)|0)+71|0;g=c>>>0>=63?63:c}c=g;e=c<<4;H[b+4>>2]=e+26432;e=e+26440|0;H[b+8>>2]=H[e>>2];H[e>>2]=b;H[H[b+8>>2]+4>>2]=b;e=H[6867];b=c&31;if((c&63)>>>0>=32){c=1<>>32-b}H[6866]=g|H[6866];H[6867]=c|e;H[f>>2]=d;H[(f+(d&-4)|0)-4>>2]=d;c=1;break c}if(c>>>0>>0){break e}d=H[b+4>>2];b=H[b+8>>2];H[d+8>>2]=b;H[b+4>>2]=d;H[f>>2]=c;H[(f+(c&-4)|0)-4>>2]=c;c=1;break c}if(e>>>0>=d+16>>>0){H[f>>2]=d;H[(f+(d&-4)|0)-4>>2]=d;b=d+f|0;c=e-d|0;H[b>>2]=c;H[(b+(c&-4)|0)-4>>2]=c|1;d=H[b>>2]-8|0;g:{if(d>>>0<=127){c=(d>>>3|0)-1|0;break g}f=Q(d);c=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break g}c=((d>>>30-f^2)-(f<<1)|0)+71|0;c=c>>>0>=63?63:c}d=c<<4;H[b+4>>2]=d+26432;d=d+26440|0;H[b+8>>2]=H[d>>2];H[d>>2]=b;H[H[b+8>>2]+4>>2]=b;d=H[6867];b=c&31;if((c&63)>>>0>=32){c=1<>>32-b;b=e}H[6866]=b|H[6866];H[6867]=c|d;c=1;break c}c=1;if(d>>>0<=e>>>0){break d}}c=0}}if(c){break a}b=fb(8,h);if(!b){break b}i=H[j>>2]-8|0;$a(b,a,h>>>0>>0?h:i);Ca(a);i=b}a=i}return a}function Zd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;h=na-16|0;na=h;k=H[a+96>>2];if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}F[e+5640|0]=I[e+5640|0]|1;a:{if(c>>>0<=4){b=0;Ba(d,1,4565,0);break a}Da(b,e,1);if(K[e>>2]>=8){b=0;Ba(d,1,4531,0);break a}Da(b+1|0,h+8|0,1);f=H[h+8>>2];H[e+4>>2]=f;if((f|0)>=5){Ba(d,1,4490,0);H[e+4>>2]=-1}Da(b+2|0,e+8|0,2);g=H[e+8>>2];if(g-65536>>>0<=4294901760){H[h>>2]=g;Ba(d,1,8111,h);b=0;break a}f=H[a+188>>2];H[e+12>>2]=f?f:g;Da(b+4|0,e+16|0,1);if(K[e+16>>2]>=2){b=0;Ba(d,1,5536,0);break a}f=b+5|0;H[h+12>>2]=c-5;b=H[k+16>>2];b:{if(!b){break b}k=b&7;i=H[e>>2]&1;e=H[e+5584>>2];c=0;if(b>>>0>=8){b=b&-8;g=0;while(1){j=e+N(c,1080)|0;H[j+7560>>2]=i;H[j+6480>>2]=i;H[j+5400>>2]=i;H[j+4320>>2]=i;H[j+3240>>2]=i;H[j+2160>>2]=i;H[j+1080>>2]=i;H[j>>2]=i;c=c+8|0;g=g+8|0;if((b|0)!=(g|0)){continue}break}if(!k){break b}}while(1){H[e+N(c,1080)>>2]=i;c=c+1|0;l=l+1|0;if((k|0)!=(l|0)){continue}break}}b=0;if(!Dc(a,0,f,h+12|0,d)){Ba(d,1,4565,0);break a}if(H[h+12>>2]){Ba(d,1,4565,0);break a}if(H[a+8>>2]==16){b=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{b=H[a+12>>2]}if(K[H[a+96>>2]+16>>2]>=2){b=H[b+5584>>2];f=H[b+4>>2]<<2;l=b+944|0;e=b+812|0;c=b;g=1;while(1){H[c+1084>>2]=H[b+4>>2];H[c+1088>>2]=H[b+8>>2];H[c+1092>>2]=H[b+12>>2];H[c+1096>>2]=H[b+16>>2];H[c+1100>>2]=H[b+20>>2];d=!f;if(!d){B(c+1892|0,e,f)}if(!d){B(c+2024|0,l,f)}c=c+1080|0;g=g+1|0;if(g>>>0>2]+16>>2]){continue}break}}b=1}na=h+16|0;return b|0}function sd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=na-80|0;na=e;a:{if(c>>>0<=2){Ba(d,1,14478,0);break a}if(I[a+124|0]){Ba(d,4,11193,0);g=1;break a}g=1;Da(b,a+40|0,1);Da(b+1|0,a+52|0,1);Da(b+2|0,a+44|0,1);f=b+3|0;b:{c:{d:{e:{f:{h=H[a+40>>2];switch(h-1|0){case 0:break f;case 1:break e;default:break d}}if(c>>>0<=6){H[e+16>>2]=c;Ba(d,1,15155,e+16|0);g=0;break a}if(!((c|0)==7|H[a+48>>2]==14)){H[e+48>>2]=c;Ba(d,2,15155,e+48|0)}Da(f,a+48|0,4);if(H[a+48>>2]!=14){break b}f=Fa(36);if(!f){g=0;Ba(d,1,7993,0);break a}H[f>>2]=14;H[e+64>>2]=0;H[e+56>>2]=0;H[e+72>>2]=0;H[e+60>>2]=0;H[e+68>>2]=0;H[e+76>>2]=0;g=4470064;H[e+52>>2]=4470064;H[f+4>>2]=1145390592;g:{if((c|0)!=7){if((c|0)==35){Da(b+7|0,e+76|0,4);Da(b+11|0,e+72|0,4);Da(b+15|0,e+68|0,4);Da(b+19|0,e- -64|0,4);Da(b+23|0,e+60|0,4);Da(b+27|0,e+56|0,4);Da(b+31|0,e+52|0,4);H[f+4>>2]=0;g=H[e+52>>2];c=H[e+56>>2];d=H[e+64>>2];i=H[e+68>>2];j=H[e+76>>2];h=H[e+72>>2];b=H[e+60>>2];break g}H[e+32>>2]=c;Ba(d,2,15191,e+32|0)}c=0;d=0;h=0;b=0}H[f+24>>2]=b;H[f+16>>2]=i;H[f+8>>2]=j;H[f+32>>2]=g;H[f+28>>2]=c;H[f+20>>2]=d;H[f+12>>2]=h;H[a+112>>2]=0;H[a+108>>2]=f;break b}b=c-3|0;H[a+112>>2]=b;d=Ea(1,b);H[a+108>>2]=d;if(!d){break c}if((c|0)<=3){break b}c=0;while(1){Da(f,e+76|0,1);F[H[a+108>>2]+c|0]=H[e+76>>2];f=f+1|0;c=c+1|0;if((b|0)!=(c|0)){continue}break}break b}if(h>>>0<3){break a}H[e>>2]=h;Ba(d,4,15950,e);break a}g=0;H[a+112>>2]=0;break a}g=1;F[a+124|0]=1}na=e+80|0;return g|0}function Ja(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0;h=H[a+48>>2];if(h>>>0>=c>>>0){if(c){B(b,H[a+36>>2],c)}H[a+36>>2]=H[a+36>>2]+c;H[a+48>>2]=H[a+48>>2]-c;b=H[a+60>>2];d=H[a+56>>2]+c|0;b=d>>>0>>0?b+1|0:b;H[a+56>>2]=d;H[a+60>>2]=b;return c}if(I[a+68|0]&4){if(h){B(b,H[a+36>>2],h)}b=H[a+48>>2];H[a+48>>2]=0;H[a+36>>2]=b+H[a+36>>2];g=H[a+60>>2];c=b;b=H[a+56>>2]+b|0;g=c>>>0>b>>>0?g+1|0:g;H[a+56>>2]=b;H[a+60>>2]=g;return h?h:-1}a:{if(h){if(h){B(b,H[a+36>>2],h)}i=H[a+32>>2];H[a+36>>2]=i;e=H[a+48>>2];H[a+48>>2]=0;f=H[a+60>>2];g=H[a+56>>2]+e|0;f=g>>>0>>0?f+1|0:f;H[a+56>>2]=g;H[a+60>>2]=f;c=c-e|0;b=b+e|0;break a}i=H[a+32>>2];H[a+36>>2]=i}b:{while(1){c:{e=H[a>>2];f=H[a+16>>2];g=H[a+64>>2];d:{if(g>>>0>c>>>0){f=ra[f|0](i,g,e)|0;H[a+48>>2]=f;if((f|0)==-1){break b}if(c>>>0>f>>>0){if(f){B(b,H[a+36>>2],f)}i=H[a+32>>2];H[a+36>>2]=i;e=H[a+48>>2];break d}if(c){B(b,H[a+36>>2],c)}H[a+36>>2]=H[a+36>>2]+c;H[a+48>>2]=H[a+48>>2]-c;b=H[a+60>>2];d=H[a+56>>2]+c|0;b=d>>>0>>0?b+1|0:b;H[a+56>>2]=d;H[a+60>>2]=b;return c+h|0}e=ra[f|0](b,c,e)|0;H[a+48>>2]=e;if((e|0)==-1){break b}if(c>>>0<=e>>>0){break c}i=H[a+32>>2];H[a+36>>2]=i;f=e}H[a+48>>2]=0;g=H[a+60>>2];j=H[a+56>>2]+e|0;g=j>>>0>>0?g+1|0:g;H[a+56>>2]=j;H[a+60>>2]=g;b=b+e|0;c=c-e|0;h=f+h|0;continue}break}H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];f=H[a+60>>2];b=H[a+56>>2]+e|0;f=b>>>0>>0?f+1|0:f;H[a+56>>2]=b;H[a+60>>2]=f;return e+h|0}Ba(d,4,15630,0);H[a+48>>2]=0;H[a+68>>2]=H[a+68>>2]|4;return h?h:-1}function Gb(){var a=0,b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;a:{c=Ea(1,256);if(c){H[c>>2]=1;H[c+208>>2]=1;F[c+212|0]=I[c+212|0]|6;a=Ea(1,5644);H[c+12>>2]=a;if(!a){break a}a=Ea(1,1e3);H[c+16>>2]=a;if(!a){break a}H[c+48>>2]=0;H[c+52>>2]=0;H[c+44>>2]=-1;H[c+20>>2]=1e3;b:{a=Ea(1,48);if(a){H[a+24>>2]=0;H[a+32>>2]=100;b=Ea(100,24);H[a+28>>2]=b;if(b){break b}Ca(a)}H[c+224>>2]=0;break a}H[a+40>>2]=0;H[c+224>>2]=a;a=pb();H[c+220>>2]=a;if(!a){break a}a=pb();H[c+216>>2]=a;if(!a){break a}a=1419;c:{while(1){b=I[a|0];if(!b|(b|0)==61){break c}a=a+1|0;if(a&3){continue}break}d:{e:{d=H[a>>2];if(((d|16843008-d)&-2139062144)!=-2139062144){break e}while(1){b=d^1027423549;if(((16843008-b|b)&-2139062144)!=-2139062144){break e}d=H[a+4>>2];b=a+4|0;a=b;if(((16843008-d|d)&-2139062144)==-2139062144){continue}break}break d}b=a}while(1){a=b;e=I[a|0];if(!e){break c}b=a+1|0;if((e|0)!=61){continue}break}}b=0;f:{if((a|0)==1419){break f}e=a-1419|0;g:{if(I[e+1419|0]){break g}h=H[6870];if(!h){break g}a=H[h>>2];if(!a){break g}while(1){h:{d=1419;b=a;i=e;g=0;i:{if(!e){break i}f=I[1419];if(f){j:{while(1){g=I[b|0];if((g|0)!=(f|0)|!g){break j}i=i-1|0;if(!i){break j}b=b+1|0;f=I[d+1|0];d=d+1|0;if(f){continue}break}f=0}}else{f=0}g=f-I[b|0]|0}if(!g){a=a+e|0;if(I[a|0]==61){break h}}a=H[h+4>>2];h=h+4|0;if(a){continue}break g}break}j=a+1|0}b=j}k:{if(!b){break k}}a=gc();H[c+236>>2]=a;if(!a){a=gc();H[c+236>>2]=a;if(!a){break a}}}else{c=0}return c}vb(c);return 0}function id(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=H[a+48>>2];if(e>>>0>=b>>>0){H[a+48>>2]=e-b;H[a+36>>2]=H[a+36>>2]+b;e=c+H[a+60>>2]|0;d=b+H[a+56>>2]|0;e=d>>>0>>0?e+1|0:e;H[a+56>>2]=d;H[a+60>>2]=e;qa=c;return b|0}if(I[a+68|0]&4){H[a+48>>2]=0;H[a+36>>2]=e+H[a+36>>2];g=H[a+60>>2];c=H[a+56>>2];b=c+e|0;H[a+56>>2]=b;H[a+60>>2]=b>>>0>>0?g+1|0:g;qa=e?0:-1;return(e?e:-1)|0}if(e){H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];f=e;b=b-e|0}a:{if((c|0)>0){i=1}else{i=!!b&(c|0)>=0}if(i){while(1){i=H[a+12>>2];e=c+g|0;h=b+f|0;e=H[a+60>>2]+(h>>>0>>0?e+1|0:e)|0;j=h;h=h+H[a+56>>2]|0;e=j>>>0>h>>>0?e+1|0:e;if((e|0)==(i|0)&h>>>0>K[a+8>>2]|e>>>0>i>>>0){Ba(d,4,15630,0);H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];b=g+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;d=H[a+8>>2];f=d-c|0;e=H[a+12>>2];g=e-((c>>>0>d>>>0)+b|0)|0;i=ra[H[a+28>>2]](d,e,H[a>>2])|0;h=H[a+68>>2];if(i){H[a+56>>2]=d;H[a+60>>2]=e}H[a+68>>2]=h|4;a=(c|0)==(d|0)&(b|0)==(e|0);b=a?-1:f;break a}e=ra[H[a+24>>2]](b,c,H[a>>2])|0;i=qa;h=i;if((e&h)==-1){Ba(d,4,15630,0);H[a+68>>2]=H[a+68>>2]|4;e=g+H[a+60>>2]|0;b=f+H[a+56>>2]|0;e=b>>>0>>0?e+1|0:e;H[a+56>>2]=b;H[a+60>>2]=e;a=!(g|f);b=a?-1:f;break a}g=g+h|0;f=e+f|0;g=f>>>0>>0?g+1|0:g;i=b;b=b-e|0;c=c-((e>>>0>i>>>0)+h|0)|0;if(!!b&(c|0)>=0|(c|0)>0){continue}break}}b=g+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;qa=g;return f|0}qa=a?-1:g;return b|0}function Sa(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;a:{b:{if(!H[a+12>>2]){k=1;if(H[a+4>>2]>0|H[a+8>>2]>1){break b}break a}e=1;if(H[a+8>>2]>0){break b}if(H[a+4>>2]<2){break a}}b=H[a>>2];f=b+(e<<5)|0;g=H[a+16>>2];h=H[a+20>>2];if(g>>>0>>0){d=g;while(1){c=(d<<6)+f|0;L[c>>2]=L[c>>2]*O(1.2301740646362305);L[c+4>>2]=L[c+4>>2]*O(1.2301740646362305);L[c+8>>2]=L[c+8>>2]*O(1.2301740646362305);L[c+12>>2]=L[c+12>>2]*O(1.2301740646362305);L[c+16>>2]=L[c+16>>2]*O(1.2301740646362305);L[c+20>>2]=L[c+20>>2]*O(1.2301740646362305);L[c+24>>2]=L[c+24>>2]*O(1.2301740646362305);L[c+28>>2]=L[c+28>>2]*O(1.2301740646362305);d=d+1|0;if((h|0)!=(d|0)){continue}break}}i=b+(k<<5)|0;j=H[a+28>>2];c=H[a+24>>2];if(j>>>0>c>>>0){d=c;while(1){b=(d<<6)+i|0;L[b>>2]=L[b>>2]*O(1.625732421875);L[b+4>>2]=L[b+4>>2]*O(1.625732421875);L[b+8>>2]=L[b+8>>2]*O(1.625732421875);L[b+12>>2]=L[b+12>>2]*O(1.625732421875);L[b+16>>2]=L[b+16>>2]*O(1.625732421875);L[b+20>>2]=L[b+20>>2]*O(1.625732421875);L[b+24>>2]=L[b+24>>2]*O(1.625732421875);L[b+28>>2]=L[b+28>>2]*O(1.625732421875);d=d+1|0;if((j|0)!=(d|0)){continue}break}}b=f+32|0;d=H[a+8>>2];a=H[a+4>>2];e=a-e|0;e=(d|0)<(e|0)?d:e;lb(i,b,g,h,e,O(-.4435068666934967));l=i+32|0;d=d-k|0;a=(a|0)<(d|0)?a:d;lb(f,l,c,j,a,O(-.8829110860824585));lb(i,b,g,h,e,O(.05298011749982834));lb(f,l,c,j,a,O(1.5861343145370483))}}function Xb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;e=H[a+28>>2]+N(b,152)|0;c=H[e-144>>2]-H[e-152>>2]|0;d=H[e-140>>2]-H[e-148>>2]|0;f=d>>>0>=64?64:d;g=c>>>0>=64?64:c;a:{if(!(!c|!d|(!g|!f)|g>>>0>4294967295/(f>>>0)>>>2>>>0)){e=Ea(1,28);H[e+12>>2]=f;H[e+8>>2]=g;H[e+4>>2]=d;H[e>>2]=c;j=d;d=d+f|0;i=j>>>0>d>>>0;d=fe(d-1|0,i-!d|0,f,0);H[e+20>>2]=d;j=c;c=c+g|0;f=j>>>0>c>>>0;c=fe(c-1|0,f-!c|0,g,0);H[e+16>>2]=c;be(d,0,c);b:{if(qa){break b}c=Ea(4,N(c,d));H[e+24>>2]=c;if(!c){break b}break a}Ca(e)}e=0}if(!e){return 0}c:{if(b){while(1){q=N(p,152);g=q+H[a+28>>2]|0;c=H[g+24>>2];if(c){j=g+28|0;d=H[g+20>>2];f=H[g+16>>2];n=0;while(1){if(N(d,f)){i=N(n,36)+j|0;o=0;while(1){k=H[i+20>>2]+N(o,40)|0;c=H[k+20>>2];h=H[k+16>>2];if(N(c,h)){f=0;while(1){l=H[k+24>>2]+N(f,68)|0;r=H[l+60>>2];if(r){h=H[l+8>>2];d=h-H[i>>2]|0;m=H[i+16>>2];if(m&1){c=H[a+28>>2]+q|0;d=(H[c-144>>2]+d|0)-H[c-152>>2]|0}s=H[l+12>>2];c=s-H[i+4>>2]|0;if(m&2){m=H[a+28>>2]+q|0;c=(c+H[m-140>>2]|0)-H[m-148>>2]|0}h=H[l+16>>2]-h|0;if(!Ya(e,d,c,d+h|0,(H[l+20>>2]-s|0)+c|0,r,1,h)){break c}h=H[k+16>>2];c=H[k+20>>2]}f=f+1|0;if(f>>>0>>0){continue}break}f=H[g+16>>2];d=H[g+20>>2]}o=o+1|0;if(o>>>0>>0){continue}break}c=H[g+24>>2]}n=n+1|0;if(n>>>0>>0){continue}break}}p=p+1|0;if((p|0)!=(b|0)){continue}break}}return e}Ua(e);return 0}function Ld(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=na-16|0;na=h;if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}a:{if(c>>>0<=1){Ba(d,1,4132,0);a=0;break a}Da(b,h+12|0,2);b:{if(H[h+12>>2]){Ba(d,2,3608,0);break b}if(c>>>0<=6){Ba(d,1,4132,0);a=0;break a}Da(b+2|0,h+12|0,2);g=H[e+5616>>2];k=I[h+12|0];f=H[e+5620>>2];c:{d:{if(!f){break d}a=g;while(1){if(H[a+8>>2]!=(k|0)){a=a+20|0;i=i+1|0;if((i|0)!=(f|0)){continue}break d}break}if((f|0)!=(i|0)){break c}}if(H[e+5624>>2]==(f|0)){a=f+10|0;H[e+5624>>2]=a;a=Ga(g,N(a,20));g=H[e+5616>>2];if(!a){Ca(g);H[e+5624>>2]=0;H[e+5616>>2]=0;H[e+5620>>2]=0;Ba(d,1,4158,0);a=0;break a}e:{if((a|0)==(g|0)){break e}l=H[e+5632>>2];if(!l){break e}m=H[e+5628>>2];i=0;while(1){f=N(i,20)+m|0;j=H[f+8>>2];if(j){H[f+8>>2]=a+(j-g|0)}j=H[f+12>>2];if(j){H[f+12>>2]=a+(j-g|0)}i=i+1|0;if((l|0)!=(i|0)){continue}break}}H[e+5616>>2]=a;g=H[e+5620>>2];f=N(H[e+5624>>2]-g|0,20);if(f){y(a+N(g,20)|0,0,f)}f=H[e+5620>>2];g=H[e+5616>>2]}H[e+5620>>2]=f+1;a=N(f,20)+g|0}e=H[a+12>>2];if(e){Ca(e);H[a+12>>2]=0;H[a+16>>2]=0}H[a+8>>2]=k;e=H[h+12>>2];H[a>>2]=e>>>10&3;H[a+4>>2]=e>>>8&3;Da(b+4|0,h+12|0,2);if(H[h+12>>2]){Ba(d,2,3023,0);break b}c=c-6|0;e=Fa(c);H[a+12>>2]=e;if(!e){Ba(d,1,4132,0);a=0;break a}if(c){B(e,b+6|0,c)}H[a+16>>2]=c}a=1}na=h+16|0;return a|0}function Qb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;if(a){b=H[a+20>>2];if(b){g=H[b>>2];if(g){d=H[g+20>>2];if(H[g+16>>2]){i=F[a+40|0]&1?16:17;while(1){c=H[d+28>>2];if(c){b=H[d+32>>2];l=(b>>>0)/152|0;j=0;if(b>>>0>=152){while(1){b=H[c+48>>2];if(b){f=H[c+52>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+48>>2]}Ca(b);H[c+48>>2]=0}b=H[c+84>>2];if(b){f=H[c+88>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+84>>2]}Ca(b);H[c+84>>2]=0}b=H[c+120>>2];if(b){f=H[c+124>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+120>>2]}Ca(b);H[c+120>>2]=0}c=c+152|0;j=j+1|0;if((l|0)!=(j|0)){continue}break}c=H[d+28>>2]}Ca(c);H[d+28>>2]=0}a:{if(!H[d+40>>2]){break a}b=H[d+36>>2];if(!b){break a}Ca(b);H[d+44>>2]=0;H[d+48>>2]=0;H[d+36>>2]=0;H[d+40>>2]=0}Ca(H[d+52>>2]);d=d+76|0;k=k+1|0;if(k>>>0>2]){continue}break}d=H[g+20>>2]}Ca(d);H[g+20>>2]=0;Ca(H[H[a+20>>2]>>2]);b=H[a+20>>2];H[b>>2]=0}Ca(b);H[a+20>>2]=0}Ca(H[a+68>>2]);Ca(a)}}function Ob(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;e=H[a+24>>2];j=H[e+16>>2];if(!j){return 0}f=H[e+24>>2];e=H[H[H[a+20>>2]>>2]+20>>2];a:{b:{if(!b){b=0;while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-140>>2];g=H[a-144>>2]-H[a-152>>2]|0;a=H[a-148>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}break b}b=0;if(!H[a+64>>2]){while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-4>>2];g=H[a-8>>2]-H[a-16>>2]|0;a=H[a-12>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}break b}while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-140>>2];g=H[a-144>>2]-H[a-152>>2]|0;a=H[a-148>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}}return a}return-1}function Yb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;c=H[a+8>>2];e=c+H[a+4>>2]|0;a:{if(!H[a+12>>2]){if((e|0)<2){break a}h=(c<<2)+b|0;d=H[h>>2];c=H[b>>2]-(d+1>>1)|0;i=H[a>>2];if(e>>>0>=4){k=(e-4>>>1|0)+1|0;a=1;while(1){f=d;d=a<<2;m=H[d+b>>2];d=H[d+h>>2];j=c;l=i+(g<<2)|0;H[l>>2]=c;c=m-((d+f|0)+2>>2)|0;H[l+4>>2]=f+(j+c>>1);g=g+2|0;f=(a|0)!=(k|0);a=a+1|0;if(f){continue}break}}H[i+(g<<2)>>2]=c;if(e&1){f=e-1|0;a=H[(f<<1)+b>>2]-(d+1>>1)|0;H[i+(f<<2)>>2]=a;c=a+c>>1;j=-8}else{j=-4}a=e<<2;H[j+(a+i|0)>>2]=c+d;if(!a){break a}B(b,i,a);return}b:{switch(e-1|0){case 0:H[b>>2]=H[b>>2]/2;return;case 1:a=H[a>>2];c=(c<<2)+b|0;d=H[b>>2]-(H[c>>2]+1>>1)|0;H[a+4>>2]=d;H[a>>2]=d+H[c>>2];c=H[a+4>>2];H[b>>2]=H[a>>2];H[b+4>>2]=c;return;default:break b}}if((e|0)<3){break a}h=H[a>>2];k=(c<<2)+b|0;d=H[k+4>>2];a=H[k>>2];c=H[b>>2]-((d+a|0)+2>>2)|0;H[h>>2]=c+a;g=1;m=e-2|0;l=e&1;a=!l;if(m-a>>>0>=2){o=((e-a|0)-4>>>1|0)+1|0;a=1;while(1){f=d;p=H[(a<<2)+b>>2];j=a+1|0;d=H[k+(j<<2)>>2];i=c;n=h+(g<<2)|0;H[n>>2]=c;c=p-((d+f|0)+2>>2)|0;H[n+4>>2]=f+(i+c>>1);g=g+2|0;f=(a|0)!=(o|0);a=j;if(f){continue}break}}H[h+(g<<2)>>2]=c;c:{if(!l){g=H[((e<<1)+b|0)-4>>2]-(d+1>>1)|0;H[h+(m<<2)>>2]=(g+c>>1)+d;break c}g=c+d|0}a=e<<2;H[(a+h|0)-4>>2]=g;if(!a){break a}B(b,h,a)}}function ce(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{e=b;if(e){if(!c){break j}if(!d){break i}e=Q(d)-Q(e)|0;if(e>>>0<=31){break h}break b}if((d|0)==1|d>>>0>1){break b}b=(a>>>0)/(c>>>0)|0;oa=a-N(b,c)|0;pa=0;qa=0;return b}if(!a){break g}if(!d){break f}f=d-1|0;if(f&d){break f}oa=a;pa=e&f;a=e>>>de(d)|0;qa=0;return a}f=c-1|0;if(!(f&c)){break e}k=(Q(c)+33|0)-Q(e)|0;g=0-k|0;break c}k=e+1|0;g=63-e|0;break c}oa=0;a=(e>>>0)/(d>>>0)|0;pa=e-N(a,d)|0;qa=0;return a}e=Q(d)-Q(e)|0;if(e>>>0<31){break d}break b}oa=a&f;pa=0;if((c|0)==1){break a}c=de(c);d=c&31;if((c&63)>>>0>=32){e=0;a=b>>>d|0}else{e=b>>>d|0;a=((1<>>d}qa=e;return a}k=e+1|0;g=63-e|0}f=a;e=k&63;h=e&31;if((e&63)>>>0>=32){e=0;f=b>>>h|0}else{e=b>>>h|0;f=((1<>>h}h=g&63;g=a;i=h&31;if((h&63)>>>0>=32){j=a<>>32-i|b<>>31;f=f<<1|b>>>31;l=e;i=g-(e+(f>>>0>h>>>0)|0)|0;m=i>>31;j=m;e=f;i=c&j;f=e-i|0;e=l-((d&j)+(e>>>0>>0)|0)|0;j=b<<1|a>>>31;a=n|a<<1;b=j|o;l=m&1;n=l;k=k-1|0;if(k){continue}break}}oa=f;pa=e;j=b<<1|a>>>31;a=l|a<<1;qa=j|o;return a}oa=a;pa=b;a=0;b=0}qa=b;return a}function Cc(a,b,c,d,e){var f=0,g=0,h=0,i=0;h=na-16|0;na=h;if(H[a+8>>2]==16){a=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{a=H[a+12>>2]}f=H[d>>2];a:{if(!f){d=0;Ba(e,1,2642,0);break a}a=H[a+5584>>2];H[d>>2]=f-1;Da(c,h+12|0,1);g=N(b,1080)+a|0;a=H[h+12>>2];H[g+804>>2]=a>>>5;b=a&31;H[g+24>>2]=b;a=c+1|0;b:{c:{d:{e:{f:{switch(b|0){case 0:f=H[d>>2];break e;case 1:break d;default:break f}}f=H[d>>2]>>>1|0}if(f>>>0>=98){H[h+4>>2]=97;H[h+8>>2]=97;H[h>>2]=f;Ba(e,2,16056,h);b=H[g+24>>2]}if(b){b=f;if(b){break d}a=0;break c}if(f){b=g+28|0;c=0;while(1){Da(a,h+12|0,1);if(c>>>0<=96){e=H[h+12>>2];i=b+(c<<3)|0;H[i+4>>2]=0;H[i>>2]=e>>>3}a=a+1|0;c=c+1|0;if((f|0)!=(c|0)){continue}break}}a=H[d>>2];if(a>>>0>>0){d=0;break a}a=a-f|0;break b}e=g+28|0;c=0;while(1){Da(a,h+12|0,2);if(c>>>0<=96){f=e+(c<<3)|0;i=H[h+12>>2];H[f+4>>2]=i&2047;H[f>>2]=i>>>11}a=a+2|0;c=c+1|0;if((c|0)!=(b|0)){continue}break}a=b<<1}b=H[d>>2];if(a>>>0>b>>>0){d=0;break a}a=b-a|0}H[d>>2]=a;d=1;if(H[g+24>>2]!=1){break a}f=g+28|0;c=H[g+32>>2];e=H[g+28>>2];a=1;while(1){b=f+(a<<3)|0;H[b+12>>2]=c;H[b+4>>2]=c;g=e-((a>>>0)/3|0)|0;H[b+8>>2]=(g|0)>0?g:0;g=b;b=e-((a-1>>>0)/3|0)|0;H[g>>2]=(b|0)>0?b:0;a=a+2|0;if((a|0)!=97){continue}break}}na=h+16|0;return d}function ud(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;h=na-16|0;na=h;a:{if(!(I[a+100|0]&2)){Ba(d,1,11356,0);a=0;break a}H[a+104>>2]=0;b:{c:{d:{if(c){while(1){if(c>>>0<=7){Ba(d,1,3403,0);break b}g=h+12|0;Da(b,g,4);e=H[h+12>>2];Da(b+4|0,g,4);f=8;g=H[h+12>>2];e:{f:{g:{switch(e|0){case 1:if(c>>>0<16){e=3443;break c}Da(b+8|0,h+8|0,4);if(H[h+8>>2]){e=8449;break c}Da(b+12|0,h+12|0,4);e=H[h+12>>2];if(e){break f}e=3268;break c;case 0:break g;default:break e}}Ba(d,1,3268,0);break b}f=16}if(e>>>0>>0){Ba(d,1,9148,0);break b}if(c>>>0>>0){Ba(d,1,9076,0);a=0;break a}h:{i:{j=b+f|0;k=e-f|0;j:{k:{l:{m:{if((g|0)<=1668246641){if((g|0)==1651532643){break m}if((g|0)==1667523942){break k}if((g|0)!=1668112752){break i}f=25264;break j}if((g|0)==1885564018){break l}f=25232;if((g|0)==1768449138){break j}if((g|0)!=1668246642){break i}f=25240;break j}f=25248;break j}f=25256;break j}f=25272}if(ra[H[f+4>>2]](a,j,k,d)|0){break h}a=0;break a}H[a+104>>2]=H[a+104>>2]|2147483647}i=(g|0)==1768449138?1:i;b=b+e|0;c=c-e|0;if(c){continue}break}if(i){break d}}Ba(d,1,8976,0);a=0;break a}F[a+132|0]=1;H[a+100>>2]=H[a+100>>2]|4;a=1;break a}Ba(d,1,e,0)}Ba(d,1,1968,0);a=0}na=h+16|0;return a|0}function Rd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;e=na-32|0;na=e;f=1;a:{if(c>>>0<=1){f=0;Ba(d,1,10062,0);break a}if(H[a+76>>2]){break a}Da(b,e+28|0,1);Da(b+1|0,e+24|0,1);h=H[e+24>>2];g=h>>>4&3;if((g|0)==3){H[a+76>>2]=1;Ba(d,2,11558,0);break a}c=c-2|0;h=(h>>>5&2)+2|0;i=h+g|0;j=(c>>>0)/(i>>>0)|0;if((N(j,i)|0)!=(c|0)){H[a+76>>2]=1;Ba(d,2,11139,0);break a}if(c>>>0>>0){break a}b:{c=H[a+68>>2];if(c>>>0<=(j^-1)>>>0){c=c+j|0;if(c>>>0<536870912){break b}}H[a+76>>2]=1;Ba(d,2,9400,0);break a}i=Ga(H[a+72>>2],c<<3);if(!i){H[a+76>>2]=1;Ba(d,2,9443,0);break a}c=b+2|0;H[a+72>>2]=i;c:{if(g){while(1){Da(c,e+20|0,g);b=H[e+20>>2];if(b>>>0>=N(H[a+132>>2],H[a+128>>2])>>>0){break c}b=c+g|0;Da(b,e+16|0,h);c=H[a+68>>2];f=i+(c<<3)|0;G[f>>1]=H[e+20>>2];H[f+4>>2]=H[e+16>>2];f=1;H[a+68>>2]=c+1;c=b+h|0;k=k+1|0;if((k|0)!=(j|0)){continue}break}break a}b=H[a+68>>2];g=0;while(1){H[e+20>>2]=b;if(N(H[a+132>>2],H[a+128>>2])>>>0<=b>>>0){break c}Da(c,e+16|0,h);k=H[a+68>>2];f=i+(k<<3)|0;G[f>>1]=b;H[f+4>>2]=H[e+16>>2];f=1;b=k+1|0;H[a+68>>2]=b;c=c+h|0;g=g+1|0;if((j|0)!=(g|0)){continue}break}break a}H[a+76>>2]=1;H[e>>2]=b;Ba(d,2,7799,e)}na=e+32|0;return f|0}function Bd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;a:{if(!Va(H[b+8>>2],46,d)){break a}i=H[b+4>>2];e=H[i>>2];g=H[i+8>>2];b:{if(e){f=1;c:{if((e|0)!=1){k=e&1;e=e&-2;while(1){h=0;d:{if(!f){break d}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break d}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}f=h;g=g+8|0;j=j+2|0;if((e|0)!=(j|0)){continue}break}if(!k){break c}}if(!f){f=0;break c}f=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(f){break b}break a}Oa(i)}i=H[b+8>>2];e=H[i>>2];g=H[i+8>>2];e:{if(e){f=1;f:{if((e|0)!=1){k=e&1;e=e&-2;j=0;while(1){h=0;g:{if(!f){break g}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break g}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}f=h;g=g+8|0;j=j+2|0;if((e|0)!=(j|0)){continue}break}if(!k){break f}}if(!f){f=0;break f}f=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(!f){break a}break e}Oa(i)}if(!I[b+132|0]){Ba(d,1,11696,0);return 0}if(!I[b+133|0]){Ba(d,1,11667,0);return 0}l=Jb(a,H[b>>2],c,d);if(!c|!l){break a}a=H[c>>2];if(!a){break a}c=H[b+48>>2]-12|0;if(c>>>0<=12){c=H[(c<<2)+25280>>2]}else{c=-1}H[a+20>>2]=c;c=H[b+108>>2];if(!c){break a}H[a+28>>2]=c;H[a+32>>2]=H[b+112>>2];H[b+108>>2]=0}return l|0}function Jb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;e=Db();H[b+96>>2]=e;a:{b:{if(!e){break b}c:{if(Va(H[b+220>>2],18,d)){if(Va(H[b+220>>2],19,d)){break c}}break a}i=H[b+220>>2];f=H[i>>2];g=H[i+8>>2];d:{if(f){e=1;e:{if((f|0)!=1){k=f&1;f=f&-2;while(1){h=0;f:{if(!e){break f}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break f}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}e=h;g=g+8|0;j=j+2|0;if((f|0)!=(j|0)){continue}break}if(!k){break e}}if(!e){e=0;break e}e=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(e){break d}break a}Oa(i)}g:{if(Va(H[b+216>>2],20,d)){if(Va(H[b+216>>2],21,d)){break g}}break a}i=H[b+216>>2];f=H[i>>2];g=H[i+8>>2];h:{if(f){e=1;i:{if((f|0)!=1){k=f&1;f=f&-2;j=0;while(1){h=0;j:{if(!e){break j}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break j}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}e=h;g=g+8|0;j=j+2|0;if((f|0)!=(j|0)){continue}break}if(!k){break i}}if(!e){e=0;break i}e=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(e){break h}break a}Oa(i)}a=Db();H[c>>2]=a;if(!a){break b}wc(H[b+96>>2],a);l=1}return l|0}Ta(H[b+96>>2]);H[b+96>>2]=0;return 0}function sc(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0;b=H[6524];c=a+7&-8;a=c+7|0;d=a>>>0<7?1:d;e=a&-8;a=e+b|0;a:{b:{if(!(a>>>0>>0?d+1|0:d)){if(a>>>0<=sa()<<16>>>0){break b}if(ga(a|0)|0){break b}}H[6605]=48;b=-1;break a}H[6524]=a}d=b;if((d|0)!=-1){a=d+c|0;H[a-4>>2]=16;f=a-16|0;H[f>>2]=16;b=H[6864];if(b){e=H[b+8>>2]}else{e=0}c:{d:{if((e|0)==(d|0)){e=H[d-4>>2]&-2;g=d-e|0;h=H[g-4>>2];H[b+8>>2]=a;b=h&-2;a=g-b|0;if(H[(a+H[a>>2]|0)-4>>2]&1){f=H[a+4>>2];g=H[a+8>>2];H[f+8>>2]=g;H[g+4>>2]=f;b=(b+(c+e|0)|0)-16|0;H[a>>2]=b;break c}a=d-16|0;break d}H[d>>2]=16;H[d+8>>2]=a;H[d+4>>2]=b;H[d+12>>2]=16;H[6864]=d;a=d+16|0}b=f-a|0;H[a>>2]=b}H[((b&-4)+a|0)-4>>2]=b|1;c=H[a>>2]-8|0;e:{if(c>>>0<=127){b=(c>>>3|0)-1|0;break e}e=Q(c);b=((c>>>29-e^4)-(e<<2)|0)+110|0;if(c>>>0<=4095){break e}b=((c>>>30-e^2)-(e<<1)|0)+71|0;b=b>>>0>=63?63:b}c=b<<4;H[a+4>>2]=c+26432;c=c+26440|0;H[a+8>>2]=H[c>>2];H[c>>2]=a;H[H[a+8>>2]+4>>2]=a;c=H[6867];a=b&31;if((b&63)>>>0>=32){b=1<>>32-a}H[6866]=e|H[6866];H[6867]=b|c}return(d|0)!=-1}function wc(a,b){var c=0,d=0,e=0,f=0,g=0;H[b>>2]=H[a>>2];H[b+4>>2]=H[a+4>>2];H[b+8>>2]=H[a+8>>2];H[b+12>>2]=H[a+12>>2];c=H[b+24>>2];if(c){d=H[b+16>>2];if(d){c=0;while(1){f=H[(H[b+24>>2]+N(c,52)|0)+44>>2];if(f){Ca(f);d=H[b+16>>2]}c=c+1|0;if(d>>>0>c>>>0){continue}break}c=H[b+24>>2]}Ca(c);H[b+24>>2]=0}c=H[a+16>>2];H[b+16>>2]=c;c=Fa(N(c,52));H[b+24>>2]=c;if(c){if(H[b+16>>2]){f=0;while(1){g=N(f,52);d=g+c|0;c=H[a+24>>2]+g|0;H[d+48>>2]=H[c+48>>2];e=H[c+44>>2];H[d+40>>2]=H[c+40>>2];H[d+44>>2]=e;e=H[c+36>>2];H[d+32>>2]=H[c+32>>2];H[d+36>>2]=e;e=H[c+28>>2];H[d+24>>2]=H[c+24>>2];H[d+28>>2]=e;e=H[c+20>>2];H[d+16>>2]=H[c+16>>2];H[d+20>>2]=e;e=H[c+12>>2];H[d+8>>2]=H[c+8>>2];H[d+12>>2]=e;e=H[c+4>>2];H[d>>2]=H[c>>2];H[d+4>>2]=e;c=H[b+24>>2];H[(g+c|0)+44>>2]=0;f=f+1|0;if(f>>>0>2]){continue}break}}H[b+20>>2]=H[a+20>>2];c=H[a+32>>2];H[b+32>>2]=c;a:{if(c){c=Fa(c);H[b+28>>2]=c;if(!c){H[b+28>>2]=0;H[b+32>>2]=0;return}b=H[a+32>>2];if(!b){break a}B(c,H[a+28>>2],b);return}H[b+28>>2]=0}return}H[b+16>>2]=0;H[b+24>>2]=0}function lb(a,b,c,d,e,f){var g=0,h=O(0),i=0,j=O(0);g=(c<<6)+b|0;b=c?g+-64|0:a;i=d>>>0>>0?d:e;if(i>>>0>c>>>0){h=L[b>>2];while(1){a=b;b=g;g=b-32|0;j=h;h=L[b>>2];L[g>>2]=O(O(j+h)*f)+L[g>>2];g=b-28|0;L[g>>2]=O(O(L[a+4>>2]+L[b+4>>2])*f)+L[g>>2];g=b-24|0;L[g>>2]=O(O(L[a+8>>2]+L[b+8>>2])*f)+L[g>>2];g=b-20|0;L[g>>2]=O(O(L[a+12>>2]+L[b+12>>2])*f)+L[g>>2];g=b-16|0;L[g>>2]=O(O(L[a+16>>2]+L[b+16>>2])*f)+L[g>>2];g=b-12|0;L[g>>2]=O(O(L[a+20>>2]+L[b+20>>2])*f)+L[g>>2];g=b-8|0;L[g>>2]=O(O(L[a+24>>2]+L[b+24>>2])*f)+L[g>>2];g=b-4|0;L[g>>2]=O(O(L[a+28>>2]+L[b+28>>2])*f)+L[g>>2];g=b- -64|0;c=c+1|0;if((i|0)!=(c|0)){continue}break}}if(d>>>0>e>>>0){a=g-32|0;f=O(f+f);L[a>>2]=O(L[b>>2]*f)+L[a>>2];a=g-28|0;L[a>>2]=O(L[b+4>>2]*f)+L[a>>2];a=g-24|0;L[a>>2]=O(L[b+8>>2]*f)+L[a>>2];a=g-20|0;L[a>>2]=O(L[b+12>>2]*f)+L[a>>2];a=g-16|0;L[a>>2]=O(L[b+16>>2]*f)+L[a>>2];a=g-12|0;L[a>>2]=O(L[b+20>>2]*f)+L[a>>2];a=g-8|0;L[a>>2]=O(L[b+24>>2]*f)+L[a>>2];a=g-4|0;L[a>>2]=O(L[b+28>>2]*f)+L[a>>2]}}function Uc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;if(K[a+44>>2]>=8){e=H[a+36>>2];n=e<<5;o=N(e,28);p=N(e,24);q=N(e,20);r=e<<4;i=N(e,12);j=e<<3;g=H[a+40>>2];k=8;while(1){yb(a,g,H[a+36>>2],8);Sa(a);h=H[a+32>>2];if(h){f=H[a>>2];b=0;a:{if((h|0)!=1){s=h&1;t=h&-2;l=0;while(1){c=(b<<2)+g|0;d=f+(b<<5)|0;L[c>>2]=L[d>>2];m=e<<2;L[c+m>>2]=L[d+4>>2];L[c+j>>2]=L[d+8>>2];L[c+i>>2]=L[d+12>>2];d=b|1;c=(d<<2)+g|0;d=f+(d<<5)|0;L[c>>2]=L[d>>2];L[c+m>>2]=L[d+4>>2];L[c+j>>2]=L[d+8>>2];L[c+i>>2]=L[d+12>>2];b=b+2|0;l=l+2|0;if((t|0)!=(l|0)){continue}break}if(!s){break a}}c=(b<<2)+g|0;b=f+(b<<5)|0;L[c>>2]=L[b>>2];L[c+(e<<2)>>2]=L[b+4>>2];L[c+j>>2]=L[b+8>>2];L[c+i>>2]=L[b+12>>2]}d=H[a>>2];b=0;while(1){f=(b<<2)+g|0;c=d+(b<<5)|0;L[f+r>>2]=L[c+16>>2];L[f+q>>2]=L[c+20>>2];L[f+p>>2]=L[c+24>>2];L[f+o>>2]=L[c+28>>2];b=b+1|0;if((h|0)!=(b|0)){continue}break}}g=g+n|0;k=k+8|0;if(k>>>0<=K[a+44>>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function pd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;f=na-16|0;na=f;a:{if(H[a+120>>2]|c>>>0<3){break a}Da(b,f+12|0,2);k=J[f+12>>1];if(k-1025>>>0<=4294966271){H[f>>2]=k;Ba(d,1,3526,f);break a}Da(b+2|0,f+12|0,1);i=J[f+12>>1];if(!i){Ba(d,1,3174,0);break a}if(i+3>>>0>c>>>0){break a}h=Fa(N(i,k)<<2);if(!h){break a}j=Fa(i);if(!j){Ca(h);break a}l=Fa(i);if(!l){Ca(h);Ca(j);break a}g=Fa(20);if(!g){Ca(h);Ca(j);Ca(l);break a}d=b+3|0;H[g+8>>2]=j;H[g+4>>2]=l;G[g+16>>1]=k;H[g>>2]=h;m=H[f+12>>2];H[g+12>>2]=0;F[g+18|0]=m;H[a+120>>2]=g;while(1){Da(d,f+12|0,1);F[e+j|0]=(I[f+12|0]&127)+1;F[e+l|0]=(H[f+12>>2]&128)>>>7;d=d+1|0;e=e+1|0;if((i|0)!=(e|0)){continue}break}g=0;while(1){e=0;a=0;while(1){e=I[e+j|0]+7>>>3|0;e=e>>>0>=4?4:e;if((e+(d-b|0)|0)>(c|0)){e=0;break a}Da(d,f+12|0,e);H[h>>2]=H[f+12>>2];h=h+4|0;d=d+e|0;a=a+1|0;e=a&65535;if(i>>>0>e>>>0){continue}break}e=1;g=g+1|0;if((g&65535)>>>0>>0){continue}break}}na=f+16|0;return e|0}function hd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;f=-1;e=-1;if(!(I[a+68|0]&8)){f=H[a+32>>2];H[a+36>>2]=f;a:{b:{c:{e=H[a+48>>2];if(e){while(1){e=ra[H[a+20>>2]](f,e,H[a>>2])|0;if((e|0)==-1){break c}f=e+H[a+36>>2]|0;H[a+36>>2]=f;e=H[a+48>>2]-e|0;H[a+48>>2]=e;if(e){continue}break}f=H[a+32>>2]}H[a+36>>2]=f;if(!!b&(c|0)>=0|(c|0)>0){break b}f=0;e=0;break a}H[a+68>>2]=H[a+68>>2]|8;Ba(d,4,15604,0);H[a+48>>2]=0;H[a+68>>2]=H[a+68>>2]|8;qa=-1;return-1}f=0;e=0;while(1){g=ra[H[a+24>>2]](b,c,H[a>>2])|0;h=qa;i=h;if((g&h)==-1){Ba(d,4,15589,0);H[a+68>>2]=H[a+68>>2]|8;b=e+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;a=!(e|f);b=a?-1:f;qa=a?-1:e;return b|0}e=e+i|0;f=f+g|0;e=f>>>0>>0?e+1|0:e;h=b;b=b-g|0;c=c-(i+(g>>>0>h>>>0)|0)|0;if(!!b&(c|0)>=0|(c|0)>0){continue}break}}b=e+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b}qa=e;return f|0}function Nd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;g=na-16|0;na=g;a:{if(c>>>0<=1){Ba(d,1,3983,0);a=0;break a}if(F[a+212|0]&1){Ba(d,1,12668,0);a=0;break a}a=H[a+180>>2]+N(H[a+228>>2],5644)|0;F[a+5640|0]=I[a+5640|0]|2;Da(b,g+12|0,1);e=H[a+5164>>2];b:{if(!e){f=H[g+12>>2]+1|0;e=Ea(f,8);H[a+5164>>2]=e;if(!e){Ba(d,1,4009,0);a=0;break a}H[a+5160>>2]=f;break b}f=H[g+12>>2];if(f>>>0>2]){break b}h=e;e=f+1|0;f=Ga(h,e<<3);if(!f){Ba(d,1,4009,0);a=0;break a}H[a+5164>>2]=f;h=H[a+5160>>2];i=e-h<<3;if(i){y(f+(h<<3)|0,0,i)}H[a+5160>>2]=e;e=H[a+5164>>2]}h=e;e=H[g+12>>2];if(H[h+(e<<3)>>2]){H[g>>2]=e;Ba(d,1,7063,g);a=0;break a}c=c-1|0;e=Fa(c);a=H[a+5164>>2];f=a+(H[g+12>>2]<<3)|0;H[f>>2]=e;if(!e){Ba(d,1,4009,0);a=0;break a}H[f+4>>2]=c;if(c){B(H[a+(H[g+12>>2]<<3)>>2],b+1|0,c)}a=1}na=g+16|0;return a|0}function Cb(a,b,c){var d=0,e=0,f=0,g=0;e=a+4|0;d=(e+b|0)-1&0-b;b=H[a>>2];if(d+c>>>0<=(b+a|0)-4>>>0){f=H[a+4>>2];g=H[a+8>>2];H[f+8>>2]=g;H[g+4>>2]=f;if((d|0)!=(e|0)){d=d-e|0;f=a-(H[a-4>>2]&-2)|0;e=d+H[f>>2]|0;H[f>>2]=e;H[(f+(e&-4)|0)-4>>2]=e;a=a+d|0;b=b-d|0;H[a>>2]=b}a:{if(c+24>>>0<=b>>>0){e=a+c|0;b=(b-c|0)-8|0;H[e+8>>2]=b;g=e+8|0;H[(g+(b&-4)|0)-4>>2]=b|1;d=H[e+8>>2]-8|0;b:{if(d>>>0<=127){b=(d>>>3|0)-1|0;break b}f=Q(d);b=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break b}b=((d>>>30-f^2)-(f<<1)|0)+71|0;b=b>>>0>=63?63:b}d=b<<4;H[e+12>>2]=d+26432;d=d+26440|0;H[e+16>>2]=H[d>>2];H[d>>2]=g;H[H[e+16>>2]+4>>2]=g;d=H[6866];f=H[6867];e=b&31;if((b&63)>>>0>=32){b=1<>>32-e}H[6866]=g|d;H[6867]=b|f;b=c+8|0;H[a>>2]=b;c=(b&-4)+a|0;break a}c=a+b|0}H[c-4>>2]=b;a=a+4|0}else{a=0}return a}function Td(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;g=na-16|0;na=g;i=H[H[a+96>>2]+16>>2];h=i>>>0<257?1:2;e=(h<<1)+5|0;f=(c>>>0)/(e>>>0)|0;a:{if(!((N(e,f)|0)==(c|0)&c>>>0>=e>>>0)){Ba(d,1,4643,0);a=0;break a}if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}a=0;c=I[e+5640|0];a=c&4?H[e+420>>2]+1|0:a;f=f+a|0;if(f>>>0>=32){H[g>>2]=f;Ba(d,1,7781,g);a=0;break a}F[e+5640|0]=c|4;if(a>>>0>>0){c=(e+N(a,148)|0)+424|0;while(1){Da(b,c,1);b=b+1|0;Da(b,c+4|0,h);b=b+h|0;Da(b,c+8|0,2);d=H[c+8>>2];j=H[e+8>>2];H[c+8>>2]=d>>>0>>0?d:j;Da(b+2|0,c+12|0,1);b=b+3|0;Da(b,c+16|0,h);b=b+h|0;Da(b,g+12|0,1);H[c+36>>2]=H[g+12>>2];d=H[c+16>>2];H[c+16>>2]=d>>>0>>0?d:i;c=c+148|0;b=b+1|0;a=a+1|0;if((f|0)!=(a|0)){continue}break}}H[e+420>>2]=f-1;a=1}na=g+16|0;return a|0}function ib(a){var b=0,c=0,d=0,e=0;a:{if(!a){break a}b=H[a+5164>>2];if(b){c=H[a+5160>>2];if(c){b=0;while(1){d=H[H[a+5164>>2]+(b<<3)>>2];if(d){Ca(d);c=H[a+5160>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+5164>>2]}H[a+5160>>2]=0;Ca(b);H[a+5164>>2]=0}b=H[a+5172>>2];if(b){Ca(b);H[a+5172>>2]=0}b=H[a+5584>>2];if(b){Ca(b);H[a+5584>>2]=0}b=H[a+5612>>2];if(b){Ca(b);H[a+5612>>2]=0}b=H[a+5608>>2];if(b){Ca(b);H[a+5608>>2]=0}b=H[a+5628>>2];if(b){Ca(b);H[a+5636>>2]=0;H[a+5628>>2]=0;H[a+5632>>2]=0}b=H[a+5616>>2];if(b){e=H[a+5620>>2];if(e){c=0;while(1){d=H[b+12>>2];if(d){Ca(d);H[b+12>>2]=0;e=H[a+5620>>2]}b=b+20|0;c=c+1|0;if(e>>>0>c>>>0){continue}break}b=H[a+5616>>2]}Ca(b);H[a+5616>>2]=0}b=H[a+5604>>2];if(b){Ca(b);H[a+5604>>2]=0}b=H[a+5596>>2];if(!b){break a}Ca(b);H[a+5596>>2]=0;H[a+5600>>2]=0}}function td(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-32|0;na=e;a:{if(H[a+72>>2]){Ba(d,2,7015,0);c=1;break a}if((c|0)!=14){c=0;Ba(d,1,14445,0);break a}Da(b,a+16|0,4);Da(b+4|0,a+12|0,4);Da(b+8|0,a+20|0,2);f=H[a+12>>2];b:{g=H[a+16>>2];c=H[a+20>>2];c:{if(!g){break c}c=H[a+20>>2];if(!f){break c}if(c){break b}c=0}H[e+8>>2]=c;H[e+4>>2]=g;H[e>>2]=f;Ba(d,1,14289,e);c=0;break a}if(c>>>0>=16385){c=0;Ba(d,1,14203,0);break a}c=Ea(c,12);H[a+72>>2]=c;if(!c){c=0;Ba(d,1,14240,0);break a}c=1;Da(b+10|0,a+24|0,1);Da(b+11|0,a+28|0,1);f=H[a+28>>2];if((f|0)!=7){H[e+16>>2]=f;Ba(d,4,16272,e+16|0)}Da(b+12|0,a+32|0,1);Da(b+13|0,a+36|0,1);b=H[a>>2];F[b+212|0]=I[b+212|0]&251|(H[a+24>>2]==255?4:0);b=H[a>>2];H[b+240>>2]=H[a+12>>2];H[b+244>>2]=H[a+16>>2];F[a+133|0]=1}na=e+32|0;return c|0}function lc(a,b,c,d){a:{switch(b-9|0){case 0:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=H[b>>2];return;case 6:b=H[c>>2];H[c>>2]=b+4;b=G[b>>1];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 7:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=J[b>>1];H[a+4>>2]=0;return;case 8:b=H[c>>2];H[c>>2]=b+4;b=F[b|0];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 9:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=I[b|0];H[a+4>>2]=0;return;case 16:b=H[c>>2]+7&-8;H[c>>2]=b+8;M[a>>3]=M[b>>3];return;case 17:ra[d|0](a,c);default:return;case 1:case 4:case 14:b=H[c>>2];H[c>>2]=b+4;b=H[b>>2];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 2:case 5:case 11:case 15:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=H[b>>2];H[a+4>>2]=0;return;case 3:case 10:case 12:case 13:break a}}b=H[c>>2]+7&-8;H[c>>2]=b+8;c=H[b+4>>2];H[a>>2]=H[b>>2];H[a+4>>2]=c}function Od(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;g=na-16|0;na=g;a:{if(c>>>0<=1){Ba(d,1,4311,0);a=0;break a}F[a+212|0]=I[a+212|0]|1;Da(b,g+12|0,1);e=H[a+140>>2];b:{if(!e){f=H[g+12>>2]+1|0;e=Ea(f,8);H[a+140>>2]=e;if(!e){Ba(d,1,4337,0);a=0;break a}H[a+136>>2]=f;break b}f=H[g+12>>2];if(f>>>0>2]){break b}h=e;e=f+1|0;f=Ga(h,e<<3);if(!f){Ba(d,1,4337,0);a=0;break a}H[a+140>>2]=f;h=H[a+136>>2];i=e-h<<3;if(i){y(f+(h<<3)|0,0,i)}H[a+136>>2]=e;e=H[a+140>>2]}h=e;e=H[g+12>>2];if(H[h+(e<<3)>>2]){H[g>>2]=e;Ba(d,1,7085,g);a=0;break a}c=c-1|0;e=Fa(c);a=H[a+140>>2];f=a+(H[g+12>>2]<<3)|0;H[f>>2]=e;if(!e){Ba(d,1,4337,0);a=0;break a}H[f+4>>2]=c;if(c){B(H[a+(H[g+12>>2]<<3)>>2],b+1|0,c)}a=1}na=g+16|0;return a|0}function cd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0;e=na-32|0;na=e;d=H[a+28>>2];H[e+16>>2]=d;g=H[a+20>>2];H[e+28>>2]=c;H[e+24>>2]=b;b=(d|0)==(g|0);i=b?1:2;f=e+16|0;b=b?f|8:f;d=g-d|0;H[e+20>>2]=d;g=c+d|0;a:{while(1){b:{c:{d:{if(!rc(Z(H[a+60>>2],b|0,i|0,e+12|0)|0)){f=H[e+12>>2];if((f|0)==(g|0)){break d}if((f|0)>=0){break c}break b}if((g|0)!=-1){break b}}b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;H[a+16>>2]=b+H[a+48>>2];a=c;break a}h=H[b+4>>2];j=h>>>0>>0;d=(j?8:0)+b|0;h=f-(j?h:0)|0;H[d>>2]=h+H[d>>2];b=(j?12:4)+b|0;H[b>>2]=H[b>>2]-h;g=g-f|0;i=i-j|0;b=d;continue}break}H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0;H[a>>2]=H[a>>2]|32;a=0;if((i|0)==2){break a}a=c-H[b+4>>2]|0}na=e+32|0;return a|0}function Ca(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;if(a){b=a-4|0;f=H[b>>2];c=f;d=b;e=H[a-8>>2];a=e&-2;if((a|0)!=(e|0)){d=b-a|0;c=H[d+4>>2];e=H[d+8>>2];H[c+8>>2]=e;H[e+4>>2]=c;c=a+f|0}a=b+f|0;b=H[a>>2];if((b|0)!=H[(a+b|0)-4>>2]){f=H[a+4>>2];a=H[a+8>>2];H[f+8>>2]=a;H[a+4>>2]=f;c=b+c|0}H[d>>2]=c;H[((c&-4)+d|0)-4>>2]=c|1;b=H[d>>2]-8|0;a:{if(b>>>0<=127){a=(b>>>3|0)-1|0;break a}c=Q(b);a=((b>>>29-c^4)-(c<<2)|0)+110|0;if(b>>>0<=4095){break a}a=((b>>>30-c^2)-(c<<1)|0)+71|0;a=a>>>0>=63?63:a}b=a<<4;H[d+4>>2]=b+26432;b=b+26440|0;H[d+8>>2]=H[b>>2];H[b>>2]=d;H[H[d+8>>2]+4>>2]=d;b=H[6866];c=H[6867];d=a&31;if((a&63)>>>0>=32){a=1<>>32-d}H[6866]=e|b;H[6867]=a|c}}function Xc(a,b){a=a|0;b=+b;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;c=a<<3;h=c+28672|0;e=M[c+28704>>3];if(e!=0){g=M[h>>3];b=S(g,b);d=b-g;i=~~d>>>0;if(P(d)>=1){c=~~(d>0?R(T(d*2.3283064365386963e-10),4294967295):U((d-+(~~d>>>0>>>0))*2.3283064365386963e-10))>>>0}else{c=0}j=~~e>>>0;if(P(e)>=1){f=~~(e>0?R(T(e*2.3283064365386963e-10),4294967295):U((e-+(~~e>>>0>>>0))*2.3283064365386963e-10))>>>0}else{f=0}c=fe(i,c,j,f);f=qa;c=c+1|0;f=c?f:f+1|0;d=(+(c>>>0)+ +(f>>>0)*4294967296)*e+g;g=d-b}M[h>>3]=d;fa(a|0,+g)|0;c=(a|0)==2?27:(a|0)==1?26:14;a=c-1|0;a:{if(H[6901]>>>a&1){H[6903]=H[6903]|1<>2];if(a){ra[a|0](c)}}}function nd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=na-16|0;na=e;a:{if(H[a+116>>2]){break a}if(c>>>0<=1){Ba(d,1,8882,0);break a}Da(b,e+12|0,2);f=H[e+12>>2];h=f&65535;if(!h){Ba(d,1,8915,0);break a}if(N(h,6)+2>>>0>c>>>0){Ba(d,1,8882,0);break a}d=Fa(N(f,6));if(!d){break a}c=Fa(8);H[a+116>>2]=c;if(!c){Ca(d);break a}H[c>>2]=d;f=c;c=J[e+12>>1];G[f+4>>1]=c;if(!c){g=1;break a}c=0;while(1){g=e+12|0;Da(b+2|0,g,2);f=d+N(c,6)|0;G[f>>1]=H[e+12>>2];Da(b+4|0,g,2);G[f+2>>1]=H[e+12>>2];b=b+6|0;Da(b,g,2);G[f+4>>1]=H[e+12>>2];g=1;c=c+1|0;if(c>>>0>2]+4>>1]){continue}break}}na=e+16|0;return g|0}function Ib(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;g=na-32|0;na=g;f=H[a+96>>2];a:{if(!f){Ba(d,1,13752,0);e=0;break a}f=Ea(4,H[f+16>>2]);e=0;if(!f){break a}if(b){j=H[a+96>>2];while(1){b:{e=H[(h<<2)+c>>2];c:{if(e>>>0>=K[j+16>>2]){H[g+16>>2]=e;Ba(d,1,2443,g+16|0);break c}i=f+(e<<2)|0;if(!H[i>>2]){break b}H[g>>2]=e;Ba(d,1,3487,g)}Ca(f);e=0;break a}H[i>>2]=1;h=h+1|0;if((h|0)!=(b|0)){continue}break}}Ca(f);Ca(H[a+64>>2]);d:{if(b){d=b<<2;e=Fa(d);H[a+64>>2]=e;if(!e){H[a+60>>2]=0;e=0;break a}if(!d){break d}B(e,c,d);break d}H[a+64>>2]=0}H[a+60>>2]=b;e=1}na=g+32|0;return e|0}function xc(a){a=a|0;var b=0,c=0;if(a){vb(H[a>>2]);H[a>>2]=0;b=H[a+72>>2];if(b){Ca(b);H[a+72>>2]=0}b=H[a+68>>2];if(b){Ca(b);H[a+68>>2]=0}b=H[a+108>>2];if(b){Ca(b);H[a+108>>2]=0}b=H[a+116>>2];if(b){c=H[b>>2];if(c){Ca(c);b=H[a+116>>2];H[b>>2]=0}Ca(b);H[a+116>>2]=0}b=H[a+120>>2];if(b){c=H[b+12>>2];if(c){Ca(c);b=H[a+120>>2];H[b+12>>2]=0}c=H[b+4>>2];if(c){Ca(c);b=H[a+120>>2];H[b+4>>2]=0}c=H[b+8>>2];if(c){Ca(c);b=H[a+120>>2];H[b+8>>2]=0}c=H[b>>2];if(c){Ca(c);b=H[a+120>>2];H[b>>2]=0}Ca(b);H[a+120>>2]=0}b=H[a+4>>2];if(b){ob(b);H[a+4>>2]=0}b=H[a+8>>2];if(b){ob(b);H[a+8>>2]=0}Ca(a)}}function sb(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0;g=na-272|0;na=g;H[g+268>>2]=c;H[g+264>>2]=b;H[g>>2]=a;k=!e;a:{b:{c:{d:{if(!(!c&(b|0)==1)){h=1;e=a;while(1){i=(d<<2)+f|0;a=e-H[i>>2]|0;if((Bc(a,H[g>>2])|0)>0){j=k^-1;k=1;if(!(((d|0)<2|j)&1)){i=H[i-8>>2];j=e-8|0;if((Bc(j,a)|0)>=0){break d}if((Bc(j-i|0,a)|0)>=0){break d}}H[((h&63)<<2)+g>>2]=a;b=qc(b,c);tb(g+264|0,b);h=h+1|0;d=b+d|0;e=a;c=H[g+268>>2];b=H[g+264>>2];if(c|(b|0)!=1){continue}break c}break}if(!k){break a}a=e;b=h&63;break b}if(e){break a}b=1;break b}a=e}b=h&63}pc(g,b);Ab(a,d,f)}na=g+272|0}function Yd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;e=na-16|0;na=e;if(H[a+8>>2]==16){g=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{g=H[a+12>>2]}h=H[a+96>>2];f=K[h+16>>2]<257?1:2;a:{if(f>>>0>=c>>>0){c=0;Ba(d,1,4669,0);break a}H[e+12>>2]=(f^-1)+c;Da(b,e+8|0,f);i=H[e+8>>2];if(i>>>0>=K[h+16>>2]){c=0;Ba(d,1,14067,0);break a}c=1;b=b+f|0;Da(b,H[g+5584>>2]+N(i,1080)|0,1);if(!Dc(a,H[e+8>>2],b+1|0,e+12|0,d)){c=0;Ba(d,1,4669,0);break a}if(!H[e+12>>2]){break a}c=0;Ba(d,1,4669,0)}na=e+16|0;return c|0}function zc(a,b){var c=0,d=0,e=0,f=0,g=0;f=na-32|0;na=f;c=H[a+60>>2];a:{b:{if(c){g=1;while(1){e=H[H[a+64>>2]+(d<<2)>>2];if(!H[(H[H[a+100>>2]+24>>2]+N(e,52)|0)+44>>2]){H[f+16>>2]=e;Ba(b,2,7604,f+16|0);g=0;c=H[a+60>>2]}d=d+1|0;if(c>>>0>d>>>0){continue}break}break b}g=1;c=H[a+100>>2];e=1;if(!H[c+16>>2]){break a}while(1){if(!H[(H[c+24>>2]+N(d,52)|0)+44>>2]){H[f>>2]=d;Ba(b,2,7604,f);g=0;c=H[a+100>>2]}d=d+1|0;if(d>>>0>2]){continue}break}}e=1;if(g){break a}Ba(b,1,2897,0);e=0}na=f+32|0;return e}function od(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;f=na-16|0;na=f;e=H[a+120>>2];a:{if(!e){Ba(d,1,8836,0);c=0;break a}if(H[e+12>>2]){Ba(d,1,11598,0);c=0;break a}e=I[e+18|0];g=e<<2;if(g>>>0>c>>>0){Ba(d,1,8803,0);c=0;break a}g=Fa(g);c=0;if(!g){break a}if(e){d=0;while(1){c=f+12|0;Da(b,c,2);h=g+(d<<2)|0;G[h>>1]=H[f+12>>2];Da(b+2|0,c,1);F[h+2|0]=H[f+12>>2];Da(b+3|0,c,1);F[h+3|0]=H[f+12>>2];b=b+4|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}}H[H[a+120>>2]+12>>2]=g;c=1}na=f+16|0;return c|0}function Jd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;e=na-16|0;na=e;g=H[H[a+96>>2]+16>>2];a:{if((g+2|0)!=(c|0)){Ba(d,1,4617,0);break a}Da(b,e+12|0,2);if(H[e+12>>2]!=(g|0)){Ba(d,1,4617,0);break a}if(!g){f=1;break a}c=b+2|0;a=H[H[a+96>>2]+24>>2];b=0;while(1){Da(c,e+8|0,1);f=H[e+8>>2];h=f&127;i=h+1|0;H[a+24>>2]=i;H[a+32>>2]=f>>>7&1;if(h>>>0>=31){H[e+4>>2]=i;H[e>>2]=b;Ba(d,1,15402,e);f=0;break a}a=a+52|0;f=1;c=c+1|0;b=b+1|0;if((g|0)!=(b|0)){continue}break}}na=e+16|0;return f|0}function oc(a,b,c,d,e){var f=0,g=0;f=na-208|0;na=f;H[f+204>>2]=c;c=f+160|0;y(c,0,40);H[f+200>>2]=H[f+204>>2];if((nc(0,b,f+200|0,f+80|0,c,d,e)|0)>=0){c=H[a>>2];H[a>>2]=c&-33;a:{b:{c:{if(!H[a+48>>2]){H[a+48>>2]=80;H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0;g=H[a+44>>2];H[a+44>>2]=f;break c}if(H[a+16>>2]){break b}}if(uc(a)){break a}}nc(a,b,f+200|0,f+80|0,f+160|0,d,e)}if(g){ra[H[a+36>>2]](a,0,0)|0;H[a+48>>2]=0;H[a+44>>2]=g;H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0}H[a>>2]=H[a>>2]|c&32}na=f+208|0}function Vd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=na-16|0;na=e;a:{b:{h=e+8|0;c:{if(K[H[a+96>>2]+16>>2]<=256){if(c){f=-1;g=1;break c}Ba(d,1,4695,0);a=0;break a}if(c>>>0<=1){break b}f=-2;g=2}Da(b,h,g);H[e+12>>2]=c+f;c=H[e+8>>2];f=H[H[a+96>>2]+16>>2];if(c>>>0>=f>>>0){H[e+4>>2]=f;H[e>>2]=c;Ba(d,1,7712,e);a=0;break a}if(!Cc(a,c,b+g|0,e+12|0,d)){Ba(d,1,4695,0);a=0;break a}a=1;if(!H[e+12>>2]){break a}Ba(d,1,4695,0);a=0;break a}Ba(d,1,4695,0);a=0}na=e+16|0;return a|0}function qd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=H[a+32>>2];H[a+36>>2]=f;a:{e=H[a+48>>2];if(e){while(1){e=ra[H[a+20>>2]](f,e,H[a>>2])|0;if((e|0)==-1){break a}f=e+H[a+36>>2]|0;H[a+36>>2]=f;e=H[a+48>>2]-e|0;H[a+48>>2]=e;if(e){continue}break}f=H[a+32>>2]}H[a+48>>2]=0;H[a+36>>2]=f;if(!(ra[H[a+28>>2]](b,c,H[a>>2])|0)){H[a+68>>2]=H[a+68>>2]|8;return 0}H[a+56>>2]=b;H[a+60>>2]=c;return 1}H[a+68>>2]=H[a+68>>2]|8;Ba(d,4,15604,0);H[a+68>>2]=H[a+68>>2]|8;return 0}function Ba(a,b,c,d){var e=0,f=0;e=na-528|0;na=e;a:{if(!a){break a}b:{c:{switch(b-1|0){case 0:b=a+12|0;break b;case 1:b=a+16|0;a=a+4|0;break b;case 3:break c;default:break a}}b=a+20|0;a=a+8|0}b=H[b>>2];if(!b|!c){break a}f=H[a>>2];y(e,0,512);H[e+524>>2]=d;a=na-160|0;na=a;H[a+148>>2]=e;H[a+152>>2]=511;y(a,0,144);H[a+76>>2]=-1;H[a+36>>2]=97;H[a+80>>2]=-1;H[a+44>>2]=a+159;H[a+84>>2]=a+148;F[e|0]=0;oc(a,c,d,95,96);na=a+160|0;F[e+511|0]=0;ra[b|0](e,f)}na=e+528|0}function cc(a){var b=0,c=0,d=0;a:{if(!a){break a}b=H[a+8>>2];if(!b){break a}c=b&3;a=H[a+12>>2];if(b>>>0>=4){d=b&-4;b=0;while(1){H[a+60>>2]=0;H[a+52>>2]=999;H[a+56>>2]=0;H[a+44>>2]=0;H[a+36>>2]=999;H[a+40>>2]=0;H[a+28>>2]=0;H[a+20>>2]=999;H[a+24>>2]=0;H[a+12>>2]=0;H[a+4>>2]=999;H[a+8>>2]=0;a=a- -64|0;b=b+4|0;if((d|0)!=(b|0)){continue}break}if(!c){break a}}b=0;while(1){H[a+12>>2]=0;H[a+4>>2]=999;H[a+8>>2]=0;a=a+16|0;b=b+1|0;if((c|0)!=(b|0)){continue}break}}}function vd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;if(H[a+100>>2]!=1){Ba(d,1,11401,0);return 0}a:{if(c>>>0<=7){break a}Da(b,a+56|0,4);Da(b+4|0,a+60|0,4);if(c&3){break a}c=c-8|0;e=c>>>2|0;H[a+64>>2]=e;b:{if(!c){break b}c=Ea(e,4);H[a+68>>2]=c;if(!c){Ba(d,1,2235,0);return 0}if(!H[a+64>>2]){break b}d=b+8|0;c=0;while(1){Da(d,H[a+68>>2]+(c<<2)|0,4);d=d+4|0;c=c+1|0;if(c>>>0>2]){continue}break}}H[a+100>>2]=H[a+100>>2]|2;return 1}Ba(d,1,5955,0);return 0}function Wd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;H[e+12>>2]=c;a:{if(!(!Cc(a,0,b,e+12|0,d)|H[e+12>>2])){if(H[a+8>>2]==16){b=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{b=H[a+12>>2]}f=1;if(K[H[a+96>>2]+16>>2]<2){break a}c=H[b+5584>>2];g=c+28|0;d=c;b=1;while(1){H[d+1104>>2]=H[c+24>>2];H[d+1884>>2]=H[c+804>>2];B(d+1108|0,g,776);d=d+1080|0;b=b+1|0;if(b>>>0>2]+16>>2]){continue}break}break a}Ba(d,1,4591,0)}na=e+16|0;return f|0}function ac(a,b,c,d){var e=0,f=0,g=0;f=na-128|0;na=f;e=f;b=H[b+12>>2]+(c<<4)|0;c=H[b>>2];if(c){while(1){H[e>>2]=b;e=e+4|0;b=c;c=H[c>>2];if(c){continue}break}}while(1){c=H[b+8>>2];if((g|0)>(c|0)){H[b+8>>2]=g;c=g}a:{if((c|0)>=(d|0)){break a}while(1){if(H[b+4>>2]<=(c|0)){break a}b:{if(Qa(a,1)){H[b+4>>2]=c;break b}c=c+1|0}if((c|0)<(d|0)){continue}break}}H[b+8>>2]=c;if((e|0)!=(f|0)){e=e-4|0;b=H[e>>2];g=c;continue}break}na=f+128|0;return H[b+4>>2]<(d|0)}function Dd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;a:{if(!Va(H[a+8>>2],46,c)){break a}h=H[a+8>>2];d=H[h>>2];f=H[h+8>>2];b:{if(d){e=1;c:{if((d|0)!=1){j=d&1;d=d&-2;while(1){g=0;d:{if(!e){break d}g=0;if(!(ra[H[f>>2]](a,b,c)|0)){break d}g=(ra[H[f+4>>2]](a,b,c)|0)!=0}e=g;f=f+8|0;i=i+2|0;if((d|0)!=(i|0)){continue}break}if(!j){break c}}if(!e){e=0;break c}e=(ra[H[f>>2]](a,b,c)|0)!=0}Oa(h);if(!e){break a}break b}Oa(h)}k=1}return k|0}function Xd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;g=H[H[a+96>>2]+16>>2];f=g>>>0<257?1:2;a:{if((f+2|0)!=(c|0)){a=0;Ba(d,1,4285,0);break a}if(H[a+8>>2]==16){c=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{c=H[a+12>>2]}Da(b,e+12|0,f);a=1;b=b+f|0;Da(b,e+8|0,1);f=H[e+12>>2];if(f>>>0>=g>>>0){H[e+4>>2]=g;H[e>>2]=f;Ba(d,1,14923,e);a=0;break a}Da(b+1|0,(H[c+5584>>2]+N(f,1080)|0)+808|0,1)}na=e+16|0;return a|0}function ae(){var a=0,b=0,c=0;while(1){b=a<<4;c=b+26432|0;H[b+26436>>2]=c;H[b+26440>>2]=c;a=a+1|0;if((a|0)!=64){continue}break}sc(48);a=na-16|0;na=a;a:{if(la(a+12|0,a+8|0)|0){break a}b=ub((H[a+12>>2]<<2)+4|0);H[6870]=b;if(!b){break a}b=ub(H[a+8>>2]);if(b){c=H[6870];H[c+(H[a+12>>2]<<2)>>2]=0;if(!(ka(c|0,b|0)|0)){break a}}H[6870]=0}na=a+16|0;H[6885]=8192;H[6883]=94272;H[6877]=42;H[6884]=65536}function db(a,b,c){var d=0;if(!H[a+12>>2]){ra[b|0](c,H[a+36>>2]);return}d=Fa(8);a:{if(!d){break a}H[d+4>>2]=c;H[d>>2]=b;b=Fa(8);if(!b){Ca(d);return}H[b>>2]=d;c=N(H[a+4>>2],100);H[a+40>>2]=c;while(1){if((c|0)>2]){continue}break}H[b+4>>2]=H[a+20>>2];H[a+20>>2]=b;H[a+24>>2]=H[a+24>>2]+1;b=H[a+28>>2];if(!b){break a}H[H[b>>2]+8>>2]=0;H[a+28>>2]=H[b+4>>2];H[a+32>>2]=H[a+32>>2]-1;Ca(b)}}function La(a,b,c){var d=0,e=0,f=0;if(!(I[a|0]&32)){a:{d=H[a+16>>2];if(!d){if(uc(a)){break a}d=H[a+16>>2]}e=H[a+20>>2];if(d-e>>>0>>0){ra[H[a+36>>2]](a,b,c)|0;break a}b:{c:{if(!c|H[a+80>>2]<0){break c}d=c;while(1){f=b+d|0;if(I[f-1|0]!=10){d=d-1|0;if(d){continue}break c}break}if(ra[H[a+36>>2]](a,b,d)>>>0>>0){break a}c=c-d|0;e=H[a+20>>2];break b}f=b}$a(e,f,c);H[a+20>>2]=H[a+20>>2]+c}}}function Ec(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;H[a+184>>2]=b;d=H[a+96>>2];a:{if(!d){break a}f=H[d+24>>2];if(!f){break a}e=H[a+12>>2];if(!e|!H[e+5584>>2]){break a}e=H[d+16>>2];if(!e){return 1}d=0;while(1){if(K[(H[H[a+12>>2]+5584>>2]+N(d,1080)|0)+4>>2]<=b>>>0){Ba(c,1,9177,0);return 0}H[(N(d,52)+f|0)+40>>2]=b;g=1;d=d+1|0;if((e|0)!=(d|0)){continue}break}}return g|0}function xb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;if(a){c=H[a+4>>2];if(c){Ca(c);H[a+4>>2]=0}if(b){c=a;while(1){d=H[c+200>>2];if(d){e=0;f=H[c+196>>2];if(f){while(1){g=H[d+12>>2];if(g){Ca(g);H[d+12>>2]=0;f=H[c+196>>2]}d=d+16|0;e=e+1|0;if(e>>>0>>0){continue}break}d=H[c+200>>2]}Ca(d);H[c+200>>2]=0}c=c+240|0;h=h+1|0;if((h|0)!=(b|0)){continue}break}}Ca(a)}}function Yc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=H[a+84>>2];f=H[e>>2];d=H[e+4>>2];h=H[a+28>>2];g=H[a+20>>2]-h|0;g=d>>>0>>0?d:g;if(g){$a(f,h,g);f=g+H[e>>2]|0;H[e>>2]=f;d=H[e+4>>2]-g|0;H[e+4>>2]=d}d=c>>>0>d>>>0?d:c;if(d){$a(f,b,d);f=d+H[e>>2]|0;H[e>>2]=f;H[e+4>>2]=H[e+4>>2]-d}F[f|0]=0;b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;return c|0}function ld(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=H[c+8>>2];d=e>>>0<=1?1:e;f=H[c+4>>2];g=f-H[c>>2]|0;while(1){h=d;d=d<<1;if(h-g>>>0>>0){continue}break}if((e|0)!=(h|0)){d=Fa(h);if(!d){return-1}e=H[c>>2];if(e){if(g){B(d,e,g)}Ca(H[c>>2])}H[c+8>>2]=h;H[c>>2]=d;f=d+g|0;H[c+4>>2]=f}if(b){B(f,a,b)}H[c+4>>2]=H[c+4>>2]+b;return b|0}function Vb(a){H[a+100>>2]=20832;H[a+96>>2]=20832;H[a+92>>2]=20832;H[a+88>>2]=20832;H[a+84>>2]=20832;H[a+80>>2]=20832;H[a+76>>2]=20832;H[a+72>>2]=20832;H[a+68>>2]=20832;H[a+64>>2]=20832;H[a+60>>2]=20832;H[a+56>>2]=20832;H[a+52>>2]=20832;H[a+48>>2]=20832;H[a+44>>2]=20832;H[a+40>>2]=20832;H[a+36>>2]=20832;H[a+32>>2]=20832;H[a+28>>2]=20832}function Qa(a,b){var c=0,d=0,e=0,f=0;if((b|0)<=0){return 0}c=H[a+12>>2];d=H[a+16>>2];while(1){e=b;a:{if(d){break a}c=c<<8&65280;H[a+12>>2]=c;d=(c|0)==65280?7:8;H[a+16>>2]=d;b=H[a+8>>2];if(b>>>0>=K[a+4>>2]){break a}H[a+8>>2]=b+1;c=I[b|0]|c;H[a+12>>2]=c}d=d-1|0;H[a+16>>2]=d;b=e-1|0;f=(c>>>d&1)<>>0>1){continue}break}return f}function rd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=na-16|0;na=f;e=H[a+24>>2];if((e|0)!=255){H[f>>2]=e;Ba(d,2,2678,f)}a:{b:{if(H[a+20>>2]==(c|0)){if(c){break b}e=1;break a}e=0;Ba(d,1,14510,0);break a}c=0;while(1){e=1;Da(b,(H[a+72>>2]+N(c,12)|0)+8|0,1);b=b+1|0;c=c+1|0;if(c>>>0>2]){continue}break}}na=f+16|0;return e|0}function Da(a,b,c){var d=0,e=0;H[b>>2]=0;a:{if(!c){break a}d=c&3;b=b+c|0;if(c>>>0>=4){e=c&-4;c=0;while(1){F[b-1|0]=I[a|0];F[b-2|0]=I[a+1|0];F[b-3|0]=I[a+2|0];b=b-4|0;F[b|0]=I[a+3|0];a=a+4|0;c=c+4|0;if((e|0)!=(c|0)){continue}break}if(!d){break a}}c=0;while(1){b=b-1|0;F[b|0]=I[a|0];a=a+1|0;c=c+1|0;if((d|0)!=(c|0)){continue}break}}}function Pd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;a:{if(!c){Ba(d,1,4106,0);a=0;break a}Da(b,e+12|0,1);f=c-1|0;a=1;if(!f){break a}a=0;c=0;while(1){b=b+1|0;Da(b,e+8|0,1);g=H[e+8>>2];c=g<<24>>31&(g&127|c)<<7;a=a+1|0;if((f|0)!=(a|0)){continue}break}a=1;if(!c){break a}Ba(d,1,4106,0);a=0}na=e+16|0;return a|0}function Ab(a,b,c){var d=0,e=0,f=0,g=0,h=0;d=na-256|0;na=d;H[d>>2]=a;f=1;if((b|0)>=2){while(1){a:{g=H[d>>2];a=a-8|0;h=b-2|0;e=a-H[(h<<2)+c>>2]|0;if((Bc(g,e)|0)>=0){if((Bc(g,a)|0)>=0){break a}}g=e;e=(Bc(e,a)|0)>=0;a=e?g:a;H[((f&63)<<2)+d>>2]=a;f=f+1|0;b=e?b-1|0:h;if((b|0)>1){continue}}break}a=f&63}else{a=1}pc(d,a);na=d+256|0}function _b(a,b,c,d){var e=0,f=0,g=O(0),h=0,i=O(0),j=0,k=O(0);if(d){while(1){e=f<<2;h=e+b|0;i=L[h>>2];j=a+e|0;g=L[j>>2];e=c+e|0;k=L[e>>2];L[j>>2]=O(k*O(1.4019999504089355))+g;L[h>>2]=O(g+O(i*O(-.3441300094127655)))+O(k*O(-.714139997959137));L[e>>2]=g+O(i*O(1.7719999551773071));f=f+1|0;if((f|0)!=(d|0)){continue}break}}}function tc(a){var b=0;b=1;a:{if((a|0)>=1024){b=898846567431158e293;if(a>>>0<2047){a=a-1023|0;break a}b=Infinity;a=(a>>>0>=3069?3069:a)-2046|0;break a}if((a|0)>-1023){break a}b=2004168360008973e-307;if(a>>>0>4294965304){a=a+969|0;break a}b=0;a=(a>>>0<=4294964336?-2960:a)+1938|0}u(0,0);u(1,a+1023<<20);return b*+w()}function pc(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;c=8;f=na-256|0;na=f;if(b>>>0>=2){h=(b<<2)+a|0;H[h>>2]=f;while(1){e=c>>>0>=256?256:c;$a(H[h>>2],H[a>>2],e);d=0;while(1){g=(d<<2)+a|0;d=d+1|0;$a(H[g>>2],H[(d<<2)+a>>2],e);H[g>>2]=H[g>>2]+e;if((b|0)!=(d|0)){continue}break}c=c-e|0;if(c){continue}break}}na=f+256|0}function _a(a,b,c){var d=0,e=0,f=0,g=0;if((b|0)==1|b>>>0>1){while(1){d=a;e=b;c=c-1|0;a=fe(a,b,10,0);b=qa;f=c,g=be(a,b,246)+d|48,F[f|0]=g;if(e>>>0>9){continue}break}}if(!b&a>>>0>=10|b){while(1){c=c-1|0;b=a;a=(b>>>0)/10|0;F[c|0]=b+N(a,246)|48;if(b>>>0>99){continue}break}}if(a){c=c-1|0;F[c|0]=a|48}return c}function Pc(a){a=a|0;var b=0,c=0,d=0,e=0;b=H[a+24>>2];if(b){c=H[a+28>>2];e=(c>>>0)/52|0;if(c>>>0>=52){while(1){c=H[b>>2];if(c){Ca(c-1|0);H[b>>2]=0}c=H[b+4>>2];if(c){Ca(c);H[b+4>>2]=0}c=H[b+8>>2];if(c){Ca(c);H[b+8>>2]=0}b=b+52|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}b=H[a+24>>2]}Ca(b);H[a+24>>2]=0}}function Qc(a){a=a|0;var b=0,c=0,d=0,e=0;b=H[a+24>>2];if(b){c=H[a+28>>2];e=(c>>>0)/68|0;if(c>>>0>=68){while(1){c=H[b>>2];if(c){Ca(c);H[b>>2]=0}c=H[b+4>>2];if(c){Ca(c);H[b+4>>2]=0}Ca(H[b+60>>2]);H[b+60>>2]=0;b=b+68|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}b=H[a+24>>2]}Ca(b);H[a+24>>2]=0}}function Vc(a,b){a=a|0;b=b|0;var c=0,d=0;c=H[a+32>>2];b=H[a+28>>2];d=b+8|0;if(c>>>0>=d>>>0){while(1){mb(a,H[a+24>>2]+(b<<2)|0,H[a+20>>2],8);c=H[a+32>>2];b=d;d=b+8|0;if(c>>>0>=d>>>0){continue}break}}if(b>>>0>>0){mb(a,H[a+24>>2]+(b<<2)|0,H[a+20>>2],c-b|0)}Ca(H[a>>2]);Ca(a)}function wd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=na-16|0;na=e;a:{if(H[a+100>>2]){Ba(d,1,11302,0);a=0;break a}if((c|0)!=4){Ba(d,1,5991,0);a=0;break a}Da(b,e+12|0,4);if(H[e+12>>2]!=218793738){Ba(d,1,5007,0);a=0;break a}H[a+100>>2]=H[a+100>>2]|1;a=1}na=e+16|0;return a|0}function Va(a,b,c){var d=0,e=0;a:{d=H[a>>2];e=H[a+4>>2];b:{if((d|0)!=(e|0)){e=H[a+8>>2];break b}d=e+10|0;H[a+4>>2]=d;e=Ga(H[a+8>>2],d<<2);if(!e){break a}H[a+8>>2]=e;d=H[a>>2]}H[(d<<2)+e>>2]=b;H[a>>2]=d+1;return 1}Ca(H[a+8>>2]);H[a>>2]=0;H[a+4>>2]=0;Ba(c,1,6123,0);return 0}function mc(a){var b=0,c=0,d=0,e=0,f=0;d=H[a>>2];b=F[d|0]-48|0;if(b>>>0>9){return 0}while(1){e=-1;if(c>>>0<=214748364){c=N(c,10);e=(c^2147483647)>>>0>>0?-1:c+b|0}b=d+1|0;H[a>>2]=b;f=F[d+1|0];c=e;d=b;b=f-48|0;if(b>>>0<10){continue}break}return c} +function kc(a,b){var c=0,d=0,e=0;x(+a);d=s(1)|0;e=s(0)|0;c=d>>>20&2047;if((c|0)!=2047){if(!c){if(a==0){c=0}else{a=kc(a*0x10000000000000000,b);c=H[b>>2]+-64|0}H[b>>2]=c;return a}H[b>>2]=c-1022;u(0,e|0);u(1,d&-2146435073|1071644672);a=+w()}return a}function Ta(a){var b=0,c=0,d=0;if(a){b=H[a+24>>2];if(b){c=H[a+16>>2];if(c){b=0;while(1){d=H[(H[a+24>>2]+N(b,52)|0)+44>>2];if(d){Ca(d);c=H[a+16>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+24>>2]}Ca(b)}b=H[a+28>>2];if(b){Ca(b)}Ca(a)}}function Ka(a,b,c,d,e,f,g,h){var i=0,j=0;i=+O(e-a|0);e=~~(i*1.402)+c|0;H[f>>2]=(e|0)>=0?(b|0)>(e|0)?e:b:0;j=+O(d-a|0);a=c-~~(j*.344+i*.714)|0;H[g>>2]=(a|0)>=0?(a|0)<(b|0)?a:b:0;a=~~(j*1.772)+c|0;H[h>>2]=(a|0)>=0?(a|0)<(b|0)?a:b:0}function kd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=H[c+4>>2];e=H[c>>2]+H[c+8>>2]|0;if((d|0)==(e|0)){qa=-1;return-1}H[c+4>>2]=a+d;f=a;c=e-d|0;d=c;e=a>>>0>>0;a=c>>31;c=e&(a|0)>=(b|0)|(a|0)>(b|0);d=c?f:d;qa=c?b:a;return d|0}function ee(a,b,c,d){var e=0,f=0,g=0,h=0;f=b^d;g=f>>31;e=b>>31;a=a^e;h=a-e|0;e=(b^e)-((a>>>0>>0)+e|0)|0;a=d>>31;b=c^a;f=f>>31;a=fe(h,e,b-a|0,(a^d)-((a>>>0>b>>>0)+a|0)|0)^f;b=a-f|0;qa=(g^qa)-((a>>>0>>0)+g|0)|0;return b}function Ua(a){var b=0,c=0,d=0,e=0;if(a){b=H[a+20>>2];c=H[a+16>>2];if(N(b,c)){while(1){e=H[H[a+24>>2]+(d<<2)>>2];if(e){Ca(e);c=H[a+16>>2];b=H[a+20>>2]}d=d+1|0;if(d>>>0>>0){continue}break}}Ca(H[a+24>>2]);Ca(a)}}function $b(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0;if(d){while(1){e=f<<2;g=e+a|0;h=c+e|0;i=H[h>>2];j=b+e|0;k=H[j>>2];e=H[g>>2]-(i+k>>2)|0;H[g>>2]=e+i;H[j>>2]=e;H[h>>2]=e+k;f=f+1|0;if((f|0)!=(d|0)){continue}break}}}function ab(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];e=ra[H[a+28>>2]](b,c,H[a>>2])|0;d=H[a+68>>2];if(!e){H[a+68>>2]=d|4;return 0}H[a+56>>2]=b;H[a+60>>2]=c;H[a+68>>2]=d&-5;return 1}function Na(a,b,c,d,e){var f=0;f=na-256|0;na=f;if(!(e&73728|(c|0)<=(d|0))){d=c-d|0;c=d>>>0<256;vc(f,b,c?d:256);if(!c){while(1){La(a,f,256);d=d-256|0;if(d>>>0>255){continue}break}}La(a,f,d)}na=f+256|0}function be(a,b,c){var d=0,e=0,f=0,g=0,h=0;e=c>>>16|0;d=a>>>16|0;h=N(e,d);f=c&65535;a=a&65535;g=N(f,a);d=(g>>>16|0)+N(d,f)|0;a=(d&65535)+N(a,e)|0;qa=h+N(b,c)+(d>>>16)+(a>>>16)|0;return g&65535|a<<16}function uc(a){var b=0;b=H[a+72>>2];H[a+72>>2]=b-1|b;b=H[a>>2];if(b&8){H[a>>2]=b|32;return-1}H[a+4>>2]=0;H[a+8>>2]=0;b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;H[a+16>>2]=b+H[a+48>>2];return 0}function ec(a){var b=0,c=0;a:{if(I[a+12|0]==255){H[a+12>>2]=65280;H[a+16>>2]=7;b=H[a+8>>2];c=0;if(b>>>0>=K[a+4>>2]){break a}H[a+8>>2]=b+1;H[a+12>>2]=I[b|0]|65280}H[a+16>>2]=0;c=1}return c}function md(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=H[c+4>>2];d=H[c>>2]+H[c+8>>2]|0;if((e|0)==(d|0)){return-1}d=d-e|0;b=b>>>0>d>>>0?d:b;if(b){B(a,e,b)}H[c+4>>2]=b+H[c+4>>2];return b|0}function Wc(a,b){a=a|0;b=b|0;b=H[a+28>>2];if(b>>>0>2]){while(1){Yb(a,H[a+24>>2]+(N(H[a+20>>2],b)<<2)|0);b=b+1|0;if(b>>>0>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function rb(a,b){var c=0,d=0;a:{b:{if(b>>>0<=31){d=H[a>>2];c=H[a+4>>2];break b}c=H[a>>2];H[a+4>>2]=c;H[a>>2]=0;b=b-32|0;if(!b){break a}}H[a>>2]=d<>2]=c<>>32-b}}function Bc(a,b){a=a|0;b=b|0;var c=0,d=0;c=H[a>>2];d=H[b>>2];a=H[a+4>>2];b=H[b+4>>2];return(c>>>0>d>>>0&(a|0)>=(b|0)|(a|0)>(b|0))-(c>>>0>>0&(a|0)<=(b|0)|(a|0)<(b|0))|0}function tb(a,b){var c=0,d=0;c=H[a+4>>2];a:{b:{if(b>>>0<=31){d=c;c=H[a>>2];break b}H[a+4>>2]=0;H[a>>2]=c;b=b-32|0;if(!b){break a}}H[a+4>>2]=d>>>b;H[a>>2]=d<<32-b|c>>>b}}function dd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=na-16|0;na=e;a=rc(ea(H[a+60>>2],b|0,c|0,d&255,e+8|0)|0);na=e+16|0;qa=a?-1:H[e+12>>2];return(a?-1:H[e+8>>2])|0}function Hb(a,b){F[b+7|0]=I[a|0];F[b+6|0]=I[a+1|0];F[b+5|0]=I[a+2|0];F[b+4|0]=I[a+3|0];F[b+3|0]=I[a+4|0];F[b+2|0]=I[a+5|0];F[b+1|0]=I[a+6|0];F[b|0]=I[a+7|0]}function Pa(a){var b=0,c=0,d=0,e=0;b=H[a+12>>2];e=b;c=H[a+8>>2];if(!(b|c)){qa=0;return 0}d=H[a+56>>2];b=c-d|0;qa=e-(H[a+60>>2]+(c>>>0>>0)|0)|0;return b}function ic(a,b){var c=0;c=na-16|0;na=c;if(a){if(b&3){a=28}else{a=fb(b,a);H[c+12>>2]=a;a=a?0:48}a=a?0:H[c+12>>2]}else{a=0}na=c+16|0;return a}function Rc(a){a=a|0;var b=0;if(a){b=H[a+116>>2];if(b){Ca(b);H[a+116>>2]=0}b=H[a+120>>2];if(b){Ca(b);H[a+120>>2]=0}Ca(H[a+148>>2]);Ca(a)}} +function Md(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(H[H[a+96>>2]+16>>2]<<2!=(c|0)){Ba(d,1,4464,0);a=0}else{a=1}return a|0}function gc(){var a=0,b=0;a=Ea(1,44);a:{if(a){H[a+16>>2]=0;b=Ea(1,8);H[a+36>>2]=b;if(b){break a}Ca(a)}a=0}return a}function Mb(a,b){a=a|0;b=b|0;if(!(!a|!b)){H[a+188>>2]=H[b+4>>2];H[a+184>>2]=H[b>>2];H[a+248>>2]=H[b+8248>>2]&2}}function pb(){var a=0,b=0;a=Ea(1,12);if(a){H[a+4>>2]=10;b=Ea(10,4);H[a+8>>2]=b;if(b){return a}Ca(a)}return 0}function Ad(a,b,c,d,e,f,g,h,i,j,k){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;D()}function eb(a){var b=0;if(a){b=H[a+4>>2];if(b){ra[b|0](H[a>>2])}Ca(H[a+32>>2]);H[a+32>>2]=0;Ca(a)}}function Lb(a,b){a=a|0;b=b|0;a:{if(!a){break a}H[a+208>>2]=b;if(!b){break a}F[a+92|0]=I[a+92|0]|8}}function jd(a,b,c){a=a|0;b=b|0;c=c|0;b=H[c+8>>2];H[c+4>>2]=H[c>>2]+(a>>>0>b>>>0?b:a);return 1}function Qd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(c){a=1}else{Ba(d,1,4375,0);a=0}return a|0}function jc(a,b){if(!((b&-128)==57216|b>>>0<=127)){H[6605]=25;return-1}F[a|0]=b;return 1}function jb(a){H[a+16>>2]=0;H[a+20>>2]=0;H[a+8>>2]=0;H[a+12>>2]=0;H[a>>2]=0;H[a+4>>2]=0}function Ra(a){if(H[a+12>>2]){H[a+40>>2]=0;while(1){if(H[a+24>>2]>0){continue}break}}}function Nc(a,b,c){a=a|0;b=b|0;c=c|0;return!H[a+8>>2]&(H[a+216>>2]!=0&H[a+220>>2]!=0)}function Fc(a,b){F[b+3|0]=I[a|0];F[b+2|0]=I[a+1|0];F[b+1|0]=I[a+2|0];F[b|0]=I[a+3|0]}function gb(a){if(a){ra[H[(H[a+76>>2]?20:16)+a>>2]](H[a+48>>2]);H[a+48>>2]=0;Ca(a)}}function Fd(a,b){a=a|0;b=b|0;Mb(H[a>>2],b);F[a+124|0]=0;H[a+128>>2]=H[b+8248>>2]&1}function Ea(a,b){if(!a|!b){a=0}else{b=N(a,b);a=fb(8,b);if(a){vc(a,0,b)}}return a}function Bb(a,b){var c=0;c=na-16|0;na=c;H[c+12>>2]=b;oc(26104,a,b,0,0);na=c+16|0}function qc(a,b){if((a|0)!=1){return de(a-1|0)}if(!b){return 0}return de(b)|32}function he(a){var b=0;while(1){if(a){a=a-1&a;b=b+1|0;continue}break}return b} +function Za(a){var b=0;if(a){b=H[a+12>>2];if(b){Ca(b);H[a+12>>2]=0}Ca(a)}}function Kc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;D()}function yd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Ib(H[a>>2],b,c,d)|0}function zd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;D()}function Nb(a){return H[a+12>>2]==H[a+4>>2]|H[a+8>>2]==H[a>>2]}function xd(a,b,c){a=a|0;b=b|0;c=c|0;return Ec(H[a>>2],b,c)|0}function ob(a){var b=0;if(a){b=H[a+8>>2];if(b){Ca(b)}Ca(a)}}function $c(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;qa=0;return 0}function Ya(a,b,c,d,e,f,g,h){return Zb(a,b,c,d,e,f,g,h,0)}function Gc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;D()}function hc(a,b,c,d){return ra[H[a+44>>2]](a,b,c,d)|0}function Wa(a,b,c){H[((b<<2)+a|0)+28>>2]=(c<<5)+20832}function Eb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return 1}function qb(a,b,c){return ra[H[a+40>>2]](a,b,0,c)|0}function de(a){if(a){return 31-Q(a-1^a)|0}return 32}function Kd(a,b,c){a=a|0;b=b|0;c=c|0;qa=-1;return-1}function Ha(a,b,c,d,e,f,g,h){Zb(a,b,c,d,e,f,g,h,1)}function rc(a){if(!a){return 0}H[6605]=a;return-1}function ge(a,b,c){ce(a,0,b,c);qa=pa;return oa}function yc(a,b,c){a=a|0;b=b|0;c=c|0;return-1}function Ud(a,b,c){a=a|0;b=b|0;c=c|0;return 0}function Kb(a,b,c){a=a|0;b=b|0;c=c|0;return 1}function fe(a,b,c,d){a=ce(a,b,c,d);return a}function Fa(a){if(!a){return 0}return ub(a)}function Ed(a,b){a=a|0;b=b|0;Lb(H[a>>2],b)}function fc(a){return H[a+8>>2]-H[a>>2]|0}function ad(a){a=a|0;ia();ha(a+128|0);D()}function Jc(a,b,c){a=a|0;b=b|0;c=c|0;D()}function ub(a){a=a|0;return fb(8,a)|0}function zb(a){return H[a+28>>2]!=2}function fd(a,b){a=a|0;b=b|0;_(a|0)}function ed(a,b){a=a|0;b=b|0;Y(a|0)}function vc(a,b,c){if(c){y(a,b,c)}}function $a(a,b,c){if(c){B(a,b,c)}}function Oc(a,b){a=a|0;b=b|0;D()}function nb(a){return ic(a,32)}function Ia(a){return ic(a,16)}function bd(a){a=a|0;ja();D()}function Db(){return Ea(1,36)}function Pb(a,b){a=a|0;b=b|0}function cb(a){if(a){Ca(a)}}function Oa(a){H[a>>2]=0}function Ic(a){a=a|0;D()} +// EMSCRIPTEN_END_FUNCS +a=I;m(n);var ra=[null,Pb,Ud,Kd,yc,yc,ab,qd,id,hd,Wc,Vc,Uc,Tc,Sc,Rc,Qc,Pc,Kb,Nc,Mc,Lc,Hc,Bc,$d,_d,Zd,Yd,Xd,Wd,Vd,Td,Sd,Rd,Qd,Pd,Od,Nd,Md,Eb,Ld,Jd,Eb,Eb,Id,Hd,Cd,wd,vd,ud,td,sd,rd,pd,od,nd,md,ld,kd,jd,Ic,Ic,Jc,Kb,Fb,Lb,Mb,vb,Jb,Oc,Ib,Ec,Gc,Kc,hb,bb,Ic,Ic,Jc,Dd,Gd,Oc,yd,xd,Gc,Kc,Ed,Fd,xc,zd,Ad,Bd,Pb,fd,ed,_c,Zc,Yc,ad,bd,Ic,cd,dd,Ic,$c];function sa(){return E.byteLength>>16}function xa(ya){ya=ya|0;var ta=sa()|0;var ua=ta+ya|0;if(ta{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=instantiateSync(wasmBinaryFile,info);return receiveInstance(result[0])}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var HEAP32;function _copy_pixels_1(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);imageData.set(compG)}function _copy_pixels_3(compR_ptr,compG_ptr,compB_ptr,nb_pixels){compR_ptr>>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*3);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);const compA=HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram;var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var HEAPU32;var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);return 70}var printCharBuffers=[null,[],[]];var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,endPtr):new Uint8Array(heapOrArray.slice(idx,endPtr)))};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};function _gray_to_rgba(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);for(let i=0;i>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compA=HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i{HEAP8.set(array,buffer)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["writeArrayToMemory"]=writeArrayToMemory;var _malloc,_free,_jp2_decode,__emscripten_timeout,dynCall_iji,dynCall_jji,dynCall_iiji,dynCall_jiji,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_malloc=Module["_malloc"]=wasmExports["t"];_free=Module["_free"]=wasmExports["u"];_jp2_decode=Module["_jp2_decode"]=wasmExports["v"];__emscripten_timeout=wasmExports["w"];dynCall_iji=wasmExports["dynCall_iji"];dynCall_jji=wasmExports["dynCall_jji"];dynCall_iiji=wasmExports["dynCall_iiji"];dynCall_jiji=wasmExports["dynCall_jiji"];memory=wasmMemory=wasmExports["r"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={n:__abort_js,m:__emscripten_runtime_keepalive_clear,j:__setitimer_js,f:_copy_pixels_1,e:_copy_pixels_3,d:_copy_pixels_4,k:_emscripten_resize_heap,o:_environ_get,p:_environ_sizes_get,i:_fd_seek,b:_fd_write,q:_gray_to_rgba,h:_graya_to_rgba,c:_jsPrintWarning,l:_proc_exit,g:_rgb_to_rgba,a:_storeErrorMessage};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=createWasm();await run(); +;return Module}export default OpenJPEG; diff --git a/lib/pdfjs/pdf.mjs b/lib/pdfjs/pdf.mjs index e81d97ac1..3c02dd0cf 100644 --- a/lib/pdfjs/pdf.mjs +++ b/lib/pdfjs/pdf.mjs @@ -22673,7 +22673,7 @@ class PDFWorker { const worker = await import( /*webpackIgnore: true*/ /*@vite-ignore*/ - this.workerSrc); + "./pdf.worker.mjs"); return worker.WorkerMessageHandler; }; return util_shadow(this, "_setupFakeWorkerGlobal", loader()); diff --git a/lib/pdfjs/pdf.worker.mjs b/lib/pdfjs/pdf.worker.mjs index cb6c79d09..f116c93f6 100644 --- a/lib/pdfjs/pdf.worker.mjs +++ b/lib/pdfjs/pdf.worker.mjs @@ -15219,10 +15219,12 @@ class WasmImage { async #getJsModule(fallbackCallback) { let instance = null; try { - const mod = await import( + const mod = await ( /*webpackIgnore: true*/ /*@vite-ignore*/ - `${WasmImage.#wasmUrl}${this._noWasmFilename}`); + this._noWasmFilename === "jbig2_nowasm_fallback.js" ? import("./jbig2_nowasm_fallback.js") + : this._noWasmFilename === "openjpeg_nowasm_fallback.js" ? import("./openjpeg_nowasm_fallback.js") + : Promise.reject(new Error("Unknown PDF image decoder"))); instance = mod.default(); } catch (ex) { warn(`#getJsModule: ${ex}`); diff --git a/lib/pet.js b/lib/pet.js index ff1bf56bf..1ae55ad1d 100644 --- a/lib/pet.js +++ b/lib/pet.js @@ -1,6 +1,5 @@ // Local pet game (W24). Sources are existing device records only; the worker reads and reconciles them in its queue. var GZ_PET = globalThis.GZ_PET || (() => { - const lib = (name, file) => globalThis[name] || require(file); const KEY = /^pet:https?:\/\/[a-z\d.-]+(?::\d+)?\/(?:\d+|_\d+_\d+)$/i; const WORK = /^(?:assignment|quiz|discussion_topic|sub_assignment):(?:\d{1,20}|_\d{1,20}_\d{1,20})$/; const ITEM = /^(?:(?:assignment|quiz|discussion_topic|sub_assignment):(?:\d{1,20}|_\d{1,20}_\d{1,20})|task:[\w-]{1,40})$/; @@ -38,7 +37,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { // Keep the first account zone and a forward-only day so tabs, reloads and clock rollback cannot refill care. Forward-only holds // to a day of skew (a zone, a few hours back). A day further ahead was a clock that was wrong, and counts as unused; // a nearer correction keeps the daily spend and never leaves a cooldown locked for hours. - const T = lib('GZ_TODO', './todo.js'), saved = cleanCare(raw), zone = T.zone(saved?.zone || tz) || new Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; + const T = (globalThis.GZ_TODO || require('./todo.js')), saved = cleanCare(raw), zone = T.zone(saved?.zone || tz) || new Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; const today = T.dayOf(now, zone), ahead = saved && saved.day > today + 1, day = ahead ? today : Math.max(today, saved?.day || 0); const used = saved ? ahead || today > saved.day ? 0 : saved.used : CARE_LIMIT; // A future timestamp must not become `now` on every check: that slides the cooldown forward forever. Preserve the @@ -49,7 +48,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const taskSource = (todo, k) => [k.startsWith('task:') ? todo.tasks.find((x) => `task:${x.id}` === k)?.done ?? null : null, todo.items[k]?.done ?? null, todo.items[k]?.on ?? null]; const sourceValid = (v) => Array.isArray(v) && v.length === 3 && (v[0] === null || Number.isFinite(v[0]) && v[0] >= 0 && v[0] < 1e14) && (v[1] === null || typeof v[1] === 'boolean') && (v[2] === null || typeof v[2] === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v[2]) && Number.isFinite(Date.parse(v[2])) && new Date(v[2]).toISOString().slice(0, 10) === v[2]); const restoredTodo = (state, raw) => { - const todo = lib('GZ_TODO', './todo.js').clean(raw); + const todo = (globalThis.GZ_TODO || require('./todo.js')).clean(raw); for (const [k, saved] of Object.entries(state.restored)) { const actual = taskSource(todo, k); actual.forEach((value, i) => { if (value !== saved.to[i]) saved.from[i] = saved.to[i] = value; }); @@ -64,7 +63,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { return todo; }; const timerSignal = (raw, now = Date.now()) => { - const timer = lib('GZ_WIDGETS', './widgets.js').cleanTimer(raw); + const timer = (globalThis.GZ_WIDGETS || require('./widgets.js')).cleanTimer(raw); return `${timer.phase}:${timer.end}:${timer.left}:${timer.phase === 'work' && timer.end > now ? 'focus' : 'break'}`; }; const backup = (raw = {}) => { @@ -84,7 +83,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const training = (brain) => brain.counts.reduce((n, v) => n + v, 0) + brain.observations * .2; const brain = current && training(a.brain) >= training(b.brain) ? a.brain : b.brain; if (tasks) { - const before = restoredTodo(a, tasks.before), after = lib('GZ_TODO', './todo.js').clean(tasks.after); + const before = restoredTodo(a, tasks.before), after = (globalThis.GZ_TODO || require('./todo.js')).clean(tasks.after); // Imported completion/plan fields are a baseline, not a new user untick or re-tick. for (const k of new Set(Object.keys(a.earned).map((id) => id.slice(5)))) { const from = taskSource(before, k), to = taskSource(after, k); @@ -121,8 +120,8 @@ var GZ_PET = globalThis.GZ_PET || (() => { positions: Object.fromEntries(Object.entries(obj(raw?.positions)).filter(([k, v]) => Object.hasOwn(DECOR, k) && position(v)).map(([k, v]) => [k, position(v)])), habitat: Object.hasOwn(HABITATS, raw?.habitat) ? raw.habitat : 'room', wander: raw?.wander === true, physics: raw?.physics !== false, - learning: raw?.learning !== false, brain: lib('GZ_PET_BRAIN', './petbrain.js').clean(raw?.brain), - needs: lib('GZ_PET_BRAIN', './petbrain.js').needs(raw?.needs, Number.isFinite(raw?.needs?.at) && raw.needs.at > 0 ? Math.min(now, raw.needs.at) : now), + learning: raw?.learning !== false, brain: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).clean(raw?.brain), + needs: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).needs(raw?.needs, Number.isFinite(raw?.needs?.at) && raw.needs.at > 0 ? Math.min(now, raw.needs.at) : now), }; }; const view = (raw) => { @@ -135,11 +134,11 @@ var GZ_PET = globalThis.GZ_PET || (() => { while (xp >= floor(level + 1)) level++; if (WEAR[s.wear].level > level) s.wear = 'plain'; s.decor = Object.fromEntries(Object.entries(s.decor).filter(([id]) => DECOR[id].level <= level)); - return { ...s, needs: lib('GZ_PET_BRAIN', './petbrain.js').needs(s.needs), xp, work, plans, streakXp: streak, level, floor: floor(level), next: floor(level + 1) }; + return { ...s, needs: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).needs(s.needs), xp, work, plans, streakXp: streak, level, floor: floor(level), next: floor(level + 1) }; }; // Absent historical data never removes earned progress; an explicit undo or a newer correction does. const reconcile = (raw, { todo, insights, planner, cache, streak, streakOn = true, todoO = { done: 'auto', sync: false }, now, tz }) => { - const T = lib('GZ_TODO', './todo.js'), I = lib('GZ_INSIGHTS', './insights.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')), I = (globalThis.GZ_INSIGHTS || require('./insights.js')); const s = clean(raw, now); if (!s) return null; const d = restoredTodo(s, todo), known = new Map(), rows = new Map(), undone = new Set(); @@ -192,7 +191,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { } } // W11's calculated record is authoritative, including restores. A stale start cannot verify later days. - const saved = lib('GZ_REMIND', './remind.js').clean({ streak }, now).streak; + const saved = (globalThis.GZ_REMIND || require('./remind.js')).clean({ streak }, now).streak; const today = T.dayOf(now, tz); const end = saved?.asOf === null ? null : Math.min(today, saved?.asOf); if (streakOn && saved && end !== null && saved.start < end) { @@ -209,7 +208,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const got = await local.get([m.key, tk, ik, rk, 'todoCache', 'petCharacter', 'widgetTimer']); const cur = clean(got[m.key]); if (!cur) return { ok: false, error: 'history' }; - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const now = Date.now(); const prefs = sync?.get ? await sync.get(['todo', 'enhance', 'card']) : {}; const sanitize = typeof GZ_SANITIZE !== 'undefined' ? GZ_SANITIZE : require('../defaults.js').GZ_SANITIZE; @@ -221,7 +220,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { if (typeof got.petCharacter === 'string') s.sprite = sprites.ID.test(got.petCharacter) ? got.petCharacter : ''; if (s.sprite && !(await sprites.change({ op: 'get', id: s.sprite }, { local })).pet) s.sprite = ''; const v = view(s); - const B = lib('GZ_PET_BRAIN', './petbrain.js'); + const B = (globalThis.GZ_PET_BRAIN || require('./petbrain.js')); const allowance = care(s.care, m.tz, now); let interaction; if ((m.action !== undefined || m.context !== undefined) && (!B.ACTIONS.includes(m.action) || !B.context(m.context))) return { ok: false, error: 'bad' }; diff --git a/lib/scene.js b/lib/scene.js index 75685e949..70156f48c 100644 --- a/lib/scene.js +++ b/lib/scene.js @@ -11,7 +11,7 @@ const { WebGLRenderer, Scene, OrthographicCamera, PerspectiveCamera, Mesh, Points, PlaneGeometry, BufferGeometry, Float32BufferAttribute, ShaderMaterial, RawShaderMaterial, Color, ColorManagement, CustomBlending, AddEquation, OneFactor, DstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, -} = await import(`./three.js${new URL(import.meta.url).search}`); +} = await import(chrome.runtime.getURL(`lib/three.js${new URL(import.meta.url).search}`)); // Colors pass through as written, the way CSS mixes them, so a scene matches the gradient under it. ColorManagement.enabled = false; diff --git a/lib/sheet-xlsx.js b/lib/sheet-xlsx.js index 0badc172f..d46f08b9e 100644 --- a/lib/sheet-xlsx.js +++ b/lib/sheet-xlsx.js @@ -20,12 +20,13 @@ // lib/zip.js are looked up when a function runs, so they may load later. module.exports for node. var GZ_SHEET_XLSX = globalThis.GZ_SHEET_XLSX || (() => { const fail = (code, message) => { throw Object.assign(new Error(message || code), { code }); }; - const dep = (name, file) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? require(file) : fail('deps', `${file} is not loaded`)); + // Firefox's linter refuses require() of a variable, so each caller passes a require of its own file. + const dep = (name, file, load) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? load() : fail('deps', `${file} is not loaded`)); let M, F, NF, D, MAX_ROWS, MAX_COLS, CAP, INDEXED; const deps = () => { if (CAP) return; - M = dep('GZ_SHEET_MODEL', './sheet-model.js'); F = dep('GZ_SHEET_FORMULA', './sheet-formula.js'); - NF = dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js'); D = dep('GZ_DOCTEXT', './doctext.js'); + M = dep('GZ_SHEET_MODEL', './sheet-model.js', () => require('./sheet-model.js')); F = dep('GZ_SHEET_FORMULA', './sheet-formula.js', () => require('./sheet-formula.js')); + NF = dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js', () => require('./sheet-numfmt.js')); D = dep('GZ_DOCTEXT', './doctext.js', () => require('./doctext.js')); ({ MAX_ROWS, MAX_COLS, CAP } = M); // Indexed colours 0-7 repeat the first eight of the palette; 64 and 65 are the system's text and window colours. INDEXED = [...NF.PALETTE.slice(0, 8), ...NF.PALETTE]; @@ -1411,7 +1412,7 @@ var GZ_SHEET_XLSX = globalThis.GZ_SHEET_XLSX || (() => { const date = opts.date ?? new Date(); const order = ['[Content_Types].xml', '_rels/.rels', ...[...files.keys()].filter((p) => p !== '_rels/.rels')]; const list = [{ name: '[Content_Types].xml', data: new TextEncoder().encode(ct), date }, ...order.slice(1).map((name) => ({ name, data: files.get(name), date }))]; - const Zp = dep('GZ_ZIP', './zip.js'); + const Zp = dep('GZ_ZIP', './zip.js', () => require('./zip.js')); const chunks = await Zp.zipAsync(list, { deflate: opts.deflate !== false }); const size = chunks.reduce((n, p) => n + p.length, 0); const bytes = new Uint8Array(size); diff --git a/options.html b/options.html index 408e6c6f1..f66ba5e29 100644 --- a/options.html +++ b/options.html @@ -1888,6 +1888,7 @@

      + diff --git a/options.js b/options.js index d8cd19727..0e8d22289 100644 --- a/options.js +++ b/options.js @@ -1098,11 +1098,11 @@ GZ_UI.load().then((s) => { const gcalSignIn = async () => { try { if (!await chrome.permissions.request({ permissions: ['identity'] })) return t("Glaze can't connect without Chrome's sign-in permission."); - const r = await chrome.identity.getAuthToken({ interactive: true }); + const r = await GZ_GCAL_AUTH.token({ interactive: true }); const scope = chrome.runtime.getManifest().oauth2.scopes[0]; if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(scope)) { // Chrome would hand the same token back: forgotten, so the next Connect asks Google again. - await chrome.identity.removeCachedAuthToken({ token: r.token }).catch(() => {}); + await GZ_GCAL_AUTH.drop(r.token).catch(() => {}); return t("Glaze needs the calendar access on Google's screen. Connect again and leave it ticked."); } return ''; diff --git a/pdf-kind.js b/pdf-kind.js index dc487d8c9..f12508914 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -65,8 +65,9 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { // ---- pdf.js, imported the first time a PDF is read let lib = null, viewerLib = null; - const pdfjs = () => (lib ||= import(url('lib/pdfjs/pdf.mjs')).then((P) => { P.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); return P; })); - const viewerjs = () => (viewerLib ||= pdfjs().then(() => import(url('lib/pdfjs/pdf_viewer.mjs')))); + // Firefox's linter accepts a dynamic import only of a literal or straight through chrome.runtime.getURL. + const pdfjs = () => (lib ||= import(chrome.runtime.getURL('lib/pdfjs/pdf.mjs')).then((P) => { P.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); return P; })); + const viewerjs = () => (viewerLib ||= pdfjs().then(() => import(chrome.runtime.getURL('lib/pdfjs/pdf_viewer.mjs')))); // lib/pdf-doc.js over this pdf.js (papers.html loads it before this file), or null where it isn't loaded: then pdf.js // directly, the same checks. const GET = { wasmUrl: url('lib/pdfjs/'), isEvalSupported: false, enableXfa: false, verbosity: 0 }; diff --git a/reader.js b/reader.js index 2d6cc9b6b..475a4a661 100644 --- a/reader.js +++ b/reader.js @@ -59,7 +59,7 @@ const pdf = async (file, langs, say) => { if (file.size > R.pdf) fail('size'); - const pdfjs = await import(url('lib/pdfjs/pdf.mjs')); + const pdfjs = await import('./lib/pdfjs/pdf.mjs'); pdfjs.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); // maxImageSize: a picture inside the PDF over the same pixel limit as a photo is left undecoded (its page then reads // as blank, which the review says), whatever the size the page is drawn at. diff --git a/scripts/package-firefox.sh b/scripts/package-firefox.sh index 5429e4c71..d6b4b4802 100644 --- a/scripts/package-firefox.sh +++ b/scripts/package-firefox.sh @@ -36,6 +36,14 @@ cat > "$stage/lib/firefox-background.js" <<'JS' // Firefox runs the background as an event page: the manifest already loads every script the worker would import. globalThis.importScripts = () => {}; JS +cat > "$stage/lib/gcal-auth.js" <<'JS' +// Firefox has no Chrome Google sign-in API; calendar-file export remains available. +var GZ_GCAL_AUTH = { + token: async () => { throw new Error('Google Calendar sign-in requires Chrome.'); }, + drop: async () => {}, + clear: async () => {}, +}; +JS node - "$stage/manifest.json" "$stage/lib/background.js" "$stage/popup.js" "$development" "$branch" "$commit" "$dirty" <<'JS' const fs = require('fs'); const path = require('path'); @@ -72,7 +80,10 @@ m.web_accessible_resources = (m.web_accessible_resources || []).map((e) => ({ .. // 140 (ESR) reads data_collection_permissions; 127+ grants host permissions at install. Glaze sends nothing anywhere // on its own: shared sessions and the Looks gallery go out only when the user starts them. Agent access gives course data to // an app on this computer, outside the browser, so it asks for website content when it is turned on (lib/editor-agents.js). -m.browser_specific_settings = { gecko: { id: 'glaze@frostdev.io', strict_min_version: '140.0', data_collection_permissions: { required: ['none'], optional: ['websiteContent'] } } }; +m.browser_specific_settings = { + gecko: { id: 'glaze@frostdev.io', strict_min_version: '140.0', data_collection_permissions: { required: ['none'], optional: ['websiteContent'] } }, + gecko_android: { strict_min_version: '142.0' }, +}; fs.writeFileSync(file, JSON.stringify(m, null, 2) + '\n'); JS rm -rf "$stage/mods" "$stage/features/mods.js" diff --git a/scripts/vendor-pdfjs.sh b/scripts/vendor-pdfjs.sh index 0f39587b5..0defccd0c 100755 --- a/scripts/vendor-pdfjs.sh +++ b/scripts/vendor-pdfjs.sh @@ -97,6 +97,13 @@ for (const file of process.argv.slice(2)) { cut(/^ filename = unescape\(filename\);\n/gm, ` filename = filename${percent};\n`); cut(/^ part = unescape\(part\);\n/gm, ` part = part${percent};\n`); } + // Firefox: AMO's linter refuses an import it can't read, so the fake worker and the image decoders' no-wasm fallbacks + // import fixed local files. + if (file.endsWith('/pdf.mjs')) cut(/this\.workerSrc\);\n return worker\.WorkerMessageHandler;/g, '"./pdf.worker.mjs");\n return worker.WorkerMessageHandler;'); + if (file.endsWith('/pdf.worker.mjs')) { + cut(/const mod = await import\(/g, 'const mod = await ('); + cut(/`\$\{WasmImage\.#wasmUrl\}\$\{this\._noWasmFilename\}`\);/g, 'this._noWasmFilename === "jbig2_nowasm_fallback.js" ? import("./jbig2_nowasm_fallback.js")\n : this._noWasmFilename === "openjpeg_nowasm_fallback.js" ? import("./openjpeg_nowasm_fallback.js")\n : Promise.reject(new Error("Unknown PDF image decoder")));'); + } if (file.endsWith('/pdf_viewer.mjs')) { // Firefox Add-ons flags Fluent's innerHTML sink even though its overlay sanitizes the result. Parse in an inert // document instead, with an explicit body to retain leading whitespace and head-like elements, then keep the same @@ -154,7 +161,7 @@ cp "$tmp/brotli-dictionary.bin" "$tmp/LICENSE_BROTLI" "$tmp/LICENSE_MPL" "$tmp/L node scripts/vendor-pdfjs-limits.js lib/pdfjs node scripts/vendor-pdfjs-brotli.js lib/pdfjs/pdf.worker.mjs lib/pdfjs/brotli-dictionary.bin cp "$src/LICENSE" lib/pdfjs/LICENSE -for f in jbig2.wasm openjpeg.wasm LICENSE_JBIG2 LICENSE_PDFJS_JBIG2 LICENSE_OPENJPEG LICENSE_PDFJS_OPENJPEG; do cp "$src/wasm/$f" lib/pdfjs/; done +for f in jbig2.wasm openjpeg.wasm jbig2_nowasm_fallback.js openjpeg_nowasm_fallback.js LICENSE_JBIG2 LICENSE_PDFJS_JBIG2 LICENSE_OPENJPEG LICENSE_PDFJS_OPENJPEG; do cp "$src/wasm/$f" lib/pdfjs/; done { echo "/* pdf.js $version legacy viewer styles (Apache-2.0, lib/pdfjs/LICENSE). Built by scripts/vendor-pdfjs.sh. */" sed '/^\/\*# sourceMappingURL=/d' "$src/legacy/web/pdf_viewer.css"; } > lib/pdfjs/pdf_viewer.css for image in $(grep -oE 'url\("?images/[A-Za-z0-9_-]+\.(svg|gif)' lib/pdfjs/pdf_viewer.css | sed -E 's/^url\("?//' | sort -u); do diff --git a/test/canvas-dash-audit.test.js b/test/canvas-dash-audit.test.js index 4c931b41b..2f3b3e9bc 100644 --- a/test/canvas-dash-audit.test.js +++ b/test/canvas-dash-audit.test.js @@ -36,7 +36,7 @@ const page = ({ where = '/', frame = '', parent = {}, enhance = {}, api = async let now = Date.UTC(2026, 9, 19), serial = 0; const listen = (map, type, fn) => { if (!map.has(type)) map.set(type, new Set()); map.get(type).add(fn); }; const fire = (map, type, extra = {}) => { for (const fn of [...map.get(type) || []]) fn({ type, ...extra }); }; - Object.assign(doc, { documentElement: doc.root, activeElement: doc.body, hidden: false, visibilityState: 'visible', createElement: (tag) => make(tag), + Object.assign(doc, { documentElement: doc.root, activeElement: doc.body, hidden: false, visibilityState: 'visible', createElement: (tag) => make(tag), createElementNS: (_, tag) => make(tag), querySelector: (s) => doc.root.querySelector(s), querySelectorAll: (s) => doc.root.querySelectorAll(s), getElementById: (id) => doc.root.all().find((e) => e.id === id) || null, addEventListener: (type, fn) => listen(documentEvents, type, fn), removeEventListener: (type, fn) => documentEvents.get(type)?.delete(fn) }); const local = { get: (keys, cb) => { diff --git a/test/firefox.test.js b/test/firefox.test.js index b2d1eb020..865c2817f 100644 --- a/test/firefox.test.js +++ b/test/firefox.test.js @@ -23,7 +23,8 @@ test('package-firefox.sh builds a Firefox manifest whose background scripts all assert.deepEqual(m.background.scripts.slice(0, 1), ['lib/firefox-background.js']); assert.equal(m.background.scripts.at(-1), 'lib/background.js'); for (const f of m.background.scripts) assert.ok(files.has(f), `${f} is a background script but isn't packaged`); - for (const f of ['lib/relay.js', 'lib/sessions.js', 'defaults.js']) assert.ok(m.background.scripts.includes(f), f); + for (const f of ['lib/relay.js', 'lib/sessions.js', 'defaults.js', 'lib/sidebar.js', 'lib/gcal-auth.js']) assert.ok(m.background.scripts.includes(f), f); + for (const f of ['features/sidebar.js', 'features/sidebar.css', 'features/docreview.js', 'features/docreview.css']) assert.ok(files.has(f), f); // Nothing without consent; agent access sends the student's course data to an app on this computer, outside the browser. assert.deepEqual(m.browser_specific_settings.gecko.data_collection_permissions, { required: ['none'], optional: ['websiteContent'] }); assert.equal(m.oauth2, undefined); diff --git a/test/folders-canvas.test.js b/test/folders-canvas.test.js index 57da7f3d3..013421bde 100644 --- a/test/folders-canvas.test.js +++ b/test/folders-canvas.test.js @@ -56,7 +56,7 @@ const fixture = () => { const local = { get: async (keys) => structuredClone(keys === null ? store : typeof keys === 'string' ? { [keys]: store[keys] } : Object.fromEntries(keys.map((k) => [k, store[k]]))), set: async (p) => { Object.assign(store, structuredClone(p)); return true; } }; const chrome = { storage: { local, onChanged: { addListener() {} } }, runtime: { sendMessage: async (m) => { sent.push(m); return F.change(m, { local }); } } }; const document = { head, body, querySelectorAll: (s) => html.querySelectorAll(s), getElementById: (id) => html.querySelector(`#${id}`) }; - html.setOwner(document); document.createElement = (t) => { const e = new E(t); e.setOwner(document); return e; }; document.activeElement = body; + html.setOwner(document); document.createElement = (t) => { const e = new E(t); e.setOwner(document); return e; }; document.createElementNS = (_, t) => document.createElement(t); document.activeElement = body; let on = true; const ctx = { site: 'canvas', on: () => on, settings: () => ({ enhance: { hideFolders: true } }), courseId: () => undefined, toggleOf: () => null, canvasCourseId: () => location.pathname.match(/^\/courses\/(\d+)/)?.[1], t: (k, args) => k.replace(/\{(\w+)\}/g, (_, p) => args[p]) }; diff --git a/test/folders-ui.test.js b/test/folders-ui.test.js index 9184c4b2d..39ab90902 100644 --- a/test/folders-ui.test.js +++ b/test/folders-ui.test.js @@ -42,6 +42,7 @@ const fixture = () => { const chrome = { storage: { local, onChanged: { addListener: (f) => changed.push(f) } }, runtime: { sendMessage: async (m) => { sent.push(m); const r = await F.change(m, { local }); if (reply) await reply; return r; } } }; const document = { head, body, createElement: (t) => new E(t), querySelectorAll: (s) => html.querySelectorAll(s), getElementById: (id) => html.querySelector(`#${id}`) }; html.setOwner(document); document.createElement = (t) => { const e = new E(t); e.setOwner(document); return e; }; document.activeElement = body; + document.createElementNS = (_, tag) => document.createElement(tag); const ctx = { on: () => on, settings: () => ({ enhance: { hideFolders: enabled } }), courseId: () => cid, toggleOf: (r) => r.querySelector(`#folder-title-${r.dataset.contentId}`) || r.querySelector(`#learning-module-title-${r.dataset.contentId}`), t: (k, args) => k.replace(/\{(\w+)\}/g, (_, p) => args[p]) }; const c = vm.createContext({ document, location, chrome, GZ_CTX: ctx, GZ_FOLDERS: F, MutationObserver: class { constructor(f) { mutations = f; } observe() { watched.on = true; } disconnect() { watched.on = false; } }, requestAnimationFrame: (f) => frames.push(f), setTimeout: (f) => { timers.set(++timerId, f); return timerId; }, clearTimeout: (id) => timers.delete(id) }); @@ -201,7 +202,7 @@ test('feedback toggles its action and restores live native accessible names with assert.equal(toggle.textContent, 'Show hidden folders (3)'); assert.equal(toggle.getAttribute('aria-pressed'), 'false'); await e.click(toggle); assert.equal(toggle.textContent, 'Hide them again (3)'); assert.equal(toggle.getAttribute('aria-pressed'), 'true'); - assert.match(toggle.querySelector('.gz-folders-toggle-icon').innerHTML, /m3 3 18 18/); + assert.equal(toggle.querySelector('.gz-folders-toggle-icon').querySelectorAll('path').at(-1).getAttribute('d'), 'm3 3 18 18'); assert.equal(parent.getAttribute('aria-label'), 'Native parent · Hidden'); assert.equal(child.getAttribute('aria-labelledby'), ' native-title native-detail gz-folder-badge-_102_1'); assert.equal(module.textContent, 'Module Hidden'); assert.equal(e.parent.querySelector('h3'), heading); diff --git a/test/import.test.js b/test/import.test.js index 7c0dcee6c..980c9bd4b 100644 --- a/test/import.test.js +++ b/test/import.test.js @@ -427,7 +427,7 @@ const reader = () => { createImageBitmap: async (b) => { decoded.push(b); throw new Error('stand-in decoder'); }, Worker: class { postMessage() {} terminate() {} }, }); - new vm.Script(fs.readFileSync(path.join(root, 'reader.js'), 'utf8'), { importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER }).runInContext(ctx); + new vm.Script(fs.readFileSync(path.join(root, 'reader.js'), 'utf8'), { filename: path.join(stand, 'reader.js'), importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER }).runInContext(ctx); // A job as any page could post it, with a channel of its own; → the reader's answer, or null when it says nothing. const post = (data) => new Promise((resolve) => { const ch = new MessageChannel(); diff --git a/test/vendor-pdfjs.test.js b/test/vendor-pdfjs.test.js index 1077f4192..1e6a5769b 100644 --- a/test/vendor-pdfjs.test.js +++ b/test/vendor-pdfjs.test.js @@ -7,7 +7,8 @@ const path = require('node:path'); const root = path.join(__dirname, '..'); const read = (f) => fs.readFileSync(path.join(root, f), 'utf8'); -const modules = fs.readdirSync(path.join(root, 'lib/pdfjs')).filter((f) => /\.m?js$/.test(f)); +// Firefox also ships pdf.js's own no-wasm decoder fallbacks (.js), which its fixed local imports name. +const modules = fs.readdirSync(path.join(root, 'lib/pdfjs')).filter((f) => /\.mjs$/.test(f)); // A call of the old global escape or unescape function; not a method (RegExp.escape), core-js's $escape, or its definition. const OLD_ESCAPE = /(? Date: Fri, 30 Oct 2026 14:22:09 -0400 Subject: [PATCH 056/907] Let a trusted release accept the site's local checks in place of site.yml --- scripts/public-release.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/public-release.mjs b/scripts/public-release.mjs index 1e02f1893..b03c4c6fd 100644 --- a/scripts/public-release.mjs +++ b/scripts/public-release.mjs @@ -313,7 +313,8 @@ async function publishWebsiteWith({ manifest, save, out, root, env, run }, bare) if (!contains(remote, state.siteCommit, site, env)) throw new Error('Website main advanced. Review the new main, discard the generated website clone and clear public.siteCommit in release.json, then resume to rebuild and validate integration.'); git(['push', 'origin', `${state.siteCommit}:refs/heads/main`], site, env); } - state.websiteCI = await workflow(siteRepo, 'site.yml', state.siteCommit, root, env); save(); + // The site's own check, gallery tests and build ran above (logs/website-*.log); GLAZE_TRUST_CI accepts them in place of site.yml. + state.websiteCI = env.GLAZE_TRUST_CI ? `trusted: ${env.GLAZE_TRUST_CI}; local website check, gallery tests, build and output check` : await workflow(siteRepo, 'site.yml', state.siteCommit, root, env); save(); // deploy stages, activates atomically, and verifies origin/public routes with rollback on failure. // A previous successful activation can be verified without trying to create its existing stage again. let staged = false; From b1079b3b6ebd00354798a9e0601922871c7cced7 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:24:43 -0400 Subject: [PATCH 057/907] Test that the browser gate runs agent-link-check --- test/workflows.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/workflows.test.js b/test/workflows.test.js index 76b8adf46..6a4a57862 100644 --- a/test/workflows.test.js +++ b/test/workflows.test.js @@ -169,7 +169,7 @@ test('the gate keeps its logs and screenshots when it fails', () => { test('the gate runs only checks that need no school, local LMS or network', () => { const { CHECKS } = require('../scripts/ci-browser-checks.js'); - assert.deepEqual(CHECKS.map((c) => c.script), ['settings-ia-check.js', 'backup-check.js', 'popup-check.js', 'sites-check.js', 'pointer-check.js', 'register-update-check.js']); + assert.deepEqual(CHECKS.map((c) => c.script), ['settings-ia-check.js', 'backup-check.js', 'popup-check.js', 'sites-check.js', 'pointer-check.js', 'register-update-check.js', 'agent-link-check.js']); for (const { script } of CHECKS) { const code = fs.readFileSync(path.join(__dirname, '../scripts', script), 'utf8'); assert.doesNotMatch(code, /localhost:(8081|3000)|127\.0\.0\.1:(8081|3000)/, `${script} reaches a local LMS`); From 1e34691f924e0e10826970f8a187c03d334f8654 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:27:14 -0400 Subject: [PATCH 058/907] Show a spreadsheet's charts on its Documents card --- lib/sheet-chart-xml.js | 22 +++++++++++++++- scripts/documents-check.js | 38 ++++++++++++++++++++++++++++ sheet-data.js | 12 +++++++-- sheet-kind.js | 52 +++++++++++++++++++++++++++++++++++++- sheet.css | 8 ++++++ test/sheet-kind.test.js | 41 ++++++++++++++++++++++++++++++ 6 files changed, 169 insertions(+), 4 deletions(-) diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index 34d66be11..15b6f7dc7 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -448,6 +448,26 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks, view3D: c.view3D ?? null, walls: c.walls ?? null }; }; + // The card's view of a sheet's charts (sheet-data.js keeps it in the record): each chart's kind, the cells it covers + // [r0, c0, r1, c1] (a size in pixels counted in the grid's default 64×20 cells) and up to five of its colours, from the + // parsed model alone. kind: column, bar, line, area, scatter, pie, doughnut, pie3D, column3D, bar3D or chart (a frame). + const glance = (wb, sh) => { + const theme = Array.isArray(wb?.keep?.theme) && wb.keep.theme.length >= 10 ? wb.keep.theme : DEFAULT_THEME; + return charts(wb, sh).slice(0, 32).map(({ anchor: a, chart: c }) => { + const at = a.from ?? (a.pos && { col: Math.floor(a.pos.x / 64), colOff: a.pos.x % 64, row: Math.floor(a.pos.y / 20), rowOff: a.pos.y % 20 }) ?? { col: 0, colOff: 0, row: 0, rowOff: 0 }; + const end = a.to ? [a.to.row - (a.to.rowOff <= 0 && a.to.row > at.row ? 1 : 0), a.to.col - (a.to.colOff <= 0 && a.to.col > at.col ? 1 : 0)] + : [at.row + Math.max(0, Math.ceil((at.rowOff + (a.ext?.h ?? 0)) / 20) - 1), at.col + Math.max(0, Math.ceil((at.colOff + (a.ext?.w ?? 0)) / 64) - 1)]; + const g = c?.kind === 'plot' ? c.groups?.[0] : null; + if (!g) return { kind: 'chart', anchor: [at.row, at.col, ...end], colours: [] }; + const kind = g.type === 'bar' ? (g.dir === 'bar' ? 'bar' : 'column') : g.type === 'bar3D' ? (g.dir === 'bar' ? 'bar3D' : 'column3D') : g.type; + const s0 = g.series[0]; + const round = /^(pie|doughnut|pie3D)$/.test(g.type) || (g.vary && g.series.length === 1); + const colours = round && s0 ? Array.from({ length: Math.min(5, Math.max(1, s0.val?.cache?.pts?.length ?? 0)) }, (_, i) => s0.points?.get(i) ?? autoColor(i, theme)) + : g.series.slice(0, 5).map((s) => { const x = g.type === 'line' || g.type === 'scatter' ? s.line : s.fill; return x && x !== 'none' ? x : s.color; }); + return { kind, anchor: [at.row, at.col, ...end], colours: colours.filter((x) => /^#[0-9a-f]{6}$/i.test(x || '')) }; + }); + }; + // ── Writing a chart (Insert): the parts in Excel's element order ─────────────────────────────────────────────────── const xesc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]); const HEAD = '\n'; @@ -612,7 +632,7 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { // Every word a frame can show, for the catalogues. const WORDS = [...new Set(['Chart', 'Combo chart', ...Object.values(OTHER), '3-D pie chart', '3-D column chart', '3-D bar chart', ...Object.values(EX)])]; - return { EMU, MAX_POINTS, DEFAULT_THEME, WORDS, logFor, owner, TYPE_CHART, TYPE_RELS, build, plan, spans, addAnchor, setAnchor, relsXml, relList, tree, anchors, chart, chartEx, charts, lookup, resolve, autoColor, transform, moveFormula, moveAnchor, relsOf, partText }; + return { EMU, MAX_POINTS, DEFAULT_THEME, WORDS, logFor, owner, TYPE_CHART, TYPE_RELS, build, plan, spans, addAnchor, setAnchor, relsXml, relList, tree, anchors, chart, chartEx, charts, glance, lookup, resolve, autoColor, transform, moveFormula, moveAnchor, relsOf, partText }; })(); if (typeof module !== 'undefined') module.exports = GZ_SHEET_CHART_XML; else GZ_SHEET_CHART_XML; diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 9e341aa8f..8ee0e17ad 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -712,6 +712,44 @@ const PROBE = ({ lang }) => { await archive.close(); await worker.evaluate(async (keys) => { const gone = new Set(keys); await chrome.storage.local.remove(keys.map((k) => `writerPaper:${k}`)); await chrome.storage.local.set({ writerPapers: (await chrome.storage.local.get('writerPapers')).writerPapers.filter((r) => !gone.has(r.key)) }); }, extras); + // A spreadsheet's card shows its charts (sheet-kind.js chartsOn): a column chart over B2:E4 drawn in those cells, two + // pies off the card counted in its corner, and a workbook without charts showing none. + const SHEET_CHARTS = `doc:${crypto.randomUUID()}`, SHEET_PLAIN = `doc:${crypto.randomUUID()}`; + await worker.evaluate(async ({ a, b }) => { + const grid = [['Month', 'Sales', 'Costs', '', '', ''], ['Jan', '10', '5', '', '', ''], ['Feb', '20', '15', '', '', ''], ['Mar', '30', '10', '', '', '']]; + const book = { active: 0, sheets: [{ id: 1, name: 'Data' }] }, now = Date.now() + 60e3; + const rec = (key, title, charts) => ({ key, kind: 'sheet', named: true, title, book, grid, charts, text: '', cells: 12, sheets: 1, created: now, updated: now, revision: 1 }); + const ra = rec(a, 'Sales charts', [{ kind: 'column', anchor: [1, 1, 3, 4], colours: ['#156082', '#E97132'] }, { kind: 'pie', anchor: [10, 8, 19, 11], colours: ['#156082', '#E97132', '#196B24', '#0F9ED5'] }, { kind: 'pie3D', anchor: [22, 8, 29, 11], colours: ['#156082'] }]); + const rb = rec(b, 'Plain sheet', []); + const rows = (await chrome.storage.local.get('writerPapers')).writerPapers; + for (const r of [ra, rb]) rows.unshift({ key: r.key, kind: 'sheet', named: true, title: r.title, course: '', url: '', words: 0, format: 'plain', updated: r.updated, submitted: null, grid }); + await chrome.storage.local.set({ [`writerPaper:${a}`]: ra, [`writerPaper:${b}`]: rb, writerPapers: rows }); + }, { a: SHEET_CHARTS, b: SHEET_PLAIN }); + const sheets = await openHome('en', 'sheet charts'); + await until(() => sheets.evaluate((ks) => ks.every((k) => !!document.querySelector(`.gd-card[data-key="${k}"] .gx-thumb`)), [SHEET_CHARTS, SHEET_PLAIN]), 'the spreadsheet cards'); + const drawn = await sheets.evaluate((k) => { + const box = document.querySelector(`.gd-card[data-key="${k}"] .gx-thumb`), cells = [...box.querySelectorAll(':scope > .gx-th-c')], at = (r, c) => cells[r * 6 + c].getBoundingClientRect(); + const m = box.querySelector('.gx-th-chart'), mr = m?.getBoundingClientRect(), b2 = at(1, 1), e4 = at(3, 4), more = box.querySelector('.gx-th-more'); + return { n: box.querySelectorAll('.gx-th-chart').length, kind: m?.dataset.kind, shapes: m ? [...m.querySelectorAll('svg > *')].map((e) => [e.tagName, e.getAttribute('fill')]) : [], + inside: !!mr && mr.left >= b2.left - 1 && mr.top >= b2.top - 1 && mr.right <= e4.right + 1 && mr.bottom <= e4.bottom + 1 && mr.width > (e4.right - b2.left) * 0.8, + count: more?.textContent, corner: !!more && more.getBoundingClientRect().right <= box.getBoundingClientRect().right && more.getBoundingClientRect().bottom <= box.getBoundingClientRect().bottom }; + }, SHEET_CHARTS); + check(drawn.n === 1 && drawn.kind === 'column' && drawn.inside && drawn.shapes.length >= 4 && drawn.shapes.length <= 5 && drawn.shapes.every(([t, f]) => t === 'rect' && /^#[0-9A-F]{6}$/i.test(f)), + `a column chart over B2:E4 is a few bars in its colours over those cells (${JSON.stringify(drawn)})`); + check(drawn.count === '2' && drawn.corner, `the two charts off the card are counted in its corner (${drawn.count})`); + check(await sheets.evaluate((k) => !document.querySelector(`.gd-card[data-key="${k}"] :is(.gx-th-chart, .gx-th-more)`), SHEET_PLAIN), 'a spreadsheet without charts shows none'); + await sheets.evaluate((k) => document.querySelector(`.gd-card[data-key="${k}"]`).scrollIntoView({ block: 'center' }), SHEET_CHARTS); + await shot(sheets, 'sheet-chart-card.png'); + const sheetMs = await sheets.evaluate(async (k) => { + const rec = (await chrome.storage.local.get(`writerPaper:${k}`))[`writerPaper:${k}`], n = 50, t = performance.now(); + for (let i = 0; i < n; i++) GZ_DOC_KINDS.get('sheet').thumb(rec); + return (performance.now() - t) / n; + }, SHEET_CHARTS); + console.log(`50 spreadsheet cards with charts back to back: ${sheetMs.toFixed(3)} ms each`); + check(sheetMs > 0 && sheetMs <= 6, `a spreadsheet card with charts costs ${sheetMs.toFixed(3)} ms (50 timed together)`); + await sheets.close(); + await worker.evaluate(async (keys) => { const gone = new Set(keys); await chrome.storage.local.remove(keys.map((k) => `writerPaper:${k}`)); await chrome.storage.local.set({ writerPapers: (await chrome.storage.local.get('writerPapers')).writerPapers.filter((r) => !gone.has(r.key)) }); }, [SHEET_CHARTS, SHEET_PLAIN]); + // Light Paper Look, both sizes and languages, with measured title and metadata contrast on the actual card. const light = G.GZ_SANITIZE({ ...settings, preset: 'Paper', colors: G.GZ_PRESET('Paper') }); await worker.evaluate(async (s) => chrome.storage.sync.set(s), light); diff --git a/sheet-data.js b/sheet-data.js index b47db504a..5a451a3f4 100644 --- a/sheet-data.js +++ b/sheet-data.js @@ -156,6 +156,14 @@ var GZ_SHEET_DATA = globalThis.GZ_SHEET_DATA || (() => { } throw new Error('torn read of a spreadsheet'); }; + // The card's charts (lib/sheet-chart-xml.js glance) on the active sheet: [] when it has no drawing, null when it has one + // but the chart reader isn't loaded (the card then counts the drawing's charts itself). + const charts = (wb) => { + const sh = wb.sheets[wb.active] || wb.sheets[0]; + if (!sh?.keep?.drawing) return []; + const CX = typeof GZ_SHEET_CHART_XML !== 'undefined' ? GZ_SHEET_CHART_XML : globalThis.GZ_SHEET_CHART_XML; + try { return CX ? CX.glance(wb, sh) : null; } catch { return null; } + }; // A spreadsheet written through the worker (papers: lib/writer-papers.js open()): the parts plan() finds changed and the // book when it changed, with the card's text, the search text and the counts; a packed version ({ reason, gz, cells }) // when one is asked for. Nothing is sent when nothing changed and nothing else is asked. show: a cell's shown text. @@ -164,14 +172,14 @@ var GZ_SHEET_DATA = globalThis.GZ_SHEET_DATA || (() => { const p = plan(wb, sigs, dirty); if (!p.changed && !version && !Object.keys(patch).length) return { sigs, saved: null }; const m = measure(wb); - const saved = await papers.save(key, { kind: 'sheet', ...(p.book && { book: p.book }), grid: glance(wb, show), text: text(wb, show), cells: m.cells, sheets: m.sheets, ...patch }, + const saved = await papers.save(key, { kind: 'sheet', ...(p.book && { book: p.book }), grid: glance(wb, show), charts: charts(wb), text: text(wb, show), cells: m.cells, sheets: m.sheets, ...patch }, { base, parts: p.parts, drop: p.drop, changed: p.changed, ...(version && { version }), ...(reason && { reason }) }); p.sigs.revision = saved.paper.revision; return { sigs: p.sigs, saved }; }; const GZ_SHEET_MODEL_OR = () => (typeof GZ_SHEET_MODEL !== 'undefined' ? GZ_SHEET_MODEL : require('./lib/sheet-model.js')); - return { BAND, TEXT_CAP, VERSIONS_CAP, PART, keyOf, enc, dec, split, join, serialize, deserialize, sig, plan, measure, text, glance, isGlance, pack, unpack, read, write }; + return { BAND, TEXT_CAP, VERSIONS_CAP, PART, keyOf, enc, dec, split, join, serialize, deserialize, sig, plan, measure, text, glance, charts, isGlance, pack, unpack, read, write }; })(); if (typeof module !== 'undefined') module.exports = GZ_SHEET_DATA; else GZ_SHEET_DATA; diff --git a/sheet-kind.js b/sheet-kind.js index c12f538b5..e5ff34d35 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -65,6 +65,54 @@ const largest = Math.max(...ws); return ws.some((w) => w !== ws[0]) && largest > 0 ? ws.map((w) => w / largest) : null; }; + // ---- the card's charts: the record's summary (sheet-data.js charts) as a small picture of each kind over the cells it + // covers in A1:F4, the rest counted in the corner; a record kept before the summary counts its drawing's charts. + const NS = 'http://www.w3.org/2000/svg'; + const svgEl = (tag, attrs) => { const e = document.createElementNS(NS, tag); for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, String(v)); return e; }; + const HEX = /^#[0-9a-f]{6}$/i; + // In a 24×16 box: 4–5 shapes in the chart's colours. + const wedge = (cx, cy, rx, ry, a0, a1) => { const p = (a) => `${(cx + rx * Math.sin(a)).toFixed(2)} ${(cy - ry * Math.cos(a)).toFixed(2)}`; return `M${cx} ${cy}L${p(a0)}A${rx} ${ry} 0 ${a1 - a0 > Math.PI ? 1 : 0} 1 ${p(a1)}Z`; }; + const glyph = (kind, colours) => { + const cs = colours.length ? colours : ['#156082', '#E97132', '#196B24', '#0F9ED5'], col = (i) => cs[i % cs.length]; + const H = [0.5, 0.8, 0.6, 1]; + if (kind === 'column' || kind === 'column3D') return H.map((v, i) => svgEl('rect', { x: 2 + i * 5.5, y: 15 - v * 13, width: 4, height: v * 13, fill: col(i) })); + if (kind === 'bar' || kind === 'bar3D') return H.map((v, i) => svgEl('rect', { x: 1, y: 1 + i * 3.8, width: v * 22, height: 2.8, fill: col(i) })); + if (kind === 'line') return [svgEl('path', { d: 'M2 13L8 8L14 10L22 3', fill: 'none', stroke: col(0), 'stroke-width': 1.6, 'stroke-linejoin': 'round' }), ...(cs.length > 1 ? [svgEl('path', { d: 'M2 15L8 12L14 13L22 9', fill: 'none', stroke: col(1), 'stroke-width': 1.6, 'stroke-linejoin': 'round' })] : [])]; + if (kind === 'area') return [svgEl('path', { d: 'M1 15L1 10L8 6L15 9L23 3L23 15Z', fill: col(0) })]; + if (kind === 'scatter') return [[4, 12], [9, 7], [15, 10], [20, 4]].map(([x, y], i) => svgEl('circle', { cx: x, cy: y, r: 1.8, fill: col(cs.length > 1 ? i : 0) })); + if (kind === 'pie' || kind === 'doughnut' || kind === 'pie3D') { + const n = Math.min(5, cs.length), flat = kind === 'pie3D', ry = flat ? 5 : 7, cy = flat ? 7 : 8, out = []; + if (flat) out.push(svgEl('ellipse', { cx: 12, cy: cy + 3, rx: 10, ry, fill: col(0), class: 'gx-th-side' })); + for (let i = 0; i < n; i++) out.push(n === 1 ? svgEl('ellipse', { cx: 12, cy, rx: flat ? 10 : 7, ry, fill: col(0) }) : svgEl('path', { d: wedge(12, cy, flat ? 10 : 7, ry, (i / n) * 2 * Math.PI, ((i + 1) / n) * 2 * Math.PI), fill: col(i) })); + if (kind === 'doughnut') out.push(svgEl('circle', { cx: 12, cy, r: 3, class: 'gx-th-hole' })); + return out; + } + return [svgEl('path', { d: 'M3 15h18M6 15v-5M11 15V3M16 15V8', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.6 })]; + }; + const svgOf = (kind, colours) => { const g = svgEl('svg', { viewBox: '0 0 24 16', preserveAspectRatio: 'xMidYMid meet' }); g.append(...glyph(kind, colours)); return g; }; + // A record without the summary: its active sheet's drawing, its chart frames counted from the kept XML (no parse). + const counted = (book) => { + const sh = book?.sheets?.[book.active] || book?.sheets?.[0], path = sh?.keep?.drawing?.path, parts = book?.keep?.parts; + const part = path && parts && (parts[path] ?? parts[Object.keys(parts).find((k) => k.toLowerCase() === String(path).toLowerCase())]); + let xml = part?.data; + if (xml && typeof xml === 'object' && typeof xml.$b64 === 'string') { try { xml = atob(xml.$b64); } catch { xml = ''; } } + return typeof xml === 'string' ? (xml.match(/<(?:\w+:)?graphicData\b[^>]*uri="[^"]*chart[^"]*"/g) || []).length : 0; + }; + const chartsOn = (box, rec) => { + const list = Array.isArray(rec?.charts) ? rec.charts.filter((x) => x && Array.isArray(x.anchor) && x.anchor.length === 4 && x.anchor.every(Number.isSafeInteger)) : null; + let away = list ? 0 : counted(rec?.book); + for (const x of list || []) { + const [r0, c0, r1, c1] = x.anchor; + if (r0 > 3 || c0 > 5 || r1 < 0 || c1 < 0 || r1 < r0 || c1 < c0) { away++; continue; } + const kind = typeof x.kind === 'string' ? x.kind : 'chart', colours = Array.isArray(x.colours) ? x.colours.filter((c) => HEX.test(c)).slice(0, 5) : []; + const m = h('i', { class: 'gx-th-chart', 'data-kind': kind }); + m.style.gridRow = `${Math.max(0, r0) + 2} / ${Math.min(3, r1) + 3}`; + m.style.gridColumn = `${Math.max(0, c0) + 2} / ${Math.min(5, c1) + 3}`; + m.append(svgOf(kind, colours)); + box.append(m); + } + if (away) box.append(h('i', { class: 'gx-th-more', 'data-count': String(away) }, svgOf('chart', []), h('b', { text: String(away) }))); + }; const thumb = (rec) => { if (rec) codecs().catch((e) => console.error(e)); const g = Array.isArray(rec?.grid) ? rec.grid : []; @@ -75,6 +123,7 @@ box.append(h('span', { class: 'gx-th-h', text: String(r + 1) })); for (let c = 0; c < 6; c++) { const s = typeof g[r]?.[c] === 'string' ? g[r][c] : ''; box.append(h('span', { class: /^[-+$€£(]?[\d.,]+%?\)?$/.test(s) ? 'gx-th-c gx-th-n' : 'gx-th-c', text: s })); } } + if (rec) chartsOn(box, rec); return box; }; const text = (rec) => (typeof rec?.text === 'string' ? rec.text : ''); @@ -92,7 +141,7 @@ const got = rec?.key ? await GZ_SHEET_DATA.read(get, rec.key) : null; if (!got) return {}; const { book, parts } = GZ_SHEET_DATA.split(got.wb); - return { book, grid: got.rec.grid, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; + return { book, grid: got.rec.grid, charts: got.rec.charts, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; }; // ---- files: .xlsx, .csv and .tsv through GZ_SHEET_XLSX @@ -109,6 +158,7 @@ p({ stage: 'read', done: raw.byteLength, total: raw.byteLength }); p({ stage: 'prepare' }); const X = await codecs(), bytes = new Uint8Array(raw); + await need(CHARTS).catch((e) => console.error(e)); const wb = /\.(csv|tsv)$/i.test(file.name) ? X.readDelimited(bytes, { name: file.name }) : await X.read(bytes).catch(() => null); if (!wb?.sheets?.length) throw new RangeError(SAYS.read); GZ_SHEET_CALC.engine(wb).recalc(); diff --git a/sheet.css b/sheet.css index b03cf6166..721fdf701 100644 --- a/sheet.css +++ b/sheet.css @@ -122,6 +122,14 @@ background: #fff; color: #1f2937; font: 9px/1 Arial, sans-serif; border-radius: 2px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,.35); } :root:not([data-dark]) .gx-thumb { box-shadow: 0 1px 3px rgba(15,23,42,.16), 0 0 0 1px rgba(15,23,42,.06); } .gx-thumb > span { border-right: 1px solid #e5e7eb; border-bottom: 1px solid #e5e7eb; padding: 0 2px; overflow: hidden; white-space: nowrap; text-overflow: clip; display: flex; align-items: center; min-width: 0; } +.gx-thumb { position: relative; } +.gx-th-chart { position: absolute; inset: 1px; display: flex; background: #fff; border: 1px solid #d1d5db; border-radius: 2px; padding: 1px; box-sizing: border-box; pointer-events: none; } +.gx-th-chart > svg, .gx-th-more > svg { display: block; width: 100%; height: 100%; } +.gx-th-side { opacity: .55; } +.gx-th-hole { fill: #fff; } +.gx-th-more { position: absolute; right: 3px; bottom: 3px; display: flex; align-items: center; gap: 2px; height: 14px; padding: 0 3px; border-radius: 7px; background: #fff; color: #4b5563; + border: 1px solid #d1d5db; font: 700 8px/1 Arial, sans-serif; font-style: normal; pointer-events: none; } +.gx-th-more > svg { width: 12px; height: 9px; } .gx-th-h { background: #f3f4f6; color: #6b7280; justify-content: center; font-size: 8px; } .gx-th-n { justify-content: flex-end; } .gd-pic .gx-thumb { zoom: var(--gd-thumb); align-self: flex-start; } diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index d81482923..dd08aa7bd 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -689,3 +689,44 @@ test('CSV and TSV imports use the codec byte reader and replace the prior tracki assert.equal(got.wb.sheets[0].rows.get(1).get(1).v, 42); assert.deepEqual(reads, ['scores.csv', 'notes.tsv']); }); + +// The card's charts: sheet-data.js keeps each chart's kind, cells and colours (lib/sheet-chart-xml.js glance); the card +// draws a small picture of the kind over the cells of A1:F4 it covers, and counts the rest in its corner. +test('the card draws a chart over the cells it covers and counts the charts outside A1:F4', async () => { + const X = require('../lib/sheet-xlsx.js'), CX = require('../lib/sheet-chart-xml.js'), FX = require('./fixtures/sheets/charts.js'); + const { El } = require('./stand-in-dom.js'); + const doc = { createElement: (tag) => new El(tag, doc), createElementNS: (ns, tag) => new El(tag, doc) }; + const { DK } = load({ sheets: true }, { document: doc, GZ_SHEET_XLSX: {} }); + await DK.settled(); + const frame = ([c0, r0, c1, r1], i) => `${c0}0${r0}0${c1}0${r1}0` + + ``; + const drawing = (places) => `\n${places.map(frame).join('')}`; + // A column chart from B2 to the start of F5 (B2:E4), then two pies far off the card. + const wb = await X.read(FX.workbook({ kinds: ['column', 'pie', 'pie'], over: { 'xl/drawings/drawing1.xml': drawing([[1, 1, 5, 4], [8, 10, 12, 20], [8, 22, 12, 30]]) } })); + assert.equal(SD.charts(wb), null, 'without the chart reader: null, and the card counts the drawing'); + globalThis.GZ_SHEET_CHART_XML = CX; + let summary; + try { summary = SD.charts(wb); } finally { delete globalThis.GZ_SHEET_CHART_XML; } + assert.deepEqual(summary.map((x) => [x.kind, x.anchor]), [['column', [1, 1, 3, 4]], ['pie', [10, 8, 19, 11]], ['pie', [22, 8, 29, 11]]]); + assert.equal(summary[0].colours.length, 2, 'the two series\' colours'); + assert.equal(summary[1].colours.length, 4, 'a slice colour per cached point'); + const rec = { kind: 'sheet', book: JSON.parse(JSON.stringify(SD.split(wb).book)), grid: SD.glance(wb), charts: summary }; + const box = DK.get('sheet').thumb(rec); + const marks = box.children.filter((e) => e.className === 'gx-th-chart'); + assert.equal(marks.length, 1); + assert.deepEqual([marks[0].dataset.kind, marks[0].style.gridRow, marks[0].style.gridColumn], ['column', '3 / 6', '3 / 7'], 'rows 2–4 and columns B–E of the card'); + const shapes = marks[0].children[0].children; + assert.ok(shapes.length >= 4 && shapes.length <= 5 && shapes.every((s) => s.tagName === 'RECT' && /^#[0-9A-F]{6}$/i.test(s.attrs.fill)), 'a few bars in the series\' colours'); + assert.deepEqual([...new Set(shapes.map((s) => s.attrs.fill))], summary[0].colours); + const more = box.children.filter((e) => e.className === 'gx-th-more'); + assert.equal(more.length, 1); + assert.equal(more[0].children.at(-1).textContent, '2', 'the two pies off the card, counted'); + // A record kept before the summary: its drawing's charts counted in the corner. + const old = DK.get('sheet').thumb({ ...rec, charts: undefined }).children.find((e) => e.className === 'gx-th-more'); + assert.equal(old?.children.at(-1).textContent, '3'); + // No charts: nothing over the cells. + const plain = M.workbook(); + assert.deepEqual(SD.charts(plain), []); + const none = DK.get('sheet').thumb({ kind: 'sheet', book: JSON.parse(JSON.stringify(SD.split(plain).book)), grid: SD.glance(plain), charts: [] }); + assert.ok(!none.children.some((e) => /gx-th-(chart|more)/.test(e.className))); +}); From b1f4a63a5af3c1bb7560d775b97f1048ac031bbf Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:28:43 -0400 Subject: [PATCH 059/907] Keep the local model's tokenizer off the 4.0.1 patch: it ships with local AI (branch feature/local-ai) Reverts merge f3e110c2. When feature/local-ai merges, revert this commit first. --- lib/ai/local/tokenizer.js | 365 ------------------ .../Resources/lib/ai/local/tokenizer.js | 365 ------------------ scripts/local-tokenizer-check.js | 66 ---- test/ai-local-tokenizer.test.js | 280 -------------- test/ai-worker.test.js | 4 +- 5 files changed, 1 insertion(+), 1079 deletions(-) delete mode 100644 lib/ai/local/tokenizer.js delete mode 100644 safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js delete mode 100644 scripts/local-tokenizer-check.js delete mode 100644 test/ai-local-tokenizer.test.js diff --git a/lib/ai/local/tokenizer.js b/lib/ai/local/tokenizer.js deleted file mode 100644 index 4b70107c5..000000000 --- a/lib/ai/local/tokenizer.js +++ /dev/null @@ -1,365 +0,0 @@ -// Glaze's own tokenizer for the local engine's models: byte-level BPE as LiquidAI's LFM3 specifies it (tokenizer.json at -// huggingface.co/LiquidAI/LFM3-1.7B, commit ac2586a3): no normalizer; the pre-tokenizer's one Split regex (Isolated), -// then the GPT-2 byte → character table; BPE by merge rank with ignore_merges (a piece the vocabulary holds whole is -// one token); added tokens; the ByteLevel decoder. A tokenizer.json or a GGUF's tokenizer.ggml.* keys load the same -// neutral vocabulary, and anything they say that this file doesn't do is refused rather than ignored. -// Page text can never produce a control token: encode() reads added tokens only when asked, and Glaze's own specials -// (add) only ever come from encodeWith. scripts/local-tokenizer-check.js holds it to HF tokenizers line for line. -// Classic script; node exports. -var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { - // GPT-2's table: printable Latin-1 bytes are themselves, the other 68 are U+0100 onwards in byte order. - const BYTE_CHAR = new Uint16Array(256); - const CHAR_BYTE = new Int16Array(0x144).fill(-1); - for (let b = 0, n = 0; b < 256; b++) { - const keep = (b >= 33 && b <= 126) || (b >= 161 && b <= 172) || b >= 174; - BYTE_CHAR[b] = keep ? b : 256 + n++; - CHAR_BYTE[BYTE_CHAR[b]] = b; - } - - // HF tokenizers' regex engine (onig) has its own Unicode tables, older than any browser's: to it 17,144 of today's letters - // (U+0C5C, U+A7CE…) and 336 digits are neither, and those decide where a word ends. Its classes are pinned here, read - // off HF tokenizers 0.22.2's own splits of every code point ("a"+c one piece: a letter; "1"+c: a digit; c+"!" two - // pieces and neither: White_Space), so every runtime splits as the model's tokenizer did. Ranges, each as base-36 - // "gap" or "gap.length" from the last one's end. - const ONIG = { - L: '1t.p,7.p,1c,b,5,6.m,2.u,2.cp,5.b,f.4,8,2,3m.4,2.1,3.3,2,7,2.2,2,2.j,2.2a,2.3u,9.4l,2.11,3,7.14,20.q,5.3,1a.16,10.1,2.2q,2,g.1,8.1,b.2,3,h,2.t,u.2g,c,p.w,a.1,5,6.l,5,a,4,o.o,8.a,6.n,2.5,i.15,1n.1h,4,j,8.9,g.f,5.7,3.1,3.l,2.6,2,4.3,4,h,e.1,2.2,f.1,b,9.5,5.1,3.l,2.6,2.1,2.1,2.1,w.3,2,k.2,h.8,2.2,2.l,2.6,2.1,2.4,4,j,g.1,o,c.7,3.1,3.l,2.6,2.1,2.4,4,v.1,2.2,g,i,2.5,4.2,2.3,4.1,2,2.1,4.1,4.2,4.b,n,1h.7,2.2,2.m,2.f,4,r.2,3,3.1,v,5.7,2.2,2.m,2.9,2.4,4,w.1,2.1,g.1,i.8,2.2,2.14,3,h,6.2,9.2,p.5,6.h,4.n,2.8,2,3.6,1n.1b,2.1,d.6,1n.1,2,2.4,2.n,2,2.9,2.1,a,3.4,2,m.3,x,1s.7,2.z,s.4,38.16,l,h.5,5.3,4,4.1,8.2,5.c,d,i.11,2,6,3.16,2.98,2.3,3.6,2,2.3,3.14,2.3,3.w,2.3,3.6,2,2.3,3.e,2.1k,2.3,3.1u,12.f,h.2d,3.5,4.h7,3.g,2.p,6.22,7.7,8.h,e.i,f.h,f.c,2.2,g.1f,10,5,1w.2g,8.4,3.x,2,6.1x,b.u,1e.t,3.4,c.17,5.p,1j.m,a.1g,2b,2m.1a,i.7,1j.t,e.1,b.17,r.z,16.2,b.z,3.a,6.16,3.2,16.3,2.5,2.1,4,6.5b,1t.7p,3.5,3.11,3.5,3.7,2,2,2,2.u,3.1g,2.6,2,4.2,2.6,4.3,3.5,5.c,6.2,2.6,39,e,h.c,2u,5,3.9,2,4.4,7,2,2,2.3,2.a,3.3,6.4,5,1h.1,22k.6c,7.3,4.1,d.11,2,6,3.1j,8,h.m,a.6,2.6,2.6,2.6,2.6,2.6,2.6,2.6,29,d2.1,17.4,6.1,5.2d,7.2,2.2h,2.3,6.16,2.2l,i.v,1d.f,e9.533,1t.h3g,1w.19,3.7g,4.f,b.1,l.1a,h.u,3.1x,1e.8,3.2u,3.1u,3.1,2,2.7,m.f,2.2,2.3,2.m,u.1f,f.1d,1r.5,4,2.1,c.r,b.m,q.s,8.1a,t,h.4,2.9,b.4,2.14,o.2,2.7,l.m,4,4.1d,2,4.1,3.4,3,2,p.2,3.a,8.2,d.5,3.5,3.5,a.6,2.6,2.16,2.d,7.36,u.8mb,d.m,5.1c,6it.a5,3.2x,13.6,d.4,6,2.9,2.c,2.4,2,2.1,2.1,2.2z,y.a2,j.1r,3.1h,15.b,39.4,2.3q,11.p,7.p,c.2g,4.5,3.5,3.5,3.2,10.b,2.p,2.i,2.1,2.e,3.d,z.3e,au.s,4.1c,1c.v,e.j,2.7,7.11,b.t,3.z,5.7,1d.4d,j.z,5.z,5.13,9.1f,d.a,2.e,2.6,2.1,2.a,2.e,2.6,2.1,4.1f,d.8m,a.l,b.7,p.5,2.15,2.8,1y.5,3,2.17,2.1,4,3.m,b.m,a.u,1u.i,2.1,b.l,b.p,1z.1j,7.1,1t,g.3,2.2,2.s,17.s,4.s,10.7,2.r,s.1h,b.l,b.i,e.h,33.20,1k.1e,e.1e,e.z,13.r,a.m,6z.15,7.1,h.2,1o.s,b,9.l,17.h,1b.k,s.m,d.1g,1m.1,3,e.18,x.o,r.z,u,3,9.y,4,d.1b,f.3,m,2,10.h,2.o,k.1,1s.6,2,2.3,2.e,2.9,8.1a,13.7,3.1,3.l,2.6,2.1,2.4,4,j,d.4,v.9,2,3,2.11,2,q,2,19.1g,j.3,l.2,v.1b,l.1,2,55.1a,16.3,11.1b,l,1o.16,e,20.q,12.6,56.17,39.1r,w.7,3,3.7,2.1,2.n,g,2,2n.7,3.12,h,2,t,b.13,8,m,c.19,k,j.20,5k.w,w.8,2.10,i,1e.t,35.6,2.1,2.11,m,q.5,2.1,2.v,f,94.i,g,2.c,2.x,3h,28.pl,6f.5f,219.2o,g.tr,i.5,q.32y,6.g6,5a2.t,1cz.fs,8.u,i.26,i.t,j.1b,h.3,w.k,6.i,c1.18,5w.1r,3l.22,6,1v.c,1t.1,2,t.4qf,9.yd,16.9,6w8.3,2.6,2.1,2.82,g,u.2,3,f.3,9.az,1s5.2y,6.c,4.8,8.9,4mf.2c,2.1y,2.1,3,3.1,3.3,2.b,2,2.6,2.1s,2.3,3.7,2.6,2.r,2.3,2.4,2,4.6,2.9f,3.o,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.7,1f9.u,7.5,7a.1p,43.18,b.6,h,8y.t,j.17,dh.r,6d.t,3,ds.6,2.3,2.1,2.e,2.5g,1o.1v,8,xh.3,2.q,2.1,2,3,2.9,2.3,2,2,7,5,2,2,2.2,2.1,2,3,2,2,2,2,2.1,2,3.3,2.6,2.3,2.3,2,2.9,2.g,6.2,2.4,2.g,3et.wyn,x.37d,7.65,3.4g1,f.5rk,g.h9,1wj.f1,15v.3t6,6.38f', - N: '1c.9,3d.1,6,3.2,142.9,3r.9,5j.9,bh.9,3b.9,5.5,31.9,3b.9,3b.9,3.5,33.c,38.9,9.6,2w.9,2x.6,8.i,32.9,2p.9,3b.9,1z.j,7h.9,1z.9,k0.j,oi.2,6o.9,7.9,n.9,8d.9,3l.a,4m.9,7.9,53.9,2f.9,3r.9,7.9,t3,4.5,7.9,5j.1e,3.4,k7.1n,27.l,hj.t,12i,lm,q.8,f.2,9k.3,3v.9,v.7,2.e,x.9,14.e,msh.9,59.9,8x.5,4b.9,13.9,5j.9,n.9,2f.9,bb.9,gev.9,dq.18,d.1k,i.1,9i.q,11.3,u,9,3r.4,5n.9,q7.7,q.6,14.8,24.4,n.5,4h.1,3.f,3.19,1t.8,1h.1,v.2,24.4,2x.7,p.7,16.6,97.5,1d.9,7.9,7r.u,4f.9,17.3,35.6,3r.t,3l.9,1p.9,41.9,8.j,70.9,9j.9,3b.9,af.9,2v.9,7.j,25.b,bp.i,2m.9,if.9,2f.s,6c.9,1z.9,br.9,2v.k,to.32,c02.9,1t3.9,2f.9,3r.9,2.6,en.9,7b.m,imy.9,153.j,d.j,31.o,uu.1d,1tt.9,br.9,dz.9,6w.9,jx.8,3l.9,m0.1m,2.2,2.3,25.18,2.e,qr.c,25g.9', - W: '9.4,j,2t,r,4bk,1vk.a,u.1,6,1c,335', - }; - const cls = (packed) => { - let at = 0, out = ''; - for (const r of packed.split(',')) { - const [g, n = '0'] = r.split('.'); - const a = at + parseInt(g, 36), b = a + parseInt(n, 36); - out += a === b ? `\\u{${a.toString(16)}}` : `\\u{${a.toString(16)}}-\\u{${b.toString(16)}}`; - at = b; - } - return out; - }; - // Pre-tokenizers by GGUF name, with the onig regex a tokenizer.json writes for each and its JS (made on first use). - // onig's \s is Unicode White_Space (U+0085 in, U+FEFF out), and its (?i:'s) also takes ſ, the one letter that - // case-folds onto those (HF: 'ſt is 'ſ, t). - const PRE = { - lfm2: { - hf: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+", - js: () => { - const L = cls(ONIG.L), N = cls(ONIG.N), W = cls(ONIG.W); - return `'(?:[sS\\u017f]|[tT]|[rR][eE]|[vV][eE]|[mM]|[lL][lL]|[dD])|[^\\r\\n${L}${N}]?[${L}]+|[${N}]{1,3}| ?[^${W}${L}${N}]+[\\r\\n]*|[${W}]*[\\r\\n]+|[${W}]+(?![^${W}])|[${W}]+`; - }, - ignoreMerges: true, - }, - }; - // GGUF token types. - const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; - // Glaze's own specials. Append-only: their ids are trained into the published weights (each takes the model's next - // reserved row, in this order), so never reorder or remove one. - const GLAZE_TOKENS = Object.freeze(['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>', - ...['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', - 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)]); - const enc = new TextEncoder(); - const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; - // Lone surrogates → U+FFFD, as TextEncoder makes them. - const well = (s) => (s.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g, (m) => (m.length === 2 ? m : '\ufffd'))); - const pair = (m) => { - if (Array.isArray(m) && m.length === 2) return m; - const s = String(m), i = s.indexOf(' '); - if (i <= 0 || i !== s.lastIndexOf(' ') || i === s.length - 1) fail(`merge ${JSON.stringify(s)}`); - return [s.slice(0, i), s.slice(i + 1)]; - }; - - // A token's bytes as the ByteLevel decoder gives them: each character back through the table, or, when one isn't in - // it, the token's own UTF-8 (HF's rule, per token). - const tokenBytes = (s) => { - const out = new Uint8Array(s.length); - for (let i = 0; i < s.length; i++) { - const c = s.charCodeAt(i), b = c < CHAR_BYTE.length ? CHAR_BYTE[c] : -1; - if (b < 0) return enc.encode(s); - out[i] = b; - } - return out; - }; - - // Merge ranks in an open-addressed table of (left id, right id) → rank, and rank → the merged id. - const mergeTable = (merges, vocab) => { - const cap = 1 << Math.max(10, Math.ceil(Math.log2(merges.length * 2 + 1))); - const ka = new Int32Array(cap).fill(-1), kb = new Int32Array(cap), rk = new Int32Array(cap), to = new Int32Array(merges.length); - const mask = cap - 1; - const slot = (a, b) => (Math.imul(a, 0x9e3779b1) ^ Math.imul(b, 0x85ebca6b)) & mask; - merges.forEach((m, r) => { - const [x, y] = pair(m); - const a = vocab.get(x), b = vocab.get(y), c = vocab.get(x + y); - if (a === undefined || b === undefined || c === undefined) fail(`merge ${JSON.stringify(`${x} ${y}`)} is not in the vocabulary`); - let i = slot(a, b); - while (ka[i] !== -1 && !(ka[i] === a && kb[i] === b)) i = (i + 1) & mask; - ka[i] = a; kb[i] = b; rk[i] = r; to[r] = c; // a merge listed twice ranks where it is last, as in HF's map - }); - const rank = (a, b) => { - for (let i = slot(a, b); ; i = (i + 1) & mask) { - const k = ka[i]; - if (k === -1) return -1; - if (k === a && kb[i] === b) return rk[i]; - } - }; - return { rank, to }; - }; - - // v: { tokens, types, merges, pre, rows?, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them - // (byte-level strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order, rows the - // model's embedding rows (default: the tokens). opts.cache: words kept. - const load = (v, opts = {}) => { - const pre = PRE[v.pre]; - if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); - const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); - if (types.length !== tokens.length) fail('types and tokens differ in length'); - const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; - const rows = v.rows ?? tokens.length; - if (!Number.isInteger(rows) || rows < tokens.length) fail(`rows ${rows}`); - const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(rows).fill(T.unused); - let longest = 0; - tokens.forEach((t, id) => { - const ty = types[id]; - kinds[id] = ty; - if (ty === T.control || ty === T.user) { if (!specials.has(t)) specials.set(t, id); } - else if (ty !== T.unused) { if (!vocab.has(t)) vocab.set(t, id); longest = Math.max(longest, t.length); } - }); - const byteId = new Int32Array(256); - for (let b = 0; b < 256; b++) { - const id = vocab.get(String.fromCharCode(BYTE_CHAR[b])); - if (id === undefined) fail(`byte 0x${b.toString(16)} has no token`); - byteId[b] = id; - } - const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); - const re = new RegExp((pre.src ||= pre.js()), 'gu'); - // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. - const matched = [...specials.keys()].filter(Boolean); - const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; - // Reserved rows, in id order: the unused ones (a GGUF's [PAD…]), then any rows past the tokens. GLAZE_TOKENS[i] takes - // the i-th, so a GGUF and a tokenizer.json with the model's rows give the same ids. - const reserved = []; - for (let id = 0; id < rows; id++) if (kinds[id] === T.unused) reserved.push(id); - const glaze = new Map(), glazeName = new Map(); // name ↔ id of the Glaze specials added - const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= rows) throw new RangeError(`tokenizer: id ${id}`); return id; }; - - // ---- BPE --------------------------------------------------------------------------------------------------------- - let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); - const grow = (n) => { - if (buf.length < n * 3) buf = new Uint8Array(n * 3 + 64); - if (sym.length < n * 3) { const m = n * 3 + 64; sym = new Int32Array(m); next = new Int32Array(m); prev = new Int32Array(m); heap = new Float64Array(m * 2); } - }; - const P = 2 ** 32; - // A min-heap of rank·2³² + left position: the lowest rank merges first, the leftmost on a tie. - let h = 0; - const push = (key) => { - if (h === heap.length) { const g = new Float64Array(h * 2); g.set(heap); heap = g; } - let i = h++; - while (i > 0) { const p = (i - 1) >> 1; if (heap[p] <= key) break; heap[i] = heap[p]; i = p; } - heap[i] = key; - }; - const pop = () => { - const top = heap[0], last = heap[--h]; - let i = 0; - for (;;) { - let c = 2 * i + 1; - if (c >= h) break; - if (c + 1 < h && heap[c + 1] < heap[c]) c++; - if (heap[c] >= last) break; - heap[i] = heap[c]; i = c; - } - heap[i] = last; - return top; - }; - const bpe = (piece) => { - grow(piece.length); - const n = enc.encodeInto(piece, buf).written; - if (ignoreMerges && n <= longest) { - if (chars.length < n) chars = new Uint16Array(longest); - for (let i = 0; i < n; i++) chars[i] = BYTE_CHAR[buf[i]]; - const whole = vocab.get(String.fromCharCode.apply(null, chars.subarray(0, n))); - if (whole !== undefined) return [whole]; - } - for (let i = 0; i < n; i++) { sym[i] = byteId[buf[i]]; prev[i] = i - 1; next[i] = i + 1 < n ? i + 1 : -1; } - h = 0; - for (let i = 0; i + 1 < n; i++) { const r = rank(sym[i], sym[i + 1]); if (r >= 0) push(r * P + i); } - while (h > 0) { - const key = pop(), r = Math.floor(key / P), i = key - r * P; - const j = next[i]; - if (sym[i] < 0 || j < 0 || rank(sym[i], sym[j]) !== r) continue; - sym[i] = to[r]; sym[j] = -1; - const k = next[j]; - next[i] = k; - if (k >= 0) prev[k] = i; - if (prev[i] >= 0) { const q = rank(sym[prev[i]], sym[i]); if (q >= 0) push(q * P + prev[i]); } - if (k >= 0) { const q = rank(sym[i], sym[k]); if (q >= 0) push(q * P + i); } - } - const out = []; - for (let i = 0; i >= 0; i = next[i]) out.push(sym[i]); - return out; - }; - - // Words (pre-tokenizer pieces) → ids. Two generations of opts.cache words: a word found in the older one moves to the - // newer, and a full newer one becomes the older (deleting a Map's oldest entry one at a time is slow in V8). - const CACHE = opts.cache ?? 16384; - let cache = new Map(), older = new Map(); - const word = (piece) => { - let ids = cache.get(piece); - if (ids) return ids; - if (piece.length > 256) return bpe(piece); - ids = older.get(piece) || bpe(piece); - if (cache.size >= CACHE) { older = cache; cache = new Map(); } - cache.set(piece, ids); - return ids; - }; - - // Ids are collected in one growing Uint32Array. - const sink = () => { - let a = new Uint32Array(256), n = 0; - return { - add(ids) { - if (n + ids.length > a.length) { const g = new Uint32Array(Math.max(a.length * 2, n + ids.length)); g.set(a.subarray(0, n)); a = g; } - for (let i = 0; i < ids.length; i++) a[n++] = ids[i]; - }, - get n() { return n; }, - done: () => a.slice(0, n), - }; - }; - const pieces = (text, each) => { - re.lastIndex = 0; - let last = 0, m; - while ((m = re.exec(text))) { - if (m.index > last) each(text.slice(last, m.index)); - each(m[0]); - last = re.lastIndex; - } - if (last < text.length) each(text.slice(last)); - }; - const plainText = (text, out) => { if (text) pieces(text, (p) => out.add(word(p))); }; - const run = (text, specialsOn, out) => { - const s = well(String(text)); - if (!specialsOn || !addedRe) return plainText(s, out); - addedRe.lastIndex = 0; - let last = 0, m; - while ((m = addedRe.exec(s))) { - plainText(s.slice(last, m.index), out); - out.add([specials.get(m[0])]); - last = addedRe.lastIndex; - } - plainText(s.slice(last), out); - }; - - // ---- decoding ---------------------------------------------------------------------------------------------------- - // Each model token's bytes, made once; null for added and unused ones. - let base = null; - const baseBytes = () => (base ||= Array.from({ length: rows }, (_, id) => (kinds[id] === T.normal || kinds[id] === T.unknown || kinds[id] === T.byte ? tokenBytes(tokens[id]) : null))); - const EMPTY = new Uint8Array(0); - const added = new Map(); // id → bytes of an added token's text, for decoding - const idBytes = (id, specialsOn) => { - idOk(id); - const k = kinds[id]; - if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; - if (k === T.control || k === T.user) { - let b = added.get(id); - if (!b) { b = glazeName.has(id) ? enc.encode(glazeName.get(id)) : tokenBytes(tokens[id]); added.set(id, b); } - return b; - } - return baseBytes()[id]; - }; - - return { - size: rows, - get cached() { return cache.size + older.size; }, - bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, - // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. - encode(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.done(); }, - count(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.n; }, - // parts: [{ text } | { special: name }], name an added token's text or one add() registered. - encodeWith(parts) { - const out = sink(); - for (const p of parts) { - const keys = p && typeof p === 'object' ? Object.keys(p) : []; - if (keys.length !== 1 || !(keys[0] === 'text' || keys[0] === 'special')) fail('a part is { text } or { special }'); - if (keys[0] === 'text') { run(p.text, false, out); continue; } - const id = this.id(p.special); - if (id === undefined) fail(`unknown special ${JSON.stringify(p.special)}`); - out.add([id]); - } - return out.done(); - }, - id(name) { return specials.get(name) ?? glaze.get(name); }, - // A Glaze special (one of GLAZE_TOKENS), in its reserved row; the same name keeps its id. → its id. - add(name) { - if (glaze.has(name)) return glaze.get(name); - const i = GLAZE_TOKENS.indexOf(name); - if (i < 0 || specials.has(name) || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); - if (i >= reserved.length) fail(`no reserved row for ${name}: the model has ${reserved.length}`); - const id = reserved[i]; - glaze.set(name, id); glazeName.set(id, name); kinds[id] = T.control; - return id; - }, - // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are - // never text. - byteStrings: () => baseBytes().map((b) => b && b.slice()), - // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). - decode(ids, { specials: sp = false } = {}) { - const parts = []; - let n = 0; - for (const id of ids) { const b = idBytes(id, sp); if (b.length) { parts.push(b); n += b.length; } } - const all = new Uint8Array(n); - let o = 0; - for (const b of parts) { all.set(b, o); o += b.length; } - return new TextDecoder('utf-8', { ignoreBOM: true }).decode(all); - }, - // push(id) → the text that is complete so far; end() → the rest (an unfinished character as U+FFFD). - decoder({ specials: sp = false } = {}) { - const td = new TextDecoder('utf-8', { ignoreBOM: true }); - return { push: (id) => td.decode(idBytes(id, sp), { stream: true }), end: () => td.decode() }; - }, - // The pre-tokenizer's pieces (for tests and the check). - split(text) { const out = []; pieces(well(String(text)), (p) => out.push(p)); return out; }, - }; - }; - - // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), rows, - // and load()'s cache. - const fromHF = (json, opts = {}) => { - const m = json && json.model; - if (!m || m.type !== 'BPE') fail('model: only BPE'); - if (m.byte_fallback) fail('byte_fallback'); - if (m.dropout) fail('dropout'); - if (m.unk_token != null) fail('unk_token'); - if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); - if (json.normalizer != null) fail('normalizer'); - if (json.truncation != null) fail('truncation'); - if (json.padding != null) fail('padding'); - const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; - const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; - const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; - const pre = Object.keys(PRE).find((k) => PRE[k].hf === split); - if (!pre || !bl) fail('pre_tokenizer'); - if (!json.decoder || json.decoder.type !== 'ByteLevel') fail('decoder'); - if (json.post_processor != null && json.post_processor.type !== 'ByteLevel') fail('post_processor'); - const tokens = [], types = []; - for (const [t, id] of Object.entries(m.vocab)) { tokens[id] = t; types[id] = T.normal; } - for (const a of json.added_tokens || []) { - if (a.lstrip || a.rstrip || a.single_word) fail(`added token ${JSON.stringify(a.content)}: lstrip, rstrip or single_word`); - if (types[a.id] !== undefined) fail(`added token ${JSON.stringify(a.content)} is in the vocabulary`); - tokens[a.id] = a.content; types[a.id] = a.special ? T.control : T.user; - } - for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } - const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; - // A tokenizer.json doesn't say how many rows the model has (its config's vocab_size does): opts.rows. - return load({ tokens, types, merges: m.merges, pre, rows: opts.rows, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); - }; - - // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. - const fromGGUF = (kv, opts) => { - if (!kv || !('tokenizer.ggml.tokens' in kv)) return load(kv, opts); - const g = (k) => kv[`tokenizer.ggml.${k}`]; - if (g('model') !== 'gpt2') fail(`model ${JSON.stringify(g('model'))}: only gpt2 (byte-level BPE)`); - if (!PRE[g('pre')]) fail(`pre-tokenizer ${JSON.stringify(g('pre'))}`); - const id = (k) => (g(k) == null ? null : Number(g(k))); - return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); - }; - - return { load, fromHF, fromGGUF, PRE, TYPES: T, GLAZE_TOKENS }; -})(); -if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js b/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js deleted file mode 100644 index 4b70107c5..000000000 --- a/safari/Glaze/Glaze Extension/Resources/lib/ai/local/tokenizer.js +++ /dev/null @@ -1,365 +0,0 @@ -// Glaze's own tokenizer for the local engine's models: byte-level BPE as LiquidAI's LFM3 specifies it (tokenizer.json at -// huggingface.co/LiquidAI/LFM3-1.7B, commit ac2586a3): no normalizer; the pre-tokenizer's one Split regex (Isolated), -// then the GPT-2 byte → character table; BPE by merge rank with ignore_merges (a piece the vocabulary holds whole is -// one token); added tokens; the ByteLevel decoder. A tokenizer.json or a GGUF's tokenizer.ggml.* keys load the same -// neutral vocabulary, and anything they say that this file doesn't do is refused rather than ignored. -// Page text can never produce a control token: encode() reads added tokens only when asked, and Glaze's own specials -// (add) only ever come from encodeWith. scripts/local-tokenizer-check.js holds it to HF tokenizers line for line. -// Classic script; node exports. -var GZ_AI_TOKENIZER = globalThis.GZ_AI_TOKENIZER || (() => { - // GPT-2's table: printable Latin-1 bytes are themselves, the other 68 are U+0100 onwards in byte order. - const BYTE_CHAR = new Uint16Array(256); - const CHAR_BYTE = new Int16Array(0x144).fill(-1); - for (let b = 0, n = 0; b < 256; b++) { - const keep = (b >= 33 && b <= 126) || (b >= 161 && b <= 172) || b >= 174; - BYTE_CHAR[b] = keep ? b : 256 + n++; - CHAR_BYTE[BYTE_CHAR[b]] = b; - } - - // HF tokenizers' regex engine (onig) has its own Unicode tables, older than any browser's: to it 17,144 of today's letters - // (U+0C5C, U+A7CE…) and 336 digits are neither, and those decide where a word ends. Its classes are pinned here, read - // off HF tokenizers 0.22.2's own splits of every code point ("a"+c one piece: a letter; "1"+c: a digit; c+"!" two - // pieces and neither: White_Space), so every runtime splits as the model's tokenizer did. Ranges, each as base-36 - // "gap" or "gap.length" from the last one's end. - const ONIG = { - L: '1t.p,7.p,1c,b,5,6.m,2.u,2.cp,5.b,f.4,8,2,3m.4,2.1,3.3,2,7,2.2,2,2.j,2.2a,2.3u,9.4l,2.11,3,7.14,20.q,5.3,1a.16,10.1,2.2q,2,g.1,8.1,b.2,3,h,2.t,u.2g,c,p.w,a.1,5,6.l,5,a,4,o.o,8.a,6.n,2.5,i.15,1n.1h,4,j,8.9,g.f,5.7,3.1,3.l,2.6,2,4.3,4,h,e.1,2.2,f.1,b,9.5,5.1,3.l,2.6,2.1,2.1,2.1,w.3,2,k.2,h.8,2.2,2.l,2.6,2.1,2.4,4,j,g.1,o,c.7,3.1,3.l,2.6,2.1,2.4,4,v.1,2.2,g,i,2.5,4.2,2.3,4.1,2,2.1,4.1,4.2,4.b,n,1h.7,2.2,2.m,2.f,4,r.2,3,3.1,v,5.7,2.2,2.m,2.9,2.4,4,w.1,2.1,g.1,i.8,2.2,2.14,3,h,6.2,9.2,p.5,6.h,4.n,2.8,2,3.6,1n.1b,2.1,d.6,1n.1,2,2.4,2.n,2,2.9,2.1,a,3.4,2,m.3,x,1s.7,2.z,s.4,38.16,l,h.5,5.3,4,4.1,8.2,5.c,d,i.11,2,6,3.16,2.98,2.3,3.6,2,2.3,3.14,2.3,3.w,2.3,3.6,2,2.3,3.e,2.1k,2.3,3.1u,12.f,h.2d,3.5,4.h7,3.g,2.p,6.22,7.7,8.h,e.i,f.h,f.c,2.2,g.1f,10,5,1w.2g,8.4,3.x,2,6.1x,b.u,1e.t,3.4,c.17,5.p,1j.m,a.1g,2b,2m.1a,i.7,1j.t,e.1,b.17,r.z,16.2,b.z,3.a,6.16,3.2,16.3,2.5,2.1,4,6.5b,1t.7p,3.5,3.11,3.5,3.7,2,2,2,2.u,3.1g,2.6,2,4.2,2.6,4.3,3.5,5.c,6.2,2.6,39,e,h.c,2u,5,3.9,2,4.4,7,2,2,2.3,2.a,3.3,6.4,5,1h.1,22k.6c,7.3,4.1,d.11,2,6,3.1j,8,h.m,a.6,2.6,2.6,2.6,2.6,2.6,2.6,2.6,29,d2.1,17.4,6.1,5.2d,7.2,2.2h,2.3,6.16,2.2l,i.v,1d.f,e9.533,1t.h3g,1w.19,3.7g,4.f,b.1,l.1a,h.u,3.1x,1e.8,3.2u,3.1u,3.1,2,2.7,m.f,2.2,2.3,2.m,u.1f,f.1d,1r.5,4,2.1,c.r,b.m,q.s,8.1a,t,h.4,2.9,b.4,2.14,o.2,2.7,l.m,4,4.1d,2,4.1,3.4,3,2,p.2,3.a,8.2,d.5,3.5,3.5,a.6,2.6,2.16,2.d,7.36,u.8mb,d.m,5.1c,6it.a5,3.2x,13.6,d.4,6,2.9,2.c,2.4,2,2.1,2.1,2.2z,y.a2,j.1r,3.1h,15.b,39.4,2.3q,11.p,7.p,c.2g,4.5,3.5,3.5,3.2,10.b,2.p,2.i,2.1,2.e,3.d,z.3e,au.s,4.1c,1c.v,e.j,2.7,7.11,b.t,3.z,5.7,1d.4d,j.z,5.z,5.13,9.1f,d.a,2.e,2.6,2.1,2.a,2.e,2.6,2.1,4.1f,d.8m,a.l,b.7,p.5,2.15,2.8,1y.5,3,2.17,2.1,4,3.m,b.m,a.u,1u.i,2.1,b.l,b.p,1z.1j,7.1,1t,g.3,2.2,2.s,17.s,4.s,10.7,2.r,s.1h,b.l,b.i,e.h,33.20,1k.1e,e.1e,e.z,13.r,a.m,6z.15,7.1,h.2,1o.s,b,9.l,17.h,1b.k,s.m,d.1g,1m.1,3,e.18,x.o,r.z,u,3,9.y,4,d.1b,f.3,m,2,10.h,2.o,k.1,1s.6,2,2.3,2.e,2.9,8.1a,13.7,3.1,3.l,2.6,2.1,2.4,4,j,d.4,v.9,2,3,2.11,2,q,2,19.1g,j.3,l.2,v.1b,l.1,2,55.1a,16.3,11.1b,l,1o.16,e,20.q,12.6,56.17,39.1r,w.7,3,3.7,2.1,2.n,g,2,2n.7,3.12,h,2,t,b.13,8,m,c.19,k,j.20,5k.w,w.8,2.10,i,1e.t,35.6,2.1,2.11,m,q.5,2.1,2.v,f,94.i,g,2.c,2.x,3h,28.pl,6f.5f,219.2o,g.tr,i.5,q.32y,6.g6,5a2.t,1cz.fs,8.u,i.26,i.t,j.1b,h.3,w.k,6.i,c1.18,5w.1r,3l.22,6,1v.c,1t.1,2,t.4qf,9.yd,16.9,6w8.3,2.6,2.1,2.82,g,u.2,3,f.3,9.az,1s5.2y,6.c,4.8,8.9,4mf.2c,2.1y,2.1,3,3.1,3.3,2.b,2,2.6,2.1s,2.3,3.7,2.6,2.r,2.3,2.4,2,4.6,2.9f,3.o,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.u,2.o,2.7,1f9.u,7.5,7a.1p,43.18,b.6,h,8y.t,j.17,dh.r,6d.t,3,ds.6,2.3,2.1,2.e,2.5g,1o.1v,8,xh.3,2.q,2.1,2,3,2.9,2.3,2,2,7,5,2,2,2.2,2.1,2,3,2,2,2,2,2.1,2,3.3,2.6,2.3,2.3,2,2.9,2.g,6.2,2.4,2.g,3et.wyn,x.37d,7.65,3.4g1,f.5rk,g.h9,1wj.f1,15v.3t6,6.38f', - N: '1c.9,3d.1,6,3.2,142.9,3r.9,5j.9,bh.9,3b.9,5.5,31.9,3b.9,3b.9,3.5,33.c,38.9,9.6,2w.9,2x.6,8.i,32.9,2p.9,3b.9,1z.j,7h.9,1z.9,k0.j,oi.2,6o.9,7.9,n.9,8d.9,3l.a,4m.9,7.9,53.9,2f.9,3r.9,7.9,t3,4.5,7.9,5j.1e,3.4,k7.1n,27.l,hj.t,12i,lm,q.8,f.2,9k.3,3v.9,v.7,2.e,x.9,14.e,msh.9,59.9,8x.5,4b.9,13.9,5j.9,n.9,2f.9,bb.9,gev.9,dq.18,d.1k,i.1,9i.q,11.3,u,9,3r.4,5n.9,q7.7,q.6,14.8,24.4,n.5,4h.1,3.f,3.19,1t.8,1h.1,v.2,24.4,2x.7,p.7,16.6,97.5,1d.9,7.9,7r.u,4f.9,17.3,35.6,3r.t,3l.9,1p.9,41.9,8.j,70.9,9j.9,3b.9,af.9,2v.9,7.j,25.b,bp.i,2m.9,if.9,2f.s,6c.9,1z.9,br.9,2v.k,to.32,c02.9,1t3.9,2f.9,3r.9,2.6,en.9,7b.m,imy.9,153.j,d.j,31.o,uu.1d,1tt.9,br.9,dz.9,6w.9,jx.8,3l.9,m0.1m,2.2,2.3,25.18,2.e,qr.c,25g.9', - W: '9.4,j,2t,r,4bk,1vk.a,u.1,6,1c,335', - }; - const cls = (packed) => { - let at = 0, out = ''; - for (const r of packed.split(',')) { - const [g, n = '0'] = r.split('.'); - const a = at + parseInt(g, 36), b = a + parseInt(n, 36); - out += a === b ? `\\u{${a.toString(16)}}` : `\\u{${a.toString(16)}}-\\u{${b.toString(16)}}`; - at = b; - } - return out; - }; - // Pre-tokenizers by GGUF name, with the onig regex a tokenizer.json writes for each and its JS (made on first use). - // onig's \s is Unicode White_Space (U+0085 in, U+FEFF out), and its (?i:'s) also takes ſ, the one letter that - // case-folds onto those (HF: 'ſt is 'ſ, t). - const PRE = { - lfm2: { - hf: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+", - js: () => { - const L = cls(ONIG.L), N = cls(ONIG.N), W = cls(ONIG.W); - return `'(?:[sS\\u017f]|[tT]|[rR][eE]|[vV][eE]|[mM]|[lL][lL]|[dD])|[^\\r\\n${L}${N}]?[${L}]+|[${N}]{1,3}| ?[^${W}${L}${N}]+[\\r\\n]*|[${W}]*[\\r\\n]+|[${W}]+(?![^${W}])|[${W}]+`; - }, - ignoreMerges: true, - }, - }; - // GGUF token types. - const T = { normal: 1, unknown: 2, control: 3, user: 4, unused: 5, byte: 6 }; - // Glaze's own specials. Append-only: their ids are trained into the published weights (each takes the model's next - // reserved row, in this order), so never reorder or remove one. - const GLAZE_TOKENS = Object.freeze(['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>', - ...['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', - 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)]); - const enc = new TextEncoder(); - const fail = (what) => { throw new TypeError(`tokenizer: ${what}`); }; - // Lone surrogates → U+FFFD, as TextEncoder makes them. - const well = (s) => (s.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g, (m) => (m.length === 2 ? m : '\ufffd'))); - const pair = (m) => { - if (Array.isArray(m) && m.length === 2) return m; - const s = String(m), i = s.indexOf(' '); - if (i <= 0 || i !== s.lastIndexOf(' ') || i === s.length - 1) fail(`merge ${JSON.stringify(s)}`); - return [s.slice(0, i), s.slice(i + 1)]; - }; - - // A token's bytes as the ByteLevel decoder gives them: each character back through the table, or, when one isn't in - // it, the token's own UTF-8 (HF's rule, per token). - const tokenBytes = (s) => { - const out = new Uint8Array(s.length); - for (let i = 0; i < s.length; i++) { - const c = s.charCodeAt(i), b = c < CHAR_BYTE.length ? CHAR_BYTE[c] : -1; - if (b < 0) return enc.encode(s); - out[i] = b; - } - return out; - }; - - // Merge ranks in an open-addressed table of (left id, right id) → rank, and rank → the merged id. - const mergeTable = (merges, vocab) => { - const cap = 1 << Math.max(10, Math.ceil(Math.log2(merges.length * 2 + 1))); - const ka = new Int32Array(cap).fill(-1), kb = new Int32Array(cap), rk = new Int32Array(cap), to = new Int32Array(merges.length); - const mask = cap - 1; - const slot = (a, b) => (Math.imul(a, 0x9e3779b1) ^ Math.imul(b, 0x85ebca6b)) & mask; - merges.forEach((m, r) => { - const [x, y] = pair(m); - const a = vocab.get(x), b = vocab.get(y), c = vocab.get(x + y); - if (a === undefined || b === undefined || c === undefined) fail(`merge ${JSON.stringify(`${x} ${y}`)} is not in the vocabulary`); - let i = slot(a, b); - while (ka[i] !== -1 && !(ka[i] === a && kb[i] === b)) i = (i + 1) & mask; - ka[i] = a; kb[i] = b; rk[i] = r; to[r] = c; // a merge listed twice ranks where it is last, as in HF's map - }); - const rank = (a, b) => { - for (let i = slot(a, b); ; i = (i + 1) & mask) { - const k = ka[i]; - if (k === -1) return -1; - if (k === a && kb[i] === b) return rk[i]; - } - }; - return { rank, to }; - }; - - // v: { tokens, types, merges, pre, rows?, ignoreMerges?, bos?, eos?, pad?, addBos? }: tokens by id, as GGUF lists them - // (byte-level strings; an added token's own text), types as GGUF's, merges as "a b" or [a, b] in rank order, rows the - // model's embedding rows (default: the tokens). opts.cache: words kept. - const load = (v, opts = {}) => { - const pre = PRE[v.pre]; - if (!pre) fail(`pre-tokenizer ${JSON.stringify(v.pre)}`); - const tokens = Array.from(v.tokens, String), types = Array.from(v.types || tokens.map(() => T.normal), Number); - if (types.length !== tokens.length) fail('types and tokens differ in length'); - const ignoreMerges = v.ignoreMerges ?? pre.ignoreMerges; - const rows = v.rows ?? tokens.length; - if (!Number.isInteger(rows) || rows < tokens.length) fail(`rows ${rows}`); - const vocab = new Map(), specials = new Map(), kinds = new Uint8Array(rows).fill(T.unused); - let longest = 0; - tokens.forEach((t, id) => { - const ty = types[id]; - kinds[id] = ty; - if (ty === T.control || ty === T.user) { if (!specials.has(t)) specials.set(t, id); } - else if (ty !== T.unused) { if (!vocab.has(t)) vocab.set(t, id); longest = Math.max(longest, t.length); } - }); - const byteId = new Int32Array(256); - for (let b = 0; b < 256; b++) { - const id = vocab.get(String.fromCharCode(BYTE_CHAR[b])); - if (id === undefined) fail(`byte 0x${b.toString(16)} has no token`); - byteId[b] = id; - } - const { rank, to } = mergeTable(Array.from(v.merges || []), vocab); - const re = new RegExp((pre.src ||= pre.js()), 'gu'); - // Added tokens in text (specials: true): leftmost, then longest, as HF's matcher; an empty one, which HF ignores, never. - const matched = [...specials.keys()].filter(Boolean); - const addedRe = matched.length ? new RegExp(matched.sort((a, b) => b.length - a.length).map((s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&')).join('|'), 'g') : null; - // Reserved rows, in id order: the unused ones (a GGUF's [PAD…]), then any rows past the tokens. GLAZE_TOKENS[i] takes - // the i-th, so a GGUF and a tokenizer.json with the model's rows give the same ids. - const reserved = []; - for (let id = 0; id < rows; id++) if (kinds[id] === T.unused) reserved.push(id); - const glaze = new Map(), glazeName = new Map(); // name ↔ id of the Glaze specials added - const idOk = (id) => { if (!Number.isInteger(id) || id < 0 || id >= rows) throw new RangeError(`tokenizer: id ${id}`); return id; }; - - // ---- BPE --------------------------------------------------------------------------------------------------------- - let buf = new Uint8Array(1024), chars = new Uint16Array(256), sym = new Int32Array(256), next = new Int32Array(256), prev = new Int32Array(256), heap = new Float64Array(256); - const grow = (n) => { - if (buf.length < n * 3) buf = new Uint8Array(n * 3 + 64); - if (sym.length < n * 3) { const m = n * 3 + 64; sym = new Int32Array(m); next = new Int32Array(m); prev = new Int32Array(m); heap = new Float64Array(m * 2); } - }; - const P = 2 ** 32; - // A min-heap of rank·2³² + left position: the lowest rank merges first, the leftmost on a tie. - let h = 0; - const push = (key) => { - if (h === heap.length) { const g = new Float64Array(h * 2); g.set(heap); heap = g; } - let i = h++; - while (i > 0) { const p = (i - 1) >> 1; if (heap[p] <= key) break; heap[i] = heap[p]; i = p; } - heap[i] = key; - }; - const pop = () => { - const top = heap[0], last = heap[--h]; - let i = 0; - for (;;) { - let c = 2 * i + 1; - if (c >= h) break; - if (c + 1 < h && heap[c + 1] < heap[c]) c++; - if (heap[c] >= last) break; - heap[i] = heap[c]; i = c; - } - heap[i] = last; - return top; - }; - const bpe = (piece) => { - grow(piece.length); - const n = enc.encodeInto(piece, buf).written; - if (ignoreMerges && n <= longest) { - if (chars.length < n) chars = new Uint16Array(longest); - for (let i = 0; i < n; i++) chars[i] = BYTE_CHAR[buf[i]]; - const whole = vocab.get(String.fromCharCode.apply(null, chars.subarray(0, n))); - if (whole !== undefined) return [whole]; - } - for (let i = 0; i < n; i++) { sym[i] = byteId[buf[i]]; prev[i] = i - 1; next[i] = i + 1 < n ? i + 1 : -1; } - h = 0; - for (let i = 0; i + 1 < n; i++) { const r = rank(sym[i], sym[i + 1]); if (r >= 0) push(r * P + i); } - while (h > 0) { - const key = pop(), r = Math.floor(key / P), i = key - r * P; - const j = next[i]; - if (sym[i] < 0 || j < 0 || rank(sym[i], sym[j]) !== r) continue; - sym[i] = to[r]; sym[j] = -1; - const k = next[j]; - next[i] = k; - if (k >= 0) prev[k] = i; - if (prev[i] >= 0) { const q = rank(sym[prev[i]], sym[i]); if (q >= 0) push(q * P + prev[i]); } - if (k >= 0) { const q = rank(sym[i], sym[k]); if (q >= 0) push(q * P + i); } - } - const out = []; - for (let i = 0; i >= 0; i = next[i]) out.push(sym[i]); - return out; - }; - - // Words (pre-tokenizer pieces) → ids. Two generations of opts.cache words: a word found in the older one moves to the - // newer, and a full newer one becomes the older (deleting a Map's oldest entry one at a time is slow in V8). - const CACHE = opts.cache ?? 16384; - let cache = new Map(), older = new Map(); - const word = (piece) => { - let ids = cache.get(piece); - if (ids) return ids; - if (piece.length > 256) return bpe(piece); - ids = older.get(piece) || bpe(piece); - if (cache.size >= CACHE) { older = cache; cache = new Map(); } - cache.set(piece, ids); - return ids; - }; - - // Ids are collected in one growing Uint32Array. - const sink = () => { - let a = new Uint32Array(256), n = 0; - return { - add(ids) { - if (n + ids.length > a.length) { const g = new Uint32Array(Math.max(a.length * 2, n + ids.length)); g.set(a.subarray(0, n)); a = g; } - for (let i = 0; i < ids.length; i++) a[n++] = ids[i]; - }, - get n() { return n; }, - done: () => a.slice(0, n), - }; - }; - const pieces = (text, each) => { - re.lastIndex = 0; - let last = 0, m; - while ((m = re.exec(text))) { - if (m.index > last) each(text.slice(last, m.index)); - each(m[0]); - last = re.lastIndex; - } - if (last < text.length) each(text.slice(last)); - }; - const plainText = (text, out) => { if (text) pieces(text, (p) => out.add(word(p))); }; - const run = (text, specialsOn, out) => { - const s = well(String(text)); - if (!specialsOn || !addedRe) return plainText(s, out); - addedRe.lastIndex = 0; - let last = 0, m; - while ((m = addedRe.exec(s))) { - plainText(s.slice(last, m.index), out); - out.add([specials.get(m[0])]); - last = addedRe.lastIndex; - } - plainText(s.slice(last), out); - }; - - // ---- decoding ---------------------------------------------------------------------------------------------------- - // Each model token's bytes, made once; null for added and unused ones. - let base = null; - const baseBytes = () => (base ||= Array.from({ length: rows }, (_, id) => (kinds[id] === T.normal || kinds[id] === T.unknown || kinds[id] === T.byte ? tokenBytes(tokens[id]) : null))); - const EMPTY = new Uint8Array(0); - const added = new Map(); // id → bytes of an added token's text, for decoding - const idBytes = (id, specialsOn) => { - idOk(id); - const k = kinds[id]; - if (k === T.unused || (k === T.control && !specialsOn)) return EMPTY; - if (k === T.control || k === T.user) { - let b = added.get(id); - if (!b) { b = glazeName.has(id) ? enc.encode(glazeName.get(id)) : tokenBytes(tokens[id]); added.set(id, b); } - return b; - } - return baseBytes()[id]; - }; - - return { - size: rows, - get cached() { return cache.size + older.size; }, - bos: v.bos ?? null, eos: v.eos ?? null, pad: v.pad ?? null, addBos: !!v.addBos, - // → Uint32Array. specials: true reads the model's added tokens in the text; never Glaze's. - encode(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.done(); }, - count(text, { specials: sp = false } = {}) { const out = sink(); run(text, sp, out); return out.n; }, - // parts: [{ text } | { special: name }], name an added token's text or one add() registered. - encodeWith(parts) { - const out = sink(); - for (const p of parts) { - const keys = p && typeof p === 'object' ? Object.keys(p) : []; - if (keys.length !== 1 || !(keys[0] === 'text' || keys[0] === 'special')) fail('a part is { text } or { special }'); - if (keys[0] === 'text') { run(p.text, false, out); continue; } - const id = this.id(p.special); - if (id === undefined) fail(`unknown special ${JSON.stringify(p.special)}`); - out.add([id]); - } - return out.done(); - }, - id(name) { return specials.get(name) ?? glaze.get(name); }, - // A Glaze special (one of GLAZE_TOKENS), in its reserved row; the same name keeps its id. → its id. - add(name) { - if (glaze.has(name)) return glaze.get(name); - const i = GLAZE_TOKENS.indexOf(name); - if (i < 0 || specials.has(name) || vocab.has(name)) fail(`special name ${JSON.stringify(name)}`); - if (i >= reserved.length) fail(`no reserved row for ${name}: the model has ${reserved.length}`); - const id = reserved[i]; - glaze.set(name, id); glazeName.set(id, name); kinds[id] = T.control; - return id; - }, - // Each id's bytes, a copy to keep (it is made afresh each call); null for added, unused and Glaze's tokens, which are - // never text. - byteStrings: () => baseBytes().map((b) => b && b.slice()), - // Control tokens (and Glaze's) are left out unless specials: true; user-defined added tokens are their text (HF's rule). - decode(ids, { specials: sp = false } = {}) { - const parts = []; - let n = 0; - for (const id of ids) { const b = idBytes(id, sp); if (b.length) { parts.push(b); n += b.length; } } - const all = new Uint8Array(n); - let o = 0; - for (const b of parts) { all.set(b, o); o += b.length; } - return new TextDecoder('utf-8', { ignoreBOM: true }).decode(all); - }, - // push(id) → the text that is complete so far; end() → the rest (an unfinished character as U+FFFD). - decoder({ specials: sp = false } = {}) { - const td = new TextDecoder('utf-8', { ignoreBOM: true }); - return { push: (id) => td.decode(idBytes(id, sp), { stream: true }), end: () => td.decode() }; - }, - // The pre-tokenizer's pieces (for tests and the check). - split(text) { const out = []; pieces(well(String(text)), (p) => out.push(p)); return out; }, - }; - }; - - // A tokenizer.json (HF tokenizers). opts: { bos, eos, pad } as token text or ids (tokenizer_config.json has them), rows, - // and load()'s cache. - const fromHF = (json, opts = {}) => { - const m = json && json.model; - if (!m || m.type !== 'BPE') fail('model: only BPE'); - if (m.byte_fallback) fail('byte_fallback'); - if (m.dropout) fail('dropout'); - if (m.unk_token != null) fail('unk_token'); - if (m.continuing_subword_prefix || m.end_of_word_suffix) fail('subword prefix or suffix'); - if (json.normalizer != null) fail('normalizer'); - if (json.truncation != null) fail('truncation'); - if (json.padding != null) fail('padding'); - const pt = json.pre_tokenizer, seq = pt && pt.type === 'Sequence' ? pt.pretokenizers : null; - const split = seq && seq.length === 2 && seq[0].type === 'Split' && seq[0].behavior === 'Isolated' && !seq[0].invert && seq[0].pattern ? seq[0].pattern.Regex : null; - const bl = seq && seq[1] && seq[1].type === 'ByteLevel' && !seq[1].add_prefix_space && seq[1].use_regex === false; - const pre = Object.keys(PRE).find((k) => PRE[k].hf === split); - if (!pre || !bl) fail('pre_tokenizer'); - if (!json.decoder || json.decoder.type !== 'ByteLevel') fail('decoder'); - if (json.post_processor != null && json.post_processor.type !== 'ByteLevel') fail('post_processor'); - const tokens = [], types = []; - for (const [t, id] of Object.entries(m.vocab)) { tokens[id] = t; types[id] = T.normal; } - for (const a of json.added_tokens || []) { - if (a.lstrip || a.rstrip || a.single_word) fail(`added token ${JSON.stringify(a.content)}: lstrip, rstrip or single_word`); - if (types[a.id] !== undefined) fail(`added token ${JSON.stringify(a.content)} is in the vocabulary`); - tokens[a.id] = a.content; types[a.id] = a.special ? T.control : T.user; - } - for (let i = 0; i < tokens.length; i++) if (types[i] === undefined) { tokens[i] = ''; types[i] = T.unused; } - const find = (x) => { if (x == null) return null; if (Number.isInteger(x)) return x; const i = tokens.indexOf(x); if (i < 0) fail(`no token ${JSON.stringify(x)}`); return i; }; - // A tokenizer.json doesn't say how many rows the model has (its config's vocab_size does): opts.rows. - return load({ tokens, types, merges: m.merges, pre, rows: opts.rows, ignoreMerges: !!m.ignore_merges, bos: find(opts.bos), eos: find(opts.eos), pad: find(opts.pad), addBos: false }, opts); - }; - - // A GGUF's tokenizer keys (tokenizer.ggml.*, values as the GGUF reader gives them), or the neutral object load() takes. - const fromGGUF = (kv, opts) => { - if (!kv || !('tokenizer.ggml.tokens' in kv)) return load(kv, opts); - const g = (k) => kv[`tokenizer.ggml.${k}`]; - if (g('model') !== 'gpt2') fail(`model ${JSON.stringify(g('model'))}: only gpt2 (byte-level BPE)`); - if (!PRE[g('pre')]) fail(`pre-tokenizer ${JSON.stringify(g('pre'))}`); - const id = (k) => (g(k) == null ? null : Number(g(k))); - return load({ tokens: g('tokens'), types: g('token_type'), merges: g('merges'), pre: g('pre'), bos: id('bos_token_id'), eos: id('eos_token_id'), pad: id('padding_token_id'), addBos: !!g('add_bos_token') }, opts); - }; - - return { load, fromHF, fromGGUF, PRE, TYPES: T, GLAZE_TOKENS }; -})(); -if (typeof module !== 'undefined') module.exports = GZ_AI_TOKENIZER; else GZ_AI_TOKENIZER; diff --git a/scripts/local-tokenizer-check.js b/scripts/local-tokenizer-check.js deleted file mode 100644 index 3ade61693..000000000 --- a/scripts/local-tokenizer-check.js +++ /dev/null @@ -1,66 +0,0 @@ -// Glaze's local tokenizer (lib/ai/local/tokenizer.js) against a model's own: every line of a JSONL of references -// { text, ids, plain } made with HF tokenizers must give exactly `ids` with added tokens parsed (specials: true), and -// exactly `plain` (HF with its added tokens taken out) the default way, and decode back to the text. Then encode speed. -// Not in the suite: it needs the model's tokenizer.json and the references, which stay out of the repo. -// node scripts/local-tokenizer-check.js [--config tokenizer_config.json] -// [--gguf kv.json (the GGUF's tokenizer.* keys as JSON: checked the same way)] [--out results.json] -const fs = require('node:fs'); -const K = require('../lib/ai/local/tokenizer.js'); - -const args = process.argv.slice(2); -const flag = (name) => { const i = args.indexOf(name); return i < 0 ? null : args.splice(i, 2)[1]; }; -const config = flag('--config'); -const gguf = flag('--gguf'); -const out = flag('--out'); -const [tokPath, refPath] = args; -if (!tokPath || !refPath) { console.error('usage: node scripts/local-tokenizer-check.js [--config f] [--gguf f] [--out f]'); process.exit(2); } - -const cfg = config ? JSON.parse(fs.readFileSync(config, 'utf8')) : {}; -const name = (t) => (t && typeof t === 'object' ? t.content : t); -const loaders = { hf: () => K.fromHF(JSON.parse(fs.readFileSync(tokPath, 'utf8')), { bos: name(cfg.bos_token), eos: name(cfg.eos_token), pad: name(cfg.pad_token) }) }; -if (gguf) loaders.gguf = () => K.fromGGUF(JSON.parse(fs.readFileSync(gguf, 'utf8'))); -const refs = fs.readFileSync(refPath, 'utf8').split('\n').filter(Boolean).map((l) => JSON.parse(l)); -const well = (s) => (String.prototype.toWellFormed ? s.toWellFormed() : s.replace(/[\ud800-\udbff](?![\udc00-\udfff])|(? n + Buffer.byteLength(well(r.text)), 0); - -const firstDiff = (a, b) => { let i = 0; while (i < a.length && i < b.length && a[i] === b[i]) i++; return i; }; -const show = (r, ours, want, what) => { - const i = firstDiff(ours, want); - return `${what} ${r.src || ''}: first difference at id ${i} of ${want.length}; ours ${JSON.stringify(ours.slice(Math.max(0, i - 3), i + 5))} want ${JSON.stringify(want.slice(Math.max(0, i - 3), i + 5))}; text ${JSON.stringify(r.text.slice(0, 160))}`; -}; - -const results = { tokenizer: tokPath, references: refPath, lines: refs.length, bytes, runs: {} }; -let failed = false; -for (const [label, make] of Object.entries(loaders)) { - let t0 = performance.now(); - const tk = make(); - const loadMs = performance.now() - t0; - const bad = []; - let ok = 0; - for (const r of refs) { - const errs = []; - const sp = [...tk.encode(r.text, { specials: true })]; - if (sp.length !== r.ids.length || firstDiff(sp, r.ids) !== sp.length) errs.push(show(r, sp, r.ids, 'specials')); - if (r.plain) { - const pl = [...tk.encode(r.text)]; - if (pl.length !== r.plain.length || firstDiff(pl, r.plain) !== pl.length) errs.push(show(r, pl, r.plain, 'plain')); - if (tk.decode(pl) !== well(r.text)) errs.push(`decode ${r.src || ''}: ${JSON.stringify(r.text.slice(0, 160))}`); - } - if (tk.decode(sp, { specials: true }) !== well(r.text)) errs.push(`decode specials ${r.src || ''}: ${JSON.stringify(r.text.slice(0, 160))}`); - if (errs.length) bad.push(...errs); else ok++; - } - // Speed: a fresh tokenizer (cold word cache), then the same text again (warm). - const fresh = make(); - const time = (tk2) => { const s = performance.now(); let n = 0; for (const r of refs) n += tk2.encode(r.text).length; return [performance.now() - s, n]; }; - const [coldMs, ids] = time(fresh); - const [warmMs] = time(fresh); - const mbs = (ms) => +(bytes / 1e6 / (ms / 1000)).toFixed(2); - results.runs[label] = { match: ok, of: refs.length, mismatches: bad.slice(0, 50), loadMs: Math.round(loadMs), ids, coldMBs: mbs(coldMs), warmMBs: mbs(warmMs) }; - console.log(`${label}: ${ok}/${refs.length} lines match exactly; load ${Math.round(loadMs)} ms; encode ${(bytes / 1e6).toFixed(2)} MB → ${ids} ids: cold ${mbs(coldMs)} MB/s, warm ${mbs(warmMs)} MB/s`); - for (const m of bad.slice(0, 20)) console.log(` ${m}`); - if (ok !== refs.length) failed = true; - if (mbs(coldMs) < 1) { console.log(` cold encode under 1 MB/s`); failed = true; } -} -if (out) fs.writeFileSync(out, JSON.stringify(results, null, 2)); -console.log(failed ? 'FAIL' : 'PASS'); -process.exit(failed ? 1 : 0); diff --git a/test/ai-local-tokenizer.test.js b/test/ai-local-tokenizer.test.js deleted file mode 100644 index a1f01402a..000000000 --- a/test/ai-local-tokenizer.test.js +++ /dev/null @@ -1,280 +0,0 @@ -// Glaze's own tokenizer for local models (lib/ai/local/tokenizer.js), on a hand-made byte-level BPE vocabulary: merges -// in rank order, bytes with no merge as their own tokens, added tokens only when asked for, page text never producing a -// control token, prompts from text and named specials, Glaze's own specials in the model's reserved rows, a streaming decoder that -// never splits a character, and decode(encode(x)) === x. scripts/local-tokenizer-check.js holds it to LFM3's real one. -const test = require('node:test'); -const assert = require('node:assert/strict'); -const fs = require('node:fs'); -const path = require('node:path'); -const vm = require('node:vm'); -const K = require('../lib/ai/local/tokenizer.js'); - -// GPT-2's byte → character table, written out here so the test doesn't trust the module's own. -const BYTE = (() => { - const keep = []; - for (let b = 33; b <= 126; b++) keep.push(b); - for (let b = 161; b <= 172; b++) keep.push(b); - for (let b = 174; b <= 255; b++) keep.push(b); - const out = new Array(256); - let n = 0; - for (let b = 0; b < 256; b++) out[b] = String.fromCharCode(keep.includes(b) ? b : 256 + n++); - return out; -})(); -const bl = (s) => [...new TextEncoder().encode(s)].map((b) => BYTE[b]).join(''); - -// ids 0–255 are the bytes in order; then merged tokens; then added tokens. -const vocab = (merges, extra = []) => { - const tokens = [...BYTE]; - for (const [a, b] of merges) if (!tokens.includes(a + b)) tokens.push(a + b); - for (const t of extra) if (!tokens.includes(t)) tokens.push(t); - return tokens; -}; -const MERGES = [['h', 'e'], ['l', 'l'], ['he', 'll'], ['hell', 'o'], ['Ġ', 'w'], ['o', 'r'], ['Ġw', 'or'], ['Ġwor', 'ld']]; -const neutral = (o = {}) => { - const merges = o.merges || [...MERGES, ['l', 'd']]; - const tokens = vocab(merges, o.extra); - const types = tokens.map(() => 1); - for (const [content, type] of o.added || []) { tokens.push(content); types.push(type); } - return { tokens, types, merges: merges.map((m) => m.join(' ')), pre: 'lfm2', bos: o.bos, eos: o.eos, ...o.more }; -}; -const ADDED = [['<|startoftext|>', 3], ['<|im_start|>', 3], ['<|im_end|>', 3], ['', 4], ['<|a|>', 3], ['<|a|>x', 3]]; -const id = (tk, s) => tk.byteStrings().findIndex((b) => b && Buffer.from(b).toString('latin1') === Buffer.from(s, 'utf8').toString('latin1')); - -test('merges apply in rank order, leftmost first on a tie, and every byte is its own token', () => { - const tk = K.load(neutral()); - const strs = (ids) => [...ids].map((i) => new TextDecoder().decode(tk.byteStrings()[i])); - assert.deepEqual(strs(tk.encode('hello world')), ['hello', ' world']); - // b+c ranks before a+b: abc → a, bc; the other way round → ab, c. - const bc = K.load(neutral({ merges: [['b', 'c'], ['a', 'b']] })); - const ab = K.load(neutral({ merges: [['a', 'b'], ['b', 'c']] })); - assert.deepEqual([...bc.encode('abc')], [97, id(bc, 'bc')]); - assert.deepEqual([...ab.encode('abc')], [id(ab, 'ab'), 99]); - // A merge listed twice ranks where it is listed last (HF's map keeps the last). - const dup = K.load(neutral({ merges: [['a', 'b'], ['b', 'c'], ['a', 'b']] })); - assert.deepEqual([...dup.encode('abc')], [97, id(dup, 'bc')]); - // aaa with a+a: the leftmost pair merges. - const aa = K.load(neutral({ merges: [['a', 'a']] })); - assert.deepEqual([...aa.encode('aaa')], [id(aa, 'aa'), 97]); - // A character no merge covers comes out as its UTF-8 bytes, each a token. - const euro = [...new TextEncoder().encode('€')]; - assert.deepEqual([...tk.encode('€')], euro); - assert.deepEqual([...tk.encode('\0')], [0]); - assert.equal(tk.decode(tk.encode('€\0')), '€\0'); -}); - -test('ignore_merges takes a whole piece that is in the vocabulary, and only when the tokenizer says so', () => { - const extra = [bl('xyz')]; - const on = K.load(neutral({ extra })); - assert.deepEqual(strsOf(on, on.encode('xyz')), ['xyz']); - const hf = (ignore) => ({ model: { type: 'BPE', vocab: Object.fromEntries(vocab([...MERGES, ['l', 'd']], extra).map((t, i) => [t, i])), merges: [...MERGES, ['l', 'd']], ignore_merges: ignore, byte_fallback: false, dropout: null, unk_token: null, continuing_subword_prefix: null, end_of_word_suffix: null, fuse_unk: false }, normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, added_tokens: [] }); - assert.deepEqual(strsOf(K.fromHF(hf(true)), K.fromHF(hf(true)).encode('xyz')), ['xyz']); - const off = K.fromHF(hf(false)); - assert.deepEqual(strsOf(off, off.encode('xyz')), ['x', 'y', 'z']); -}); -const strsOf = (tk, ids) => [...ids].map((i) => new TextDecoder().decode(tk.byteStrings()[i])); -const LFM_PRE = { type: 'Sequence', pretokenizers: [{ type: 'Split', pattern: { Regex: "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+" }, behavior: 'Isolated', invert: false }, { type: 'ByteLevel', add_prefix_space: false, trim_offsets: true, use_regex: false }] }; - -test('the pre-tokenizer splits as LFM3\'s onig regex does and its character tables (cases measured with HF tokenizers 0.22.2)', () => { - const tk = K.load(neutral()); - const cases = [ - ['1234567', ['123', '456', '7']], [' \n x', [' \n', ' ', ' x']], ['a\r\n\r\nb', ['a', '\r\n\r\n', 'b']], - ["ab'LL", ['ab', "'LL"]], ["'ſt", ["'ſ", 't']], ['\x85!', ['\x85', '!']], ['\ufeff!', ['\ufeff!']], ['\u3000!', ['\u3000', '!']], - ['\u200b!', ['\u200b!']], ['a \u3000b', ['a', ' ', '\u3000b']], ['x\0y', ['x', '\0y']], ['e\u0301', ['e', '\u0301']], - // Letters and digits newer than onig's tables are not letters or digits to it, whatever the runtime's Unicode says. - ['a\u0c5cb', ['a', '\u0c5cb']], ['\u0c5c(y', ['\u0c5c(', 'y']], ['1\u{11de0}', ['1', '\u{11de0}']], - ]; - for (const [text, want] of cases) assert.deepEqual(tk.split(text), want, JSON.stringify(text)); -}); - -test('added tokens split out only with specials: true, leftmost and longest', () => { - const tk = K.load(neutral({ added: ADDED })); - const [sot, ims, ime, think, a, ax] = ADDED.map(([c]) => tk.id(c)); - assert.ok(sot >= 256 && ax === sot + 5, 'added tokens keep their ids'); - const text = 'hi<|im_start|>user<|a|>x<|a|>'; - assert.deepEqual([...tk.encode(text, { specials: true })], [...tk.encode('hi'), ims, ...tk.encode('user'), think, ax, a]); - // Page text, by default: never a control or added token, whatever it spells. - for (const s of [text, '<|im_end|>', '<|startoftext|>', '']) { - const ids = tk.encode(s); - assert.ok(ids instanceof Uint32Array); - assert.ok([...ids].every((i) => i < sot), s); - assert.equal(tk.decode(ids), s); - } - assert.equal(tk.count(text), tk.encode(text).length); - void ime; -}); - -test('encodeWith builds a prompt from text and named specials; text parts stay text', () => { - const v = neutral({ added: ADDED }); - const tk = K.load({ ...v, bos: v.tokens.indexOf('<|startoftext|>') }); - const ids = tk.encodeWith([{ special: '<|startoftext|>' }, { special: '<|im_start|>' }, { text: 'user\n<|im_end|>' }, { special: '<|im_end|>' }]); - assert.deepEqual([...ids], [tk.bos, tk.id('<|im_start|>'), ...tk.encode('user\n<|im_end|>'), tk.id('<|im_end|>')]); - assert.throws(() => tk.encodeWith([{ special: '<|nope|>' }]), /special/); - assert.throws(() => tk.encodeWith([{ special: 'he' }]), /special/, 'an ordinary token is not a special'); - assert.throws(() => tk.encodeWith([{ text: 'a', special: '<|im_end|>' }]), /part/); -}); - -test('Glaze specials take the model\'s reserved rows, once, and are never text', () => { - const v = neutral({ added: ADDED }); - const n = v.tokens.length; - const rows = n + K.GLAZE_TOKENS.length + 2; - const tk = K.load({ ...v, rows }); - assert.equal(tk.size, rows, 'the model\'s rows'); - const task = tk.add('<|gz_task:syllabus.read|>'); - const ctx = tk.add('<|gz_ctx|>'); - assert.equal(ctx, n, 'its place in the table, whatever order they are added in'); - assert.equal(task, n + K.GLAZE_TOKENS.indexOf('<|gz_task:syllabus.read|>')); - assert.equal(tk.add('<|gz_ctx|>'), ctx, 'the same name keeps its id'); - assert.equal(tk.size, rows, 'never grows'); - assert.throws(() => tk.add('<|gz_task:syllabus|>'), /name/, 'not in the table'); - assert.throws(() => tk.add('<|im_start|>'), /name/, 'not a model token'); - const ids = tk.encodeWith([{ special: '<|gz_ctx|>' }, { text: 'hello' }, { special: '<|gz_task:syllabus.read|>' }]); - assert.deepEqual([...ids], [ctx, ...tk.encode('hello'), task]); - assert.ok(![...tk.encode('<|gz_ctx|>', { specials: true })].includes(ctx), 'text never spells a Glaze special'); - assert.equal(tk.decode(ids), 'hello'); - assert.equal(tk.decode(ids, { specials: true }), '<|gz_ctx|>hello<|gz_task:syllabus.read|>'); - assert.equal(tk.decode([n + 1, n + 2], { specials: true }), '', 'a reserved row nothing has taken is no text'); - const bytes = tk.byteStrings(); - assert.equal(bytes.length, tk.size); - assert.equal(bytes[ctx], null); - assert.equal(bytes[n + 1], null); - assert.equal(bytes[tk.id('<|im_start|>')], null); - assert.equal(bytes[tk.id('')], null); - assert.deepEqual([...bytes[0x41]], [0x41]); - assert.deepEqual([...bytes[id(tk, ' world')]], [...Buffer.from(' world')]); - assert.throws(() => tk.decode([tk.size]), /id/); -}); - -test('the Glaze table: fixed order, and the same ids from a GGUF\'s [PAD] rows and after a tokenizer.json\'s last token', () => { - assert.deepEqual(K.GLAZE_TOKENS.slice(0, 4), ['<|gz_ctx|>', '<|/gz_ctx|>', '<|gz_data|>', '<|/gz_data|>']); - assert.deepEqual(K.GLAZE_TOKENS.slice(4), ['writer.complete', 'writer.review', 'writer.feedback', 'writer.outline', 'writer.requirements', 'writer.cite', 'writer.sources', 'writer.decide', 'writer.image', 'syllabus.read'].map((t) => `<|gz_task:${t}|>`)); - assert.ok(Object.isFrozen(K.GLAZE_TOKENS)); - const v = neutral({ added: ADDED }); - const n = v.tokens.length, rows = n + 20; - // LFM3's layout: tokenizer.json ends at its last added token; the GGUF lists the rest of the rows as [PADi], unused. - const hf = { - model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i]).filter(([, i]) => v.types[i] === 1)), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false }, - normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, - added_tokens: v.tokens.map((content, i) => ({ id: i, content, special: v.types[i] === 3 })).filter((t) => v.types[t.id] !== 1), - }; - const pads = Array.from({ length: rows - n }, (_, i) => `[PAD${n + i}]`); - const gguf = { 'tokenizer.ggml.model': 'gpt2', 'tokenizer.ggml.pre': 'lfm2', 'tokenizer.ggml.tokens': [...v.tokens, ...pads], 'tokenizer.ggml.token_type': [...v.types, ...pads.map(() => 5)], 'tokenizer.ggml.merges': v.merges }; - const a = K.fromHF(hf, { rows }), b = K.fromGGUF(gguf); - assert.equal(a.size, rows); - assert.equal(b.size, rows); - const order = [...K.GLAZE_TOKENS].reverse(); - const ia = order.map((t) => a.add(t)), ib = order.map((t) => b.add(t)); - assert.deepEqual(ia, ib); - assert.deepEqual(K.GLAZE_TOKENS.map((t) => a.id(t)), K.GLAZE_TOKENS.map((_, i) => n + i), 'from the first free row, in table order'); - for (const tk of [a, b]) { - assert.equal(tk.decode([a.id('<|gz_data|>')]), ''); - assert.equal(tk.decode([a.id('<|gz_data|>')], { specials: true }), '<|gz_data|>'); - assert.equal(tk.byteStrings()[a.id('<|gz_data|>')], null); - } - // Fewer reserved rows than the table: the rest are refused, and nothing grows past the model. - const few = K.load({ ...v, rows: n + 3 }); - assert.equal(few.add('<|gz_data|>'), n + 2); - assert.throws(() => few.add('<|/gz_data|>'), /reserved/); - assert.equal(few.size, n + 3); - assert.throws(() => K.fromHF(hf).add('<|gz_ctx|>'), /reserved/, 'a tokenizer.json alone says nothing of the model\'s rows'); -}); - -test('decode skips control tokens unless asked and keeps user-defined ones, as HF does', () => { - const tk = K.load(neutral({ added: ADDED })); - const ids = [tk.id('<|im_start|>'), ...tk.encode('hi'), tk.id('')]; - assert.equal(tk.decode(ids), 'hi'); - assert.equal(tk.decode(ids, { specials: true }), '<|im_start|>hi'); - assert.throws(() => tk.decode([tk.size]), /id/); -}); - -test('the streaming decoder emits only complete characters', () => { - const tk = K.load(neutral({ added: ADDED })); - const text = 'é€😀 👩\u200d👩\u200d👧 hello'; - const ids = [...tk.encode(text)]; - const d = tk.decoder(); - const outs = ids.map((i) => d.push(i)); - assert.deepEqual(outs.slice(0, 2), ['', 'é'], 'é is two byte tokens: nothing until the second'); - assert.ok(outs.every((s) => !/[\ud800-\udbff]$/.test(s) && !s.includes('\ufffd')), 'never half a character'); - assert.equal(outs.join('') + d.end(), text); - const bom = tk.decoder(); - assert.equal([...tk.encode('\ufeffx')].map((i) => bom.push(i)).join('') + bom.end(), '\ufeffx', 'a leading BOM is text'); - const cut = tk.decoder(); - assert.equal(cut.push(0xe2), ''); - assert.equal(cut.end(), '\ufffd', 'an unfinished character at the end becomes U+FFFD'); - const sp = tk.decoder({ specials: true }); - assert.equal(sp.push(0xc3) + sp.push(tk.id('<|im_end|>')) + sp.push(0xa9) + sp.end(), '\ufffd<|im_end|>\ufffd', 'a special between bytes ends the character'); -}); - -test('decode(encode(x)) === x, with any cache size', () => { - const texts = ['hello world', '', ' ', '\r\n\r\n\t x', 'Ünïcödé — 日本語 العربية עברית हिन्दी', '👩\u200d👩\u200d👧\u200d👦🏳\ufe0f\u200d🌈', 'x\0y', '\ufeffa leading BOM', 'a'.repeat(5000), ' '.repeat(300) + 'end', '1234567890']; - for (const cache of [1, 4, undefined]) { - const tk = K.load(neutral({ added: ADDED }), cache && { cache }); - for (const t of texts) { - assert.equal(tk.decode(tk.encode(t)), t, `${JSON.stringify(t.slice(0, 20))} cache ${cache}`); - assert.deepEqual([...tk.encode(t)], [...tk.encode(t)], 'cached the same'); - } - } - const small = K.load(neutral(), { cache: 4 }); - for (let i = 0; i < 100; i++) small.encode(`word${i} `); - assert.ok(small.cached > 0 && small.cached <= 8, `the word cache stays bounded (${small.cached})`); - // A lone surrogate has no UTF-8: it becomes U+FFFD, as TextEncoder makes it (HF refuses such a string). - const tk = K.load(neutral()); - assert.deepEqual([...tk.encode('a\ud800b')], [...tk.encode('a\ufffdb')]); -}); - -test('loaders: HF tokenizer.json and GGUF metadata give the same tokenizer; anything else is refused', () => { - const v = neutral({ added: ADDED }); - const hf = { - model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i]).filter(([, i]) => v.types[i] === 1)), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false, dropout: null, unk_token: null, continuing_subword_prefix: null, end_of_word_suffix: null, fuse_unk: false }, - normalizer: null, pre_tokenizer: LFM_PRE, post_processor: { type: 'ByteLevel' }, decoder: { type: 'ByteLevel' }, - added_tokens: v.tokens.map((content, id) => ({ id, content, special: v.types[id] === 3, normalized: false, lstrip: false, rstrip: false, single_word: false })).filter((t) => v.types[t.id] !== 1), - }; - const gguf = { 'tokenizer.ggml.model': 'gpt2', 'tokenizer.ggml.pre': 'lfm2', 'tokenizer.ggml.tokens': v.tokens, 'tokenizer.ggml.token_type': Int32Array.from(v.types), 'tokenizer.ggml.merges': v.merges, 'tokenizer.ggml.bos_token_id': v.tokens.length - 6, 'tokenizer.ggml.eos_token_id': v.tokens.length - 4, 'tokenizer.ggml.add_bos_token': false }; - const a = K.fromHF(hf, { bos: '<|startoftext|>', eos: '<|im_end|>' }); - const b = K.fromGGUF(gguf); - const c = K.load(v); - const text = 'hello world <|im_start|>€ 12345 '; - for (const tk of [b, c]) { - assert.deepEqual([...tk.encode(text)], [...a.encode(text)]); - assert.deepEqual([...tk.encode(text, { specials: true })], [...a.encode(text, { specials: true })]); - } - assert.equal(a.bos, b.bos); - assert.equal(a.eos, b.eos); - assert.equal(b.addBos, false); - const bad = (patch) => () => K.fromHF({ ...hf, ...patch(hf) }); - assert.throws(bad((h) => ({ model: { ...h.model, byte_fallback: true } })), /byte_fallback/); - assert.throws(bad((h) => ({ model: { ...h.model, type: 'Unigram' } })), /model/); - assert.throws(bad(() => ({ normalizer: { type: 'NFC' } })), /normalizer/); - assert.throws(bad(() => ({ pre_tokenizer: { type: 'Whitespace' } })), /pre_tokenizer/); - assert.throws(bad(() => ({ decoder: { type: 'Metaspace' } })), /decoder/); - assert.throws(bad((h) => ({ added_tokens: [{ ...h.added_tokens[0], lstrip: true }] })), /added/); - assert.throws(() => K.fromGGUF({ ...gguf, 'tokenizer.ggml.pre': 'qwen2' }), /pre/); - assert.throws(() => K.fromGGUF({ ...gguf, 'tokenizer.ggml.model': 'llama' }), /model/); - assert.throws(() => K.load({ ...v, tokens: v.tokens.slice(1), types: v.types.slice(1) }), /byte/); -}); - -test('a classic script, as importScripts loads it: one global, and it works without toWellFormed', () => { - const ctx = vm.createContext({ TextEncoder, TextDecoder }); - vm.runInContext(fs.readFileSync(path.join(__dirname, '../lib/ai/local/tokenizer.js'), 'utf8'), ctx); - assert.equal(typeof ctx.GZ_AI_TOKENIZER.load, 'function'); - vm.runInContext('delete String.prototype.toWellFormed', ctx); - const tk = ctx.GZ_AI_TOKENIZER.load(neutral()); - assert.equal(tk.decode(tk.encode('a\ud800b\udc00😀')), 'a\ufffdb\ufffd😀'); -}); - -test('an empty added token is never matched in text, as HF ignores it', () => { - // In a context of its own with a timeout: a matcher that loops on empty matches never returns to the event loop. - const ctx = vm.createContext({ TextEncoder, TextDecoder, v: neutral({ added: [['', 3], ...ADDED] }) }); - vm.runInContext(fs.readFileSync(path.join(__dirname, '../lib/ai/local/tokenizer.js'), 'utf8'), ctx); - const [got, want] = JSON.parse(vm.runInContext(`const tk = GZ_AI_TOKENIZER.load(v); - JSON.stringify([[...tk.encode('hi<|im_start|>', { specials: true }), -1, ...tk.encode('hello', { specials: true })], - [...tk.encode('hi'), tk.id('<|im_start|>'), -1, ...tk.encode('hello')]])`, ctx, { timeout: 2000 })); - assert.deepEqual(got, want); -}); - -test('fromHF refuses truncation and padding, which it would not apply', () => { - const v = neutral(); - const hf = { model: { type: 'BPE', vocab: Object.fromEntries(v.tokens.map((t, i) => [t, i])), merges: [...MERGES, ['l', 'd']], ignore_merges: true, byte_fallback: false }, normalizer: null, pre_tokenizer: LFM_PRE, decoder: { type: 'ByteLevel' }, added_tokens: [], truncation: null, padding: null }; - assert.equal(K.fromHF(hf).decode(K.fromHF(hf).encode('hello world')), 'hello world'); - assert.throws(() => K.fromHF({ ...hf, truncation: { max_length: 8, strategy: 'LongestFirst' } }), /truncation/); - assert.throws(() => K.fromHF({ ...hf, padding: { strategy: 'BatchLongest', pad_id: 0 } }), /padding/); -}); diff --git a/test/ai-worker.test.js b/test/ai-worker.test.js index b14f6c5c2..cf3396ae6 100644 --- a/test/ai-worker.test.js +++ b/test/ai-worker.test.js @@ -155,9 +155,7 @@ test('Firefox\'s event page loads every file of the harness the worker would imp // scripts/package-firefox.sh: the files named in the worker's importScripts calls, as it reads them. const bg = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); const named = new Set([...bg.matchAll(/importScripts\(([^)]*)\)/g)].flatMap(([, a]) => [...a.matchAll(/'([^']+)'/g)].map(([, f]) => `lib/${f}`))); - // The local engine's files before its provider step names them (4.0.1): the worker doesn't load them yet. - const unwired = ['local/tokenizer.js']; - const shipped = fs.readdirSync(path.join(__dirname, '..', 'lib/ai'), { recursive: true }).filter((f) => f.endsWith('.js') && f !== 'providers/mock.js' && !unwired.includes(f)).map((f) => `lib/ai/${f}`); + const shipped = fs.readdirSync(path.join(__dirname, '..', 'lib/ai'), { recursive: true }).filter((f) => f.endsWith('.js') && f !== 'providers/mock.js').map((f) => `lib/ai/${f}`); assert.ok(shipped.length >= 14); for (const f of shipped) assert.ok(named.has(f), `${f} is named in an importScripts call`); assert.ok(!named.has('lib/ai/providers/mock.js'), 'the mock is for tests and the eval'); From fc93c0cb346671ed97b3388097e25f50564d8ba0 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:30:52 -0400 Subject: [PATCH 060/907] Print slides to PDF: a page per shown slide at the deck's size, notes optionally below --- i18n/de.js | 3 +- i18n/es.js | 3 +- i18n/fr.js | 3 +- i18n/nl.js | 3 +- i18n/pt-BR.js | 3 +- lib/slides-render.js | 15 +++++++++- scripts/slides-check.js | 55 +++++++++++++++++++++++++++++++++++- slides-kind.css | 13 +++++++++ slides-kind.js | 58 +++++++++++++++++++++++++++++++++++--- test/slides-render.test.js | 19 +++++++++++++ 10 files changed, 164 insertions(+), 11 deletions(-) diff --git a/i18n/de.js b/i18n/de.js index de78f17ff..c5eb46d75 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "nach Angaben der App", "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", - "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast." + "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", + "Print with notes…": "Mit Notizen drucken…" }; diff --git a/i18n/es.js b/i18n/es.js index eabe7a199..9635b6f0e 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "según la propia app", "Don't allow": "No permitir", "Unnamed app": "App sin nombre", - "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras." + "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", + "Print with notes…": "Imprimir con notas…" }; diff --git a/i18n/fr.js b/i18n/fr.js index c2b2a093b..1eeac37f9 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "selon l’app elle-même", "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", - "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse." + "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", + "Print with notes…": "Imprimer avec les notes…" }; diff --git a/i18n/nl.js b/i18n/nl.js index 5bb584391..3fae266f9 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "zoals de app zichzelf noemt", "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", - "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde." + "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", + "Print with notes…": "Afdrukken met notities…" }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index f76333eaf..890233169 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "segundo o próprio app", "Don't allow": "Não permitir", "Unnamed app": "App sem nome", - "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta." + "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", + "Print with notes…": "Imprimir com anotações…" }; diff --git a/lib/slides-render.js b/lib/slides-render.js index 8da99b3bc..45f10a355 100644 --- a/lib/slides-render.js +++ b/lib/slides-render.js @@ -12,6 +12,7 @@ // stage(slide, deck, { scale, … }) → the slide scaled, in a box of the scaled size. // fit(size, w, h) → the scale that fits a slide in w × h pixels. text(slide) → its shapes' text; count(text, term); // visible(deck) → the indices of the slides a show plays (not hidden). +// printPage(deck, { notes }) → the page a slide prints on, in CSS px: { w, h, scale, margin, notes }, or null. // Classic script; module.exports for node. var GZ_SLIDES_RENDER = globalThis.GZ_SLIDES_RENDER || (() => { const G = () => globalThis.GZ_PPTX_GEOM || require('./pptx-geom.js'); @@ -397,9 +398,21 @@ var GZ_SLIDES_RENDER = globalThis.GZ_SLIDES_RENDER || (() => { const bodyText = (t) => (t ? t.paragraphs.map((p) => p.runs.map((r) => r.text).join('')).join('\n') : ''); const shapeText = (s) => (s.type === 'group' ? s.shapes.map(shapeText) : s.type === 'table' ? s.rows.flatMap((r) => r.cells.filter((c) => !c.merged).map((c) => bodyText(c.text))) : [bodyText(s.text)]).flat(); const text = (s) => (s?.shapes || []).flatMap(shapeText).filter(Boolean).join('\n'); + // Print…: the slide's own size, the slide filling it. Print with notes: the deck's notes page (PowerPoint's 7.5 × 10 in where + // it names none), the slide fitted across its top half inside a half inch, the notes below. Sizes floor to hundredths and the + // scale to ten-thousandths, so a page never outgrows the box it holds (Chromium prints a blank page after one that does). + const NOTES_PAGE = { w: 6858000, h: 9144000 }, MARGIN = 48; + const down = (v, k) => Math.floor(v * k + 1e-9) / k; + const printPage = (deck, { notes = false } = {}) => { + const s = deck?.size; + if (!(s?.w > 0 && s?.h > 0)) return null; + if (!notes) return { w: down(s.w / EMU, 100), h: down(s.h / EMU, 100), scale: 1, margin: 0, notes: false }; + const n = deck.notesSize?.w > 0 && deck.notesSize?.h > 0 ? deck.notesSize : NOTES_PAGE, w = down(n.w / EMU, 100), h = down(n.h / EMU, 100); + return { w, h, scale: down(fit(s, w - 2 * MARGIN, h / 2 - MARGIN), 1e4), margin: MARGIN, notes: true }; + }; const visible = (deck) => deck.slides.flatMap((s, i) => (s.hidden ? [] : [i])); - return { EMU, px, fit, slide, stage, text, count, visible, family }; + return { EMU, px, fit, slide, stage, text, count, visible, printPage, family }; })(); if (typeof module !== 'undefined') module.exports = GZ_SLIDES_RENDER; else GZ_SLIDES_RENDER; diff --git a/scripts/slides-check.js b/scripts/slides-check.js index b691ccdf6..762c22392 100644 --- a/scripts/slides-check.js +++ b/scripts/slides-check.js @@ -3,7 +3,7 @@ // with lib/zip.js) imports as a card showing slide 1, opens in the viewer (the rail, slide 1, arrows and Page keys, zoom, // find in the slides' text, the notes), presents in a dialog whose keys are Rimeward's and which skips the hidden slide, // exports the bytes it was given, and Esc and Back behave as a paper's. The chrome wears a dark Look while the slide stays -// as authored, and nothing errors, in the page or at the worker's sender check. Screenshots at 1280 and 640 px. The browser +// as authored, prints a page per shown slide at the deck's size (notes optionally below), and nothing errors, in the page or at the worker's sender check. Screenshots at 1280 and 640 px. The browser // reaches no network. // NODE_PATH= nice -n 10 node scripts/slides-check.js [evidence directory] const { chromium } = require('playwright'); @@ -283,6 +283,59 @@ const fixture = (extra = []) => { check(await page.evaluate(() => document.activeElement?.id === 'gs-present' && !document.fullscreenElement), 'Esc ends it, out of full screen, focus back on Present'); check(page.url().includes('?slides='), 'and the deck stays open'); + // ---- Print…: the print view (the dialog itself stubbed) holds a page per slide a show plays at the slide's own size, its + // pictures loaded first; printed for real by page.pdf with the page's own @page, then Print with notes on the notes page + await page.evaluate(() => { globalThis.printed = 0; window.print = () => { globalThis.printed++; }; }); + const printView = () => page.evaluate(() => { + const b = document.querySelector('body > .gs-print[data-ready]'); + if (!b || !globalThis.printed) return null; + const pics = [...b.querySelectorAll('img, image')], pages = [...b.querySelectorAll('.gs-print-page')]; + const rule = document.adoptedStyleSheets.flatMap((x) => [...x.cssRules]).find((r) => r instanceof CSSPageRule); + return { pages: pages.length, slides: pages.map((x) => x.dataset.slide).join(), size: rule?.style.getPropertyValue('size'), pics: pics.length, loaded: pics.filter((x) => x.hasAttribute('data-gs-loaded')).length, + notes: b.querySelectorAll('.gs-print-notes').length, title: document.title, styled: pages.filter((x) => x.hasAttribute('style')).length, printed: globalThis.printed }; + }); + const pdfPages = async () => { + await page.emulateMedia({ media: 'print' }); + const laid = await page.evaluate(() => ({ view: getComputedStyle(document.querySelector('.gs-view')).display, boxes: [...document.querySelectorAll('.gs-print-page')].map((x) => { const r = x.getBoundingClientRect(); return `${Math.round(r.width)}x${Math.round(r.height)}`; }) })); + const pdf = (await page.pdf({ preferCSSPageSize: true, printBackground: true })).toString('latin1'); + await page.emulateMedia({ media: 'screen' }); + return { ...laid, pages: [...pdf.matchAll(/\/Type\s*\/Page(?![s\w])/g)].length, media: [...new Set([...pdf.matchAll(/\/MediaBox\s*\[([^\]]+)\]/g)].map((m) => m[1].trim().split(/\s+/).map((v) => Math.round(+v)).join(' ')))] }; + }; + const afterPrint = async () => { + await page.evaluate(() => dispatchEvent(new Event('afterprint'))); + return page.evaluate(() => !document.querySelector('.gs-print') && !document.adoptedStyleSheets.length && !document.documentElement.hasAttribute('data-gs-print') && document.title !== ''); + }; + await page.click('.gs-more'); + const printItems = await until(() => page.evaluate(() => [...document.querySelectorAll('.gs-menu [role="menuitem"]')].map((b) => b.textContent)), 'Print… in ⋯'); + check(printItems[0] === 'Print…' && printItems[1] === 'Print with notes…', `⋯ offers Print… and Print with notes… (${JSON.stringify(printItems)})`); + await page.click('.gs-menu [data-act="print"]'); + let pv = await until(printView, 'the print view'); + check(pv.pages === 9 && pv.slides === '0,1,2,3,4,5,7,8,9' && pv.size === '1280px 720px' && pv.notes === 0 && pv.styled === 0 && pv.printed === 1 && pv.title === 'Cell Biology week 3', + `Print…: 9 pages, the hidden slide 7 left out, @page 1280 × 720 px from the deck, no notes, the page boxes styled through the CSSOM (${JSON.stringify(pv)})`); + check(pv.pics > 0 && pv.loaded === pv.pics, `the pictures loaded before the print dialog (${pv.loaded} of ${pv.pics})`); + let pp = await pdfPages(); + check(pp.view === 'none' && pp.boxes.length === 9 && pp.boxes.every((b) => b === '1280x720'), `print media: only the slides, each page box 1280 × 720 (${JSON.stringify({ view: pp.view, boxes: [...new Set(pp.boxes)] })})`); + check(pp.pages === 9 && pp.media.length === 1 && pp.media[0] === '0 0 960 540', `the printed PDF: 9 pages of 960 × 540 pt, 13.33 × 7.5 in (${JSON.stringify({ pages: pp.pages, media: pp.media })})`); + check(await afterPrint(), 'after the print the view, its @page and the mark go'); + await page.click('.gs-more'); + await until(() => page.locator('.gs-menu [data-act="print-notes"]').count(), 'Print with notes…'); + await page.click('.gs-menu [data-act="print-notes"]'); + pv = await until(printView, 'the print view with notes'); + const noted = await page.evaluate(() => [...document.querySelectorAll('.gs-print-page')].filter((x) => x.querySelector('.gs-print-notes')).map((x) => x.dataset.slide).join()); + check(pv.pages === 9 && pv.size === '720px 960px' && pv.notes > 0 && /Start with the quiz/.test(await page.locator('.gs-print').textContent()) && pv.loaded === pv.pics, + `Print with notes: 9 notes pages of 720 × 960 px, the notes below their slides (slides ${noted}) (${JSON.stringify(pv)})`); + await page.emulateMedia({ media: 'print' }); + await shot('print-notes-1280.png'); + await page.emulateMedia({ media: 'screen' }); + pp = await pdfPages(); + check(pp.pages === 9 && pp.media.length === 1 && pp.media[0] === '0 0 540 720', `the printed PDF with notes: 9 pages of 540 × 720 pt, 7.5 × 10 in (${JSON.stringify({ pages: pp.pages, media: pp.media })})`); + check(await afterPrint(), 'after printing with notes the view goes'); + await page.focus('#gs-canvas'); + await page.keyboard.press('Control+p'); + pv = await until(printView, 'Ctrl+P'); + check(pv.pages === 9 && pv.size === '1280px 720px' && pv.printed === 3, `Ctrl+P prints the slides (${JSON.stringify(pv)})`); + check(await afterPrint(), 'and its view goes after'); + // ---- the ⋯ menu: Export .pptx gives back the bytes await page.click('.gs-more'); const items = await until(() => page.evaluate(() => [...document.querySelectorAll('.gs-menu [role="menuitem"]')].map((b) => b.textContent)), 'the ⋯ menu'); diff --git a/slides-kind.css b/slides-kind.css index 2b5873263..4442cb839 100644 --- a/slides-kind.css +++ b/slides-kind.css @@ -73,3 +73,16 @@ dialog.gs-present::backdrop { background: #000; } .gs-find { grid-row: 2; grid-column: 1 / -1; min-width: 0; } .gs-canvas { padding: 12px; } } + +/* Print: the slides alone, a page each (its size and @page: slides-kind.js print, through the CSSOM), the notes below + with Print with notes; nothing of the Look around them */ +.gs-print { display: none; } +@media print { + :root[data-gs-print], :root[data-gs-print] body { height: auto !important; overflow: visible !important; background: #fff !important; } + :root[data-gs-print] > :not(head, body), :root[data-gs-print] body > :not(.gs-print) { display: none !important; } + :root[data-gs-print] body > .gs-print { display: block !important; } + .gs-print-page { box-sizing: border-box; overflow: hidden; break-after: page; break-inside: avoid; background: #fff; color: #000; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + .gs-print-page:last-child { break-after: auto; } + .gs-print[data-notes] .gs-stage { margin: 0 auto; box-shadow: 0 0 0 1px #000; } + .gs-print-notes { margin-top: 24px; white-space: pre-wrap; font: 12pt/1.4 Calibri, Carlito, sans-serif; overflow: hidden; } +} diff --git a/slides-kind.js b/slides-kind.js index ef175d587..8c00f9d61 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -1,8 +1,8 @@ // Slide decks on the Documents page (papers.html). Settings → Writer → Slides (writer.slides, its default in defaults.js) permits // importing .pptx and .ppsx files; existing decks always remain available for viewing. Documents of the slides kind // (lib/doc-kinds.js), each card showing slide 1, and open in a viewer: a rail of thumbnails, the slide (lib/slides-render.js -// on lib/pptx.js's model), zoom, find in the slides' and notes' text, the speaker notes, and Present (a dialog, full screen -// where the browser allows, Rimeward's keys, hidden slides skipped). Its chrome wears the Look; the slide stays as authored. +// on lib/pptx.js's model), zoom, find in the slides' and notes' text, the speaker notes, Present (a dialog, full screen +// where the browser allows, Rimeward's keys, hidden slides skipped) and Print (a page per slide, notes optionally below). Its chrome wears the Look; the slide stays as authored. // Export gives back the bytes imported. A viewer, never an editor: the record only changes by the page's rename. // Storage (STORE): the deck's bytes in the worker's file store (lib/doc-files.js, over its port), the record pointing at // them (file: { hash, size, type }), up to the kind's max; a record from before the store holds them as base64, read as @@ -505,10 +505,58 @@ }; presentB.addEventListener('click', present); - // ---- ⋯: export in the deck's own format + // ---- Print…: the slides a show plays, each on a page of its own (GZ_SLIDES_RENDER.printPage, its @page through the + // CSSOM), with Print with notes each slide's notes below it on the deck's notes page; the pictures loaded before the + // browser's print dialog opens. Only the pages print (slides-kind.css @media print), never the Look's chrome. + let printing = null; + const PICTURE_WAIT = 15000; + const loaded = (el) => new Promise((ok) => { + const done = () => { clearTimeout(timer); el.setAttribute('data-gs-loaded', ''); ok(); }; + const timer = setTimeout(ok, PICTURE_WAIT); + el.addEventListener('load', done, { once: true }); + el.addEventListener('error', () => { clearTimeout(timer); ok(); }, { once: true }); + }); + const endPrint = () => { + if (!printing) return; + printing.box.remove(); + document.adoptedStyleSheets = document.adoptedStyleSheets.filter((x) => x !== printing.sheet); + root.removeAttribute('data-gs-print'); + document.title = printing.title; + printing = null; + }; + const print = async (withNotes = false) => { + if (!deck || printing) return; + const pg = Rr.printPage(deck, { notes: withNotes }), seq = Rr.visible(deck); + if (!pg || !seq.length) return; + const box = h('div', { class: 'gs-print', 'data-notes': withNotes, 'aria-hidden': 'true' }); + for (const i of seq) { + const s = deck.slides[i]; + box.append(h('section', { class: 'gs-print-page', 'data-slide': String(i) }, Rr.stage(s, deck, { document, media, labels, scale: pg.scale }), + withNotes && s.notes ? h('div', { class: 'gs-print-notes', text: s.notes }) : null)); + } + // Each picture heard from as it is drawn, before the pages join the document (a load can't come first). + const pictures = [...box.querySelectorAll('img, image')].map(loaded); + const sheet = new CSSStyleSheet(); + sheet.replaceSync(`@page { size: ${pg.w}px ${pg.h}px; margin: 0; } .gs-print-page { width: ${pg.w}px; height: ${pg.h}px; padding: ${pg.margin}px; }`); + printing = { box, sheet, title: document.title }; + document.title = title; + document.body.append(box); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + root.setAttribute('data-gs-print', withNotes ? 'notes' : 'slides'); + await Promise.all([...pictures, document.fonts?.ready]); + if (printing?.box !== box) return; + box.setAttribute('data-ready', ''); + addEventListener('afterprint', endPrint, { once: true, signal }); + window.print(); + }; + const notesAny = !!deck?.slides.some((s) => !s.hidden && s.notes); + + // ---- ⋯: print, and export in the deck's own format const closeMenu = (back) => { if (!menu) return; menu.remove(); menu = null; moreB.setAttribute('aria-expanded', 'false'); if (back) moreB.focus(); }; const openMenu = () => { menu = h('div', { class: 'pop gp-menu gs-menu', role: 'menu', 'aria-label': t('More') }, + btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print', disabled: !deck, onclick: () => { closeMenu(true); print(false).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print…')), + notesAny ? btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print-notes', onclick: () => { closeMenu(true); print(true).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print with notes…')) : null, btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'export', disabled: !bytes, onclick: () => { closeMenu(true); exportFile(key).catch((e) => console.warn(e)); } }, t('Export .pptx').replace('.pptx', rec?.ext === '.ppsx' ? '.ppsx' : '.pptx'))); document.body.append(menu); const r = moreB.getBoundingClientRect(); @@ -533,6 +581,8 @@ addEventListener('keydown', (e) => { if (e.defaultPrevented || presenting || menu || !deck) return; if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'f') { e.preventDefault(); e.stopPropagation(); q.focus(); q.select(); } + // ⌘P / Ctrl+P: the slides, not the viewer around them. + if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'p') { e.preventDefault(); e.stopPropagation(); print(false).catch((x) => { console.warn(x); endPrint(); }); } }, { capture: true, signal }); document.addEventListener('keydown', (e) => { if (e.defaultPrevented || presenting || menu || !deck) return; @@ -548,7 +598,7 @@ // What an assignment's submit sheet sends (writer-submit.js): the deck as it was brought. return { pages: () => null, say: (text) => { if (state.textContent !== text) state.textContent = text || ''; }, ready: () => !!deck, save: async () => true, dispose: () => { - life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); + life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); endPrint(); for (const u of media.values()) { URL.revokeObjectURL(u); made.delete(u); } }, file: async () => { const f = await file(key); if (!f) throw new Error('no file'); return { bytes: f.bytes, mime: f.mime, ext: rec?.ext === '.ppsx' ? '.ppsx' : '.pptx' }; } }; diff --git a/test/slides-render.test.js b/test/slides-render.test.js index 93224acd2..8d2794b14 100644 --- a/test/slides-render.test.js +++ b/test/slides-render.test.js @@ -342,3 +342,22 @@ test('find work stays bounded for text fragmented into many formatted runs', asy assert.equal(all(el).filter(tag('mark')).length, 256); assert.ok(work < 256 * 12, `${work} range-bound operations for 256 runs and matches`); }); + +// ---- print: each slide on a page of its own size, or the deck's notes page with the slide across its top +test('a printed slide fills a page of the slide\'s own size; with notes, the notes page holds it inside a half inch', async () => { + const d = await deck(); + const plain = R.printPage(d); + assert.deepEqual(plain, { w: 1280, h: 720, scale: 1, margin: 0, notes: false }, 'a 16:9 deck prints on 1280 × 720 px (13.333 × 7.5 in) pages'); + const noted = R.printPage(d, { notes: true }); + // The fixture's notesSz is 7.5 × 10 in: 720 × 960 px, the slide fitted in 624 px across and the top half less its margin. + assert.deepEqual(noted, { w: 720, h: 960, scale: 0.4875, margin: 48, notes: true }); + assert.ok(1280 * noted.scale <= noted.w - 2 * noted.margin && 720 * noted.scale <= noted.h / 2 - noted.margin, 'the slide fits its half'); + // A 4:3 deck and a portrait one; a deck without a notes size uses PowerPoint's 7.5 × 10 in. + assert.deepEqual(R.printPage({ size: { w: 9144000, h: 6858000 } }), { w: 960, h: 720, scale: 1, margin: 0, notes: false }); + assert.deepEqual(R.printPage({ size: { w: 6858000, h: 9144000 } }, { notes: true }), { w: 720, h: 960, scale: 0.45, margin: 48, notes: true }); + // An odd size in EMU stays in whole hundredths, never larger than the slide (a page past it would print a blank one). + const odd = R.printPage({ size: { w: 12192001, h: 6858007 } }); + assert.ok(odd.w <= 12192001 / 9525 && odd.h <= 6858007 / 9525 && odd.w === Math.floor(odd.w * 100) / 100, JSON.stringify(odd)); + assert.equal(R.printPage({ size: { w: 0, h: 5 } }), null); + assert.equal(R.printPage(null), null); +}); From 3ae4e4f85e711a765da096bad79ce0d6f8d4759d Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:31:30 -0400 Subject: [PATCH 061/907] Run agent-link-check in the browser gate --- .github/workflows/browser.yml | 2 +- scripts/ci-browser-checks.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml index 3f0483320..917686f56 100644 --- a/.github/workflows/browser.yml +++ b/.github/workflows/browser.yml @@ -33,7 +33,7 @@ jobs: PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1 "$RUNNER_TEMP/playwright/node_modules/.bin/playwright" install --with-deps --no-shell chromium node scripts/ci-chromium.js - - name: Settings, popup, sites and pointer checks + - name: Settings, popup, sites, pointer and agent link checks run: nice -n 10 node scripts/ci-browser-checks.js "$RUNNER_TEMP/browser-evidence" - name: Keep the logs and screenshots diff --git a/scripts/ci-browser-checks.js b/scripts/ci-browser-checks.js index fc78da44b..9070e30b1 100644 --- a/scripts/ci-browser-checks.js +++ b/scripts/ci-browser-checks.js @@ -1,4 +1,5 @@ -// The browser gate: verified Chromium, settings, popup, site permissions and the student's pointer, one check at a time. +// The browser gate: verified Chromium, settings, popup, site permissions, the student's pointer and the agents' native +// host link, one check at a time. // NODE_PATH= PLAYWRIGHT_BROWSERS_PATH= nice -n 10 node scripts/ci-browser-checks.js // Holds the shared lock throughout cleanup. Each check gets its own lock and temporary directory. const { spawn, execFileSync } = require('node:child_process'); @@ -12,6 +13,8 @@ const CHECKS = [ { name: 'sites', script: 'sites-check.js', args: (dir) => [dir] }, { name: 'pointer', script: 'pointer-check.js', args: (dir) => [dir] }, { name: 'register-update', script: 'register-update-check.js', limit: 60 * 1000, args: (dir) => [dir] }, + // Its headed step needs Xvfb, which the gate's Chromium install (--with-deps) brings. + { name: 'agent-link', script: 'agent-link-check.js', limit: 5 * 60 * 1000, args: (dir) => [dir] }, ]; const LIMIT = 2 * 60 * 1000, BUDGET = 11 * 60 * 1000; const pause = (ms) => new Promise((r) => setTimeout(r, ms)); From b674e5432570b4f9ca9487acb82f3937123751c7 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:32:46 -0400 Subject: [PATCH 062/907] Refresh the Safari resource mirror for the Firefox lint fixes --- .../Resources/features/folders.js | 15 +++++++++--- .../Resources/features/notes.js | 9 +++++++- .../Resources/lib/background.js | 8 +++---- .../Resources/lib/calexport.js | 9 ++++---- .../Resources/lib/gcal-auth.js | 6 +++++ .../Glaze Extension/Resources/lib/insights.js | 13 +++++------ .../lib/pdfjs/jbig2_nowasm_fallback.js | 15 ++++++++++++ .../lib/pdfjs/openjpeg_nowasm_fallback.js | 17 ++++++++++++++ .../Resources/lib/pdfjs/pdf.mjs | 2 +- .../Resources/lib/pdfjs/pdf.worker.mjs | 6 +++-- .../Glaze Extension/Resources/lib/pet.js | 23 +++++++++---------- .../Glaze Extension/Resources/lib/scene.js | 2 +- .../Resources/lib/sheet-xlsx.js | 9 ++++---- .../Glaze Extension/Resources/options.html | 1 + .../Glaze Extension/Resources/options.js | 4 ++-- .../Glaze Extension/Resources/pdf-kind.js | 5 ++-- .../Glaze/Glaze Extension/Resources/reader.js | 2 +- 17 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/gcal-auth.js create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/pdfjs/jbig2_nowasm_fallback.js create mode 100644 safari/Glaze/Glaze Extension/Resources/lib/pdfjs/openjpeg_nowasm_fallback.js diff --git a/safari/Glaze/Glaze Extension/Resources/features/folders.js b/safari/Glaze/Glaze Extension/Resources/features/folders.js index ea9455596..9b7eb0c52 100644 --- a/safari/Glaze/Glaze Extension/Resources/features/folders.js +++ b/safari/Glaze/Glaze Extension/Resources/features/folders.js @@ -96,7 +96,16 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { } catch { if (sameAction(k, g)) fail(); } finally { if (a === action) pendingFocus = null; } }; - const icon = (hidden) => ``; + const icon = (hidden) => { + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + for (const [k, v] of Object.entries({ 'aria-hidden': 'true', width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.7' })) svg.setAttribute(k, v); + for (const [tag, attrs] of [['path', { d: 'M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z' }], ['circle', { cx: '12', cy: '12', r: '3' }], ...(hidden ? [['path', { d: 'm3 3 18 18' }]] : [])]) { + const node = document.createElementNS('http://www.w3.org/2000/svg', tag); + for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, v); + svg.append(node); + } + return svg; + }; const button = () => { const b = document.createElement('button'); b.type = 'button'; for (const ev of ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'keydown', 'keyup']) b.addEventListener(ev, (e) => e.stopPropagation()); @@ -134,7 +143,7 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { if (hidden && data.show) titles.forEach((el, n) => markHidden(el, row, id, n)); const label = t(hidden ? WORDS.unhide : WORDS.hide, { title: titleOf(toggle) }); if (b.getAttribute('aria-label') !== label) { b.setAttribute('aria-label', label); b.title = label; } - if (b.dataset.hidden !== String(hidden)) { b.dataset.hidden = String(hidden); b.innerHTML = icon(hidden); } + if (b.dataset.hidden !== String(hidden)) { b.dataset.hidden = String(hidden); b.replaceChildren(icon(hidden)); } } const bar = document.getElementById('gz-progress'), top = canvas ? rows.length && document.getElementById('context_modules') : rows.find((r) => !r.parentElement.closest(ROW)); if (bar) { if (status.previousElementSibling !== bar) bar.after(status); } @@ -150,7 +159,7 @@ var GZ_FOLDER_UI = globalThis.GZ_FOLDER_UI || (() => { else if (status.isConnected && control.nextElementSibling !== status) status.before(control); const label = t(data.show ? 'Hide them again ({n})' : WORDS.toggle, { n: ids.size }), text = control.querySelector('.gz-folders-toggle-label'); if (text.textContent !== label) text.textContent = label; - if (control.dataset.show !== String(data.show)) { control.dataset.show = String(data.show); control.querySelector('.gz-folders-toggle-icon').innerHTML = icon(data.show); } + if (control.dataset.show !== String(data.show)) { control.dataset.show = String(data.show); control.querySelector('.gz-folders-toggle-icon').replaceChildren(icon(data.show)); } if (control.getAttribute('aria-pressed') !== String(data.show)) control.setAttribute('aria-pressed', String(data.show)); if (focusToggle) { control.focus({ preventScroll: true }); pendingFocus = null; } }; diff --git a/safari/Glaze/Glaze Extension/Resources/features/notes.js b/safari/Glaze/Glaze Extension/Resources/features/notes.js index 80d614dac..70916cc4c 100644 --- a/safari/Glaze/Glaze Extension/Resources/features/notes.js +++ b/safari/Glaze/Glaze Extension/Resources/features/notes.js @@ -514,7 +514,14 @@ var GZ_NOTES_UI = globalThis.GZ_NOTES_UI || (() => { // A note that can't be read stays closed: never an empty note that a save could write over the real one. if (!r?.ok) { el.body.textContent = t(r?.error === 'missing' ? "This note's text couldn't be found on this device." : "Couldn't open this note. Try again in a moment."); cur.saved = null; cur.ro = true; return; } cur.rev = r.rev; - el.body.innerHTML = N.sanitize(r.html); + // Build from the sanitizer's allowlisted tree, without interpreting another HTML string. + const node = (n) => { + if (typeof n === 'string') return n; + const e = attr(make(n.tag, {}, ...n.kids.map(node)), n.attrs); + if (n.tag === 'a' && n.attrs.href) attr(e, { rel: 'noopener noreferrer', target: '_blank' }); + return e; + }; + el.body.replaceChildren(...N.parse(r.html).kids.map(node)); cur.saved = serialize(); el.body.setAttribute('contenteditable', String(!cur.ro)); if (keep?.sel) el.body.focus(); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 755b32f2a..cc16f4e4d 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -1,4 +1,4 @@ -importScripts('download.js', 'color.js', 'fonts.js', 'surfaces.js', '../defaults.js', 'lookset.js', 'history.js', 'cards.js', 'nav.js', 'nav-items.js', 'navlayout.js', 'motionx.js', 'parts.js', 'nav-store.js', 'todo.js', 'petbrain.js', 'pet.js', 'petsprites.js', 'remind.js', 'widgets.js', 'sidebar.js', 'lookparts.js', 'share.js', 'lookx.js', 'looks.js', 'gradescope.js', 'doctext.js', 'folders.js', 'notes.js', 'calexport.js', 'gcal.js', 'site.js', 'coursetabs.js'); +importScripts('download.js', 'color.js', 'fonts.js', 'surfaces.js', '../defaults.js', 'lookset.js', 'history.js', 'cards.js', 'nav.js', 'nav-items.js', 'navlayout.js', 'motionx.js', 'parts.js', 'nav-store.js', 'todo.js', 'petbrain.js', 'pet.js', 'petsprites.js', 'remind.js', 'widgets.js', 'sidebar.js', 'lookparts.js', 'share.js', 'lookx.js', 'looks.js', 'gradescope.js', 'doctext.js', 'folders.js', 'notes.js', 'calexport.js', 'gcal.js', 'gcal-auth.js', 'site.js', 'coursetabs.js'); // Cache packaged broker scripts at install; parse them on first use rather than every worker wake. const BROKER_FILES = { @@ -3124,11 +3124,11 @@ const gcalEnv = { sleep: (ms) => new Promise((res) => setTimeout(res, ms)), // Chrome's cached token, never a prompt (the editor's click asks); none without the calendar scope. token: async () => { - const r = await chrome.identity.getAuthToken({ interactive: false }); + const r = await GZ_GCAL_AUTH.token({ interactive: false }); if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(GZ_GCAL.SCOPE)) throw new GZ_GCAL.Fail('auth'); return typeof r === 'string' ? r : r?.token; }, - drop: (token) => Promise.resolve().then(() => chrome.identity.removeCachedAuthToken({ token })).catch(() => {}), + drop: (token) => Promise.resolve().then(() => GZ_GCAL_AUTH.drop(token)).catch(() => {}), }; // gcalEpoch: Disconnect moves it on as it arrives, so every sync already running or waiting in the queue stops at its // next step, before Disconnect revokes anything. @@ -3163,7 +3163,7 @@ const gcalOps = { if (m.drop === true && g?.cal) await GZ_GCAL.remove(gcalEnv, g.cal); const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); - await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => GZ_GCAL_AUTH.clear()).catch(() => {}); await chrome.storage.local.remove(['gcal', 'gcalSync']); serial(ensure); return { ok: true, revoked, had: !!token }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/calexport.js b/safari/Glaze/Glaze Extension/Resources/lib/calexport.js index e0c507dff..8227a4d6a 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/calexport.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/calexport.js @@ -2,7 +2,6 @@ // lib/todo.js gives the days in the student's Canvas time zone; lib/ics.js writes the file and makes the UIDs. // Classic script, loaded on demand after those two (a second injection keeps the first); module.exports for node. var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { - const lib = (name, file) => globalThis[name] || require(file); const WEEKS = { '2w': 2, '4w': 4, '8w': 8, '16w': 16 }; // From the start of today to the end of the range, in tz, as instants and as lib/todo.js day numbers [first, last). @@ -10,7 +9,7 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // not ended (a course's own dates when it keeps to them, else its term's), at most a year ahead. Canvas's default // term has no dates: with none to go by it's the next 16 weeks, and guessed says so. const range = (pick, now, tz, courses = []) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const first = T.dayOf(now, tz); let last = first + 7 * (WEEKS[pick] || 16), guessed = false; if (pick === 'term') { @@ -31,8 +30,8 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // Deadlines are instants (UTC in the file); all-day items and "do on" days are dates; focus sessions run from their // start for their length. const events = ({ items = [], gs = [], data, o, from, to, first, last, site, host, cids = null, names = {}, words, classes = [] }) => { - const T = lib('GZ_TODO', './todo.js'); - const { uid } = lib('GZ_ICS', './ics.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); + const { uid } = (globalThis.GZ_ICS || require('./ics.js')); const d = T.clean(data); const shown = (cid) => !cid || !cids || cids.has(cid); const inDays = (n) => n >= first && n < last; @@ -83,7 +82,7 @@ var GZ_CALEXPORT = globalThis.GZ_CALEXPORT || (() => { // The keys of the student's items with a "do on" day or a focus session in the range: what the planner read must // cover for their titles (features/calexport.js asks further back for those it didn't). const planned = (data, { from, to, first, last }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); return Object.entries(T.clean(data).items).filter(([, l]) => { const on = T.keyDay(l.on); return (on !== null && on >= first && on < last) || (l.sessions || []).some((s) => s.at >= from && s.at < to); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/gcal-auth.js b/safari/Glaze/Glaze Extension/Resources/lib/gcal-auth.js new file mode 100644 index 000000000..85006efe3 --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/gcal-auth.js @@ -0,0 +1,6 @@ +// Chrome's Google sign-in adapter. Firefox packaging replaces it with an unavailable adapter. +var GZ_GCAL_AUTH = globalThis.GZ_GCAL_AUTH || { + token: (options) => chrome.identity.getAuthToken(options), + drop: (token) => chrome.identity.removeCachedAuthToken({ token }), + clear: () => chrome.identity.clearAllCachedAuthTokens(), +}; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/insights.js b/safari/Glaze/Glaze Extension/Resources/lib/insights.js index 31374c5ad..b7bc6abcc 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/insights.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/insights.js @@ -6,7 +6,6 @@ // Classic script, loaded on demand after those two (a second injection keeps the first); the service worker loads it // for change(); module.exports for node. var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { - const lib = (name, file) => globalThis[name] || require(file); const DAY = 864e5; const isObj = (o) => !!o && typeof o === 'object' && !Array.isArray(o); const arr = (v) => (Array.isArray(v) ? v : []); @@ -56,7 +55,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // afresh: every point is then its own period's score, [ms, percent, period id], so the current period's last point // is the score Canvas shows. Without one, the whole course, as Canvas totals it. const trend = (groups, info, tz, current = null) => { - const C = lib('GZ_GRADECALC', './gradecalc.js'); + const C = (globalThis.GZ_GRADECALC || require('./gradecalc.js')); const course = C.fromApi(groups, info); const split = !!(course.periods && current && course.periods.some((p) => p.id === String(current))); const dated = []; @@ -73,7 +72,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // The points themselves, for any course in lib/gradecalc.js's shape and its dated graded work [[id, ms]]: Blackboard's // (lib/ultra.js trend) too, whose overall grade is calc's final where it counts work not graded yet as zero (zeros). const along = (course, dated, tz, split = false) => { - const C = lib('GZ_GRADECALC', './gradecalc.js'), T = lib('GZ_TODO', './todo.js'); + const C = (globalThis.GZ_GRADECALC || require('./gradecalc.js')), T = (globalThis.GZ_TODO || require('./todo.js')); const periodOf = new Map(course.groups.flatMap((g) => g.items.map((i) => [i.id, i.period]))); dated = [...dated].sort((a, b) => a[1] - b[1]); const out = []; @@ -107,7 +106,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // The best run of weeks in a row where everything due that week was on time, and the run going now. Weeks with // nothing decided don't count and don't break a run. const runs = (items, tz) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const weeks = new Map(); for (const i of items) if (i.d && DECIDED.includes(i.s)) { const w = week(T.dayOf(i.d, tz)); weeks.set(w, (weeks.get(w) ?? true) && i.s === 'on'); } let best = 0, now = 0; @@ -118,7 +117,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // past, now, heavy }. The term runs from its start (or the first due date) to its end (or the last), at most half a // year either side of this week. heavy: the three busiest weeks from this one on (most items, then most points). const workload = (items, { from, to, now, tz }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const due = items.filter((i) => i.d && i.s !== 'excused').map((i) => ({ ...i, w: week(T.dayOf(i.d, tz)) })); const here = week(T.dayOf(now, tz)); const ends = [...due.map((i) => i.w), ...[from, to].filter(Boolean).map((ms) => week(T.dayOf(ms, tz)))]; @@ -141,7 +140,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // this term's work and days that are over count; work ticked off at an unknown time (a Canvas item's tick) is left // out. items: this term's (with a, handed in); fromDay: the term's first day. const planning = (data, items, { now, tz, fromDay = -Infinity }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const today = T.dayOf(now, tz); const done = new Map(items.map((i) => [i.k, i.a || null])); for (const x of arr(data?.tasks)) done.set(`task:${x.id}`, x.done || null); @@ -163,7 +162,7 @@ var GZ_INSIGHTS = globalThis.GZ_INSIGHTS || (() => { // left: what couldn't be counted, by course name (work past the cache's cap, or past the pages Glaze reads), and how // many courses past the cap; the view says so. const view = (rec, { now, tz, data = null, streak = null }) => { - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const all = rec.courses.flatMap((c) => c.items); const courses = rec.courses.map((c) => { // With grading periods: First, Now and the change are the current period's, as Canvas's current score is. diff --git a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/jbig2_nowasm_fallback.js b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/jbig2_nowasm_fallback.js new file mode 100644 index 000000000..c63f15e71 --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/jbig2_nowasm_fallback.js @@ -0,0 +1,15 @@ +/* THIS FILE IS GENERATED - DO NOT EDIT */ +async function JBig2(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";var readAsync;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=( +// EMSCRIPTEN_START_ASM +function instantiate(ha){var a;var b=new Uint8Array(123);for(var c=25;c>=0;--c){b[48+c]=52+c;b[65+c]=c;b[97+c]=26+c}b[43]=62;b[47]=63;function i(j,k,l){var d,e,c=0,f=k,g=l.length,h=k+(g*3>>2)-(l[g-2]=="=")-(l[g-1]=="=");for(;c>4;if(f>2;if(f>>0;s=s>>>0;if(q+s>a.length)throw"trap: invalid memory.fill";a.fill(u,q,q+s)}function ga(n){var v=new ArrayBuffer(16777216);var w=new Int8Array(v);var x=new Int16Array(v);var y=new Int32Array(v);var z=new Uint8Array(v);var A=new Uint16Array(v);var B=new Uint32Array(v);var C=new Float32Array(v);var D=new Float64Array(v);var E=Math.imul;var F=Math.fround;var G=Math.abs;var H=Math.clz32;var I=Math.min;var J=Math.max;var K=Math.floor;var L=Math.ceil;var M=Math.trunc;var N=Math.sqrt;var O=n.a;var P=O.a;var Q=O.b;var R=O.c;var S=O.d;var T=O.e;var U=O.f;var V=O.g;var W=O.h;var X=O.i;var Y=72384;var Z=0; +// EMSCRIPTEN_START_FUNCS +function yc(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,u=0,v=0,C=0,D=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,Ia=0,Ma=0,Pa=0,Qa=0,Ta=0,Xa=0,Ya=0,gb=0,hb=0,qb=0,rb=0,sb=0,tb=0,ub=0;T=Y-16|0;Y=T;b=y[a+4>>2];c=y[b+8>>2];b=y[b+4>>2];i=b;a:{b:{c:{d:{e:{if(b>>>0>>0){break e}if((b|0)==(c|0)){sb=2;break c}ba=a+80|0;while(1){if(c>>>0>i>>>0){break e}if(i-c>>>0<11){break c}f:{g:{h:{n=y[ba>>2];i:{if(!n){b=na(72);w[b+4|0]=0;y[b>>2]=0;t(b+8|0,0,64);y[T+4>>2]=0;eb(ba,b);Vb(T+4|0);j:{i=y[a+80>>2];k:{if(qa(y[a+4>>2],i)){break k}if(wa(y[a+4>>2],i+4|0)){break k}g=y[a+4>>2];d=y[g+8>>2];b=y[g>>2];l:{m:{if(B[g+4>>2]<=d>>>0){w[T+15|0]=0;break m}b=z[b+d|0];w[T+15|0]=b;if(b>>>0<224){break m}if(qa(g,i+8|0)){break k}c=y[i+8>>2]&536870911;y[i+8>>2]=c;if(c>>>0>64){break k}d=y[a+4>>2];b=y[d+8>>2];g=b+(c+8>>>3|0)|0;if(b>>>0>g>>>0){break l}b=y[d+4>>2];y[d+8>>2]=b>>>0>g>>>0?g:b;break l}if(wa(g,T+15|0)){break k}c=z[T+15|0]>>>5|0;y[i+8>>2]=c}d=z[i+4|0];n:{if(!c){break n}b=y[i>>2];Rb(i+12|0,c);c=0;b=(b>>>0>65536?4:b>>>0>256?2:1)-1|0;while(1){if(y[i+8>>2]<=(c|0)){break n}o:{p:{switch(b-1|0){default:if(wa(y[a+4>>2],T+15|0)){break k}n=z[T+15|0];y[y[i+12>>2]+(c<<2)>>2]=n;break o;case 0:if(Sa(y[a+4>>2],T+12|0)){break k}n=A[T+12>>1];y[y[i+12>>2]+(c<<2)>>2]=n;break o;case 1:break e;case 2:break p}}if(qa(y[a+4>>2],T+8|0)){break k}n=y[T+8>>2];y[y[i+12>>2]+(c<<2)>>2]=n}c=c+1|0;if(B[i>>2]>n>>>0){continue}break}break k}b=y[a+4>>2];q:{if(!(d&64)){if(wa(b,T+15|0)){break k}y[i+24>>2]=z[T+15|0];break q}if(qa(b,i+24|0)){break k}}if(!qa(y[a+4>>2],i+28|0)){break j}}eb(ba,0);break i}c=y[a+4>>2];b=y[c+20>>2];y[i+40>>2]=y[c+16>>2];y[i+44>>2]=b;b=y[c+8>>2];y[i+48>>2]=1;y[i+36>>2]=b;y[a+84>>2]=b;n=y[a+80>>2]}r:{while(1){g=0;m=0;c=0;f=Y-304|0;Y=f;b=2;s:{t:{u:{v:{w:{x:{y:{z:{A:{B:{C:{D:{E:{F:{G:{H:{I:{J:{K:{L:{M:{N:{O:{P:{Q:{R:{S:{d=z[n+4|0]&63;switch(d-16|0){case 0:break R;case 1:case 2:case 3:case 5:case 8:case 9:case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:case 18:case 19:case 21:case 25:case 28:case 29:case 30:case 31:break B;case 4:case 6:case 7:break Q;case 20:case 22:case 23:break P;case 24:case 26:case 27:break O;case 32:break N;case 33:break K;case 34:break J;case 35:break u;case 36:break I;case 37:break H;default:break S}}T:{switch(d|0){case 0:b=0;h=0;q=0;e=Y-432|0;Y=e;c=1;if(!Sa(y[a+4>>2],e+210|0)){l=na(56);y[l+40>>2]=0;y[l+32>>2]=0;y[l+36>>2]=0;y[l+24>>2]=0;y[l+28>>2]=0;y[l+16>>2]=0;y[l+20>>2]=0;u=A[e+210>>1];i=u&1;w[l|0]=i;g=u>>>10&3;w[l+3|0]=g;d=u&12288;w[l+2|0]=(d|0)!=0;c=(u&254)>>>1&1;w[l+1|0]=c;ga=l+16|0;U:{V:{W:{if(!i){i=g?2:8;g=l+44|0;while(1){if((b|0)!=(i|0)){c=1;d=b+g|0;b=b+1|0;if(!wa(y[a+4>>2],d)){continue}break U}break}if(z[l+1|0]!=1){break V}if(!z[l+2|0]){break W}break V}if(d|!c){break V}}g=l+52|0;b=0;while(1){if((b|0)==4){break V}c=1;d=b+g|0;b=b+1|0;if(!wa(y[a+4>>2],d)){continue}break}break U}c=1;if(qa(y[a+4>>2],l+12|0)){break U}if(qa(y[a+4>>2],l+8|0)|B[l+12>>2]>1048576|B[l+8>>2]>1048576){break U}b=y[n+8>>2];k=(b|0)>0?b:0;b=0;while(1){if((b|0)==(k|0)){I=1;b=0;while(1)if((b|0)==(k|0)){X:{Y:{Z:{_:{$:{aa:{ba:{ca:{da:{ea:{fa:{if(!(I&1)){break fa}y[l+4>>2]=h;gb=Kb(e+196|0,h);k=y[gb>>2];ga:{if((k|0)==y[gb+4>>2]){break ga}b=y[n+8>>2];d=(b|0)>0?b:0;I=1;h=0;g=0;while(1){if((d|0)==(g|0)){break ga}ha:{b=Ka(a,y[y[n+12>>2]+(g<<2)>>2]);if(z[b+4|0]&63){break ha}b=y[b+56>>2];i=y[b+24>>2];m=y[b+28>>2]-i>>2;b=0;while(1){if((b|0)==(m|0)){j=0;i=0;if(I&1){b=h+m|0;c=b>>>0>=m>>>0;j=c;i=c?b:0}h=i;I=j;break ha}c=b+h|0;if(!(c>>>0>=b>>>0&I)){break fa}y[k+(c<<2)>>2]=y[i+(b<<2)>>2];b=b+1|0;continue}}g=g+1|0;continue}}$a(ga,gb);c=1;ia:{if(z[l|0]!=1){break ia}b=u>>>2&3;if((b|0)==2){break X}d=u>>>4&3;if((d|0)==2){break X}ja:{ka:{switch(b|0){case 0:I=ra(a,4);b=0;break ja;case 1:I=ra(a,5);b=0;break ja;default:break ka}}b=Ga(a,n,0);if(!b){break X}I=y[b+68>>2];b=1}y[l+28>>2]=I;la:{ma:{switch(d|0){case 0:d=ra(a,2);break la;case 1:d=ra(a,3);break la;default:break ma}}d=Ga(a,n,b);if(!d){break X}b=b+1|0;d=y[d+68>>2]}y[l+32>>2]=d;na:{if(!(u&64)){d=ra(a,1);break na}d=Ga(a,n,b);if(!d){break X}b=b+1|0;d=y[d+68>>2]}y[l+36>>2]=d;if(z[l+1|0]!=1){break ia}oa:{if(!(u&128)){b=ra(a,1);break oa}b=Ga(a,n,b);if(!b){break X}b=y[b+68>>2]}y[l+40>>2]=b}O=z[l+1|0];hb=z[l|0];c=z[l+3|0];b=z[l+2|0];y[e+192>>2]=0;y[e+184>>2]=0;y[e+188>>2]=0;y[e+180>>2]=0;y[e+172>>2]=0;y[e+176>>2]=0;g=b?1024:8192;d=c?(c|0)==1?8192:1024:65536;pa:{if(!(!(u&256)|!q)){c=1;if(!(hb&1)){b=Jb(e+184|0,y[q+56>>2]);if((d|0)!=y[e+188>>2]-y[b>>2]>>1){break Y}}if(!(O&1)){break pa}b=Jb(e+172|0,y[q+56>>2]+12|0);if((g|0)==y[e+176>>2]-y[b>>2]>>1){break pa}break Y}if(!(hb&1)){fb(e+184|0,d)}if(!(O&1)){break pa}fb(e+172|0,g)}d=y[n+44>>2];k=d;c=y[n+40>>2];y[e+152>>2]=c;y[e+156>>2]=d;i=y[n+36>>2];y[e+160>>2]=i;y[n+52>>2]=2;qa:{if(!(c|d)|z[a+48|0]!=1){break qa}g=y[a+108>>2];b=g;while(1){b=y[b+4>>2];if((g|0)==(b|0)){break qa}if((c|0)!=y[b+8>>2]|(k|0)!=y[b+12>>2]|(i|0)!=y[b+16>>2]){continue}break}c=Dd(y[b+24>>2]);y[e+232>>2]=0;Ib(n+56|0,c);Va(e+232|0);Cd(y[a+108>>2],e+152|0,b+24|0);g=y[a+108>>2];d=y[b>>2];c=y[b+4>>2];y[d+4>>2]=c;y[c>>2]=d;y[g+8>>2]=y[g+8>>2]-1;pc(b);break _}if(!(hb&1)){v=Na(y[a+4>>2]);y[e+344>>2]=v;g=y[e+172>>2];c=y[e+176>>2];b=y[e+188>>2];Q=y[e+184>>2];qb=Da();y[e+428>>2]=qb;ca=Da();y[e+400>>2]=ca;L=Da();y[e+396>>2]=L;M=Da();y[e+392>>2]=M;aa=Q+810|0;da=Q+79434|0;ha=Q+3882|0;Ma=Q+458|0;Pa=g;ea=c-g>>1;Qa=Fd(e+232|0,ib(y[l+8>>2]+y[l+4>>2]|0));Ia=cb(e+416|0,y[l+8>>2]);R=b-Q>>1;Ta=R>>>0>405;Xa=R>>>0>39717;Ya=R>>>0>1941;rb=R>>>0>229;q=0;I=0;while(1){ra:{sa:{ta:{b=y[l+8>>2];if(b>>>0>I>>>0){y[e+388>>2]=0;Aa(qb,v,e+384|0);q=y[e+384>>2]+q|0;if(q>>>0<=65535){fa=0;while(1){if(!Aa(ca,v,e+380|0)){break ra}if(B[l+8>>2]<=I>>>0){break ta}fa=y[e+380>>2]+fa|0;if(fa>>>0>65535){break ta}if(!(!q|!fa)){ua:{va:{wa:{xa:{ya:{za:{Aa:{if(!z[l+1|0]){D=lb();y[e+376>>2]=D;y[D+8>>2]=q;y[D+4>>2]=fa;w[D|0]=0;b=z[l+3|0];w[D+1|0]=0;w[D+2|0]=0;w[D+3|0]=b;w[D+16|0]=z[l+44|0];w[D+17|0]=z[l+45|0];w[D+18|0]=z[l+46|0];w[D+19|0]=z[l+47|0];w[D+20|0]=z[l+48|0];w[D+21|0]=z[l+49|0];w[D+22|0]=z[l+50|0];w[D+23|0]=z[l+51|0];U=D+8|0;V=D+4|0;Ba:{switch(b|0){case 0:break Ba;case 1:break ya;case 2:break za;default:break Aa}}if(!Bd(D)){break xa}C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break wa}b=y[V>>2];if(!b){break fa}P=y[U>>2]&2147483647;b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;Ca:{Da:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break Da}if(z[v|0]){break wa}if(!Xa){break fa}g=0;k=ka(v,da);if((k|0)==(c|0)){break Da}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+40>>2]=y[e+328>>2];y[e+44>>2]=g;g=y[e+224>>2];y[e+32>>2]=y[e+220>>2];y[e+36>>2]=g;Ca(e+40|0,e+32|0);c=c^k;break Ca}y[e+212>>2]=0;break va}Ea:{Fa:{Ga:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r&240;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break wa}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c&16|i&63470);s=j<>>0>=R>>>0){break fa}k=h<<1;K=ka(v,k+Q|0);i=7-c|0;h=K|(j>>>i&16|k&63470);s=K<>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<6;h=r&240|J&14336;H=d+N|0;k=m;j=d;while(1){Ha:{K=J<<8;if((j|0)==(H|0)){break Ha}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<6;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break wa}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c&16|(J>>>c&2048|h&63470));s=K<>>0>=R>>>0){break fa}i=h<<1;c=ka(v,i+Q|0);j=7-s|0;G=c<>>j&16|(K>>>j&2048|i&63470));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break Ea}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}Aa(L,v,e+328|0);c=y[e+328>>2];if(c>>>0>=2){F=xa(6);y[e+220>>2]=F;u=xa(8);y[e+212>>2]=u;m=xa(11);y[e+376>>2]=m;h=xa(15);y[e+372>>2]=h;j=xa(15);y[e+368>>2]=j;k=xa(15);y[e+364>>2]=k;i=xa(15);y[e+360>>2]=i;g=xa(1);y[e+356>>2]=g;H=Qb();y[e+352>>2]=H;b=z[l|0];y[H+20>>2]=1;y[H+16>>2]=c;y[H+12>>2]=q;y[H+8>>2]=fa;w[H+1|0]=1;w[H|0]=b;b=y[l+4>>2]+I|0;y[H+24>>2]=b;K=Kb(e+404|0,b);b=y[l+4>>2];c=y[l+16>>2];if(b>>>0>y[l+20>>2]-c>>2>>>0){break fa}P=y[K>>2];zd(c,b,P,y[e+408>>2]-P>>2);d=y[Ia>>2];b=0;while(1)if((b|0)==(I|0)){$a(H+124|0,K);x[H+4>>1]=0;y[H+136>>2]=0;y[H+140>>2]=1;w[H+3|0]=0;y[H+172>>2]=g;y[H+168>>2]=i;y[H+164>>2]=k;y[H+160>>2]=j;y[H+156>>2]=h;y[H+152>>2]=m;y[H+148>>2]=u;y[H+144>>2]=F;w[H+2|0]=z[l+2|0];w[H+176|0]=z[l+52|0];w[H+177|0]=z[l+53|0];w[H+178|0]=z[l+54|0];w[H+179|0]=z[l+55|0];b=Ed(H,v,Pa,ea,Qa);y[e+348>>2]=0;oa(e+388|0,b);la(e+348|0);b=y[e+388>>2];va(K);qc(e+352|0);pa(e+356|0);pa(e+360|0);pa(e+364|0);pa(e+368|0);pa(e+372|0);pa(e+376|0);pa(e+212|0);pa(e+220|0);if(!b){break ta}break ua}else{c=b<<2;y[(c+P|0)+(y[l+4>>2]<<2)>>2]=y[c+d>>2];b=b+1|0;continue}}b=0;if((c|0)!=1){break ua}b=y[l+4>>2];Ac(y[Qa+36>>2],v,e+404|0);d=y[e+404>>2];if(d>>>0>=b+I>>>0){break ta}b=y[l+4>>2];if(b>>>0>d>>>0){b=y[ga>>2]+(d<<2)|0}else{b=d-b|0;c=y[Ia>>2];if(b>>>0>=y[Ia+4>>2]-c>>2>>>0){break fa}b=c+(b<<2)|0}c=y[b>>2];if(!c){break ta}Aa(y[Qa+28>>2],v,e+220|0);Aa(y[Qa+32>>2],v,e+212|0);d=kb();y[e+376>>2]=d;y[d+8>>2]=q;y[d+4>>2]=fa;b=z[l+2|0];y[d+20>>2]=c;w[d|0]=b;y[d+12>>2]=y[e+220>>2];b=y[e+212>>2];w[d+1|0]=0;y[d+16>>2]=b;w[d+24|0]=z[l+52|0];w[d+25|0]=z[l+53|0];w[d+26|0]=z[l+54|0];w[d+27|0]=z[l+55|0];b=jb(d,v,Pa,ea);y[e+372>>2]=0;oa(e+388|0,b);la(e+372|0);b=y[e+388>>2];Ua(e+376|0);if(!b){break ta}break ua}Ia:{Ja:{if(Hb(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break Ja}b=y[V>>2];if(!b){break fa}b=b-1|0;K=b>>>3|0;P=(b&7)+1|0;h=0;g=0;d=0;c=0;while(1){Ka:{La:{if(B[U>>2]>d>>>0){ja(e+404|0,C,d);j=y[e+408>>2];i=y[e+404>>2];if(z[D+1|0]!=1){k=c;break La}if(z[v|0]){break Ja}if(!Ta){break fa}k=0;m=ka(v,aa);if((m|0)==(c|0)){break La}y[e+332>>2]=j;y[e+328>>2]=i;y[e+224>>2]=h;y[e+220>>2]=g;b=y[e+332>>2];y[e+8>>2]=y[e+328>>2];y[e+12>>2]=b;b=y[e+224>>2];y[e>>2]=y[e+220>>2];y[e+4>>2]=b;Ca(e+8|0,e);c=c^m;break Ka}y[e+212>>2]=0;break Ia}c=0;r=0;Ma:{if(!d){Na:{while(1){h=7;s=0;if((r|0)==(K|0)){break Na}Oa:{while(1){if((h|0)>=0){if(z[v|0]){break Oa}if(c>>>0>=R>>>0){break fa}b=c<<1;g=ka(v,b+Q|0);c=g|b&1006;s=g<>>0>>0){break Ja}}h=0;s=0;while(1){if((h|0)==(P|0)){break Ma}if(z[v|0]){break Ja}if(c>>>0>=R>>>0){break fa}b=c<<1;g=ka(v,b+Q|0);c=g|b&1006;s=g<<7-h|s;h=h+1|0;continue}}if(!h){break fa}m=h-1|0;r=z[g|0];h=r>>>1&112;u=0;while(1){Pa:{if((u|0)!=(K|0)){if((m|0)==(u|0)){break fa}b=u+1|0;r=z[b+g|0]|r<<8;c=7;s=0;while(1){if((c|0)<0){break Pa}if(z[v|0]){break Ja}if(h>>>0>=R>>>0){break fa}h=h<<1;F=ka(v,h+Q|0);h=F|(r>>>c+1&16|h&1006);s=F<>>0>=R>>>0){break fa}b=h<<1;m=ka(v,b+Q|0);h=m|(g>>>8-c&16|b&1006);s=m<<7-c|s;c=c+1|0;continue}break}break Ma}if((j|0)==(u|0)){break fa}w[i+u|0]=s;u=b;continue}}if(j>>>0<=K>>>0){break fa}w[i+K|0]=s;c=k}d=d+1|0;h=j;g=i;continue}}C=ua(V,U);y[e+212>>2]=C;Qa:{Ra:{c=y[C+16>>2];if(!c){break Ra}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}b=0;u=0;c=0;_=0;while(1){Sa:{Ta:{if(B[U>>2]>_>>>0){ja(e+404|0,C,_);g=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){m=c;break Ta}if(z[v|0]){break Ra}if(!Ta){break fa}m=0;i=ka(v,aa);if((i|0)==(c|0)){break Ta}y[e+332>>2]=g;y[e+328>>2]=d;y[e+224>>2]=u;y[e+220>>2]=b;b=y[e+332>>2];y[e+24>>2]=y[e+328>>2];y[e+28>>2]=b;b=y[e+224>>2];y[e+16>>2]=y[e+220>>2];y[e+20>>2]=b;c=c^i;Ca(e+24|0,e+16|0);break Sa}y[e+212>>2]=0;break Qa}G=0;j=0;i=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],_);j=y[e+408>>2];i=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+_|0);P=y[e+408>>2];F=y[e+404>>2];h=y[C+20>>2];G=!u|(h|0)<2?G:z[b|0]>>>6&1;G=!u|(h|0)<=0?G:z[b|0]>>>6&2|G;r=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=m;break Sa}Ua:{Va:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,i,j)){break Va}}k=ia(h,w[D+16|0]+c|0,F,P);if(z[v|0]){break Ra}k=G<<5|k<<4|r;if(k>>>0>=R>>>0){break fa}k=ka(v,Q+(k<<1)|0);h=y[C+20>>2];if(!k){break Va}Ea(h,c,d,g,1);h=y[C+20>>2];k=1;break Ua}k=0}G=ia(h,c+2|0,b,u)|G<<1&30;c=c+1|0;r=r<<1&14|k;continue}}_=_+1|0;b=d;u=g;continue}}C=0}break va}C=0}break va}Wa:{Xa:{if(Hb(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break Xa}b=y[V>>2];if(!b){break fa}b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;P=y[U>>2];b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;Ya:{Za:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break Za}if(z[v|0]){break Xa}if(!rb){break fa}g=0;k=ka(v,Ma);if((k|0)==(c|0)){break Za}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+104>>2]=y[e+328>>2];y[e+108>>2]=g;g=y[e+224>>2];y[e+96>>2]=y[e+220>>2];y[e+100>>2]=g;Ca(e+104|0,e+96|0);c=c^k;break Ya}y[e+212>>2]=0;break Wa}_a:{$a:{ab:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r>>>3&28;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break Xa}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c+3&4|i&890);s=j<>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(k>>>10-c&4|i&890);s=j<<7-c|s;c=c+1|0;continue}}if(!h|!F|(b>>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<1;h=r>>>3&28|J&384;H=d+N|0;k=m;j=d;while(1){bb:{K=J<<8;if((j|0)==(H|0)){break bb}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<1;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break Xa}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c+3&4|(J>>>c&128|h&890));s=K<>>0>=R>>>0){break fa}k=h<<1;i=ka(v,k+Q|0);c=7-s|0;G=i<>>10-s&4|(K>>>c&128|k&890));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break _a}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}C=ua(V,U);y[e+212>>2]=C;cb:{db:{c=y[C+16>>2];if(!c){break db}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}i=0;d=0;g=0;b=0;c=0;S=0;while(1){eb:{fb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];j=y[e+404>>2];if(z[D+1|0]!=1){k=c;break fb}if(z[v|0]){break db}if(!rb){break fa}k=0;h=ka(v,Ma);if((h|0)==(c|0)){break fb}y[e+332>>2]=u;y[e+328>>2]=j;y[e+224>>2]=b;y[e+220>>2]=g;d=y[e+332>>2];y[e+120>>2]=y[e+328>>2];y[e+124>>2]=d;d=y[e+224>>2];y[e+112>>2]=y[e+220>>2];y[e+116>>2]=d;c=c^h;Ca(e+120|0,e+112|0);break eb}y[e+212>>2]=0;break cb}G=0;_=0;m=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];m=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];G=!d|(h|0)<2?G:z[i|0]>>>6&1;G=!d|(h|0)<=0?G:z[i|0]>>>6&2|G;r=0;r=!b|(h|0)<2?r:z[g|0]>>>6&1;r=!b|(h|0)<=0?r:z[g|0]>>>6&2|r;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=k;break eb}gb:{hb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,m,_)){break hb}}if(z[v|0]){break db}h=r<<3|G<<7|ia(h,w[D+16|0]+c|0,P,K)<<2|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break hb}Ea(h,c,j,u,1);h=y[C+20>>2];H=1;break gb}H=0}F=c+2|0;G=ia(h,F,i,d)|G<<1&6;r=ia(h,F,g,b)|r<<1&14;c=c+1|0;J=J<<1&2|H;continue}}S=S+1|0;i=g;d=b;g=j;b=u;continue}}C=0}break va}C=0}break va}ib:{jb:{if(yd(D)){C=ua(V,U);y[e+212>>2]=C;if(!y[C+16>>2]){break jb}b=y[V>>2];if(!b){break fa}b=b-1|0;N=b>>>3|0;W=(b&7)+1|0;P=y[U>>2];b=0;d=0;h=0;i=0;u=0;c=0;while(1){m=d;F=b;kb:{lb:{if((u|0)!=(P|0)){ja(e+404|0,C,u);b=y[e+408>>2];d=y[e+404>>2];if(z[D+1|0]!=1){g=c;break lb}if(z[v|0]){break jb}if(!Ya){break fa}g=0;k=ka(v,ha);if((k|0)==(c|0)){break lb}y[e+332>>2]=b;y[e+328>>2]=d;y[e+224>>2]=F;y[e+220>>2]=m;g=y[e+332>>2];y[e+72>>2]=y[e+328>>2];y[e+76>>2]=g;g=y[e+224>>2];y[e+64>>2]=y[e+220>>2];y[e+68>>2]=g;Ca(e+72|0,e- -64|0);c=c^k;break kb}y[e+212>>2]=0;break ib}mb:{nb:{ob:{if(u>>>0<=1){J=0;r=0;k=(u|0)!=1;if(!k){if(!F){break fa}r=z[m|0]}h=r>>>1&120;while(1){if((J|0)!=(N|0)){if(!k){c=J+1|0;if(c>>>0>=F>>>0){break fa}r=z[c+m|0]|r<<8}c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break jb}if(h>>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(r>>>c+1&8|i&7670);s=j<>>0>=R>>>0){break fa}i=h<<1;j=ka(v,i+Q|0);h=j|(k>>>8-c&8|i&7670);s=j<<7-c|s;c=c+1|0;continue}}if(!h|!F|(b>>>0>>0|N>>>0>F-1>>>0)|N>>>0>h-1>>>0){break fa}r=z[m|0];J=z[i|0]<<4;h=r>>>1&120|J&3584;H=d+N|0;k=m;j=d;while(1){pb:{K=J<<8;if((j|0)==(H|0)){break pb}k=k+1|0;r=z[k|0]|r<<8;i=i+1|0;J=K|z[i|0]<<4;c=7;s=0;while(1){if((c|0)>=0){if(z[v|0]){break jb}if(h>>>0>=R>>>0){break fa}h=h<<1;K=ka(v,h+Q|0);h=K|(r>>>c+1&8|(J>>>c&512|h&7670));s=K<>>0>=R>>>0){break fa}k=h<<1;i=ka(v,k+Q|0);c=7-s|0;G=i<>>8-s&8|(K>>>c&512|k&7670));s=s+1|0;continue}}if(b>>>0<=N>>>0){break fa}w[d+N|0]=s;break mb}if(b>>>0<=N>>>0){break fa}w[H|0]=G}c=g}u=u+1|0;h=F;i=m;continue}}C=ua(V,U);y[e+212>>2]=C;qb:{rb:{c=y[C+16>>2];if(!c){break rb}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}i=0;g=0;d=0;b=0;c=0;S=0;while(1){sb:{tb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];j=y[e+404>>2];if(z[D+1|0]!=1){k=c;break tb}if(z[v|0]){break rb}if(!Ya){break fa}k=0;h=ka(v,ha);if((h|0)==(c|0)){break tb}y[e+332>>2]=u;y[e+328>>2]=j;y[e+224>>2]=b;y[e+220>>2]=d;g=y[e+332>>2];y[e+88>>2]=y[e+328>>2];y[e+92>>2]=g;g=y[e+224>>2];y[e+80>>2]=y[e+220>>2];y[e+84>>2]=g;c=c^h;Ca(e+88|0,e+80|0);break sb}y[e+212>>2]=0;break qb}G=0;_=0;m=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];m=y[e+404>>2]}ja(e+404|0,C,w[D+17|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];G=!g|(h|0)<3?G:z[i|0]>>>5&1;G=!g|(h|0)<2?G:z[i|0]>>>5&2|G;G=!g|(h|0)<=0?G:z[i|0]>>>5&4|G;r=0;r=!b|(h|0)<3?r:z[d|0]>>>5&1;r=!b|(h|0)<2?r:z[d|0]>>>5&2|r;r=!b|(h|0)<=0?r:z[d|0]>>>5&4|r;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=k;break sb}ub:{vb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,m,_)){break vb}}if(z[v|0]){break rb}h=r<<4|G<<9|ia(h,w[D+16|0]+c|0,P,K)<<3|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break vb}Ea(h,c,j,u,1);h=y[C+20>>2];H=1;break ub}H=0}F=c+3|0;G=ia(h,F,i,g)|G<<1&14;r=ia(h,F,d,b)|r<<1&30;c=c+1|0;J=J<<1&6|H;continue}}S=S+1|0;i=d;g=b;d=j;b=u;continue}}C=0}break va}C=0}break va}C=ua(V,U);y[e+212>>2]=C;wb:{xb:{c=y[C+16>>2];if(!c){break xb}h=y[C+12>>2];while(1){if((c|0)>0){w[h|0]=0;c=c-1|0;h=h+1|0;continue}break}j=0;g=0;b=0;d=0;c=0;S=0;while(1){yb:{zb:{if(B[U>>2]>S>>>0){ja(e+404|0,C,S);u=y[e+408>>2];m=y[e+404>>2];if(z[D+1|0]!=1){i=c;break zb}if(z[v|0]){break xb}if(!Xa){break fa}i=0;k=ka(v,da);if((k|0)==(c|0)){break zb}y[e+332>>2]=u;y[e+328>>2]=m;y[e+224>>2]=d;y[e+220>>2]=b;g=y[e+332>>2];y[e+56>>2]=y[e+328>>2];y[e+60>>2]=g;g=y[e+224>>2];y[e+48>>2]=y[e+220>>2];y[e+52>>2]=g;c=c^k;Ca(e+56|0,e+48|0);break yb}y[e+212>>2]=0;break wb}r=0;k=0;_=0;if(z[D+2|0]==1){ja(e+404|0,y[D+12>>2],S);_=y[e+408>>2];k=y[e+404>>2]}c=e+404|0;ja(c,C,w[D+17|0]+S|0);X=y[e+408>>2];$=y[e+404>>2];ja(c,C,w[D+19|0]+S|0);s=y[e+408>>2];N=y[e+404>>2];ja(c,C,w[D+21|0]+S|0);W=y[e+408>>2];H=y[e+404>>2];ja(c,C,w[D+23|0]+S|0);K=y[e+408>>2];P=y[e+404>>2];h=y[C+20>>2];r=!g|(h|0)<2?r:z[j|0]>>>6&1;r=!g|(h|0)<=0?r:z[j|0]>>>6&2|r;G=0;G=!d|(h|0)<3?G:z[b|0]>>>5&1;G=!d|(h|0)<2?G:z[b|0]>>>5&2|G;G=!d|(h|0)<=0?G:z[b|0]>>>5&4|G;J=0;c=0;while(1){if(B[V>>2]<=c>>>0){c=i;break yb}Ab:{Bb:{if(z[D+2|0]==1){if(ia(y[y[D+12>>2]+20>>2],c,k,_)){break Bb}}if(z[v|0]){break xb}h=G<<5|r<<12|ia(h,w[D+16|0]+c|0,$,X)<<4|ia(h,w[D+18|0]+c|0,N,s)<<10|ia(h,w[D+20|0]+c|0,H,W)<<11|ia(h,w[D+22|0]+c|0,P,K)<<15|J;if(h>>>0>=R>>>0){break fa}F=ka(v,Q+(h<<1)|0);h=y[C+20>>2];if(!F){break Bb}Ea(h,c,m,u,1);h=y[C+20>>2];F=1;break Ab}F=0}r=ia(h,c+2|0,j,g)|r<<1&6;G=ia(h,c+3|0,b,d)|G<<1&30;c=c+1|0;J=J<<1&14|F;continue}}S=S+1|0;j=b;g=d;b=m;d=u;continue}}C=0}break va}C=0}la(e+212|0);y[e+404>>2]=0;oa(e+388|0,C);la(e+404|0);b=y[e+388>>2];ta(e+376|0);if(b){break ua}break ta}c=y[Ia>>2];y[e+388>>2]=0;oa((I<<2)+c|0,b)}I=I+1|0;continue}}break ta}y[e+412>>2]=0;y[e+404>>2]=0;y[e+408>>2]=0;xd(e+404|0,b+y[l+4>>2]|0);k=y[e+404>>2];I=0;b=0;s=0;Cb:{while(1){if(y[l+8>>2]+y[l+4>>2]>>>0>b>>>0){Aa(M,v,e+212|0);g=y[e+212>>2];c=g+b|0;if(!(c>>>0<=y[l+8>>2]+y[l+4>>2]>>>0&c>>>0>=g>>>0)){b=0;break Cb}y[e+328>>2]=k;y[e+332>>2]=0;d=oc(e+328|0,b);wd(e+220|0,y[d>>2],y[d+4>>2],g,I);s=(I&1?g:0)+s|0;I=I^1;b=c;continue}break}b=0;if(B[l+12>>2]>>0){break Cb}b=nc();i=y[Ia>>2];c=0;I=0;while(1){d=y[l+4>>2];if(d+y[l+8>>2]>>>0<=c>>>0){break Cb}if(!(!(y[k+(c>>>3&536870908)>>2]>>>c&1)|B[l+12>>2]<=I>>>0)){Db:{if(c>>>0>>0){d=y[y[ga>>2]+(c<<2)>>2];if(!d){d=0;break Db}d=mc(d);break Db}g=i+(c-d<<2)|0;d=y[g>>2];y[g>>2]=0}ud(b,d);I=I+1|0}c=c+1|0;continue}}lc(e+404|0);break sa}la(e+388|0);b=0}Za(Ia);rc(Qa);Ba(e+392|0);Ba(e+396|0);Ba(e+400|0);Ba(e+428|0);y[e+232>>2]=0;Ib(n+56|0,b);Va(e+232|0);if(y[n+56>>2]){sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(e+344|0);break $}ta(e+344|0);break Z}la(e+388|0);continue}}c=y[e+172>>2];b=y[e+176>>2];M=y[a+4>>2];X=td(M);y[e+428>>2]=X;$=cb(e+416|0,y[l+8>>2]);y[e+412>>2]=0;y[e+404>>2]=0;y[e+408>>2]=0;if(!z[l+1|0]){Rb(e+404|0,y[l+8>>2])}N=c;i=b-c>>1;y[e+400>>2]=0;y[e+396>>2]=0;g=0;q=0;Eb:{while(1){if(B[l+8>>2]>q>>>0){if(ya(X,y[l+28>>2],e+212|0)){break ba}g=y[e+212>>2]+g|0;y[e+400>>2]=g;if(g>>>0>65535){break ba}I=0;y[e+392>>2]=0;k=0;c=q;while(1){Fb:{b=ya(X,y[l+32>>2],e+388|0);y[e+384>>2]=b;Gb:{switch(b|0){case 0:break Gb;case 1:break Fb;default:break ca}}if(B[l+8>>2]<=c>>>0){break ca}I=y[e+388>>2]+I|0;if(I>>>0>65535){break ca}k=k+I|0;Hb:{if(!I|!g){break Hb}if(z[l+1|0]==1){if(ya(X,y[l+40>>2],e+380|0)){break ca}oa(e+396|0,0);d=y[e+380>>2];Ib:{if(d>>>0>=2){H=xa(6);y[e+376>>2]=H;K=xa(8);y[e+372>>2]=K;P=xa(11);y[e+368>>2]=P;ea=xa(15);y[e+364>>2]=ea;F=xa(15);y[e+360>>2]=F;u=xa(15);y[e+356>>2]=u;m=xa(15);y[e+352>>2]=m;j=xa(1);y[e+348>>2]=j;L=Qb();y[e+344>>2]=L;b=z[l|0];y[L+20>>2]=1;y[L+16>>2]=d;y[L+12>>2]=g;y[L+8>>2]=I;w[L+1|0]=1;w[L|0]=b;b=y[l+4>>2];h=b+c|0;y[L+24>>2]=h;d=ib(b+y[l+8>>2]|0);y[e+336>>2]=0;y[e+328>>2]=0;y[e+332>>2]=0;b=e+328|0;y[e+232>>2]=b;if(h){d=d>>>0<=1?1:d;Kd(b,h);b=y[e+332>>2];h=h+b|0;while(1){if((b|0)!=(h|0)){w[b|0]=d;b=b+1|0;continue}break}y[e+332>>2]=h}w[e+236|0]=1;d=e+232|0;gc(d);b=y[e+328>>2];b=tc(d,b,y[e+332>>2]-b|0);Id(L+28|0,b);Oa(b);s=Kb(d,y[L+24>>2]);b=y[l+4>>2];d=y[l+16>>2];if(b>>>0>y[l+20>>2]-d>>2>>>0){break ea}W=y[s>>2];zd(d,b,W,y[e+236>>2]-W>>2);h=y[$>>2];b=0;while(1)if((b|0)==(c|0)){$a(L+124|0,s);x[L+4>>1]=0;y[L+136>>2]=0;y[L+140>>2]=1;w[L+3|0]=0;y[L+172>>2]=j;y[L+168>>2]=m;y[L+164>>2]=u;y[L+160>>2]=F;y[L+156>>2]=ea;y[L+152>>2]=P;y[L+148>>2]=K;y[L+144>>2]=H;w[L+2|0]=z[l+2|0];w[L+176|0]=z[l+52|0];w[L+177|0]=z[l+53|0];w[L+178|0]=z[l+54|0];w[L+179|0]=z[l+55|0];b=Gd(L,M,N,i);y[e+228>>2]=0;oa(e+396|0,b);la(e+228|0);b=y[e+396>>2];va(s);va(e+328|0);qc(e+344|0);pa(e+348|0);pa(e+352|0);pa(e+356|0);pa(e+360|0);pa(e+364|0);pa(e+368|0);pa(e+372|0);pa(e+376|0);if(b){break Ib}break ca}else{d=b<<2;y[(d+W|0)+(y[l+4>>2]<<2)>>2]=y[d+h>>2];b=b+1|0;continue}}if((d|0)!=1){h=y[$>>2];b=y[e+396>>2];break Ib}d=y[l+4>>2];b=y[l+8>>2];y[e+232>>2]=0;b=ib(b+d|0);if(Fa(M,b>>>0<=1?1:b,e+232|0)){break ca}d=y[e+232>>2];b=y[l+4>>2];if(d>>>0>=b+c>>>0){break ca}h=y[$>>2];if(b>>>0>d>>>0){b=y[ga>>2]+(d<<2)|0}else{b=d-b|0;if(b>>>0>=y[$+4>>2]-h>>2>>>0){break ea}b=(b<<2)+h|0}d=y[b>>2];if(!d){break ca}j=xa(15);y[e+328>>2]=j;b=xa(1);y[e+376>>2]=b;Jb:{Kb:{if(ya(X,j,e+372|0)){break Kb}if(ya(X,j,e+368|0)){break Kb}if(!ya(X,b,e+384|0)){break Jb}}y[e+392>>2]=k;pa(e+376|0);pa(e+328|0);break ba}sa(M);m=y[M+8>>2];j=kb();y[e+364>>2]=j;y[j+8>>2]=g;y[j+4>>2]=I;b=z[l+2|0];y[j+20>>2]=d;w[j|0]=b;y[j+12>>2]=y[e+372>>2];b=y[e+368>>2];d=0;w[j+1|0]=0;y[j+16>>2]=b;w[j+24|0]=z[l+52|0];w[j+25|0]=z[l+53|0];w[j+26|0]=z[l+54|0];w[j+27|0]=z[l+55|0];b=Na(M);y[e+360>>2]=b;b=jb(j,b,N,i);y[e+356>>2]=0;oa(e+396|0,b);la(e+356|0);b=y[e+396>>2];if(b){sa(M);d=y[M+8>>2];if(d>>>0<=4294967293){j=d+2|0;d=y[M+4>>2];d=d>>>0>j>>>0?j:d;y[M+8>>2]=d}d=y[e+384>>2]==(d-m|0)}ta(e+360|0);Ua(e+364|0);pa(e+376|0);pa(e+328|0);if(!d){break ca}}y[e+396>>2]=0;oa((c<<2)+h|0,b);if(z[l+1|0]){break Hb}}y[y[e+404>>2]+(c<<2)>>2]=I}c=c+1|0;continue}break}y[e+392>>2]=k;if(!z[l+1|0]){if(ya(X,y[l+36>>2],e+232|0)){break ba}sa(M);y[e+328>>2]=0;Lb:{if(!y[e+232>>2]){if((k|0)>65535){break Eb}j=k+7>>>3|0;b=ze(j,0,g,0);if(Z){break Eb}k=y[M+4>>2];d=y[M+8>>2];if(k>>>0>>0){break fa}if(b>>>0>k-d>>>0){break Eb}I=0;b=ua(e+392|0,e+400|0);y[e+388>>2]=0;oa(e+328|0,b);la(e+388|0);s=y[e+328>>2];while(1){if((g|0)==(I|0)){break Lb}ja(e+220|0,s,I);d=y[M+8>>2];b=y[M+4>>2];if(d>>>0>b>>>0|j>>>0>b-d>>>0|j>>>0>B[e+224>>2]){break fa}if(j){if(j){p(y[e+220>>2],y[M>>2]+d|0,j)}d=y[M+8>>2]}b=d;d=d+j|0;if(b>>>0<=d>>>0){b=y[M+4>>2];y[M+8>>2]=b>>>0>d>>>0?d:b}I=I+1|0;continue}}b=lb();y[e+388>>2]=b;y[b+8>>2]=g;y[b+4>>2]=k;w[b|0]=1;db(b,e+328|0,M);sa(M);ta(e+388|0);s=y[e+328>>2]}Mb:{if(!s){break Mb}u=c>>>0>>0?q:c;m=y[$>>2];b=0;h=y[e+404>>2];while(1){if((q|0)==(u|0)){break Mb}j=q<<2;k=j+h|0;d=nb(s,b,0,y[k>>2],g);y[e+388>>2]=0;oa(j+m|0,d);q=q+1|0;la(e+388|0);b=y[k>>2]+b|0;continue}}la(e+328|0)}q=c;continue}break}i=xa(1);y[e+392>>2]=i;y[e+240>>2]=0;y[e+232>>2]=0;y[e+236>>2]=0;xd(e+232|0,y[l+8>>2]+y[l+4>>2]|0);k=y[e+232>>2];c=0;b=0;h=0;while(1){if(y[l+8>>2]+y[l+4>>2]>>>0>b>>>0){Nb:{Ob:{if(ya(X,i,e+388|0)){break Ob}j=y[e+388>>2];d=j+b|0;if(d>>>0>>0){break Ob}if(d>>>0<=y[l+8>>2]+y[l+4>>2]>>>0){break Nb}}b=0;break da}y[e+328>>2]=k;y[e+332>>2]=0;g=oc(e+328|0,b);wd(e+212|0,y[g>>2],y[g+4>>2],j,c);h=(c&1?j:0)+h|0;c=c^1;b=d;continue}break}b=0;if(B[l+12>>2]>>0){break da}b=nc();i=y[$>>2];c=0;I=0;while(1){d=y[l+4>>2];if(d+y[l+8>>2]>>>0<=c>>>0){break da}if(!(!(y[k+(c>>>3&536870908)>>2]>>>c&1)|B[l+12>>2]<=I>>>0)){Pb:{if(c>>>0>>0){d=y[y[ga>>2]+(c<<2)>>2];if(!d){d=0;break Pb}d=mc(d);break Pb}g=i+(c-d<<2)|0;d=y[g>>2];y[g>>2]=0}ud(b,d);I=I+1|0}c=c+1|0;continue}}la(e+328|0);break ba}o()}y[e+392>>2]=k;o()}lc(e+232|0);pa(e+392|0);break aa}y[e+392>>2]=k}b=0}la(e+396|0);va(e+404|0);Za($);Ua(e+428|0);y[e+232>>2]=0;Ib(n+56|0,b);Va(e+232|0);if(!y[n+56>>2]){break Z}sa(y[a+4>>2])}if(z[a+48|0]!=1){break _}tb=e,ub=Dd(y[n+56>>2]),y[tb+232>>2]=ub;b=y[a+108>>2];c=y[b+8>>2];while(1){if(c>>>0>=2){i=y[b>>2];g=y[i>>2];d=y[i+4>>2];y[g+4>>2]=d;y[d>>2]=g;y[b+8>>2]=y[b+8>>2]-1;pc(i);c=c-1|0;b=y[a+108>>2];continue}break}c=b;b=e+232|0;Cd(c,e+152|0,b);Va(b)}c=0;if(!(z[e+211|0]&2)){break Y}if(!(hb&1)){g=y[n+56>>2];y[e+140>>2]=y[e+184>>2];d=y[e+188>>2];b=y[e+192>>2];y[e+188>>2]=0;y[e+192>>2]=0;y[e+144>>2]=d;y[e+148>>2]=b;y[e+184>>2]=0;b=e+140|0;_a(g,b);Ha(b)}if(!(O&1)){break Y}g=y[n+56>>2];y[e+128>>2]=y[e+172>>2];d=y[e+176>>2];b=y[e+180>>2];y[e+176>>2]=0;y[e+180>>2]=0;y[e+132>>2]=d;y[e+136>>2]=b;y[e+172>>2]=0;b=e+128|0;_a(g+12|0,b);Ha(b);break Y}c=1}Ha(e+172|0);Ha(e+184|0)}va(gb);break U}else{c=Ka(a,y[y[n+12>>2]+(b<<2)>>2]);if(!(z[c+4|0]&63)){j=0;i=0;if(I&1){d=y[c+56>>2];g=y[d+28>>2]-y[d+24>>2]>>2;d=g+h|0;i=d;d=d>>>0>=g>>>0;i=d?i:0}else{d=j}I=d;q=c;h=i}b=b+1|0;continue}}d=b<<2;b=b+1|0;if(Ka(a,y[d+y[n+12>>2]>>2])){continue}break}}va(ga);ma(l)}Y=e+432|0;b=c;break u;case 1:case 2:case 3:case 5:break B;case 4:case 6:case 7:break T;default:break G}}b=1;if(z[a+49|0]!=1){break u}if(ob(a,f+120|0)){break u}i=f+142|0;if(Sa(y[a+4>>2],i)){break u}c=y[f+120>>2];if(c-1>>>0>65534){break u}u=y[f+124>>2];if(u-1>>>0>65534){break u}h=Qb();y[f+116>>2]=h;y[h+12>>2]=u;y[h+8>>2]=c;k=A[f+142>>1];g=k&1;w[h|0]=g;w[h+2|0]=k>>>15;y[h+140>>2]=k>>>4&3;y[h+136>>2]=k>>>7&3;w[h+3|0]=k>>>6&1;w[h+4|0]=k>>>9&1;y[h+20>>2]=1<<(k>>>2&3);d=(k&254)>>>1&1;w[h+1|0]=d;c=k>>>10&31;w[h+5|0]=(c>>>0>15?-32:0)|c;Qb:{if(g){if(Sa(y[a+4>>2],i)){break v}if(z[h+1|0]!=1){break w}if(!z[h+2|0]){break Qb}break w}if(!d|k<<16>>16<0){break w}}g=h+176|0;c=0;while(1){if((c|0)==4){break w}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}break v}h=na(12);y[h+8>>2]=0;y[h>>2]=0;y[h+4>>2]=0;if(wa(y[a+4>>2],f+292|0)){break y}b=1;if(wa(y[a+4>>2],h+1|0)){break x}if(wa(y[a+4>>2],h+2|0)){break x}if(qa(y[a+4>>2],h+4|0)){break x}j=y[h+4>>2];if(j>>>0>65535){break x}c=z[f+292|0];b=c&1;w[h|0]=b;k=c>>>1&3;w[h+8|0]=k;y[n+52>>2]=3;if(b){b=y[a+4>>2];c=xc(h);y[f+160>>2]=c;if(c){y[f+8>>2]=0;db(c,f+8|0,b);Rb:{i=y[f+8>>2];if(!i){break Rb}g=wc(j+1|0);b=0;while(1){if(b>>>0>j>>>0){break Rb}c=z[h+1|0];d=nb(i,E(c,b),0,c,z[h+2|0]);c=y[g+4>>2];y[f+256>>2]=0;oa((b<<2)+c|0,d);b=b+1|0;la(f+256|0);continue}}la(f+8|0)}ta(f+160|0);y[f+8>>2]=0;Pb(n+60|0,g);Sb(f+8|0);if(!y[n+60>>2]){break y}sa(y[a+4>>2]);b=0;break x}mb(f+8|0,k?(k|0)==1?8192:1024:65536);d=Na(y[a+4>>2]);y[f+104>>2]=d;b=y[f+8>>2];c=y[f+12>>2];m=xc(h);y[f+256>>2]=m;if(!m){break z}w[m+1|0]=0;w[m+2|0]=0;w[m+3|0]=k;i=z[h+1|0];w[m+17|0]=0;w[m+16|0]=0-i;if(!k){x[m+22>>1]=65278;x[m+18>>1]=65533;x[m+20>>1]=65026}y[f+120>>2]=0;y[f+176>>2]=0;y[f+168>>2]=b;y[f+172>>2]=c;y[f+164>>2]=d;y[f+160>>2]=f+120;b=Ob(m,f+160|0);y[f+176>>2]=0;while(1){if((b|0)==3){b=Nb(m,f+160|0);continue}break}y[m+44>>2]=0;y[m+48>>2]=0;y[m+36>>2]=0;y[m+40>>2]=0;y[m+28>>2]=0;y[m+32>>2]=0;if(!y[f+120>>2]){break A}g=wc(j+1|0);b=0;while(1){if(b>>>0>j>>>0){break A}d=nb(y[f+120>>2],E(b,i),0,i,z[h+2|0]);c=y[g+4>>2];y[f+148>>2]=0;oa((b<<2)+c|0,d);b=b+1|0;la(f+148|0);continue}}b=1;if(z[a+49|0]!=1){break u}O=na(56);y[O+16>>2]=0;Sb:{if(ob(a,f+256|0)){break Sb}if(wa(y[a+4>>2],f+144|0)){break Sb}q=O+32|0;if(qa(y[a+4>>2],q)){break Sb}h=O+36|0;if(qa(y[a+4>>2],h)){break Sb}if(qa(y[a+4>>2],O+40|0)){break Sb}if(qa(y[a+4>>2],O+44|0)){break Sb}if(Sa(y[a+4>>2],O+48|0)){break Sb}if(Sa(y[a+4>>2],O+50|0)){break Sb}H=y[q>>2];if(H-1>>>0>65534){break Sb}K=y[h>>2];if(K-1>>>0>65534){break Sb}k=y[f+256>>2];if(k-1>>>0>65534){break Sb}X=y[f+260>>2];if(X-1>>>0>65534){break Sb}y[O+4>>2]=X;y[O>>2]=k;g=z[f+144|0];ha=g&1;w[O+8|0]=ha;w[O+20|0]=g>>>7;j=g>>>3|0;Xa=j&1;w[O+28|0]=Xa;aa=g>>>1&3;w[O+9|0]=aa;d=g>>>4|0;y[O+24>>2]=(d&7)==4?4:d&3;if(y[n+8>>2]!=1){break Sb}d=Ka(a,y[y[n+12>>2]>>2]);if(!d|(z[d+4|0]&63)!=16){break Sb}d=y[d+60>>2];if(!d){break Sb}da=y[d>>2];if(!da){break Sb}y[O+12>>2]=da;y[O+16>>2]=d+4;b=y[y[d+4>>2]>>2];i=y[b+20>>2];w[O+52|0]=i;d=y[b+24>>2];w[O+53|0]=d;y[n+52>>2]=1;Tb:{Ub:{Vb:{if(g&1){j=y[a+4>>2];b=1;while(1){c=b;b=b+1|0;if(da>>>0>1<>>0){continue}break}x[f+216>>1]=0;y[f+172>>2]=0;y[f+184>>2]=0;y[f+188>>2]=0;y[f+192>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+204>>2]=0;y[f+208>>2]=0;y[f+220>>2]=0;y[f+224>>2]=0;y[f+228>>2]=0;y[f+232>>2]=0;y[f+236>>2]=0;y[f+168>>2]=K;y[f+164>>2]=H;w[f+160|0]=ha;d=c&255;i=cb(f+8|0,d);g=y[i>>2];b=(g+(d<<2)|0)-4|0;db(f+160|0,b,j);c=0;Wb:{if(!y[b>>2]){break Wb}sa(j);b=y[j+8>>2];if(b>>>0<=4294967292){c=b+3|0;b=y[j+4>>2];y[j+8>>2]=b>>>0>c>>>0?c:b}b=d-2|0;while(1){if((b|0)>=0){k=g+(b<<2)|0;db(f+160|0,k,j);c=0;if(!y[k>>2]){break Wb}sa(j);c=y[j+8>>2];if(c>>>0<=4294967292){d=c+3|0;c=y[j+4>>2];y[j+8>>2]=c>>>0>d>>>0?d:c}bb(y[k>>2],0,0,0,0,y[k+4>>2],2);b=b-1|0;continue}break}c=vc(O,i)}Za(i);y[f+120>>2]=0;oa(n- -64|0,c);la(f+120|0);if(!y[n+64>>2]){break Tb}sa(y[a+4>>2]);break Vb}mb(f+148|0,aa?(aa|0)==1?8192:1024:65536);Ya=Na(y[a+4>>2]);y[f+300>>2]=Ya;s=y[f+148>>2];rb=y[f+152>>2];g=0;y[f+104>>2]=0;Xb:{if(!(j&1)){break Xb}b=ua(q,h);y[f+160>>2]=0;oa(f+104|0,b);b=d&255;N=1-b|0;Ma=0-(b>>>0>1)|0;b=i&255;W=1-b|0;Pa=0-(b>>>0>1)|0;la(f+160|0);h=X;g=y[f+104>>2];Yb:while(1){if(!c&(m|0)==(K|0)){break Xb}ja(f+160|0,g,m);b=y[O+44>>2];P=A[O+48>>1];d=ze(m,c,P,0);$=b+d|0;b=Z+(b>>31)|0;ea=d>>>0>$>>>0?b+1|0:b;d=y[O+40>>2];I=A[O+50>>1];b=ze(m,c,I,0);F=d+b|0;d=Z+(d>>31)|0;l=b>>>0>F>>>0?d+1|0:d;j=0;i=0;q=y[f+164>>2];u=y[f+160>>2];while(1)if(!i&(j|0)==(H|0)){b=c;d=m+1|0;b=d?b:b+1|0;m=d;c=b;continue Yb}else{qb=y[g+20>>2];b=ze(j,i,I,0);d=$-b|0;M=ea-(Z+(b>>>0>$>>>0)|0)|0;b=M>>8;ca=(M&255)<<24|d>>>8;d=ca;L=(b|0)>=0&d>>>0>=h>>>0|(b|0)>0;M=ze(j,i,P,0);Ta=M+F|0;d=Z+l|0;d=M>>>0>Ta>>>0?d+1|0:d;M=d>>8;d=(d&255)<<24|Ta>>>8;ca=(M|0)<=(Pa|0)&W>>>0>d>>>0|(M|0)<(Pa|0)|((b|0)<=(Ma|0)&N>>>0>ca>>>0|(b|0)<(Ma|0));b=M;if((b|0)>0){b=1}else{b=(b|0)>=0&d>>>0>=k>>>0}Ea(qb,j,u,q,ca|b|L);j=j+1|0;i=j?i:i+1|0;continue}}}b=1;while(1){c=b;b=b+1|0;if(da>>>0>1<>>0){continue}break}y[f+184>>2]=0;y[f+188>>2]=0;x[f+216>>1]=0;y[f+192>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+204>>2]=0;y[f+208>>2]=0;y[f+220>>2]=0;y[f+224>>2]=0;y[f+228>>2]=0;y[f+232>>2]=0;y[f+236>>2]=0;y[f+168>>2]=K;y[f+164>>2]=H;w[f+160|0]=ha;y[f+172>>2]=g;w[f+162|0]=Xa;w[f+161|0]=0;w[f+163|0]=aa;Zb:{if(aa>>>0>=2){x[f+176>>1]=65282;break Zb}x[f+176>>1]=65283;if(aa){break Zb}x[f+182>>1]=65278;x[f+178>>1]=65533;x[f+180>>1]=65026}b=c&255;k=cb(f+120|0,b);g=y[k>>2];c=b-1|0;d=c;_b:{$b:{while(1){if((d|0)>=0){y[f+292>>2]=0;y[f+24>>2]=0;y[f+16>>2]=s;y[f+20>>2]=rb;y[f+12>>2]=Ya;y[f+8>>2]=f+292;b=Ob(f+160|0,f+8|0);y[f+24>>2]=0;while(1){if((b|0)==3){b=Nb(f+160|0,f+8|0);continue}break}y[f+204>>2]=0;y[f+208>>2]=0;y[f+196>>2]=0;y[f+200>>2]=0;y[f+188>>2]=0;y[f+192>>2]=0;b=y[f+292>>2];if(!b){break $b}y[f+292>>2]=0;i=g+(d<<2)|0;oa(i,b);if((c|0)>(d|0)){bb(y[i>>2],0,0,0,0,y[i+4>>2],2)}d=d-1|0;la(f+292|0);continue}break}b=vc(O,k);break _b}la(f+292|0);b=0}Za(k);la(f+104|0);y[f+160>>2]=0;oa(n- -64|0,b);la(f+160|0);if(!y[n+64>>2]){break Ub}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(f+300|0);Ja(f+148|0)}if((z[n+4|0]&63)==20){b=0;break Sb}ac:{if(z[a+50|0]){break ac}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break ac}b=X+y[f+268>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break ac}ab(c,b,z[d+16|0])}b=y[f+264>>2];c=b;g=b>>31;b=y[f+268>>2];d=b;i=b>>31;b=z[f+272|0];bb(y[a+32>>2],c,g,d,i,y[n+64>>2],(b&7)==4?4:b&3);b=0;oa(n- -64|0,0);break Sb}ta(f+300|0);Ja(f+148|0)}b=1}ma(O);break u}b=1;if(z[a+49|0]!=1){break u}k=a+76|0;c=y[a+76>>2];if(!c){i=lb();y[f+160>>2]=i;bc:{cc:{if(ob(a,a+88|0)){break cc}if(wa(y[a+4>>2],f+8|0)){break cc}d=y[a+92>>2];if((d|0)<0){break cc}c=y[a+88>>2];if((c|0)<0){break cc}y[i+8>>2]=d;y[i+4>>2]=c;g=z[f+8|0];c=g&1;w[i|0]=c;d=g>>>1&3;w[i+3|0]=d;w[i+1|0]=g>>>3&1;if(c){break bc}g=i+16|0;c=0;if(!d){while(1){if((c|0)==8){break bc}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break cc}}while(1){if((c|0)==2){break bc}d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}}ta(f+160|0);break u}w[i+2|0]=0;y[f+160>>2]=0;Ra(k,i);ta(f+160|0);c=y[k>>2]}y[n+52>>2]=1;dc:{if(z[c|0]==1){db(c,n- -64|0,y[a+4>>2]);if(!y[n+64>>2]){Ra(k,0);break M}sa(y[a+4>>2]);break dc}if(y[a+60>>2]==y[a+64>>2]){b=z[c+3|0];fb(a+60|0,b?(b|0)==1?8192:1024:65536)}i=a+72|0;d=y[a+72>>2];if(d){b=d}else{c=Na(y[a+4>>2]);y[f+160>>2]=0;Ra(i,c);ta(f+160|0);b=y[a+72>>2]}y[f+164>>2]=b;g=n- -64|0;y[f+160>>2]=g;b=y[a+64>>2];c=y[a+60>>2];y[f+176>>2]=0;y[f+168>>2]=c;y[f+172>>2]=b-c>>1;b=y[a+76>>2];ec:{if(!d){b=Ob(b,f+160|0);break ec}b=Nb(b,f+160|0)}y[a+56>>2]=b;if((b|0)==3){if((z[n+4|0]&63)==36){break B}fc:{if(z[a+50|0]){break fc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break fc}b=y[a+92>>2]+y[a+100>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break fc}ab(c,b,z[d+16|0])}c=y[a+76>>2];b=y[c+64>>2]+y[a+96>>2]|0;d=b;j=b>>31;b=y[c+68>>2]+y[a+100>>2]|0;i=b;h=b>>31;b=z[a+104|0];uc(y[a+32>>2],d,j,i,h,y[g>>2],c- -64|0,(b&7)==4?4:b&3);break B}b=y[a+76>>2];y[b+44>>2]=0;y[b+48>>2]=0;y[b+36>>2]=0;y[b+40>>2]=0;y[b+28>>2]=0;y[b+32>>2]=0;Ra(i,0);y[a+64>>2]=y[a+60>>2];if(!y[g>>2]){y[a+56>>2]=-1;Ra(k,0);break M}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0>4294967293){break dc}c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}if((z[n+4|0]&63)!=36){gc:{if(z[a+50|0]){break gc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break gc}b=y[a+92>>2]+y[a+100>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break gc}ab(c,b,z[d+16|0])}c=y[a+76>>2];b=y[c+64>>2]+y[a+96>>2]|0;d=b;i=b>>31;b=y[c+68>>2]+y[a+100>>2]|0;g=b;j=b>>31;b=z[a+104|0];uc(y[a+32>>2],d,i,g,j,y[n+64>>2],c- -64|0,(b&7)==4?4:b&3);oa(n- -64|0,0)}b=0;Ra(k,0);break u}b=1;if(z[a+49|0]!=1){break u}if(ob(a,f+160|0)){break u}if(wa(y[a+4>>2],f+292|0)){break u}i=y[f+160>>2];if(i-1>>>0>65534){break u}k=y[f+164>>2];if(k-1>>>0>65534){break u}y[f+256>>2]=0;j=kb();y[f+120>>2]=j;y[j+8>>2]=k;y[j+4>>2]=i;d=z[f+292|0];b=d&1;w[j|0]=b;w[j+1|0]=d>>>1&1;hc:{ic:{if(b){break ic}g=j+24|0;while(1){if((c|0)==4){break ic}b=1;d=c+g|0;c=c+1|0;if(!wa(y[a+4>>2],d)){continue}break}break hc}g=y[n+8>>2];jc:{if((g|0)>0){c=0;b=1;while(1){if((c|0)==(g|0)){break hc}i=Ka(a,y[y[n+12>>2]>>2]);if(!i){break hc}d=z[i+4|0];d=(d<<6|d>>>2)&207;if(!(1<>>0<=10:0)){c=c+1|0;continue}break}b=i- -64|0;break jc}c=nb(y[a+32>>2],y[f+168>>2],y[f+172>>2],i,k);y[f+8>>2]=0;b=f+256|0;oa(b,c);la(f+8|0)}b=y[b>>2];y[j+12>>2]=0;y[j+16>>2]=0;y[j+20>>2]=b;mb(f+8|0,z[j|0]?1024:8192);c=Na(y[a+4>>2]);y[f+148>>2]=c;y[n+52>>2]=1;b=jb(j,c,y[f+8>>2],y[f+12>>2]);y[f+104>>2]=0;g=n- -64|0;oa(g,b);la(f+104|0);b=1;kc:{if(!y[n+64>>2]){break kc}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}b=0;if((z[n+4|0]&63)==40){break kc}lc:{if(z[a+50|0]){break lc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break lc}b=k+y[f+172>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break lc}ab(c,b,z[d+16|0])}b=y[f+168>>2];c=b;i=b>>31;b=y[f+172>>2];d=b;j=b>>31;b=z[f+176|0];bb(y[a+32>>2],c,i,d,j,y[g>>2],(b&7)==4?4:b&3);oa(g,0);b=0}ta(f+148|0);Ja(f+8|0)}Ua(f+120|0);la(f+256|0);break u}j=na(20);y[j+16>>2]=0;y[j+8>>2]=0;y[j+12>>2]=0;y[j>>2]=0;y[j+4>>2]=0;y[f+160>>2]=j;mc:{if(qa(y[a+4>>2],j)){break mc}if(qa(y[a+4>>2],j+4|0)){break mc}if(qa(y[a+4>>2],j+8|0)){break mc}if(qa(y[a+4>>2],j+12|0)){break mc}if(wa(y[a+4>>2],f+148|0)){break mc}if(Sa(y[a+4>>2],f+120|0)){break mc}w[j+16|0]=z[f+148|0]>>>2&1;d=A[f+120>>1];b=d&32767;x[j+18>>1]=b;w[j+17|0]=d>>>15;c=y[j+4>>2];nc:{oc:{if((c|0)==-1){if(d<<16>>16>=0){w[j+17|0]=1}if(z[a+50|0]){break nc}c=b;break oc}if(z[a+50|0]){break nc}}y[f+8>>2]=c;b=ua(j,f+8|0);y[f+256>>2]=0;oa(a+32|0,b);la(f+256|0)}c=y[a+32>>2];b=y[c+16>>2];if(b){break L}y[a+56>>2]=-1}Ub(f+160|0)}b=1;break u}Mb(y[c+12>>2],b,z[j+16|0]);b=y[a+24>>2];c=y[a+28>>2];pc:{if(b>>>0>>0){y[f+160>>2]=0;y[b>>2]=j;b=b+4|0;break pc}d=y[a+20>>2];b=b-d|0;k=b>>2;i=k+1|0;if(i>>>0>=1073741824){break F}g=c-d|0;c=g>>1;i=g>>>0>=2147483644?1073741823:c>>>0>i>>>0?c:i;qc:{if(!i){g=b;h=0;break qc}if(i>>>0>=1073741824){break E}h=na(i<<2);d=y[a+20>>2];g=y[a+24>>2]-d|0;k=g>>2}y[f+160>>2]=0;b=b+h|0;y[b>>2]=j;c=b-(k<<2)|0;if(g){p(c,d,g)}y[a+28>>2]=(i<<2)+h;b=b+4|0;y[a+24>>2]=b;y[a+20>>2]=c;if(!d){break pc}ma(d)}w[a+49|0]=1;y[a+24>>2]=b;Ub(f+160|0);break B}w[a+49|0]=0;break u}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}y[n+52>>2]=4;k=n+68|0;pb(k,0);m=y[a+4>>2];q=na(96);x[q>>1]=0;t(q+4|0,0,92);if((wa(m,f+120|0)|0)==-1){break D}b=1;d=z[f+120|0];w[q+1|0]=d&1;rc:{sc:{if((qa(m,f+8|0)|0)==-1){break sc}if((qa(m,f+256|0)|0)==-1){break sc}c=y[f+8>>2];j=y[f+256>>2];if((c|0)>(j|0)){break sc}i=(d>>>4&7)+1|0;h=(d>>>1&7)+1|0;Lb(q,0);y[f+164>>2]=c;w[f+160|0]=1;b=c;while(1){if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}if((Fa(m,i,y[q+20>>2]+(y[q+4>>2]<<2)|0)|0)==-1){break rc}g=y[q+20>>2];d=y[q+4>>2]<<2;if(B[g+d>>2]>63){break rc}y[d+y[q+32>>2]>>2]=b;b=y[g+(y[q+4>>2]<<2)>>2];if((b|0)>31){break rc}if(z[La(f+160|0,1<>2];if((j|0)>(b|0)){continue}break}if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[y[q+20>>2]+(y[q+4>>2]<<2)>>2]=32;if((c|0)==-2147483648){break rc}y[y[q+32>>2]+(y[q+4>>2]<<2)>>2]=c-1;Lb(q,1);if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[y[q+20>>2]+(y[q+4>>2]<<2)>>2]=32;y[y[q+32>>2]+(y[q+4>>2]<<2)>>2]=j;Lb(q,1);if(z[q+1|0]==1){if((Fa(m,h,y[q+8>>2]+(y[q+4>>2]<<3)|0)|0)==-1){break rc}y[q+4>>2]=y[q+4>>2]+1}c=Ld(q);w[q|0]=c;y[f+160>>2]=q;b=1;if(!c){break C}b=0;y[f+160>>2]=0;pb(k,q);sa(y[a+4>>2]);break C}w[q|0]=0;y[f+160>>2]=q;break C}break D}if((d|0)!=62){break B}c=y[a+4>>2];b=y[c+8>>2];d=b+y[n+28>>2]|0;if(b>>>0>d>>>0){break B}b=y[c+4>>2];y[c+8>>2]=b>>>0>d>>>0?d:b;break B}za();o()}Wa();o()}w[q|0]=0;y[f+160>>2]=q;b=1}pa(f+160|0);break u}b=0;break u}la(f+120|0)}ta(f+256|0);y[f+160>>2]=0;Pb(n+60|0,g);Sb(f+160|0);if(y[n+60>>2]){sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}ta(f+104|0);Ja(f+8|0);b=0;break x}ta(f+104|0);Ja(f+8|0)}b=1}ma(h);break u}if(qa(y[a+4>>2],h+16|0)){break v}c=y[a+4>>2];d=y[c>>2];c=y[c+4>>2];if((c|0)<0){break t}d=((c&134217727)<<5|d>>>27)&-32;c=c>>>27&15;if(!c&d>>>0>=2147483648|c){break t}if(!c&B[h+16>>2]>d>>>0){break v}k=0;b=y[n+8>>2];g=(b|0)>0?b:0;c=0;while(1){if((c|0)==(g|0)){d=1;b=0;while(1)if((b|0)==(g|0)){if(!(d&1)){break t}y[h+24>>2]=k;F=Kb(f+104|0,k);q=y[F>>2];tc:{if((q|0)==y[F+4>>2]){break tc}b=y[n+8>>2];m=(b|0)>0?b:0;d=1;k=0;g=0;while(1){if((g|0)==(m|0)){break tc}uc:{b=Ka(a,y[y[n+12>>2]+(g<<2)>>2]);if(z[b+4|0]&63){break uc}b=y[b+56>>2];i=y[b+24>>2];l=y[b+28>>2]-i>>2;b=0;while(1){if((b|0)==(l|0)){j=0;i=0;if(d&1){b=k+l|0;c=b>>>0>=l>>>0;j=c;i=c?b:0}k=i;d=j;break uc}c=b+k|0;if(!(c>>>0>=b>>>0&d)){break t}y[q+(c<<2)>>2]=y[i+(b<<2)>>2];b=b+1|0;continue}}g=g+1|0;continue}}$a(h+124|0,F);vc:{wc:{if(z[h|0]==1){q=y[h+24>>2];b=0;xc:{yc:{while(1){if((b|0)==35){break yc}if(!Fa(y[a+4>>2],4,f+160|0)){w[(f+256|0)+b|0]=y[f+160>>2];b=b+1|0;continue}break}x[f+8>>1]=0;t(f+12|0,0,92);break xc}m=tc(f+160|0,f+256|0,35);if(!z[m|0]){x[f+8>>1]=0;t(f+12|0,0,92);Oa(m);break xc}y[f+300>>2]=y[a+4>>2];d=0;y[f+156>>2]=0;y[f+148>>2]=0;y[f+152>>2]=0;b=f+148|0;y[f+292>>2]=b;if(q){Kd(b,q);b=y[f+152>>2];c=q+b|0;while(1){if((b|0)!=(c|0)){w[b|0]=0;b=b+1|0;continue}break}y[f+152>>2]=c}w[f+296|0]=1;gc(f+292|0);k=0;zc:while(1){Ac:{Bc:{if((d|0)<(q|0)){if(ya(f+300|0,m,f+292|0)){break Bc}g=y[f+292>>2];if((g|0)<=31){w[y[f+148>>2]+d|0]=g;k=0;break Ac}Cc:{Dc:{switch(g-32|0){case 0:if(Fa(y[a+4>>2],2,f+144|0)){break Bc}k=y[f+144>>2]+3|0;break Cc;case 1:if(Fa(y[a+4>>2],3,f+144|0)){break Bc}k=y[f+144>>2]+3|0;break Cc;case 2:break Dc;default:break Cc}}if(Fa(y[a+4>>2],7,f+144|0)){break Bc}k=y[f+144>>2]+11|0}if((k|0)<=0){break Ac}c=d+k|0;if((c|0)>(q|0)){break Bc}b=0;j=(g|0)==32&(d|0)>0;while(1)if((b|0)==(k|0)){d=c;continue zc}else{i=y[f+148>>2]+d|0;w[i+b|0]=j?z[i-1|0]:0;b=b+1|0;continue}}b=y[f+148>>2];c=tc(f+8|0,b,y[f+152>>2]-b|0);b=z[c|0];va(f+148|0);Oa(m);if(!b){break xc}sa(y[a+4>>2]);Id(h+28|0,c);Oa(c);b=1;c=0;if(z[h|0]!=1){break wc}q=A[f+142>>1];m=q>>>12&3;if((m|0)==2){break vc}j=q>>>10&3;if((j|0)==2){break vc}k=q>>>8&3;if((k|0)==2){break vc}d=q&3;if((d|0)==2){break vc}i=q>>>6&3;if((i|0)==2){break vc}Ec:{Fc:{switch(d|0){case 0:g=ra(a,6);d=0;break Ec;case 1:g=ra(a,7);d=0;break Ec;default:break Fc}}d=Ga(a,n,0);if(!d){break vc}g=y[d+68>>2];d=1}y[h+144>>2]=g;Gc:{Hc:{switch((q>>>2&3)-1|0){default:g=ra(a,8);break Gc;case 0:g=ra(a,9);break Gc;case 1:g=ra(a,10);break Gc;case 2:break Hc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+148>>2]=g;Ic:{Jc:{switch((q>>>4&3)-1|0){default:g=ra(a,11);break Ic;case 0:g=ra(a,12);break Ic;case 1:g=ra(a,13);break Ic;case 2:break Jc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+152>>2]=g;Kc:{Lc:{switch(i|0){case 0:g=ra(a,14);break Kc;case 1:g=ra(a,15);break Kc;default:break Lc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+156>>2]=g;Mc:{Nc:{switch(k|0){case 0:g=ra(a,14);break Mc;case 1:g=ra(a,15);break Mc;default:break Nc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+160>>2]=g;Oc:{Pc:{switch(j|0){case 0:g=ra(a,14);break Oc;case 1:g=ra(a,15);break Oc;default:break Pc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+164>>2]=g;Qc:{Rc:{switch(m|0){case 0:g=ra(a,14);break Qc;case 1:g=ra(a,15);break Qc;default:break Rc}}g=Ga(a,n,d);if(!g){break vc}d=d+1|0;g=y[g+68>>2]}y[h+168>>2]=g;Sc:{if(!(q&16384)){b=ra(a,1);break Sc}d=Ga(a,n,d);if(!d){break vc}b=y[d+68>>2]}y[h+172>>2]=b;break wc}x[f+8>>1]=0;t(f+12|0,0,92);va(f+148|0);Oa(m);break xc}d=d+1|0;continue}}Oa(f+8|0);b=1;break vc}c=ib(y[h+24>>2])}j=0;i=0;y[f+8>>2]=0;y[f+12>>2]=0;if(z[h+1|0]==1){g=f+160|0;mb(g,z[h+2|0]?1024:8192);b=y[f+160>>2];y[f+160>>2]=0;Hd(f+8|0,b);d=y[f+164>>2];y[f+12>>2]=d;y[f+164>>2]=0;Ja(g);i=d}y[n+52>>2]=1;Tc:{Uc:{Vc:{Wc:{if(z[h|0]==1){b=Gd(h,y[a+4>>2],y[f+8>>2]|j,i);y[f+160>>2]=0;oa(n- -64|0,b);la(f+160|0);if(!y[n+64>>2]){break Uc}sa(y[a+4>>2]);break Wc}b=Na(y[a+4>>2]);y[f+256>>2]=b;g=Fd(f+160|0,c);b=Ed(h,b,y[f+8>>2]|j,i,g);y[f+148>>2]=0;oa(n- -64|0,b);la(f+148|0);if(!y[n+64>>2]){break Vc}sa(y[a+4>>2]);d=y[a+4>>2];b=y[d+8>>2];if(b>>>0<=4294967293){c=b+2|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}rc(g);ta(f+256|0)}b=0;if((z[n+4|0]&63)==4){break Tc}Xc:{if(z[a+50|0]){break Xc}d=y[y[a+24>>2]-4>>2];if(z[d+17|0]!=1){break Xc}b=u+y[f+132>>2]|0;c=y[a+32>>2];if((b|0)<=y[c+24>>2]){break Xc}ab(c,b,z[d+16|0])}b=y[f+128>>2];c=b;g=b>>31;b=y[f+132>>2];d=b;i=b>>31;b=z[f+136|0];bb(y[a+32>>2],c,g,d,i,y[n+64>>2],(b&7)==4?4:b&3);oa(n- -64|0,0);b=0;break Tc}rc(g);ta(f+256|0)}b=1}Ja(f+8|0)}va(F);break v}else{c=Ka(a,y[y[n+12>>2]+(b<<2)>>2]);if(!(z[c+4|0]&63)){j=0;i=0;if(d&1){c=y[c+56>>2];d=y[c+28>>2]-y[c+24>>2]>>2;c=d+k|0;i=c;c=c>>>0>=d>>>0;d=c?i:0;j=c}else{d=i}k=d;d=j}b=b+1|0;continue}}b=1;d=c<<2;c=c+1|0;if(Ka(a,y[d+y[n+12>>2]>>2])){continue}break}}qc(f+116|0)}Y=f+304|0;break s}o()}if(y[a+56>>2]!=3){break r}b=y[a+4>>2];c=y[b+8>>2];b=y[b+4>>2];if(c>>>0>b>>>0){break e}if((b|0)!=(c|0)){continue}break}y[a+52>>2]=2;break c}Yc:{switch(b|0){default:eb(ba,0);break i;case 0:break Yc;case 2:break d}}n=y[ba>>2];b=y[n+28>>2];if((b|0)==-1){break g}c=b;b=y[a+84>>2];c=c+b|0;if(b>>>0<=c>>>0){break h}}sb=1;break c}y[a+84>>2]=c;d=y[a+4>>2];b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b;break f}d=y[a+4>>2];b=y[d+8>>2];if(b>>>0>4294967291){break f}c=b+4|0;b=y[d+4>>2];y[d+8>>2]=b>>>0>c>>>0?c:b}b=y[a+12>>2];c=y[a+16>>2];Zc:{if(b>>>0>>0){y[ba>>2]=0;y[b>>2]=n;c=b+4|0;break Zc}d=y[a+8>>2];b=b-d|0;h=b>>2;i=h+1|0;if(i>>>0>=1073741824){break b}g=c-d|0;c=g>>1;k=g>>>0>=2147483644?1073741823:c>>>0>i>>>0?c:i;_c:{if(!k){g=0;c=b;break _c}if(k>>>0>=1073741824){break a}g=na(k<<2);d=y[a+8>>2];c=y[a+12>>2]-d|0;h=c>>2;n=y[a+80>>2]}y[a+80>>2]=0;i=b+g|0;y[i>>2]=n;b=i-(h<<2)|0;if(c){p(b,d,c)}y[a+16>>2]=(k<<2)+g;c=i+4|0;y[a+12>>2]=c;y[a+8>>2]=b;if(d){ma(d)}d=y[a+4>>2]}y[a+12>>2]=c;b=y[d+4>>2];i=b;c=y[d+8>>2];if(b>>>0>=c>>>0){continue}break}}o()}eb(ba,0)}Y=T+16|0;return sb}za();o()}Wa();o()}function jb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0;e=Y-96|0;Y=e;D=a+8|0;p=a+4|0;a:{b:{m=y[a+4>>2];if(m-1>>>0<=65534){s=y[a+8>>2];if(s-1>>>0<65535){break b}}l=ua(p,D);break a}c:{d:{e:{f:{g:{h:{i:{j:{k:{l:{if(!z[a|0]){m:{if(z[a+24|0]!=255|z[a+25|0]!=255|(z[a+26|0]!=255|z[a+27|0]!=255)){break m}if(y[a+12>>2]){break m}j=y[a+20>>2];if((m|0)!=y[j+20>>2]){break m}if(!y[j+16>>2]){break c}l=hc(m,s);y[e+44>>2]=l;if(!y[l+16>>2]){break e}u=d;E=c;c=y[a+20>>2];F=y[c+20>>2];q=y[c+24>>2];c=y[a+16>>2];if(!((1-q|0)<=(c|0)&(c|0)<(q|0))){y[a+16>>2]=0}L=q+1|0;I=q-1|0;J=E+32|0;p=0;while(1){if((p|0)!=(s|0)){if(z[a+1|0]==1){if(z[b|0]){break e}if((d|0)==16|d>>>0<16){break l}v=ka(b,J)^v}ja(e+32|0,l,p);i=0;n:{if(!p){D=0;t=0;j=0;break n}ja(e+72|0,l,p-1|0);t=y[e+76>>2];if(!t){break l}D=y[e+72>>2];j=z[D|0]<<4}c=y[a+16>>2];y[e+88>>2]=0;y[e+92>>2]=0;y[e+80>>2]=0;y[e+84>>2]=0;y[e+72>>2]=0;y[e+76>>2]=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;h=p-c|0;if(!((h|0)<=0|(h|0)>(q|0))){ja(e+48|0,y[a+20>>2],h-1|0);c=y[e+52>>2];y[e+76>>2]=c;f=y[e+48>>2];y[e+72>>2]=f;if(!c){break l}c=z[f|0];y[e+8>>2]=c;i=c&192}k=0;c=0;o:{if((h|0)<0){break o}c=0;if((h|0)>=(q|0)){break o}ja(e+48|0,y[a+20>>2],h);c=y[e+52>>2];y[e+84>>2]=c;f=y[e+48>>2];y[e+80>>2]=f;if(!c){break l}c=z[f|0];y[e+12>>2]=c;c=c>>>3&24}if(!((h|0)<-1|(h|0)>=(I|0))){ja(e+48|0,y[a+20>>2],h+1|0);h=y[e+52>>2];y[e+92>>2]=h;f=y[e+48>>2];y[e+88>>2]=f;if(!h){break l}h=z[f|0];y[e+16>>2]=h;k=h>>>6|0}p:{if(!v){g=c|(j&3072|i)|k;n=0;x=y[e+32>>2];A=y[e+36>>2];k=0;q:while(1){if((k|0)>=(m|0)){break p}r:{if(!p){break r}j=j<<8;if((m|0)<=(k+8|0)){break r}c=(k>>>3|0)+1|0;if(c>>>0>=t>>>0){break l}j=z[c+D|0]<<4|j}s:{if((L+y[a+16>>2]|0)<(p|0)){h=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;r=0;c=0;break s}c=(k>>>3|0)+1|0;f=0;i=e+8|0;h=(F|0)<=(k+8|0);while(1){if((f|0)!=24){r=(e+72|0)+f|0;G=y[r+4>>2];t:{if(!G){break t}C=y[i>>2]<<8;y[i>>2]=C;if(h){break t}if(c>>>0>=G>>>0){break l}y[i>>2]=C|z[c+y[r>>2]|0]}i=i+4|0;f=f+8|0;continue}break}h=y[e+16>>2];r=y[e+8>>2];c=y[e+12>>2]}f=0;i=n+m|0;i=(i|0)>0?i:0;G=(i|0)>=8?8:i;i=0;while(1){if((f|0)==(G|0)){c=k>>>3|0;if(c>>>0>=A>>>0){break l}w[c+x|0]=i;n=n-8|0;k=k+8|0;continue q}if(g>>>0>=u>>>0){break l}C=g<<1;H=ka(b,C+E|0);g=f^7;i=H<>>13-f&1|(c>>>10-f&8|(r>>>g&64|(j>>>g&1024|C&6582|H<<9)));f=f+1|0;continue}}}Cb(e+48|0,y[a+20>>2],p);i=c|(j&3072|i)|k;h=0;A=y[e+32>>2];G=y[e+36>>2];C=y[e+60>>2];H=y[e+56>>2];k=0;while(1){if((h|0)>=(m|0)){break p}u:{if(!p){c=h+8|0;break u}j=j<<8;c=h+8|0;if((m|0)<=(c|0)){break u}f=(h>>>3|0)+1|0;if(f>>>0>=t>>>0){break l}j=z[f+D|0]<<4|j}r=h>>>3|0;n=r+1|0;f=0;g=e+8|0;while(1){v:{if((f|0)==24){f=0;g=k+m|0;g=(g|0)>0?g:0;K=(g|0)>=8?8:g;N=y[e+16>>2];M=y[e+12>>2];O=y[e+8>>2];n=0;while(1){if((f|0)==(K|0)){break v}x=y[y[a+20>>2]+20>>2];P=f+h|0;g=ia(x,P,H,C);w:{if(z[a+1|0]==1){if(Bb(x,P,g,e+48|0)){break w}}if(z[b|0]){break e}if(i>>>0>=u>>>0){break l}g=ka(b,E+(i<<1)|0)}x=f^7;i=N>>>13-f&1|(M>>>10-f&8|(j>>>x&1024|i<<1&6582|g<<9|O>>>x&64));f=f+1|0;n=g<>2];x:{if(!K){break x}N=y[g>>2]<<8;y[g>>2]=N;if((c|0)>=(F|0)){break x}if(n>>>0>=K>>>0){break l}y[g>>2]=N|z[n+y[x>>2]|0]}g=g+4|0;f=f+8|0;continue}break}if(r>>>0>=G>>>0){break l}w[r+A|0]=n;k=k-8|0;h=c;continue}}p=p+1|0;continue}break}y[e+44>>2]=0;break d}l=ua(p,D);y[e+44>>2]=l;f=y[l+16>>2];if(!f){break g}E=c;g=y[l+12>>2];while(1){if((f|0)>0){w[g|0]=0;f=f-1|0;g=g+1|0;continue}break}t=E+32|0;while(1){if(B[D>>2]>h>>>0){if(z[a+1|0]==1){if(z[b|0]){break g}if((d|0)==16|d>>>0<16){break l}k=ka(b,t)^k}ja(e+32|0,l,h);c=e+72|0;ja(c,l,h-1|0);j=y[e+76>>2];u=y[e+72>>2];v=y[a+16>>2];i=y[a+20>>2];jd(c,v,i,h);f=0;c=y[l+20>>2];f=!j|(c|0)<2?f:z[u|0]>>>6&1;if(!(!j|(c|0)<=0)){f=z[u|0]>>>6&2|f}y[e+8>>2]=f;y[e+12>>2]=0;c=y[i+20>>2];f=y[a+12>>2];g=1-f|0;m=y[e+72>>2];q=y[e+76>>2];n=0-f|0;Q=e,R=ia(c,g,m,q)|ia(c,n,m,q)<<1,y[Q+16>>2]=R;m=y[e+80>>2];q=y[e+84>>2];s=ia(c,g,m,q);r=s|ia(c,n,m,q)<<1;s=f^-1;Q=e,R=r|ia(c,s,m,q)<<2,y[Q+20>>2]=R;m=g;f=y[e+88>>2];g=y[e+92>>2];m=ia(c,m,f,g);Q=e,R=m|ia(c,n,f,g)<<1|ia(c,s,f,g)<<2,y[Q+24>>2]=R;c=e+48|0;ja(c,i,w[a+27|0]+(h-v|0)|0);f=y[e+52>>2];g=y[e+48>>2];ja(c,l,w[a+25|0]+h|0);c=y[e+52>>2];n=y[e+48>>2];y:{if(!k){m=u;u=j;j=c;c=f;q=g;f=0;i=y[e+32>>2];v=y[e+36>>2];while(1){if(B[p>>2]<=f>>>0){break y}s=e+8|0;g=id(a,y[l+20>>2],s,f,q,c,n,j);if(z[b|0]){break g}if(d>>>0<=g>>>0){break l}hd(a,l,s,f,ka(b,E+(g<<1)|0),e+72|0,m,u,i,v);f=f+1|0;continue}}Cb(e+48|0,i,h);m=u;u=j;j=c;c=f;q=g;f=0;i=y[e+32>>2];v=y[e+36>>2];s=y[e+60>>2];F=y[e+56>>2];while(1){if(B[p>>2]<=f>>>0){break y}r=y[y[a+20>>2]+20>>2];g=ia(r,f,F,s);z:{if(z[a+1|0]==1){if(Bb(r,f,g,e+48|0)){break z}}g=id(a,y[l+20>>2],e+8|0,f,q,c,n,j);if(z[b|0]==1){break g}if(d>>>0<=g>>>0){break l}g=ka(b,E+(g<<1)|0)}hd(a,l,e+8|0,f,g,e+72|0,m,u,i,v);f=f+1|0;continue}}h=h+1|0;continue}break}y[e+44>>2]=0;break f}A:{if(y[a+12>>2]){break A}j=y[a+20>>2];if((m|0)!=y[j+20>>2]){break A}if(!y[j+16>>2]){break c}l=hc(m,s);y[e+44>>2]=l;if(!y[l+16>>2]){break i}u=d;E=c;c=y[a+20>>2];r=y[c+20>>2];v=y[c+24>>2];c=y[a+16>>2];if(!((1-v|0)<=(c|0)&(c|0)<(v|0))){y[a+16>>2]=0}x=v-1|0;L=E+16|0;p=0;while(1){B:{C:{if((p|0)!=(s|0)){if(z[a+1|0]==1){if(z[b|0]){break i}if((d|0)==8|d>>>0<8){break l}q=ka(b,L)^q}ja(e+32|0,l,p);i=0;D:{if(!p){t=0;F=0;j=0;break D}ja(e+72|0,l,p-1|0);F=y[e+76>>2];if(!F){break l}t=y[e+72>>2];j=z[t|0]<<1}c=y[a+16>>2];y[e+88>>2]=0;y[e+92>>2]=0;y[e+80>>2]=0;y[e+84>>2]=0;y[e+72>>2]=0;y[e+76>>2]=0;y[e+16>>2]=0;y[e+8>>2]=0;y[e+12>>2]=0;h=p-c|0;if(!((h|0)<=0|(h|0)>(v|0))){ja(e+48|0,y[a+20>>2],h-1|0);c=y[e+52>>2];y[e+76>>2]=c;f=y[e+48>>2];y[e+72>>2]=f;if(!c){break l}c=z[f|0];y[e+8>>2]=c;i=c>>>2&32}k=0;c=0;E:{if((h|0)<0){break E}c=0;if((h|0)>=(v|0)){break E}ja(e+48|0,y[a+20>>2],h);c=y[e+52>>2];y[e+84>>2]=c;f=y[e+48>>2];y[e+80>>2]=f;if(!c){break l}c=z[f|0];y[e+12>>2]=c;c=c>>>4&12}if(!((h|0)<-1|(h|0)>=(x|0))){ja(e+48|0,y[a+20>>2],h+1|0);h=y[e+52>>2];y[e+92>>2]=h;f=y[e+48>>2];y[e+88>>2]=f;if(!h){break l}h=z[f|0];y[e+16>>2]=h;k=h>>>6|0}if(q){break C}i=c|(j&384|i)|k;f=0;D=y[e+32>>2];I=y[e+36>>2];n=0;F:while(1){if((f|0)>=(m|0)){break B}G:{if(!p){c=f+8|0;break G}j=j<<8;c=f+8|0;if((m|0)<=(c|0)){break G}h=(f>>>3|0)+1|0;if(h>>>0>=F>>>0){break l}j=z[h+t|0]<<1|j}h=f>>>3|0;k=h+1|0;f=0;g=e+8|0;while(1){if((f|0)==24){f=0;g=n+m|0;g=(g|0)>0?g:0;k=(g|0)>=8?8:g;J=y[e+16>>2];A=y[e+12>>2];G=y[e+8>>2];g=0;while(1){if((f|0)==(k|0)){if(h>>>0>=I>>>0){break l}w[h+D|0]=g;n=n-8|0;f=c;continue F}if(i>>>0>=u>>>0){break l}i=i<<1;C=ka(b,i+E|0);H=f^7;g=C<>>13-f&1|(A>>>11-f&4|(G>>>9-f&32|(j>>>H&128|i&794|C<<6)));f=f+1|0;continue}}J=(e+72|0)+f|0;A=y[J+4>>2];H:{if(!A){break H}G=y[g>>2]<<8;y[g>>2]=G;if((c|0)>=(r|0)){break H}if(k>>>0>=A>>>0){break l}y[g>>2]=G|z[k+y[J>>2]|0]}g=g+4|0;f=f+8|0;continue}}}y[e+44>>2]=0;break h}Cb(e+48|0,y[a+20>>2],p);i=c|(j&384|i)|k;h=0;I=y[e+32>>2];J=y[e+36>>2];A=y[e+60>>2];G=y[e+56>>2];D=0;while(1){if((h|0)>=(m|0)){break B}I:{if(!p){c=h+8|0;break I}j=j<<8;c=h+8|0;if((m|0)<=(c|0)){break I}f=(h>>>3|0)+1|0;if(f>>>0>=F>>>0){break l}j=z[f+t|0]<<1|j}n=h>>>3|0;k=n+1|0;f=0;g=e+8|0;while(1){J:{if((f|0)==24){f=0;g=m+D|0;g=(g|0)>0?g:0;C=(g|0)>=8?8:g;H=y[e+16>>2];K=y[e+12>>2];N=y[e+8>>2];k=0;while(1){if((f|0)==(C|0)){break J}M=y[y[a+20>>2]+20>>2];O=f+h|0;g=ia(M,O,G,A);K:{if(z[a+1|0]==1){if(Bb(M,O,g,e+48|0)){break K}}if(z[b|0]){break i}if(i>>>0>=u>>>0){break l}g=ka(b,E+(i<<1)|0)}M=f^7;i=H>>>13-f&1|(K>>>11-f&4|(N>>>9-f&32|(j>>>M&128|i<<1&794|g<<6)));f=f+1|0;k=g<>2];L:{if(!H){break L}K=y[g>>2]<<8;y[g>>2]=K;if((c|0)>=(r|0)){break L}if(k>>>0>=H>>>0){break l}y[g>>2]=K|z[k+y[C>>2]|0]}g=g+4|0;f=f+8|0;continue}break}if(n>>>0>=J>>>0){break l}w[n+I|0]=k;D=D-8|0;h=c;continue}}p=p+1|0;continue}}l=ua(p,D);y[e+32>>2]=l;f=y[l+16>>2];if(!f){break k}E=c;g=y[l+12>>2];while(1){if((f|0)>0){w[g|0]=0;f=f-1|0;g=g+1|0;continue}break}J=E+16|0;while(1){if(B[D>>2]>q>>>0){if(z[a+1|0]==1){if(z[b|0]){break k}if((d|0)==8|d>>>0<8){break l}v=ka(b,J)^v}ja(e+8|0,l,q);c=e+72|0;ja(c,l,q-1|0);u=y[e+76>>2];m=y[e+72>>2];j=y[a+20>>2];jd(c,y[a+16>>2],j,q);i=0;t=y[l+20>>2];i=!u|(t|0)<2?i:z[m|0]>>>6&1;i=!u|(t|0)<=0?i:z[m|0]>>>6&2|i;g=y[j+20>>2];f=y[a+12>>2];c=0-f|0;r=y[e+72>>2];x=y[e+76>>2];ia(g,c,r,x);h=ia(g,c,r,x);n=1-f|0;s=y[e+80>>2];F=y[e+84>>2];k=ia(g,n,s,F)|ia(g,c,s,F)<<1|ia(g,f^-1,s,F)<<2;L=y[e+88>>2];I=y[e+92>>2];n=ia(g,n,L,I)|ia(g,c,L,I)<<1;M:{if(!v){g=0;j=y[e+12>>2];t=y[e+8>>2];f=0;while(1){if(B[p>>2]<=f>>>0){break M}if(z[b|0]){break k}c=k<<2|h<<5|g<<6|i<<7|n;if(c>>>0>=d>>>0){break l}g=ka(b,E+(c<<1)|0);Ea(y[l+20>>2],f,t,j,g);h=y[a+12>>2];i=ia(y[l+20>>2],f+2|0,m,u)|i<<1&6;c=y[y[a+20>>2]+20>>2];A=f-h|0;h=ia(c,A+1|0,r,x);A=A+2|0;k=ia(c,A,s,F)|k<<1&6;n=ia(c,A,L,I)|n<<1&2;f=f+1|0;continue}}Cb(e+48|0,j,q);c=0;A=y[e+12>>2];G=y[e+8>>2];C=y[e+60>>2];H=y[e+56>>2];f=0;while(1){if(B[p>>2]<=f>>>0){break M}j=ia(g,f,H,C);N:{if(z[a+1|0]==1){if(Bb(g,f,j,e+48|0)){break N}}if(z[b|0]==1){break k}c=k<<2|h<<5|c<<6|i<<7|n;if(c>>>0>=d>>>0){break l}j=ka(b,E+(c<<1)|0);t=y[l+20>>2]}Ea(t,f,G,A,j);c=y[a+12>>2];t=y[l+20>>2];i=ia(t,f+2|0,m,u)|i<<1&6;g=y[y[a+20>>2]+20>>2];c=f-c|0;h=ia(g,c+1|0,r,x);c=c+2|0;k=ia(g,c,s,F)|k<<1&6;n=ia(g,c,L,I)|n<<1&2;f=f+1|0;c=j;continue}}q=q+1|0;continue}break}y[e+32>>2]=0;break j}o()}l=0}la(e+32|0);break a}l=0}la(e+44|0);break a}l=0}la(e+44|0);break a}l=0}la(e+44|0)}}Y=e+96|0;return l}function cd(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0;i=Y-32|0;Y=i;a:{b:{d=d-(c>>>0<1048577)|0;k=(d|0)==-1&c-1048577>>>0<4292870143|(d|0)!=-1;d=f-(e>>>0<1048577)|0;if(k|((d|0)==-1&e-1048577>>>0<4292870143|(d|0)!=-1)){break b}k=y[h+4>>2];l=y[h+12>>2];j=y[h>>2];p=y[h+8>>2];y[i+28>>2]=y[b+20>>2];w[i+24|0]=1;d=c;f=bd(i+24|0,c);q=z[f|0];h=y[i+28>>2];m=y[b+24>>2];y[f>>2]=1;y[f+4>>2]=m;m=bd(f,e);f=l-k|0;l=y[i+28>>2];m=z[m|0]?(f|0)<(l|0)?f:l:f;l=e&e>>31;f=p-j|0;A=q?(f|0)<(h|0)?f:h:f;c=c&c>>31;r=0-c|0;if(!((m|0)>(0-l|0)&(A|0)>(r|0))){break b}f=j-c|0;if((f|0)<0){break a}c=y[a+28>>2];x=(c|0)/4|0;p=f>>>5|0;if(x>>>0<=p>>>0){break a}h=y[b+28>>2];s=(h|0)/4|0;f=(d|0)>0?d:0;q=f>>>5|0;if(s>>>0<=q>>>0){break a}j=i+16|0;n=k-l|0;k=l+m|0;cc(j,a,n,k);l=y[i+20>>2];m=y[i+16>>2];cc(j,b,(e|0)>0?e:0,k);j=y[i+20>>2];if(!j){break a}F=-1>>>f|0;k=d+A|0;G=-1<<0-k;e=r&31;n=f&31;t=x-p|0;if((f^k-1)>>>0<=31){d=F&G;if((r^A-1)>>>0>=32){k=e-n|0;n=32-k|0;r=p<<2;while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;y[i+8>>2]=m+r;e=(h|0)/4|0;if(e>>>0>j>>>0){break a}f=y[i+16>>2];c=Ya(i+8|0);if(!y[i+12>>2]|e>>>0>>0|(e|0)==(q|0)){break a}e=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))<>2]>>2];h=e|(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>n;e=f+(q<<2)|0;c=y[e>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);h=((Ae(c,16)^c)&-16711936)>>>8|0;K=e,L=Ae(c,8)^h,y[K>>2]=L;c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}h=y[b+28>>2];e=(h|0)/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=f+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue}}if(e>>>0>n>>>0){k=e-n|0;n=p<<2;while(1){if(!j){break b}c=(c|0)/4|0;if(c>>>0>l>>>0){break a}e=(h|0)/4|0;if(c>>>0

      >>0|e>>>0>j>>>0|((c|0)==(p|0)|e>>>0>>0)|(e|0)==(q|0)){break a}t=y[i+16>>2];e=t+(q<<2)|0;c=y[m+n>>2];f=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))<>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;c=y[a+28>>2];e=(c|0)/4|0;if(e>>>0>l>>>0){break a}h=y[b+28>>2];f=(h|0)/4|0;if(f>>>0>j>>>0){break a}y[i+16>>2]=t+(f<<2);j=j-f|0;l=l-e|0;m=(e<<2)+m|0;continue}}k=n-e|0;n=p<<2;while(1){if(!j){break b}c=(c|0)/4|0;if(c>>>0>l>>>0){break a}e=(h|0)/4|0;if(c>>>0

      >>0|e>>>0>j>>>0|((c|0)==(p|0)|e>>>0>>0)|(e|0)==(q|0)){break a}t=y[i+16>>2];e=t+(q<<2)|0;c=y[m+n>>2];f=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>k|0;c=y[e>>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),d);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;c=y[a+28>>2];e=(c|0)/4|0;if(e>>>0>l>>>0){break a}h=y[b+28>>2];f=(h|0)/4|0;if(f>>>0>j>>>0){break a}y[i+16>>2]=t+(f<<2);j=j-f|0;l=l-e|0;m=(e<<2)+m|0;continue}}A=k&31;d=s-q|0;x=x-(r>>>5|0)|0;if(e>>>0<=n>>>0){if((e|0)==(n|0)){k=((k|0)/32|0)-(f+31>>>5|0)|0;if((k|0)<0){break a}C=p<<2;c:while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;y[i+8>>2]=m+C;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;u=y[i+16>>2];y[i>>2]=u+(q<<2);f=d;if(n){c=Ya(i+8|0);e=bc(i);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[e>>2];c=Ma(g,f,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;f=y[i+4>>2]}if(f>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];D=k<<2;r=h+D|0;x=y[i+12>>2];e=c;while(1)if((h|0)==(r|0)){if(A){if((k|0)==(x|0)|k>>>0>x>>>0|(f|0)==(k|0)){break a}c=y[e+D>>2];e=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[r>>2];c=Ma(g,e,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=r,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=u+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue c}else{s=y[c>>2];v=((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8);s=y[h>>2];s=yb(g,v,((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8));K=h,L=((Ae(s,16)^s)&-16711936)>>>8^Ae(s,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}k=((k|0)/32|0)-(f+31>>>5|0)|0;if((k|0)<0){break a}n=n-e|0;s=32-n|0;D=p<<2;d:while(1){if(!j){break b}if(l>>>0

      >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;c=m+D|0;y[i+8>>2]=c;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;v=y[i+16>>2];y[i>>2]=v+(q<<2);if(A){if(t>>>0>>0){break a}y[i+12>>2]=x;y[i+8>>2]=c}c=Ya(i+8|0);e=bc(i);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);c=y[e>>2];c=Ma(g,f>>>n|0,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;H=y[i+4>>2];if(H>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];E=k<<2;r=h+E|0;C=y[i+12>>2];e=c;while(1)if((h|0)==(r|0)){if(A){if(k>>>0>C>>>0){break a}h=f<>2];h=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>n|h}if((k|0)==(H|0)){break a}c=y[r>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=r,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=v+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue d}else{I=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);u=y[h>>2];u=yb(g,I|f>>>n,((Ae(u,16)^u)&-16711936)>>>8^Ae(u,8));K=h,L=((Ae(u,16)^u)&-16711936)>>>8^Ae(u,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}r=((k|0)/32|0)-(f+31>>>5|0)|0;if((r|0)<0){break a}s=e-n|0;C=32-s|0;H=p<<2;e:while(1){if(!j){break b}if(l>>>0

      >>0|i>>>0>P>>>0){break K}while(1){O:{p=H[n+16>>2];H[n+4>>2]=p+H[n+4>>2];m=b-p|0;if((b|0)==(p|0)){break O}h=h+1|0;H[o+40>>2]=h;p=H[n+44>>2];i=p+i|0;if(i>>>0

      >>0){break L}n=n+24|0;b=m;if(i>>>0<=P>>>0){continue}break L}break}H[o+36>>2]=m}o=o+68|0;r=r+1|0;if((R|0)!=(r|0)){continue}break}}s=s+36|0;k=k+1|0;if((Q|0)!=(k|0)){continue}break}u=i+u|0;break C}H[o+36>>2]=m}if(!H[H[W+4>>2]+104>>2]){H[t+24>>2]=q;H[t+20>>2]=M;H[t+16>>2]=k;H[t+12>>2]=C;H[t+8>>2]=r;H[t+4>>2]=P;H[t>>2]=p;Ba(f,2,14608,t);q=H[w+28>>2];u=u+P|0;break C}H[t+56>>2]=q;H[t+52>>2]=M;H[t+48>>2]=k;H[t+44>>2]=C;H[t+40>>2]=r;H[t+36>>2]=P;H[t+32>>2]=p;Ba(f,1,14608,t+32|0);break B}P:{if(!H[l+(q<<2)>>2]){break P}b=H[Z+24>>2]+N(q,52)|0;if(H[b+36>>2]){break P}H[b+36>>2]=H[(H[S+20>>2]+N(q,76)|0)+24>>2]-1}j=j-u|0;v=u+v|0;if(Sb(w)){continue}break}}Ca(l);w=w+240|0;x=x+1|0;if(x>>>0<=K[J+420>>2]){continue}break}xb(g,A);H[pa+8>>2]=v-V;b=1;break y}xb(g,A);Ca(l);break z}xb(g,A)}b=0}na=t+144|0;cb(W);if(!b){break b}X=H[H[D+32>>2]+5584>>2];p=H[H[D+20>>2]>>2];z=H[p+20>>2];H[pa+12>>2]=1;v=0;b=H[D+32>>2];s=H[X+16>>2]>>>4&1&H[b+12>>2]==H[b+8>>2];E=H[p+16>>2];Q:{if(!E){break Q}while(1){b=H[D+68>>2];if(!(H[b+(v<<2)>>2]?0:b)){r=pa+12|0;E=0;b=H[z+24>>2];R:{if(!b){break R}q=H[D+44>>2];while(1){m=H[z+28>>2]+N(E,152)|0;o=H[m+24>>2];if(o){k=m+28|0;b=H[m+20>>2];w=H[m+16>>2];l=0;while(1){if(N(b,w)){i=k+N(l,36)|0;n=0;while(1){g=H[i+20>>2]+N(n,40)|0;j=wb(D,H[z+16>>2],E,H[i+16>>2],H[g>>2],H[g+4>>2],H[g+8>>2],H[g+12>>2]);h=H[g+16>>2];o=H[g+20>>2];b=N(h,o);S:{if(j){if(!b){break S}h=0;while(1){j=H[g+24>>2]+N(h,68)|0;T:{if(!wb(D,H[z+16>>2],E,H[i+16>>2],H[j+8>>2],H[j+12>>2],H[j+16>>2],H[j+20>>2])){b=H[j+60>>2];if(!b){break T}Ca(b);H[j+60>>2]=0;break T}if(!H[D+64>>2]){if(H[j+60>>2]|H[j+16>>2]==H[j+8>>2]|H[j+20>>2]==H[j+12>>2]){break T}}b=Ea(1,44);if(!b){H[pa+12>>2]=0;break R}o=H[D+64>>2];H[b+36>>2]=0;H[b+28>>2]=r;H[b+20>>2]=X;H[b+16>>2]=z;H[b+12>>2]=i;H[b+8>>2]=j;H[b+4>>2]=E;H[b>>2]=o;H[b+40>>2]=s;H[b+32>>2]=f;H[b+24>>2]=H[q+4>>2]>1;db(q,14,b);if(!H[pa+12>>2]){break R}}h=h+1|0;if(h>>>0>2],H[g+16>>2])>>>0){continue}break}break S}if(!b){break S}w=0;while(1){b=H[g+24>>2]+N(w,68)|0;j=H[b+60>>2];if(j){Ca(j);H[b+60>>2]=0;o=H[g+20>>2];h=H[g+16>>2]}w=w+1|0;if(w>>>0>>0){continue}break}}n=n+1|0;b=H[m+20>>2];w=H[m+16>>2];if(n>>>0>>0){continue}break}o=H[m+24>>2]}l=l+1|0;if(l>>>0>>0){continue}break}b=H[z+24>>2]}E=E+1|0;if(E>>>0>>0){continue}break}}if(!H[pa+12>>2]){break Q}E=H[p+16>>2]}X=X+1080|0;z=z+76|0;v=v+1|0;if(E>>>0>v>>>0){continue}break}}X=0;Ra(H[D+44>>2]);if(!H[pa+12>>2]){break b}U:{if(H[D+64>>2]){break U}v=H[D+24>>2];if(!H[v+16>>2]){break U}z=0;while(1){b=H[H[H[D+20>>2]>>2]+20>>2]+N(z,76)|0;g=H[b+28>>2]+N(H[(H[v+24>>2]+N(z,52)|0)+36>>2],152)|0;j=H[g+136>>2];h=H[g+144>>2];m=H[g+140>>2];g=H[g+148>>2];Ca(H[b+52>>2]);H[b+52>>2]=0;V:{o=H[D+68>>2];if((h|0)==(j|0)|(g|0)==(m|0)|(H[o+(z<<2)>>2]?0:o)){break V}g=g-m|0;j=h-j|0;be(g,0,j);if(qa){Ba(f,1,2982,0);break b}g=N(g,j);if(g>>>0>=1073741824){Ba(f,1,2982,0);break b}j=b;b=Ia(g<<2);H[j+52>>2]=b;if(b){break V}Ba(f,1,2982,0);break b}z=z+1|0;v=H[D+24>>2];if(z>>>0>2]){continue}break}}v=H[D+32>>2];w=H[H[D+20>>2]>>2];if(H[w+16>>2]){z=H[w+20>>2];v=H[v+5584>>2];E=H[H[D+24>>2]+24>>2];o=0;while(1){W:{b=H[D+68>>2];if(H[b+(o<<2)>>2]?0:b){break W}m=H[E+36>>2]+1|0;if(H[v+20>>2]==1){Z=m;b=0;$=na-32|0;na=$;X:{Y:{if(H[D+64>>2]){g=1;if((m|0)==1){break X}n=H[z+28>>2];b=n+N(H[z+24>>2],152)|0;h=H[b-144>>2];q=H[b-152>>2];if((h|0)==(q|0)){break X}s=m-1|0;b=0;l=H[D+44>>2];p=H[l+4>>2];j=n;Z:{if((m|0)!=2){m=s&1;i=s&-2;g=0;while(1){r=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+312>>2]-H[j+304>>2]|0;b=b>>>0>r>>>0?b:r;r=H[j+316>>2]-H[j+308>>2]|0;b=b>>>0>r>>>0?b:r;j=j+304|0;g=g+2|0;if((i|0)!=(g|0)){continue}break}if(!m){break Z}}g=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>g>>>0?b:g;g=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>g>>>0?b:g}g=0;if(b>>>0>134217727){break X}j=H[n+4>>2];m=H[n+12>>2];r=H[n>>2];u=H[n+8>>2];k=b<<5;i=nb(k);H[$+16>>2]=i;if(!i){break X}q=h-q|0;g=m-j|0;m=u-r|0;H[$>>2]=i;while(1){r=H[z+36>>2];h=g;H[$+8>>2]=g;b=m;H[$+24>>2]=b;g=H[n+156>>2];j=H[n+164>>2];m=H[n+160>>2];u=H[n+152>>2];H[$+28>>2]=(u|0)%2;m=m-u|0;H[$+20>>2]=m-b;t=(p|0)<2;g=j-g|0;_:{if(!(!t&g>>>0>1)){j=0;if(!g){break _}while(1){Yb($+16|0,r+(N(j,q)<<2)|0);j=j+1|0;if((j|0)!=(g|0)){continue}break}break _}u=g>>>0

      >>0?g:p;Z=u-1|0;V=(g>>>0)/(u>>>0)|0;b=0;while(1){j=Fa(36);if(!j){break Y}W=H[$+28>>2];H[j+8>>2]=H[$+24>>2];H[j+12>>2]=W;W=H[$+20>>2];H[j>>2]=H[$+16>>2];H[j+4>>2]=W;H[j+24>>2]=r;H[j+20>>2]=q;H[j+16>>2]=m;H[j+28>>2]=N(b,V);W=(b|0)==(Z|0);b=b+1|0;H[j+32>>2]=W?g:N(V,b);W=nb(k);H[j>>2]=W;if(!W){g=0;Ra(l);Ca(j);Ca(i);break X}db(l,10,j);if((b|0)!=(u|0)){continue}break}Ra(l)}H[$+4>>2]=g-h;H[$+12>>2]=H[n+156>>2]%2;$:{if(!(!t&m>>>0>1)){b=8;j=0;if(m>>>0>=8){while(1){mb($,r+(j<<2)|0,q,8);j=b;b=b+8|0;if(m>>>0>=b>>>0){continue}break}}if(j>>>0>=m>>>0){break $}mb($,r+(j<<2)|0,q,m-j|0);break $}h=m>>>0

      >>0?m:p;V=h-1|0;u=(m>>>0)/(h>>>0)|0;b=0;while(1){j=Fa(36);if(!j){break Y}t=H[$+12>>2];H[j+8>>2]=H[$+8>>2];H[j+12>>2]=t;t=H[$+4>>2];H[j>>2]=H[$>>2];H[j+4>>2]=t;H[j+24>>2]=r;H[j+20>>2]=q;H[j+16>>2]=g;H[j+28>>2]=N(b,u);t=(b|0)==(V|0);b=b+1|0;H[j+32>>2]=t?m:N(u,b);t=nb(k);H[j>>2]=t;if(!t){g=0;Ra(l);Ca(j);Ca(i);break X}db(l,11,j);if((b|0)!=(h|0)){continue}break}Ra(l)}n=n+152|0;s=s-1|0;if(s){continue}break}g=1;Ca(i);break X}g=1;n=H[z+28>>2];oa=n+N(Z,152)|0;La=oa-152|0;if(H[La>>2]==H[oa-144>>2]){break X}Ma=oa-148|0;if(H[Ma>>2]==H[oa-140>>2]){break X}h=H[n+4>>2];m=H[n+12>>2];l=H[n>>2];i=H[n+8>>2];W=H[z+68>>2];P=H[z+64>>2];A=H[z+60>>2];C=H[z+56>>2];ia=Xb(z,Z);if(!ia){g=0;break X}aa:{ba:{if((Z|0)!=1){j=n;ca:{if((Z|0)!=2){g=Z-1|0;p=g&1;q=g&-2;g=0;while(1){s=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+312>>2]-H[j+304>>2]|0;b=b>>>0>s>>>0?b:s;s=H[j+316>>2]-H[j+308>>2]|0;b=b>>>0>s>>>0?b:s;j=j+304|0;g=g+2|0;if((q|0)!=(g|0)){continue}break}if(!p){break ca}}g=H[j+160>>2]-H[j+152>>2]|0;b=b>>>0>g>>>0?b:g;g=H[j+164>>2]-H[j+156>>2]|0;b=b>>>0>g>>>0?b:g}if(b>>>0>=268435456){break aa}k=nb(b<<4);if(!k){break aa}da:{if(!Z){break da}p=m-h|0;u=i-l|0;Na=k+28|0;Q=k+24|0;Ka=k+16|0;Y=k+4|0;ra=1;ea:while(1){b=H[n+156>>2];ha=(b|0)%2|0;g=H[n+152>>2];ma=(g|0)%2|0;S=H[n+164>>2]-b|0;ca=S-p|0;x=H[n+160>>2]-g|0;ba=x-u|0;j=C;g=j;r=A;m=r;b=P;ea=b;h=W;s=h;l=H[z+20>>2];fa:{if((l|0)==(ra|0)){break fa}q=l-ra|0;m=0;g=0;if(j){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=j+(g^-1)|0;g=i^-1;j=b>>>0>>0?g+1|0:g;g=q&31;if((q&63)>>>0>=32){g=j>>>g|0}else{g=((1<>>g}}if(A){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=A+(j^-1)|0;j=i^-1;h=b>>>0>>0?j+1|0:j;j=q&31;if((q&63)>>>0>=32){m=h>>>j|0}else{m=((1<>>j}}h=0;b=0;if(P){b=q&31;if((q&63)>>>0>=32){i=-1<>>32-b}b=P+(j^-1)|0;j=i^-1;l=b>>>0

      >>0?j+1|0:j;j=q&31;if((q&63)>>>0>=32){b=l>>>j|0}else{b=((1<>>j}}if(W){j=q&31;if((q&63)>>>0>=32){i=-1<>>32-j}j=W+(h^-1)|0;h=i^-1;l=j>>>0>>0?h+1|0:h;h=q&31;if((q&63)>>>0>=32){h=l>>>h|0}else{h=((1<>>h}}ea=0;j=0;V=1<>>0>>0){j=q&31;if((q&63)>>>0>=32){i=-1<>>32-j}l=l^-1;j=l+(C-V|0)|0;i=i^-1;i=j>>>0>>0?i+1|0:i;l=q&31;if((q&63)>>>0>=32){j=i>>>l|0}else{j=((1<>>l}}if(P>>>0>V>>>0){l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}s=s^-1;l=s+(P-V|0)|0;i=i^-1;s=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){ea=s>>>i|0}else{ea=((1<>>i}}s=0;r=0;if(A>>>0>V>>>0){l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}r=r^-1;l=r+(A-V|0)|0;i=i^-1;r=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){r=r>>>i|0}else{r=((1<>>i}}if(W>>>0<=V>>>0){break fa}l=q&31;if((q&63)>>>0>=32){i=-1<>>32-l}s=s^-1;l=s+(W-V|0)|0;i=i^-1;s=l>>>0>>0?i+1|0:i;i=q&31;if((q&63)>>>0>=32){s=s>>>i|0}else{s=((1<>>i}}l=H[n+180>>2];i=ea-l|0;i=i>>>0<=ea>>>0?i:0;q=i+2|0;i=i>>>0>q>>>0?-1:q;fa=i>>>0>>0?i:ba;i=H[n+216>>2];q=b-i|0;b=b>>>0>=q>>>0?q:0;q=b+2|0;b=b>>>0>q>>>0?-1:q;ga=b>>>0>>0?b:u;b=(ma?fa:ga)<<1;q=(ma?ga:fa)<<1|1;ta=b>>>0>q>>>0?b:q;b=ta>>>0>>0;l=j-l|0;j=j>>>0>=l>>>0?l:0;l=j-2|0;q=j>>>0>=l>>>0?l:0;j=g-i|0;g=g>>>0>=j>>>0?j:0;j=g-2|0;j=g>>>0>=j>>>0?j:0;g=(ma?q:j)<<1;l=(ma?j:q)<<1|1;t=g>>>0>>0;V=H[n+184>>2];i=m-V|0;m=i>>>0<=m>>>0?i:0;i=m-2|0;m=i>>>0<=m>>>0?i:0;R=m;J=H[n+220>>2];i=r-J|0;i=i>>>0<=r>>>0?i:0;r=i-2|0;i=i>>>0>=r>>>0?r:0;_=i;r=h-V|0;h=h>>>0>=r>>>0?r:0;r=h+2|0;h=h>>>0>r>>>0?-1:r;V=h>>>0

      >>0?h:p;aa=V;h=s-J|0;h=h>>>0<=s>>>0?h:0;s=h+2|0;h=h>>>0>s>>>0?-1:s;la=h>>>0>>0?h:ca;s=la;if(ha){R=i;_=m;aa=s;s=V}Aa=b?ta:x;l=t?g:l;Oa=p+la|0;Qa=i+p|0;if(S){sa=k+(j<<3)|0;b=k+(ba<<3)|0;ua=b-4|0;g=(j|0)<(ba|0);_a=g?sa+4|0:ua;va=u-1|0;$a=(fa|0)<(va|0)?fa:va;t=0;ab=(u|0)>1|(ba|0)>0;h=ma<<2;bb=(Y-h|0)+(q<<3)|0;eb=h+sa|0;M=(ba|0)>(ga|0)?ga:ba;ka=j+1|0;fb=u+fa|0;Ta=q+u|0;gb=k+(l<<2)|0;Va=!u&(ba|0)==1;h=k+(u<<3)|0;ja=h-8|0;wa=h-4|0;xa=b-8|0;Wa=k+(Aa<<2)|0;hb=Wa-4|0;jb=(k+((g?j:ba)<<3)|0)-4|0;while(1){ga:{if(!(t>>>0>>0&m>>>0<=t>>>0|t>>>0>>0&t>>>0>=Qa>>>0)){J=t+1|0;break ga}if(x>>>0>ta>>>0){H[hb>>2]=0;H[Wa>>2]=0}J=t+1|0;Ha(ia,j,t,ga,J,eb,2,0);Ha(ia,Ta,t,fb,J,bb,2,0);ha:{ia:{ja:{if(!ma){if(!ab){break ha}if((j|0)>=(ga|0)){break ia}ka:{la:{if((j|0)>0){b=H[jb>>2];break la}b=H[Y>>2];g=b;if((j|0)<0){break ka}}g=b;b=H[_a>>2]}H[sa>>2]=H[sa>>2]-((b+g|0)+2>>2);b=ka;h=b;g=j;if((b|0)>=(M|0)){break ja}while(1){b=g;g=h;h=k+(g<<3)|0;H[h>>2]=H[h>>2]-((H[(k+(b<<3)|0)+4>>2]+H[h+4>>2]|0)+2>>2);h=g+1|0;if((M|0)!=(h|0)){continue}break}b=M;break ja}ma:{if(!Va){b=j;if((ga|0)<=(b|0)){break ma}while(1){g=k+(b<<3)|0;h=H[g+4>>2];r=xa;na:{oa:{if((b|0)>=0){ea=H[((b|0)<(ba|0)?g:xa)>>2];b=b+1|0;break oa}ea=H[k>>2];if((b|0)!=-1){b=b+1|0;r=k;break na}b=0}if((b|0)>=(ba|0)){break na}r=k+(b<<3)|0}H[g+4>>2]=h-((H[r>>2]+ea|0)+2>>2);if((b|0)<(ga|0)){continue}break}break ma}H[k>>2]=H[k>>2]/2;break ha}b=q;if((fa|0)<=(b|0)){break ha}while(1){g=k+(b<<3)|0;h=H[g>>2];pa:{if((b|0)<0){r=H[Y>>2];ea=Y;break pa}r=H[((b|0)<(u|0)?g+4|0:wa)>>2];ea=Y;if(!b){break pa}ea=(b|0)>(u|0)?wa:g-4|0}H[g>>2]=h+(H[ea>>2]+r>>1);b=b+1|0;if((fa|0)!=(b|0)){continue}break}break ha}if((b|0)>=(ga|0)){break ia}while(1){g=k+(b<<3)|0;r=H[g>>2];qa:{ra:{if((b|0)>0){h=H[(k+(((b|0)<(ba|0)?b:ba)<<3)|0)-4>>2];break ra}h=H[Y>>2];ea=Y;if((b|0)<0){break qa}}ea=ua;if((b|0)>=(ba|0)){break qa}ea=(k+(b<<3)|0)+4|0}H[g>>2]=r-((H[ea>>2]+h|0)+2>>2);b=b+1|0;if((ga|0)!=(b|0)){continue}break}}if((q|0)>=(fa|0)){break ha}b=q;if((va|0)>(b|0)){while(1){g=k+(b<<3)|0;b=b+1|0;H[g+4>>2]=H[g+4>>2]+(H[k+(b<<3)>>2]+H[g>>2]>>1);if((b|0)<($a|0)){continue}break}}if((b|0)>=(fa|0)){break ha}while(1){g=ja;r=b;sa:{ta:{if((b|0)>=0){h=H[((b|0)<(u|0)?k+(b<<3)|0:g)>>2];b=b+1|0;break ta}h=H[k>>2];if((r|0)!=-1){b=r+1|0;g=k;break sa}b=0}if((u|0)<=(b|0)){break sa}g=k+(b<<3)|0}r=k+(r<<3)|0;H[r+4>>2]=H[r+4>>2]+(H[g>>2]+h>>1);if((b|0)<(fa|0)){continue}break}}if(!Ya(ia,l,t,Aa,J,gb,1,0)){break ba}}t=J;if((S|0)!=(t|0)){continue}break}}n=n+152|0;b=aa<<1;g=s<<1|1;b=b>>>0>g>>>0?b:g;_a=b>>>0>>0?b:S;J=k+(m<<5)|0;b=k+(ca<<5)|0;ta=b-4|0;g=(m|0)<(ca|0);$a=g?J+28|0:ta;va=b-8|0;ab=g?J+24|0:va;wa=b-12|0;bb=g?J+20|0:wa;xa=b-16|0;eb=(m|0)<0?Ka:g?J+16|0:xa;ua=p-1|0;fb=(la|0)<(ua|0)?la:ua;j=k+((g?m:ca)<<5)|0;Ta=(m|0)<=0;gb=Ta?Ka:j-16|0;Va=(ca|0)>0;Wa=Va|(p|0)>1;hb=J+(ha<<4)|0;jb=(k+(4-(ha<<2)<<2)|0)+(i<<5)|0;q=(V|0)<(ca|0)?V:ca;u=m+1|0;g=R<<1;h=_<<1|1;lb=g>>>0>>0?g:h;qb=k+(lb<<4)|0;rb=!p&(ca|0)==1;g=k+(p<<5)|0;M=g-20|0;R=g-24|0;_=g-28|0;aa=g-32|0;ka=g-4|0;ja=g-8|0;ba=g-12|0;fa=g-16|0;ga=b-20|0;ea=b-24|0;ma=b-28|0;sa=b-32|0;sb=j-4|0;t=j-8|0;tb=j-12|0;while(1){s=l;ua:{va:{wa:{xa:{if(l>>>0>>0){b=Aa-l|0;l=(b>>>0>=4?4:b)+l|0;Ha(ia,s,m,l,V,hb,1,8);Ha(ia,s,Qa,l,Oa,jb,1,8);if(!ha){if(!Wa){break ua}if((m|0)>=(V|0)){break va}H[J>>2]=H[J>>2]-((H[gb>>2]+H[eb>>2]|0)+2>>2);ya:{if(!Ta){b=H[tb>>2];h=t;g=sb;break ya}b=H[k+20>>2];if((m|0)<0){break xa}h=Q;g=Na}H[J+4>>2]=H[J+4>>2]-((H[bb>>2]+b|0)+2>>2);H[J+8>>2]=H[J+8>>2]-((H[h>>2]+H[ab>>2]|0)+2>>2);b=H[$a>>2];g=H[g>>2];break wa}if(rb){H[k>>2]=H[k>>2]/2;H[k+4>>2]=H[k+4>>2]/2;H[k+8>>2]=H[k+8>>2]/2;H[k+12>>2]=H[k+12>>2]/2;break ua}g=m;if((V|0)>(g|0)){while(1){b=k+(g<<5)|0;za:{if((g|0)<0){h=H[k>>2];j=Va|(g|0)!=-1;H[b+16>>2]=H[b+16>>2]-(((j?h:H[sa>>2])+h|0)+2>>2);h=H[Y>>2];H[b+20>>2]=H[b+20>>2]-(((j?h:H[ma>>2])+h|0)+2>>2);h=H[k+8>>2];H[b+24>>2]=H[b+24>>2]-(((j?h:H[ea>>2])+h|0)+2>>2);h=b;r=H[b+28>>2];b=H[k+12>>2];H[h+28>>2]=r-(((j?b:H[ga>>2])+b|0)+2>>2);g=g+1|0;break za}j=g+1|0;Aa:{if((j|0)<(ca|0)){g=k+(j<<5)|0;H[b+16>>2]=H[b+16>>2]-((H[b>>2]+H[g>>2]|0)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[b+4>>2]+H[g+4>>2]|0)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[b+8>>2]+H[g+8>>2]|0)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[b+12>>2]+H[g+12>>2]|0)+2>>2);break Aa}h=H[b+16>>2];if((g|0)>=(ca|0)){H[b+16>>2]=h-((H[sa>>2]<<1)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[ma>>2]<<1)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[ea>>2]<<1)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[ga>>2]<<1)+2>>2);break Aa}H[b+16>>2]=h-((H[b>>2]+H[sa>>2]|0)+2>>2);H[b+20>>2]=H[b+20>>2]-((H[b+4>>2]+H[ma>>2]|0)+2>>2);H[b+24>>2]=H[b+24>>2]-((H[b+8>>2]+H[ea>>2]|0)+2>>2);H[b+28>>2]=H[b+28>>2]-((H[b+12>>2]+H[ga>>2]|0)+2>>2)}g=j}if((V|0)!=(g|0)){continue}break}}g=i;if((la|0)<=(g|0)){break ua}while(1){b=k+(g<<5)|0;Ba:{if((g|0)<0){H[b>>2]=H[b>>2]+(H[k+16>>2]<<1>>1);H[b+4>>2]=H[b+4>>2]+(H[k+20>>2]<<1>>1);H[b+8>>2]=H[b+8>>2]+(H[k+24>>2]<<1>>1);H[b+12>>2]=H[b+12>>2]+(H[k+28>>2]<<1>>1);break Ba}j=H[b>>2];if(!g){h=(g|0)<(p|0);H[b>>2]=j+(H[k+16>>2]+H[(h?b+16|0:fa)>>2]>>1);H[b+4>>2]=H[b+4>>2]+(H[k+20>>2]+H[(h?b+20|0:ba)>>2]>>1);H[b+8>>2]=H[b+8>>2]+(H[k+24>>2]+H[(h?b+24|0:ja)>>2]>>1);H[b+12>>2]=H[b+12>>2]+(H[k+28>>2]+H[(h?b+28|0:ka)>>2]>>1);break Ba}if((g|0)<=(p|0)){h=(g|0)<(p|0);H[b>>2]=j+(H[b-16>>2]+H[(h?b+16|0:fa)>>2]>>1);H[b+4>>2]=H[b+4>>2]+(H[b-12>>2]+H[(h?b+20|0:ba)>>2]>>1);H[b+8>>2]=H[b+8>>2]+(H[b-8>>2]+H[(h?b+24|0:ja)>>2]>>1);H[b+12>>2]=H[b+12>>2]+(H[b-4>>2]+H[(h?b+28|0:ka)>>2]>>1);break Ba}H[b>>2]=j+(H[fa>>2]<<1>>1);H[b+4>>2]=H[b+4>>2]+(H[ba>>2]<<1>>1);H[b+8>>2]=H[b+8>>2]+(H[ja>>2]<<1>>1);H[b+12>>2]=H[b+12>>2]+(H[ka>>2]<<1>>1)}g=g+1|0;if((la|0)!=(g|0)){continue}break}break ua}p=S;u=x;ra=ra+1|0;if((Z|0)!=(ra|0)){continue ea}break da}H[J+4>>2]=H[J+4>>2]-((b<<1)+2>>2);H[J+8>>2]=H[J+8>>2]-((H[Q>>2]<<1)+2>>2);b=H[Na>>2];g=b}H[J+12>>2]=H[J+12>>2]-((b+g|0)+2>>2);j=m;b=u;g=b;if((b|0)<(q|0)){while(1){g=k+(b<<5)|0;j=k+(j<<5)|0;H[g>>2]=H[g>>2]-((H[j+16>>2]+H[g+16>>2]|0)+2>>2);H[g+4>>2]=H[g+4>>2]-((H[j+20>>2]+H[g+20>>2]|0)+2>>2);H[g+8>>2]=H[g+8>>2]-((H[j+24>>2]+H[g+24>>2]|0)+2>>2);H[g+12>>2]=H[g+12>>2]-((H[j+28>>2]+H[g+28>>2]|0)+2>>2);j=b;b=b+1|0;if((q|0)!=(b|0)){continue}break}g=q}if((g|0)>=(V|0)){break va}while(1){b=k+(g<<5)|0;j=(g|0)<(ca|0);Ca:{if((g|0)<=0){h=H[Ka>>2];if((g|0)>=0){H[b>>2]=H[b>>2]-((h+H[(j?b+16|0:xa)>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[k+20>>2]+H[(j?b+20|0:wa)>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[k+24>>2]+H[(j?b+24|0:va)>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[k+28>>2]+H[(j?b+28|0:ta)>>2]|0)+2>>2);break Ca}H[b>>2]=H[b>>2]-((h<<1)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[k+20>>2]<<1)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[k+24>>2]<<1)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[k+28>>2]<<1)+2>>2);break Ca}h=k+((j?g:ca)<<5)|0;r=H[h-16>>2];if(!j){H[b>>2]=H[b>>2]-((r+H[xa>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[h-12>>2]+H[wa>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[h-8>>2]+H[va>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[h-4>>2]+H[ta>>2]|0)+2>>2);break Ca}H[b>>2]=H[b>>2]-((r+H[b+16>>2]|0)+2>>2);H[b+4>>2]=H[b+4>>2]-((H[h-12>>2]+H[b+20>>2]|0)+2>>2);H[b+8>>2]=H[b+8>>2]-((H[h-8>>2]+H[b+24>>2]|0)+2>>2);H[b+12>>2]=H[b+12>>2]-((H[h-4>>2]+H[b+28>>2]|0)+2>>2)}g=g+1|0;if((V|0)!=(g|0)){continue}break}}if((i|0)>=(la|0)){break ua}g=i;if((ua|0)>(g|0)){while(1){b=k+(g<<5)|0;H[b+16>>2]=H[b+16>>2]+(H[b+32>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[b+36>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[b+40>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[b+44>>2]+H[b+12>>2]>>1);g=g+1|0;if((fb|0)>(g|0)){continue}break}}if((g|0)>=(la|0)){break ua}while(1){b=k+(g<<5)|0;Da:{Ea:{Fa:{if((g|0)<0){j=H[k>>2];if((g|0)!=-1){break Fa}if((p|0)<=0){H[b+16>>2]=H[b+16>>2]+(j+H[aa>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[_>>2]+H[k+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[R>>2]+H[k+8>>2]>>1);r=H[k+12>>2];j=H[M>>2];break Ea}break Fa}j=g+1|0;Ga:{if((j|0)<(p|0)){g=k+(j<<5)|0;H[b+16>>2]=H[b+16>>2]+(H[g>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[g+4>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[g+8>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[g+12>>2]+H[b+12>>2]>>1);break Ga}if((g|0)>=(p|0)){H[b+16>>2]=H[b+16>>2]+H[aa>>2];H[b+20>>2]=H[b+20>>2]+H[_>>2];H[b+24>>2]=H[b+24>>2]+H[R>>2];H[b+28>>2]=H[b+28>>2]+H[M>>2];break Ga}H[b+16>>2]=H[b+16>>2]+(H[aa>>2]+H[b>>2]>>1);H[b+20>>2]=H[b+20>>2]+(H[_>>2]+H[b+4>>2]>>1);H[b+24>>2]=H[b+24>>2]+(H[R>>2]+H[b+8>>2]>>1);H[b+28>>2]=H[b+28>>2]+(H[M>>2]+H[b+12>>2]>>1)}g=j;break Da}H[b+16>>2]=j+H[b+16>>2];H[b+20>>2]=H[b+20>>2]+H[k+4>>2];H[b+24>>2]=H[b+24>>2]+H[k+8>>2];r=H[k+12>>2];j=r}H[b+28>>2]=H[b+28>>2]+(j+r>>1);g=g+1|0}if((la|0)!=(g|0)){continue}break}}if(Ya(ia,s,lb,l,_a,qb,1,4)){continue}break}break}break ba}Ca(k);g=1}b=H[oa-16>>2];j=H[La>>2];h=H[Ma>>2];m=H[oa-8>>2];Ha(ia,b-j|0,H[oa-12>>2]-h|0,m-j|0,H[oa-4>>2]-h|0,H[z+52>>2],1,m-b|0);Ua(ia);break X}Ua(ia);Ca(k);g=0;break X}Ua(ia);g=0;break X}g=0;Ra(l);Ca(i)}na=$+32|0;if(g){break W}break b}h=0;A=na+-64|0;na=A;Ha:{Ia:{if(H[D+64>>2]){n=H[z+28>>2];j=n+N(H[z+24>>2],152)|0;l=H[j-152>>2];k=1;V=H[D+44>>2];P=H[V+4>>2];if((m|0)==1){break Ha}p=m-1|0;b=n;Ja:{if((m|0)!=2){m=p&1;i=p&-2;g=0;while(1){q=H[b+160>>2]-H[b+152>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+164>>2]-H[b+156>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+312>>2]-H[b+304>>2]|0;h=h>>>0>q>>>0?h:q;q=H[b+316>>2]-H[b+308>>2]|0;h=h>>>0>q>>>0?h:q;b=b+304|0;g=g+2|0;if((i|0)!=(g|0)){continue}break}if(!m){break Ja}}g=H[b+160>>2]-H[b+152>>2]|0;g=g>>>0>>0?h:g;b=H[b+164>>2]-H[b+156>>2]|0;h=b>>>0>>0?g:b}k=0;if(h>>>0>134217727){break Ha}b=H[j-144>>2];g=H[n+4>>2];j=H[n+12>>2];i=H[n>>2];q=H[n+8>>2];R=h<<5;r=Ia(R);H[A+32>>2]=r;if(!r){break Ha}m=j-g|0;j=q-i|0;g=P>>>1|0;_=g>>>0<=2?2:g;s=b-l|0;ka=s<<5;ja=N(s,28);$=N(s,24);ba=N(s,20);ca=s<<4;C=N(s,12);S=s<<3;H[A>>2]=r;aa=r+32|0;b=H[z+36>>2];while(1){q=m;H[A+8>>2]=m;h=j;H[A+40>>2]=h;Z=H[n+156>>2];W=H[n+164>>2];u=H[n+160>>2];g=H[n+152>>2];H[A+56>>2]=0;H[A+52>>2]=h;H[A+48>>2]=0;J=(g|0)%2|0;H[A+44>>2]=J;j=u-g|0;k=j-h|0;H[A+60>>2]=k;H[A+36>>2]=k;M=(P|0)<2;m=W-Z|0;Ka:{if(!(!M&m>>>0>15)){l=0;i=b;if(m>>>0<8){break Ka}t=j&-2;x=j&1;k=0;J=(u|0)==(g+1|0);while(1){g=A+32|0;yb(g,i,s,8);Sa(g);if(j){h=0;l=0;La:{if(!J){while(1){g=(h<<2)+i|0;u=r+(h<<5)|0;L[g>>2]=L[u>>2];Q=s<<2;L[Q+g>>2]=L[u+4>>2];L[g+S>>2]=L[u+8>>2];L[g+C>>2]=L[u+12>>2];u=h|1;g=(u<<2)+i|0;u=r+(u<<5)|0;L[g>>2]=L[u>>2];L[g+Q>>2]=L[u+4>>2];L[g+S>>2]=L[u+8>>2];L[g+C>>2]=L[u+12>>2];h=h+2|0;l=l+2|0;if((t|0)!=(l|0)){continue}break}if(!x){break La}}g=(h<<2)+i|0;h=r+(h<<5)|0;L[g>>2]=L[h>>2];L[g+(s<<2)>>2]=L[h+4>>2];L[g+S>>2]=L[h+8>>2];L[g+C>>2]=L[h+12>>2]}h=0;while(1){g=(h<<2)+i|0;l=r+(h<<5)|0;L[g+ca>>2]=L[l+16>>2];L[g+ba>>2]=L[l+20>>2];L[g+$>>2]=L[l+24>>2];L[g+ja>>2]=L[l+28>>2];h=h+1|0;if((j|0)!=(h|0)){continue}break}}i=i+ka|0;g=k+15|0;l=k+8|0;k=l;if(g>>>0>>0){continue}break}break Ka}g=m>>>3|0;t=g>>>0

      >>0?g:P;x=(m>>>0)/(t>>>0)&-8;l=m&-8;g=0;i=b;while(1){u=Fa(48);if(!u){break Ia}Q=Ia(R);H[u>>2]=Q;if(!Q){k=0;Ra(V);Ca(u);Ca(r);break Ha}H[u+40>>2]=i;H[u+36>>2]=s;H[u+32>>2]=j;H[u+28>>2]=k;H[u+24>>2]=0;H[u+20>>2]=h;H[u+16>>2]=0;H[u+12>>2]=J;H[u+8>>2]=h;H[u+4>>2]=k;Y=l-N(g,x)|0;g=g+1|0;Q=(t|0)==(g|0)?Y:x;H[u+44>>2]=Q;db(V,12,u);i=(N(s,Q)<<2)+i|0;if((g|0)!=(t|0)){continue}break}Ra(V)}Ma:{if(m>>>0<=l>>>0){break Ma}h=A+32|0;g=m-l|0;yb(h,i,s,g);Sa(h);if(!j){break Ma}J=g&-4;x=g&3;k=0;Q=Z+(l-W|0)>>>0>4294967292;while(1){u=(k<<2)+i|0;t=r+(k<<5)|0;h=0;l=0;g=0;Na:{if(!Q){while(1){L[u+(N(h,s)<<2)>>2]=L[t+(h<<2)>>2];g=h|1;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=h|2;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=h|3;L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];h=h+4|0;l=l+4|0;if((J|0)!=(l|0)){continue}break}g=h;if(!x){break Na}}h=0;while(1){L[u+(N(g,s)<<2)>>2]=L[t+(g<<2)>>2];g=g+1|0;h=h+1|0;if((x|0)!=(h|0)){continue}break}}k=k+1|0;if((k|0)!=(j|0)){continue}break}}u=m-q|0;H[A+4>>2]=u;g=H[n+156>>2];H[A+28>>2]=u;H[A+24>>2]=0;H[A+20>>2]=q;H[A+16>>2]=0;J=(g|0)%2|0;H[A+12>>2]=J;Oa:{if(!(!M&j>>>0>15)){g=b;if(j>>>0<8){break Oa}ia=m&-2;fa=m&1;ga=u&-2;Y=u&1;la=q&-2;oa=q&1;h=J<<5;M=aa-h|0;Q=h+r|0;ea=N(q,s)<<2;ma=W-1|0;ra=(ma|0)==(q+Z|0);l=j;while(1){h=0;i=0;Pa:{Qa:{switch(q|0){default:while(1){k=(N(h,s)<<2)+g|0;x=H[k+28>>2];t=Q+(h<<6)|0;H[t+24>>2]=H[k+24>>2];H[t+28>>2]=x;x=H[k+20>>2];H[t+16>>2]=H[k+16>>2];H[t+20>>2]=x;x=H[k+12>>2];H[t+8>>2]=H[k+8>>2];H[t+12>>2]=x;x=H[k+4>>2];H[t>>2]=H[k>>2];H[t+4>>2]=x;t=h|1;k=Q+(t<<6)|0;t=(N(t,s)<<2)+g|0;x=H[t+28>>2];H[k+24>>2]=H[t+24>>2];H[k+28>>2]=x;x=H[t+20>>2];H[k+16>>2]=H[t+16>>2];H[k+20>>2]=x;x=H[t+12>>2];H[k+8>>2]=H[t+8>>2];H[k+12>>2]=x;x=H[t+4>>2];H[k>>2]=H[t>>2];H[k+4>>2]=x;h=h+2|0;i=i+2|0;if((la|0)!=(i|0)){continue}break};if(!oa){break Pa}break;case 0:break Pa;case 1:break Qa}}i=Q+(h<<6)|0;h=(N(h,s)<<2)+g|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}Ra:{if((m|0)==(q|0)){break Ra}t=g+ea|0;h=0;x=0;if(!ra){while(1){i=t+(N(h,s)<<2)|0;ha=H[i+28>>2];k=M+(h<<6)|0;H[k+24>>2]=H[i+24>>2];H[k+28>>2]=ha;ha=H[i+20>>2];H[k+16>>2]=H[i+16>>2];H[k+20>>2]=ha;ha=H[i+12>>2];H[k+8>>2]=H[i+8>>2];H[k+12>>2]=ha;ha=H[i+4>>2];H[k>>2]=H[i>>2];H[k+4>>2]=ha;k=h|1;i=M+(k<<6)|0;k=t+(N(k,s)<<2)|0;ha=H[k+28>>2];H[i+24>>2]=H[k+24>>2];H[i+28>>2]=ha;ha=H[k+20>>2];H[i+16>>2]=H[k+16>>2];H[i+20>>2]=ha;ha=H[k+12>>2];H[i+8>>2]=H[k+8>>2];H[i+12>>2]=ha;ha=H[k+4>>2];H[i>>2]=H[k>>2];H[i+4>>2]=ha;h=h+2|0;x=x+2|0;if((ga|0)!=(x|0)){continue}break}if(!Y){break Ra}}i=M+(h<<6)|0;h=t+(N(h,s)<<2)|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}Sa(A);Sa:{if(!m){break Sa}h=0;i=0;if((Z|0)!=(ma|0)){while(1){k=r+(h<<5)|0;x=H[k+28>>2];t=(N(h,s)<<2)+g|0;H[t+24>>2]=H[k+24>>2];H[t+28>>2]=x;x=H[k+20>>2];H[t+16>>2]=H[k+16>>2];H[t+20>>2]=x;x=H[k+12>>2];H[t+8>>2]=H[k+8>>2];H[t+12>>2]=x;x=H[k+4>>2];H[t>>2]=H[k>>2];H[t+4>>2]=x;t=h|1;k=(N(t,s)<<2)+g|0;t=r+(t<<5)|0;x=H[t+28>>2];H[k+24>>2]=H[t+24>>2];H[k+28>>2]=x;x=H[t+20>>2];H[k+16>>2]=H[t+16>>2];H[k+20>>2]=x;x=H[t+12>>2];H[k+8>>2]=H[t+8>>2];H[k+12>>2]=x;x=H[t+4>>2];H[k>>2]=H[t>>2];H[k+4>>2]=x;h=h+2|0;i=i+2|0;if((ia|0)!=(i|0)){continue}break}if(!fa){break Sa}}i=(N(h,s)<<2)+g|0;h=r+(h<<5)|0;k=H[h+28>>2];H[i+24>>2]=H[h+24>>2];H[i+28>>2]=k;k=H[h+20>>2];H[i+16>>2]=H[h+16>>2];H[i+20>>2]=k;k=H[h+12>>2];H[i+8>>2]=H[h+8>>2];H[i+12>>2]=k;k=H[h+4>>2];H[i>>2]=H[h>>2];H[i+4>>2]=k}g=g+32|0;l=l-8|0;if(l>>>0>7){continue}break}break Oa}g=j>>>3|0;l=g>>>0<_>>>0?g:_;i=(j>>>0)/(l>>>0)&-8;t=j&-8;k=0;g=b;while(1){h=Fa(48);if(!h){break Ia}x=Ia(R);H[h>>2]=x;if(!x){k=0;Ra(V);Ca(h);Ca(r);break Ha}H[h+40>>2]=g;H[h+36>>2]=s;H[h+32>>2]=m;H[h+28>>2]=u;H[h+24>>2]=0;H[h+20>>2]=q;H[h+16>>2]=0;H[h+12>>2]=J;H[h+8>>2]=q;H[h+4>>2]=u;Y=t-N(i,k)|0;k=k+1|0;x=(l|0)==(k|0)?Y:i;H[h+44>>2]=x;db(V,13,h);g=(x<<2)+g|0;if((k|0)!=(l|0)){continue}break}Ra(V)}k=j&7;Ta:{if(!k){break Ta}x=J<<5;Ua:{if(!q){break Ua}t=r+x|0;i=k<<2;h=0;if((q|0)!=1){J=q&1;M=q&-2;l=0;while(1){Q=!i;if(!Q){B(t+(h<<6)|0,(N(h,s)<<2)+g|0,i)}if(!Q){Q=h|1;B(t+(Q<<6)|0,(N(s,Q)<<2)+g|0,i)}h=h+2|0;l=l+2|0;if((M|0)!=(l|0)){continue}break}if(!J){break Ua}}if(!i){break Ua}B(t+(h<<6)|0,(N(h,s)<<2)+g|0,i)}Va:{if((m|0)==(q|0)){break Va}t=aa-x|0;x=(N(q,s)<<2)+g|0;i=k<<2;h=0;if((Z|0)!=(W+(q^-1)|0)){q=u&1;u=u&-2;l=0;while(1){J=!i;if(!J){B(t+(h<<6)|0,x+(N(h,s)<<2)|0,i)}if(!J){J=h|1;B(t+(J<<6)|0,x+(N(s,J)<<2)|0,i)}h=h+2|0;l=l+2|0;if((u|0)!=(l|0)){continue}break}if(!q){break Va}}if(!i){break Va}B(t+(h<<6)|0,x+(N(h,s)<<2)|0,i)}Sa(A);if(!m){break Ta}i=k<<2;h=0;if((W|0)!=(Z+1|0)){q=m&1;k=m&-2;l=0;while(1){u=!i;if(!u){B((N(h,s)<<2)+g|0,r+(h<<5)|0,i)}if(!u){u=h|1;B((N(u,s)<<2)+g|0,r+(u<<5)|0,i)}h=h+2|0;l=l+2|0;if((k|0)!=(l|0)){continue}break}if(!q){break Ta}}if(!i){break Ta}B((N(h,s)<<2)+g|0,r+(h<<5)|0,i)}n=n+152|0;p=p-1|0;if(p){continue}break}k=1;Ca(r);break Ha}k=1;j=H[z+28>>2];J=j+N(m,152)|0;ga=J-152|0;if(H[ga>>2]==H[J-144>>2]){break Ha}Y=J-148|0;if(H[Y>>2]==H[J-140>>2]){break Ha}g=H[j+4>>2];n=H[j+12>>2];l=H[j>>2];i=H[j+8>>2];u=H[z+68>>2];V=H[z+64>>2];t=H[z+60>>2];Z=H[z+56>>2];S=Xb(z,m);if(!S){k=0;break Ha}if((m|0)==1){b=H[J-16>>2];g=H[ga>>2];j=H[Y>>2];h=H[J-8>>2];Ha(S,b-g|0,H[J-12>>2]-j|0,h-g|0,H[J-4>>2]-j|0,H[z+52>>2],1,h-b|0);Ua(S);break Ha}k=0;b=j;Wa:{if((m|0)!=2){h=m-1|0;p=h&1;q=h&-2;h=0;while(1){s=H[b+160>>2]-H[b+152>>2]|0;s=k>>>0>s>>>0?k:s;r=H[b+164>>2]-H[b+156>>2]|0;s=r>>>0>>0?s:r;r=H[b+312>>2]-H[b+304>>2]|0;s=r>>>0>>0?s:r;r=H[b+316>>2]-H[b+308>>2]|0;k=r>>>0>>0?s:r;b=b+304|0;h=h+2|0;if((q|0)!=(h|0)){continue}break}if(!p){break Wa}}h=H[b+160>>2]-H[b+152>>2]|0;h=h>>>0>>0?k:h;b=H[b+164>>2]-H[b+156>>2]|0;k=b>>>0>>0?h:b}Xa:{if(k>>>0>=134217728){break Xa}M=Ia(k<<5);H[A+32>>2]=M;if(!M){break Xa}H[A>>2]=M;Ya:{if(m){n=n-g|0;b=i-l|0;la=M+32|0;P=m;oa=H[z+20>>2];ka=1;ba=0;while(1){H[A+8>>2]=n;H[A+40>>2]=b;h=H[j+164>>2];m=H[j+160>>2];g=H[j+156>>2];l=H[j+152>>2];R=(l|0)%2|0;H[A+44>>2]=R;ea=(g|0)%2|0;H[A+12>>2]=ea;W=m-l|0;_=W-b|0;H[A+36>>2]=_;s=h-g|0;aa=s-n|0;H[A+4>>2]=aa;l=Z;x=l;r=t;h=r;g=V;k=g;m=u;q=m;Za:{if(!ba&(ka|0)==(oa|0)){break Za}C=oa-ka|0;h=0;x=0;if(l){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=l+(m^-1)|0;m=i^-1;l=g>>>0>>0?m+1|0:m;m=C&31;if((C&63)>>>0>=32){x=l>>>m|0}else{x=((1<>>m}}if(t){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=t+(h^-1)|0;h=i^-1;m=g>>>0>>0?h+1|0:h;h=C&31;if((C&63)>>>0>=32){h=m>>>h|0}else{h=((1<>>h}}m=0;g=0;if(V){g=C&31;if((C&63)>>>0>=32){i=-1<>>32-g}g=V+(l^-1)|0;l=i^-1;i=g>>>0>>0?l+1|0:l;l=C&31;if((C&63)>>>0>=32){g=i>>>l|0}else{g=((1<>>l}}if(u){m=C&31;if((C&63)>>>0>=32){i=-1<>>32-m}m=u+(l^-1)|0;l=i^-1;i=m>>>0>>0?l+1|0:l;l=C&31;if((C&63)>>>0>=32){m=i>>>l|0}else{m=((1<>>l}}k=0;l=0;Q=1<>>0>>0){l=C&31;if((C&63)>>>0>=32){i=-1<>>32-l}p=p^-1;l=p+(Z-Q|0)|0;i=i^-1;p=l>>>0

      >>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){l=p>>>i|0}else{l=((1<>>i}}if(Q>>>0>>0){i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}q=p^-1;p=q+(V-Q|0)|0;i=i^-1;q=q>>>0>p>>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){k=q>>>i|0}else{k=((1<>>i}}q=0;r=0;if(t>>>0>Q>>>0){i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}r=p^-1;p=r+(t-Q|0)|0;i=i^-1;r=p>>>0>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){r=r>>>i|0}else{r=((1<>>i}}if(u>>>0<=Q>>>0){break Za}i=C&31;if((C&63)>>>0>=32){i=-1<>>32-i}q=p^-1;p=q+(u-Q|0)|0;i=i^-1;q=q>>>0>p>>>0?i+1|0:i;i=C&31;if((C&63)>>>0>=32){q=q>>>i|0}else{q=((1<>>i}}i=H[j+180>>2];p=k-i|0;p=k>>>0>=p>>>0?p:0;k=p+4|0;p=k>>>0

      >>0?-1:k;fa=p>>>0<_>>>0?p:_;p=H[j+216>>2];k=g-p|0;g=g>>>0>=k>>>0?k:0;k=g+4|0;g=g>>>0>k>>>0?-1:k;Q=b>>>0>g>>>0?g:b;g=(R?fa:Q)<<1;k=(R?Q:fa)<<1|1;_=g>>>0>k>>>0?g:k;$=_>>>0>>0;g=l-i|0;g=g>>>0<=l>>>0?g:0;l=g-4|0;ca=g>>>0>=l>>>0?l:0;g=x-p|0;g=g>>>0<=x>>>0?g:0;l=g-4|0;C=g>>>0>=l>>>0?l:0;x=(R?ca:C)<<1;ma=(R?C:ca)<<1|1;ra=x>>>0>>0;i=H[j+184>>2];g=h-i|0;g=g>>>0<=h>>>0?g:0;h=g-4|0;g=g>>>0>=h>>>0?h:0;p=g;h=H[j+220>>2];l=r-h|0;l=l>>>0<=r>>>0?l:0;r=l-4|0;ja=l>>>0>=r>>>0?r:0;l=ja;i=m-i|0;m=i>>>0<=m>>>0?i:0;i=m+4|0;m=i>>>0>>0?-1:i;i=m>>>0>>0?m:n;k=i;h=q-h|0;h=h>>>0<=q>>>0?h:0;m=h+4|0;h=h>>>0>m>>>0?-1:m;ia=h>>>0>>0?h:aa;q=ia;if(ea){k=q;q=i;p=l;l=g}$=$?_:W;x=ra?x:ma;H[A+60>>2]=fa;H[A+56>>2]=ca;H[A+52>>2]=Q;H[A+48>>2]=C;_a:{if(s>>>0<8){m=7;h=0;break _a}h=R<<5;_=(la-h|0)+(ca<<6)|0;ma=_+28|0;aa=(h+M|0)+(C<<6)|0;ra=aa+28|0;ha=_+24|0;Aa=aa+24|0;Ka=_+20|0;sa=aa+20|0;Na=_+16|0;ta=aa+16|0;Oa=_+12|0;Qa=aa+12|0;va=_+8|0;wa=aa+8|0;xa=_+4|0;ua=aa+4|0;r=b+fa|0;R=b+ca|0;fa=n+ia|0;La=n+ja|0;Ma=M+(x<<5)|0;b=0;while(1){h=b|7;$a:{if(!(b>>>0>>0&h>>>0>=g>>>0|b>>>0>>0&h>>>0>=La>>>0)){h=b+8|0;break $a}h=b+1|0;Ha(S,C,b,Q,h,aa,16,0);Ha(S,R,b,r,h,_,16,0);m=b+2|0;Ha(S,C,h,Q,m,ua,16,0);Ha(S,R,h,r,m,xa,16,0);h=b+3|0;Ha(S,C,m,Q,h,wa,16,0);Ha(S,R,m,r,h,va,16,0);m=b+4|0;Ha(S,C,h,Q,m,Qa,16,0);Ha(S,R,h,r,m,Oa,16,0);h=b+5|0;Ha(S,C,m,Q,h,ta,16,0);Ha(S,R,m,r,h,Na,16,0);m=b+6|0;Ha(S,C,h,Q,m,sa,16,0);Ha(S,R,h,r,m,Ka,16,0);ca=b+7|0;Ha(S,C,m,Q,ca,Aa,16,0);Ha(S,R,m,r,ca,ha,16,0);h=b+8|0;Ha(S,C,ca,Q,h,ra,16,0);Ha(S,R,ca,r,h,ma,16,0);Sa(A+32|0);if(!Ya(S,x,b,$,h,Ma,8,1)){break Ya}}b=h;m=b|7;if(s>>>0>m>>>0){continue}break}}if(!(!(h>>>0>>0&g>>>0<=m>>>0)&(n+ia>>>0<=h>>>0|n+ja>>>0>m>>>0)|h>>>0>=s>>>0)){b=A+32|0;m=0;Q=s-h|0;if(Q){while(1){r=h+m|0;C=r+1|0;R=H[b+16>>2];_=m<<2;Ha(S,R,r,H[b+20>>2],C,_+((H[b>>2]+(H[b+12>>2]<<5)|0)+(R<<6)|0)|0,16,0);R=H[b+24>>2];aa=H[b+8>>2];Ha(S,R+aa|0,r,aa+H[b+28>>2]|0,C,(_+((H[b>>2]-(H[b+12>>2]<<5)|0)+(R<<6)|0)|0)+32|0,16,0);m=m+1|0;if((Q|0)!=(m|0)){continue}break}}Sa(b);if(!Ya(S,x,h,$,s,M+(x<<5)|0,8,1)){break Ya}}H[A+28>>2]=ia;H[A+24>>2]=ja;H[A+20>>2]=i;H[A+16>>2]=g;if(x>>>0<$>>>0){b=k<<1;h=q<<1|1;b=b>>>0>h>>>0?b:h;h=b>>>0>>0?b:s;b=ea<<5;m=(la-b|0)+(ja<<6)|0;q=(b+M|0)+(g<<6)|0;r=n+ia|0;n=n+ja|0;b=p<<1;l=l<<1|1;l=b>>>0>>0?b:l;p=M+(l<<5)|0;while(1){b=$-x|0;b=(b>>>0>=8?8:b)+x|0;Ha(S,x,g,b,i,q,1,16);Ha(S,x,n,b,r,m,1,16);Sa(A);if(!Ya(S,x,l,b,h,p,1,8)){break Ya}x=x+8|0;if($>>>0>x>>>0){continue}break}}j=j+152|0;n=s;b=W;ka=ka+1|0;ba=ka?ba:ba+1|0;if(ba|(P|0)!=(ka|0)){continue}break}}k=1;b=H[J-16>>2];g=H[ga>>2];j=H[Y>>2];h=H[J-8>>2];Ha(S,b-g|0,H[J-12>>2]-j|0,h-g|0,H[J-4>>2]-j|0,H[z+52>>2],1,h-b|0);Ua(S);Ca(M);break Ha}Ua(S);Ca(M);k=0;break Ha}Ua(S);k=0;break Ha}k=0;Ra(V);Ca(r)}na=A- -64|0;if(k){break W}break b}v=v+1080|0;E=E+52|0;z=z+76|0;o=o+1|0;if(o>>>0>2]){continue}break}v=H[D+32>>2];w=H[H[D+20>>2]>>2]}n=H[v+16>>2];ab:{if(H[D+68>>2]|!n){break ab}E=H[w+20>>2];g=H[E+28>>2];bb:{cb:{j=H[D+64>>2];if(j){o=H[w+16>>2];if(o>>>0<3){break bb}b=H[E+24>>2];if(!((b|0)==H[E+100>>2]&(b|0)==H[E+176>>2])){Ba(f,1,10089,0);break b}h=H[H[D+24>>2]+24>>2];m=H[h+36>>2];db:{if((m|0)!=H[h+88>>2]|(m|0)!=H[h+140>>2]){break db}h=N(b,152);b=h+g|0;b=N(H[b-140>>2]-H[b-148>>2]|0,H[b-144>>2]-H[b-152>>2]|0);g=h+H[E+104>>2]|0;if((b|0)!=(N(H[g-140>>2]-H[g-148>>2]|0,H[g-144>>2]-H[g-152>>2]|0)|0)){break db}g=h+H[E+180>>2]|0;if((N(H[g-140>>2]-H[g-148>>2]|0,H[g-144>>2]-H[g-152>>2]|0)|0)==(b|0)){break cb}}Ba(f,1,10089,0);break b}o=H[w+16>>2];if(o>>>0<3){break bb}h=H[H[D+24>>2]+24>>2];b=H[h+36>>2];eb:{if((b|0)!=H[h+88>>2]){break eb}h=H[h+140>>2];if((h|0)!=(b|0)){break eb}m=N(b,152);b=g+m|0;b=N(H[b+148>>2]-H[b+140>>2]|0,H[b+144>>2]-H[b+136>>2]|0);g=m+H[E+104>>2]|0;if((b|0)!=(N(H[g+148>>2]-H[g+140>>2]|0,H[g+144>>2]-H[g+136>>2]|0)|0)){break eb}g=H[E+180>>2]+N(h,152)|0;if((N(H[g+148>>2]-H[g+140>>2]|0,H[g+144>>2]-H[g+136>>2]|0)|0)==(b|0)){break cb}}Ba(f,1,10089,0);break b}if((n|0)==2){if(!H[v+5608>>2]){break ab}j=Fa(o<<2);if(!j){break b}l=H[w+16>>2];fb:{if(!l){break fb}gb:{hb:{if(H[D+64>>2]){o=l&3;g=0;if(l>>>0>=4){break hb}z=0;break gb}o=l&3;g=0;ib:{if(l>>>0<4){z=0;break ib}n=l&-4;z=0;h=0;while(1){m=j+(z<<2)|0;H[m>>2]=H[E+52>>2];H[m+4>>2]=H[E+128>>2];H[m+8>>2]=H[E+204>>2];H[m+12>>2]=H[E+280>>2];z=z+4|0;E=E+304|0;h=h+4|0;if((n|0)!=(h|0)){continue}break}if(!o){break fb}}while(1){H[j+(z<<2)>>2]=H[E+52>>2];z=z+1|0;E=E+76|0;g=g+1|0;if((o|0)!=(g|0)){continue}break}break fb}n=l&-4;z=0;h=0;while(1){m=j+(z<<2)|0;H[m>>2]=H[E+36>>2];H[m+4>>2]=H[E+112>>2];H[m+8>>2]=H[E+188>>2];H[m+12>>2]=H[E+264>>2];z=z+4|0;E=E+304|0;h=h+4|0;if((n|0)!=(h|0)){continue}break}if(!o){break fb}}while(1){H[j+(z<<2)>>2]=H[E+36>>2];z=z+1|0;E=E+76|0;g=g+1|0;if((o|0)!=(g|0)){continue}break}}h=H[v+5608>>2];g=j;n=0;i=Fa(l<<3);j=0;jb:{if(!i){break jb}if(!(!b|!l)){j=b;k=i+(l<<2)|0;s=l&-4;p=l&3;r=l>>>0<4;while(1){w=0;o=0;b=0;kb:{if(!r){while(1){b=w<<2;L[b+i>>2]=L[H[b+g>>2]>>2];m=b|4;L[m+i>>2]=L[H[g+m>>2]>>2];m=b|8;L[m+i>>2]=L[H[g+m>>2]>>2];b=b|12;L[b+i>>2]=L[H[b+g>>2]>>2];w=w+4|0;o=o+4|0;if((s|0)!=(o|0)){continue}break}b=w;if(!p){break kb}}w=0;while(1){m=b<<2;L[m+i>>2]=L[H[g+m>>2]>>2];b=b+1|0;w=w+1|0;if((p|0)!=(w|0)){continue}break}}m=0;b=h;while(1){u=m<<2;v=u+k|0;H[v>>2]=0;da=O(0);w=0;o=0;lb:{if(!r){while(1){q=i+(w<<2)|0;da=O(O(L[b>>2]*L[q>>2])+da);L[v>>2]=da;da=O(O(L[b+4>>2]*L[q+4>>2])+da);L[v>>2]=da;da=O(O(L[b+8>>2]*L[q+8>>2])+da);L[v>>2]=da;da=O(O(L[b+12>>2]*L[q+12>>2])+da);L[v>>2]=da;w=w+4|0;b=b+16|0;o=o+4|0;if((s|0)!=(o|0)){continue}break}o=w;if(!p){break lb}}w=0;while(1){da=O(O(L[b>>2]*L[i+(o<<2)>>2])+da);L[v>>2]=da;o=o+1|0;b=b+4|0;w=w+1|0;if((p|0)!=(w|0)){continue}break}}w=g+u|0;o=H[w>>2];H[w>>2]=o+4;L[o>>2]=da;m=m+1|0;if((l|0)!=(m|0)){continue}break}n=n+1|0;if((j|0)!=(n|0)){continue}break}}Ca(i);j=1}b=j;Ca(g);if(b){break ab}break b}if(H[H[v+5584>>2]+20>>2]==1){if(j){$b(H[E+36>>2],H[E+112>>2],H[E+188>>2],b);break ab}$b(H[E+52>>2],H[E+128>>2],H[E+204>>2],b);break ab}if(j){_b(H[E+36>>2],H[E+112>>2],H[E+188>>2],b);break ab}_b(H[E+52>>2],H[E+128>>2],H[E+204>>2],b);break ab}H[pa>>2]=o;Ba(f,1,10150,pa)}q=H[H[D+20>>2]>>2];if(!H[q+16>>2]){X=1;break b}s=H[D+68>>2];n=H[q+20>>2];b=H[H[D+32>>2]+5584>>2];m=H[H[D+24>>2]+24>>2];j=0;while(1){mb:{if(H[s+(j<<2)>>2]?0:s){break mb}h=H[n+28>>2];g=h+N(H[m+36>>2],152)|0;nb:{if(!H[D+64>>2]){h=H[g+148>>2]-H[g+140>>2]|0;w=H[g+144>>2]-H[g+136>>2]|0;l=0;o=52;break nb}h=h+N(H[n+24>>2],152)|0;w=H[g+8>>2]-H[g>>2]|0;l=H[h-144>>2]-(w+H[h-152>>2]|0)|0;h=H[g+12>>2]-H[g+4>>2]|0;o=36}g=H[m+24>>2];ob:{if(H[m+32>>2]){g=1<>2];if(H[b+20>>2]==1){v=w&-2;p=w&1;E=0;l=l<<2;while(1){o=0;pb:{if((w|0)!=1){while(1){g=H[b+1076>>2]+H[X>>2]|0;H[X>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;g=H[b+1076>>2]+H[X+4>>2]|0;H[X+4>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;X=X+8|0;o=o+2|0;if((v|0)!=(o|0)){continue}break}if(!p){break pb}}g=H[b+1076>>2]+H[X>>2]|0;H[X>>2]=(g|0)<(i|0)?i:(g|0)<(z|0)?g:z;X=X+4|0}X=l+X|0;E=E+1|0;if((E|0)!=(h|0)){continue}break}break mb}r=i>>31;g=0;while(1){o=0;while(1){da=L[X>>2];p=z;qb:{if(da>O(2147483648)){break qb}p=i;if(da>2];Xa=O(T(da));ob=O(da-Xa);if(obO(.5)){break rb}da=O(Xa*O(.5));Za=O(da-O(T(da)))==O(0)?Xa:Za}}p=~~Za;v=k+p|0;k=(k>>31)+(p>>31)|0;p=p>>>0>v>>>0?k+1|0:k;p=(p|0)<=(r|0)&i>>>0>v>>>0|(p|0)<(r|0)?i:v>>>0>>0&(p|0)<=0|(p|0)<0?v:z}H[X>>2]=p;X=X+4|0;o=o+1|0;if((w|0)!=(o|0)){continue}break}X=(l<<2)+X|0;g=g+1|0;if((h|0)!=(g|0)){continue}break}}n=n+76|0;b=b+1080|0;m=m+52|0;X=1;j=j+1|0;if(j>>>0>2]){continue}break}break b}X=0;Ba(f,1,3372,0)}na=pa+16|0;if(!X){ib(ya);H[a+8>>2]=H[a+8>>2]|32768;Ba(f,1,11451,0);break a}sb:{if(!c){break sb}b=0;m=H[a+232>>2];g=Ob(m,1);if(!((g|0)==-1|d>>>0>>0)){tb:{b=1;d=H[m+24>>2];if(!H[d+16>>2]){break tb}h=H[d+24>>2];n=H[H[H[m+20>>2]>>2]+20>>2];while(1){b=H[h+24>>2];j=b&7;l=b>>>3|0;d=H[n+28>>2];b=d+N(H[h+36>>2],152)|0;ub:{if(H[m+64>>2]){g=d+N(H[n+24>>2],152)|0;d=H[b+8>>2]-H[b>>2]|0;o=H[g-144>>2]-(d+H[g-152>>2]|0)|0;g=H[b+12>>2]-H[b+4>>2]|0;b=36;break ub}g=H[b+148>>2]-H[b+140>>2]|0;d=H[b+144>>2]-H[b+136>>2]|0;o=0;b=52}b=H[b+n>>2];vb:{wb:{switch((l+((j|0)!=0)|0)-1|0){case 2:case 3:if(!g){break vb}d=d<<2;if((g|0)!=1){j=g&1;l=g&-2;g=0;while(1){i=!d;if(!i){B(c,b,d)}w=o<<2;b=w+(b+d|0)|0;c=c+d|0;if(!i){B(c,b,d)}c=c+d|0;b=w+(b+d|0)|0;g=g+2|0;if((l|0)!=(g|0)){continue}break}if(!j){break vb}}if(d){B(c,b,d)}c=c+d|0;break vb;case 0:j=!g|!d;if(H[h+32>>2]){if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;xb:{if(!w){while(1){F[c|0]=H[b>>2];F[c+1|0]=H[b+4>>2];F[c+2|0]=H[b+8>>2];F[c+3|0]=H[b+12>>2];F[c+4|0]=H[b+16>>2];F[c+5|0]=H[b+20>>2];F[c+6|0]=H[b+24>>2];F[c+7|0]=H[b+28>>2];c=c+8|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break xb}}d=0;while(1){F[c|0]=H[b>>2];c=c+1|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}break vb}if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;o=o<<2;while(1){d=0;yb:{if(!w){while(1){F[c|0]=H[b>>2];F[c+1|0]=H[b+4>>2];F[c+2|0]=H[b+8>>2];F[c+3|0]=H[b+12>>2];F[c+4|0]=H[b+16>>2];F[c+5|0]=H[b+20>>2];F[c+6|0]=H[b+24>>2];F[c+7|0]=H[b+28>>2];c=c+8|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break yb}}d=0;while(1){F[c|0]=H[b>>2];c=c+1|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=b+o|0;j=j+1|0;if((j|0)!=(g|0)){continue}break};break vb;case 1:break wb;default:break vb}}j=!g|!d;if(H[h+32>>2]){if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;zb:{if(!w){while(1){G[c>>1]=H[b>>2];G[c+2>>1]=H[b+4>>2];G[c+4>>1]=H[b+8>>2];G[c+6>>1]=H[b+12>>2];G[c+8>>1]=H[b+16>>2];G[c+10>>1]=H[b+20>>2];G[c+12>>1]=H[b+24>>2];G[c+14>>1]=H[b+28>>2];c=c+16|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break zb}}d=0;while(1){G[c>>1]=H[b>>2];c=c+2|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}break vb}if(j){break vb}i=d&-8;l=d&7;j=0;w=d-1>>>0<7;while(1){d=0;Ab:{if(!w){while(1){G[c>>1]=H[b>>2];G[c+2>>1]=H[b+4>>2];G[c+4>>1]=H[b+8>>2];G[c+6>>1]=H[b+12>>2];G[c+8>>1]=H[b+16>>2];G[c+10>>1]=H[b+20>>2];G[c+12>>1]=H[b+24>>2];G[c+14>>1]=H[b+28>>2];c=c+16|0;b=b+32|0;d=d+8|0;if((i|0)!=(d|0)){continue}break}if(!l){break Ab}}d=0;while(1){G[c>>1]=H[b>>2];c=c+2|0;b=b+4|0;d=d+1|0;if((l|0)!=(d|0)){continue}break}}b=(o<<2)+b|0;j=j+1|0;if((j|0)!=(g|0)){continue}break}}n=n+76|0;h=h+52|0;b=1;pb=pb+1|0;if(pb>>>0>2]+16>>2]){continue}break}}}if(!b){break a}b=H[ya+5596>>2];if(!b){break sb}Ca(b);H[ya+5596>>2]=0;H[ya+5600>>2]=0}F[a+92|0]=I[a+92|0]&254;H[a+8>>2]=H[a+8>>2]&-129;kb=1;c=Pa(e);b=H[a+8>>2];if(!(c|qa)&(b|0)==64|(b|0)==256){break a}if((Ja(e,za+10|0,2,f)|0)!=2){Ba(f,H[a+208>>2]?1:2,2472,0);kb=!H[a+208>>2];break a}Da(za+10|0,za+12|0,2);b=H[za+12>>2];if((b|0)==65424){break a}if((b|0)==65497){H[a+8>>2]=256;H[a+228>>2]=0;break a}if(!(Pa(e)|qa)){H[a+8>>2]=64;Ba(f,2,8419,0);break a}kb=0;Ba(f,1,8306,0)}na=za+16|0;return kb|0}function bb(a,b,c,d,e,f,g,h,i,j,k){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;var l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,J=0,M=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,oa=0,pa=0,sa=O(0);s=na-80|0;na=s;H[s+40>>2]=65424;u=N(H[a+132>>2],H[a+128>>2]);a:{b:{c:{n=H[a+8>>2];d:{if((n|0)!=8){j=0;if((n|0)!=256){break a}H[s+40>>2]=65497;break d}if(F[a+92|0]&1){break d}T=u&-2;A=u&1;M=s+77|0;S=s+76|0;P=s+72|0;n=65424;e:{f:{while(1){g:{h:{i:{j:{k:{l:{m:{n:{o=H[a+84>>2];if(!o){break n}l=H[a+80>>2];if(o>>>0<=l>>>0){break n}n=H[a+88>>2]+(l<<3)|0;o=H[n>>2];n=H[n+4>>2];H[a+80>>2]=l+1;if(!ab(j,o,n,k)){Ba(k,1,5440,0);j=0;break a}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]==65424){break m}Ba(k,1,4073,0);j=0;break a}if((n|0)==65427){break l}}while(1){if(!(Pa(j)|qa)){H[a+8>>2]=64;break l}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+36|0,2);if(K[s+36>>2]<=1){Ba(k,1,6048,0);j=0;break a}o:{if(H[s+40>>2]!=32896){break o}if(Pa(j)|qa){break o}H[a+8>>2]=64;break l}m=H[a+8>>2];p:{if(!(m&16)){n=H[s+36>>2];break p}n=H[s+36>>2];l=H[a+24>>2];if(!l){break p}o=n+2|0;if(o>>>0>l>>>0){Ba(k,1,8370,0);j=0;break a}H[a+24>>2]=l-o}l=n-2|0;H[s+36>>2]=l;n=24912;w=H[s+40>>2];while(1){o=n;p=H[n>>2];if(p){n=n+12|0;if((p|0)!=(w|0)){continue}}break}if(!(m&H[o+4>>2])){Ba(k,1,5397,0);j=0;break a}q:{if(K[a+20>>2]>=l>>>0){n=H[a+16>>2];break q}n=Pa(j);m=qa;if((m|0)<0){l=1}else{l=(m|0)<=0&l>>>0>n>>>0}if(l){Ba(k,1,5797,0);j=0;break a}n=Ga(H[a+16>>2],H[s+36>>2]);if(!n){Ca(H[a+16>>2]);H[a+16>>2]=0;H[a+20>>2]=0;Ba(k,1,4973,0);j=0;break a}H[a+16>>2]=n;l=H[s+36>>2];H[a+20>>2]=l}n=Ja(j,n,l,k);if((n|0)!=H[s+36>>2]){Ba(k,1,2472,0);j=0;break a}l=H[o+8>>2];if(!l){Ba(k,1,11725,0);j=0;break a}if(!(ra[l|0](a,H[a+16>>2],n,k)|0)){H[s+32>>2]=H[s+40>>2];Ba(k,1,13959,s+32|0);j=0;break a}q=H[j+56>>2];m=H[s+36>>2];w=H[a+224>>2];l=H[w+40>>2];v=H[a+228>>2];r=N(v,40);n=l+r|0;x=H[n+20>>2];C=x+1|0;o=H[n+28>>2];r:{if(C>>>0<=o>>>0){n=H[n+24>>2];break r}l=~~O(O(o>>>0)+O(100))>>>0;H[n+28>>2]=l;n=Ga(H[n+24>>2],N(l,24));l=H[w+40>>2];o=r+l|0;if(!n){break k}H[o+24>>2]=n;x=H[o+20>>2];C=x+1|0}n=N(x,24)+n|0;H[n+16>>2]=m+4;o=(q-m|0)-4|0;H[n+8>>2]=o;H[n+12>>2]=o>>31;G[n>>1]=p;n=l+r|0;H[n+20>>2]=C;s:{if((p|0)!=65424){break s}l=H[n+16>>2];t:{if(!l){break t}p=H[n+12>>2];if(p>>>0>=K[n+4>>2]){break t}n=l+N(p,24)|0;H[n>>2]=o;H[n+4>>2]=0}n=(H[j+56>>2]-H[s+36>>2]|0)-4|0;l=H[a+48>>2];o=H[a+52>>2];if((o|0)>0){l=1}else{l=(o|0)>=0&l>>>0>=n>>>0}if(l){break s}H[a+48>>2]=n;H[a+52>>2]=0}if(I[a+92|0]&4){if((qb(j,H[a+24>>2],k)|0)!=H[a+24>>2]|qa){Ba(k,1,2472,0);j=0;break a}H[s+40>>2]=65427;break l}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]!=65427){continue}break}}if(!(!(Pa(j)|qa)&H[a+8>>2]==64)){n=I[a+92|0];if(!(n&4)){l=N(H[a+228>>2],5644);o=H[a+180>>2];u:{v:{if(H[a+56>>2]){n=Pa(j);break v}n=H[a+24>>2];if(n>>>0<2){break u}}n=n-2|0;H[a+24>>2]=n}l=l+o|0;if(!n){break j}o=Pa(j);p=qa;if((p|0)<0){m=1}else{m=(p|0)<=0&n>>>0>o>>>0}if(m){if(H[a+208>>2]){Ba(k,1,5842,0);j=0;break a}Ba(k,2,5842,0)}o=H[a+24>>2];if(o>>>0>=4294967294){Ba(k,1,1480,0);j=0;break a}p=H[l+5596>>2];w:{if(p){m=H[l+5600>>2];if(m>>>0>-3-o>>>0){Ba(k,1,1211,0);j=0;break a}o=Ga(p,(m+o|0)+2|0);if(o){H[l+5596>>2]=o;break j}Ca(H[l+5596>>2]);H[l+5596>>2]=0;break w}o=Fa(o+2|0);H[l+5596>>2]=o;if(o){break j}}Ba(k,1,6176,0);j=0;break a}H[a+8>>2]=8;F[a+92|0]=n&250;break i}n=H[s+40>>2];break g}Ca(H[o+24>>2]);a=H[w+40>>2]+N(v,40)|0;H[a+28>>2]=0;H[a+20>>2]=0;H[a+24>>2]=0;Ba(k,1,3863,0);j=0;break a}o=H[j+60>>2];w=o;r=H[j+56>>2];p=r-2|0;q=o-(r>>>0<2)|0;v=H[a+224>>2];E=H[v+40>>2];U=H[a+228>>2];D=N(U,40);o=E+D|0;m=H[o+16>>2];x:{if(!m){break x}x=H[o+12>>2];if(x>>>0>=K[o+4>>2]){break x}m=m+N(x,24)|0;H[m+8>>2]=p;H[m+12>>2]=q;x=r;r=H[a+24>>2];q=x+r|0;H[m+16>>2]=q;H[m+20>>2]=q>>>0>>0?w+1|0:w}m=H[a+24>>2];x=H[o+20>>2];C=x+1|0;w=H[o+28>>2];y:{if(C>>>0<=w>>>0){q=H[o+24>>2];break y}w=~~O(O(w>>>0)+O(100))>>>0;H[o+28>>2]=w;q=Ga(H[o+24>>2],N(w,24));E=H[v+40>>2];o=D+E|0;if(!q){break f}H[o+24>>2]=q;x=H[o+20>>2];C=x+1|0}o=N(x,24)+q|0;H[o+16>>2]=m+2;H[o+8>>2]=p;H[o+12>>2]=p>>31;G[o>>1]=65427;H[(D+E|0)+20>>2]=C;z:{if(n){q=Ja(j,H[l+5596>>2]+H[l+5600>>2]|0,H[a+24>>2],k);m=8;if((q|0)==H[a+24>>2]){break z}m=64;if((q|0)!=-1){break z}Ba(k,1,2472,0);j=0;break a}q=0;m=H[a+24>>2]?64:8}H[a+8>>2]=m;H[l+5600>>2]=H[l+5600>>2]+q;A:{if(F[a+92|0]&1){break A}n=H[a+44>>2];if(H[a+76>>2]|((n|0)<0|(n|0)!=H[a+228>>2])){break A}if(!zb(j)){break A}l=H[a+228>>2];o=H[a+180>>2]+N(l,5644)|0;n=H[o+5592>>2];l=H[H[a+224>>2]+40>>2]+N(l,40)|0;if((n|0)!=H[l+4>>2]){break A}o=H[o+5588>>2]+1|0;if(n>>>0<=o>>>0){break A}B:{n=H[l+16>>2]+N(o,24)|0;l=H[n>>2];n=H[n+4>>2];if((l|0)==H[j+56>>2]&(n|0)==H[j+60>>2]){break B}if(ab(j,l,n,k)){break B}Ba(k,1,5440,0);j=0;break a}if((Ja(j,H[a+16>>2],2,k)|0)!=2){Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2);if(H[s+40>>2]==65424){break h}Ba(k,1,4073,0);j=0;break a}n=I[a+92|0];if((n&9)!=1){break i}F[a+92|0]=n|8;o=H[a+228>>2];if(H[(H[a+180>>2]+N(o,5644)|0)+5592>>2]==1){break i}if(!zb(j)){break i}n=H[j+60>>2];p=n;m=H[j+56>>2];if((n&m)==-1){break i}C:{while(1){n=1;l=s+70|0;if((Ja(j,l,2,k)|0)!=2){break C}Da(l,s- -64|0,2);if(H[s+64>>2]!=65424){break C}q=2472;if((Ja(j,l,2,k)|0)!=2){break c}Da(l,s+60|0,2);if(H[s+60>>2]!=10){q=6048;break c}H[s+60>>2]=8;l=Ja(j,s+70|0,8,k);if((l|0)!=H[s+60>>2]){break c}if((l|0)!=8){q=4047;break c}Da(s+70|0,s+56|0,2);Da(P,s+52|0,4);Da(S,s+48|0,1);Da(M,s+44|0,1);if((o|0)!=H[s+56>>2]){l=H[s+52>>2];if(l>>>0<14){break C}l=l-12|0;H[s+52>>2]=l;l=qb(j,l,k);if(!qa&H[s+52>>2]==(l|0)){continue}break C}break}n=H[s+48>>2]!=H[s+44>>2]}if(!hc(j,m,p,k)){break b}if(n){break i}F[a+92|0]=I[a+92|0]&238|16;D:{if(!u){break D}o=H[a+180>>2];q=0;n=0;if((u|0)!=1){while(1){l=o+N(q,5644)|0;p=H[l+5592>>2];if(p){H[l+5592>>2]=p+1}p=H[l+11236>>2];if(p){H[l+11236>>2]=p+1}q=q+2|0;n=n+2|0;if((T|0)!=(n|0)){continue}break}if(!A){break D}}n=o+N(q,5644)|0;l=H[n+5592>>2];if(!l){break D}H[n+5592>>2]=l+1}Ba(k,2,9035,0)}if(F[a+92|0]&1){break h}if((Ja(j,H[a+16>>2],2,k)|0)!=2){if(!(!u|(u|0)!=(H[a+228>>2]+1|0))){j=H[a+180>>2];n=0;while(1){l=j+N(n,5644)|0;if(!(H[l+5588>>2]|H[l+5592>>2])){break e}n=n+1|0;if((u|0)!=(n|0)){continue}break}}Ba(k,1,2472,0);j=0;break a}Da(H[a+16>>2],s+40|0,2)}n=H[s+40>>2];if(F[a+92|0]&1){break g}if((n|0)!=65497){continue}}break}if(H[a+8>>2]==256|(n|0)!=65497){break d}H[a+8>>2]=256;H[a+228>>2]=0;break d}Ca(H[o+24>>2]);a=H[v+40>>2]+N(U,40)|0;H[a+28>>2]=0;H[a+20>>2]=0;H[a+24>>2]=0;Ba(k,1,3863,0);j=0;break a}H[s+16>>2]=n;Ba(k,4,11004,s+16|0);H[a+228>>2]=n;H[s+40>>2]=65497;H[a+8>>2]=256}n=H[a+228>>2];j=H[a+180>>2];E:{F:{if(F[a+92|0]&1){break F}G:{H:{if(n>>>0>=u>>>0){break H}q=j+N(n,5644)|0;while(1){if(H[q+5596>>2]){break H}n=n+1|0;H[a+228>>2]=n;q=q+5644|0;if((n|0)!=(u|0)){continue}break}break G}if((n|0)!=(u|0)){break F}}H[i>>2]=0;break E}I:{J:{o=j+N(n,5644)|0;if(H[o+5172>>2]){a=6837}else{if(!(I[o+5640|0]&2)){break I}l=H[o+5160>>2];K:{if(!l){q=0;break K}p=l&3;m=H[o+5164>>2];j=0;q=0;n=0;if(l>>>0>=4){w=l&-4;l=0;while(1){u=m+(n<<3)|0;q=H[u+28>>2]+(H[u+20>>2]+(H[u+12>>2]+(H[u+4>>2]+q|0)|0)|0)|0;n=n+4|0;l=l+4|0;if((w|0)!=(l|0)){continue}break}if(!p){break K}}while(1){q=H[(m+(n<<3)|0)+4>>2]+q|0;n=n+1|0;j=j+1|0;if((p|0)!=(j|0)){continue}break}}j=Fa(q);H[o+5172>>2]=j;if(j){break J}a=4009}Ba(k,1,a,0);Ba(k,1,8059,0);j=0;break a}H[o+5180>>2]=q;q=H[o+5164>>2];j=H[o+5160>>2];if(j){l=0;n=0;while(1){u=n<<3;p=u+q|0;m=H[p>>2];if(m){j=H[p+4>>2];if(j){B(H[o+5172>>2]+l|0,m,j)}j=u+H[o+5164>>2]|0;p=H[j+4>>2];Ca(H[j>>2]);q=H[o+5164>>2];j=u+q|0;H[j>>2]=0;H[j+4>>2]=0;l=l+p|0;j=H[o+5160>>2]}n=n+1|0;if(n>>>0>>0){continue}break}}H[o+5160>>2]=0;Ca(q);H[o+5164>>2]=0;H[o+5168>>2]=H[o+5172>>2];H[o+5176>>2]=H[o+5180>>2]}n=H[a+232>>2];U=H[n+28>>2];l=H[a+228>>2];C=H[(H[U+76>>2]+N(l,5644)|0)+5584>>2];j=H[n+24>>2];W=H[j+24>>2];u=H[U+24>>2];o=(l>>>0)/(u>>>0)|0;T=H[H[n+20>>2]>>2];p=H[U+12>>2];n=H[U+4>>2]+N(p,l-N(o,u)|0)|0;l=H[j>>2];l=l>>>0>>0?n:l;H[T>>2]=l;u=n+p|0;n=n>>>0>u>>>0?-1:u;u=H[j+8>>2];n=n>>>0>>0?n:u;H[T+8>>2]=n;L:{M:{if(!((l|0)<(n|0)&(l|0)>=0)){Ba(k,1,6682,0);break M}q=H[T+20>>2];u=H[U+16>>2];n=H[U+8>>2]+N(o,u)|0;l=H[j+4>>2];l=l>>>0>>0?n:l;H[T+4>>2]=l;o=n+u|0;n=n>>>0>o>>>0?-1:o;j=H[j+12>>2];j=j>>>0>n>>>0?n:j;H[T+12>>2]=j;if(!((j|0)>(l|0)&(l|0)>=0)){Ba(k,1,6644,0);break M}N:{if(H[C+4>>2]){if(H[T+16>>2]){break N}j=1;break L}Ba(k,1,5358,0);break M}O:{P:{while(1){H[W+36>>2]=0;j=H[W>>2];l=j>>31;p=j-1|0;n=H[T>>2];o=p+n|0;m=l-!j|0;u=m+(n>>31)|0;oa=q,pa=ee(o,n>>>0>o>>>0?u+1|0:u,j,l),H[oa>>2]=pa;n=H[W+4>>2];o=n>>31;u=n-1|0;w=H[T+4>>2];r=u+w|0;v=o-!n|0;x=v+(w>>31)|0;oa=q,pa=ee(r,r>>>0>>0?x+1|0:x,n,o),H[oa+4>>2]=pa;x=p;p=H[T+8>>2];w=x+p|0;m=(p>>31)+m|0;oa=q,pa=ee(w,p>>>0>w>>>0?m+1|0:m,j,l),H[oa+8>>2]=pa;j=H[T+12>>2];H[q+16>>2]=ba;l=v+(j>>31)|0;j=j+u|0;l=j>>>0>>0?l+1|0:l;oa=q,pa=ee(j,l,n,o),H[oa+12>>2]=pa;j=H[C+4>>2];H[q+20>>2]=j;n=H[U+80>>2];H[q+24>>2]=j>>>0>>0?1:j-n|0;Ca(H[q+52>>2]);H[q+68>>2]=0;H[q+60>>2]=0;H[q+64>>2]=0;H[q+52>>2]=0;H[q+56>>2]=0;j=N(j,152);n=H[q+28>>2];Q:{if(!n){n=Fa(j);H[q+28>>2]=n;if(!n){break M}H[q+32>>2]=j;if(!j){break Q}y(n,0,j);break Q}if(j>>>0<=K[q+32>>2]){break Q}n=Ga(n,j);if(!n){Ba(k,1,3090,0);Ca(H[q+28>>2]);H[q+28>>2]=0;H[q+32>>2]=0;break M}H[q+28>>2]=n;l=H[q+32>>2];o=j-l|0;if(o){y(l+n|0,0,o)}H[q+32>>2]=j}j=H[q+20>>2];if(j){da=C+944|0;ea=C+812|0;$=C+28|0;x=H[q+28>>2];X=0;while(1){n=j-1|0;l=n&31;if((n&63)>>>0>=32){l=-1<>>32-l}o=o^-1;m=H[q>>2];p=o+m|0;u=l^-1;l=u+(m>>31)|0;m=m>>>0>p>>>0?l+1|0:l;l=n&31;if((n&63)>>>0>=32){r=m>>l}else{r=((1<>>l}H[x>>2]=r;p=H[q+4>>2];l=p+o|0;m=(p>>31)+u|0;m=l>>>0

      >>0?m+1|0:m;p=n&31;if((n&63)>>>0>=32){v=m>>p}else{v=((1<>>p}H[x+4>>2]=v;p=H[q+8>>2];l=p+o|0;m=(p>>31)+u|0;m=l>>>0

      >>0?m+1|0:m;p=n&31;if((n&63)>>>0>=32){m=m>>p}else{m=((1<>>p}H[x+8>>2]=m;p=H[q+12>>2];l=p+o|0;w=(p>>31)+u|0;w=l>>>0

      >>0?w+1|0:w;p=n&31;if((n&63)>>>0>=32){w=w>>p}else{w=((1<>>p}H[x+12>>2]=w;D=m>>31;A=X<<2;E=H[A+ea>>2];l=E&31;if((E&63)>>>0>=32){l=1<>>32-l}M=p;p=M+m|0;S=p-1|0;l=l+D|0;p=(p>>>0>>0?l+1|0:l)-!p|0;l=E&31;if((E&63)>>>0>=32){l=p>>l}else{l=((1<>>l}M=l<>31;D=H[A+da>>2];l=D&31;if((D&63)>>>0>=32){l=-1<>>32-l}A=p^-1;p=A+w|0;l=(l^-1)+S|0;A=p>>>0>>0?l+1|0:l;l=D&31;if((D&63)>>>0>=32){l=A>>l}else{l=((1<>>l}l=l<>D:0;H[x+20>>2]=p;Z=r&-1<>E:0;H[x+16>>2]=l;be(l,0,p);if(!(!l|!qa)){break P}_=N(l,p);if(_>>>0>=107374183){break P}A=N(_,40);if(X){D=D-1|0;E=E-1|0;l=Y>>31;m=l;p=l+1|0;l=Y+1|0;Y=((l?m:p)&1)<<31|l>>>1;l=Z>>31;m=l;p=l+1|0;l=Z+1|0;Z=((l?m:p)&1)<<31|l>>>1;l=3}else{l=1}H[x+24>>2]=l;fa=1<>>0>=32){l=1<>>32-l}ca=j;w=l;j=H[C+12>>2];M=j>>>0>>0?j:D;j=M&31;if((M&63)>>>0>=32){l=-1<>>32-j}ha=m^-1;ia=l^-1;j=H[C+8>>2];S=j>>>0>>0?j:E;j=S&31;if((S&63)>>>0>=32){l=-1<>>32-j}ja=m^-1;ka=l^-1;aa=0;while(1){R:{if(!X){l=H[q+4>>2];j=l+o|0;m=(l>>31)+u|0;m=j>>>0>>0?m+1|0:m;l=n&31;if((n&63)>>>0>=32){Q=m>>l}else{Q=((1<>>l}l=H[q>>2];j=l+o|0;m=(l>>31)+u|0;m=j>>>0>>0?m+1|0:m;l=n&31;if((n&63)>>>0>=32){z=m>>l}else{z=((1<>>l}j=0;m=o;P=m;l=u;R=l;r=n;break R}j=aa+1|0;r=j>>>1|0;m=n&31;if((n&63)>>>0>=32){l=r<>>32-m;m=r<>2];m=P+r|0;l=(l^-1)+w|0;R=t>>>0>P>>>0?l+1|0:l;l=R+(r>>31)|0;r=m>>>0>>0?l+1|0:l;l=p&31;if((p&63)>>>0>=32){Q=r>>l}else{Q=((1<>>l}r=j&1;m=n&31;if((n&63)>>>0>=32){l=r<>>32-m;m=r<>2];r=m+t|0;l=(l^-1)+w|0;l=m>>>0>>0?l+1|0:l;z=l+(t>>31)|0;z=r>>>0>>0?z+1|0:z;t=p&31;if((p&63)>>>0>=32){z=z>>t}else{z=((1<>>t}r=p}t=r;J=H[q+8>>2];V=J>>31;r=H[q+12>>2];H[v+4>>2]=Q;H[v>>2]=z;H[v+16>>2]=j;Q=(r>>31)+R|0;z=r;r=r+P|0;Q=z>>>0>r>>>0?Q+1|0:Q;P=t&31;if((t&63)>>>0>=32){r=Q>>P}else{r=((1<>>P}H[v+12>>2]=r;l=l+V|0;z=l+1|0;r=l;l=m+J|0;r=l>>>0>>0?z:r;m=t&31;if((t&63)>>>0>=32){l=r>>m}else{l=((1<>>m}H[v+8>>2]=l;l=H[W+24>>2]+(!H[C+20>>2]|!j?0:(j|0)==3?2:1)|0;j=H[$>>2];oa=v,sa=O((+H[$+4>>2]*.00048828125+1)*tc(l-j|0)),L[oa+32>>2]=sa;H[v+28>>2]=(j+H[C+804>>2]|0)-1;j=H[v+20>>2];S:{T:{if(!(j|!_)){j=Fa(A);H[v+20>>2]=j;if(!j){Ba(k,1,2854,0);break M}if(A){y(j,0,A)}H[v+24>>2]=A;break T}if(A>>>0>K[v+24>>2]){j=Ga(j,A);if(!j){Ba(k,1,2854,0);Ca(H[v+20>>2]);H[v+20>>2]=0;H[v+24>>2]=0;break M}H[v+20>>2]=j;l=H[v+24>>2];m=A-l|0;if(m){y(j+l|0,0,m)}H[v+24>>2]=A}if(!_){break S}}j=H[v+20>>2];P=0;while(1){m=H[x+16>>2];l=(P>>>0)/(m>>>0)|0;m=(P-N(l,m)<>2];Q=(m|0)>(r|0)?m:r;H[j>>2]=Q;l=(l<>2];z=(l|0)>(r|0)?l:r;H[j+4>>2]=z;m=m+ga|0;r=H[v+8>>2];m=(m|0)<(r|0)?m:r;H[j+8>>2]=m;l=l+fa|0;r=H[v+12>>2];r=(l|0)<(r|0)?l:r;H[j+12>>2]=r;l=(m>>31)+ka|0;R=l+1|0;t=l;l=m+ja|0;t=m>>>0>l>>>0?R:t;Q=Q>>S;m=S&31;if((S&63)>>>0>=32){l=t>>m}else{l=((1<>>m}t=l-Q<>S;H[j+16>>2]=t;l=(r>>31)+ia|0;R=l+1|0;m=l;l=r+ha|0;r=l>>>0>>0?R:m;z=z>>M;m=M&31;if((M&63)>>>0>=32){l=r>>m}else{l=((1<>>m}l=l-z<>M;H[j+20>>2]=l;r=N(l,t);be(r,0,68);if(qa){Ba(k,1,2935,0);break M}l=N(r,68);m=H[j+24>>2];U:{V:{if(!(m|!r)){m=Fa(l);H[j+24>>2]=m;if(!m){break M}if(!l){break V}y(m,0,l);break V}if(l>>>0<=K[j+28>>2]){break U}m=Ga(m,l);if(!m){Ca(H[j+24>>2]);H[j+24>>2]=0;H[j+28>>2]=0;Ba(k,1,2549,0);break M}H[j+24>>2]=m;t=H[j+28>>2];R=l-t|0;if(!R){break V}y(m+t|0,0,R)}H[j+28>>2]=l}l=H[j+20>>2];m=H[j+16>>2];t=H[j+32>>2];W:{if(!t){l=dc(m,l,k);break W}l=bc(t,m,l,k)}H[j+32>>2]=l;l=H[j+20>>2];m=H[j+16>>2];t=H[j+36>>2];X:{if(!t){l=dc(m,l,k);break X}l=bc(t,m,l,k)}H[j+36>>2]=l;if(r){l=0;while(1){V=H[j+16>>2];R=(l>>>0)/(V>>>0)|0;m=H[j+24>>2]+N(l,68)|0;J=H[m>>2];Y:{if(J){la=H[m+56>>2];ma=H[m+4>>2];t=H[m+48>>2];Ca(H[m+60>>2]);H[m+48>>2]=0;H[m+52>>2]=0;H[m+64>>2]=0;H[m+56>>2]=0;H[m+60>>2]=0;H[m+40>>2]=0;H[m+44>>2]=0;H[m+32>>2]=0;H[m+36>>2]=0;H[m+24>>2]=0;H[m+28>>2]=0;H[m+16>>2]=0;H[m+20>>2]=0;H[m+8>>2]=0;H[m+12>>2]=0;H[m>>2]=J;H[m+48>>2]=t;Z:{if(!t){break Z}t=N(t,24);if(!t){break Z}y(J,0,t)}H[m+56>>2]=la;H[m+4>>2]=ma;break Y}t=Ea(10,24);H[m>>2]=t;if(!t){break M}H[m+48>>2]=10}t=(l-N(R,V)|0)+Q|0;J=t<>2];H[m+8>>2]=(J|0)>(V|0)?J:V;R=z+R|0;J=R<>2];H[m+12>>2]=(J|0)>(V|0)?J:V;t=t+1<>2];H[m+16>>2]=(t|0)<(J|0)?t:J;J=m;m=R+1<>2];H[J+20>>2]=(m|0)<(t|0)?m:t;l=l+1|0;if((r|0)!=(l|0)){continue}break}}j=j+40|0;P=P+1|0;if((P|0)!=(_|0)){continue}break}}$=$+8|0;v=v+36|0;aa=aa+1|0;if(aa>>>0>2]){continue}break}x=x+152|0;j=n;X=X+1|0;if(X>>>0>2]){continue}break}}W=W+52|0;q=q+76|0;C=C+1080|0;ba=ba+1|0;if(ba>>>0>2]){continue}break}j=1;break L}Ba(k,1,2982,0);break M}Ba(k,1,2373,0)}j=0}if(!j){Ba(k,1,3668,0);j=0;break a}j=H[a+228>>2];H[s+4>>2]=N(H[a+128>>2],H[a+132>>2]);H[s>>2]=j+1;Ba(k,4,11825,s);H[b>>2]=H[a+228>>2];H[i>>2]=1;if(c){b=Ob(H[a+232>>2],0);H[c>>2]=b;j=0;if((b|0)==-1){break a}}b=H[H[H[a+232>>2]+20>>2]>>2];H[d>>2]=H[b>>2];H[e>>2]=H[b+4>>2];H[f>>2]=H[b+8>>2];H[g>>2]=H[b+12>>2];H[h>>2]=H[b+16>>2];H[a+8>>2]=H[a+8>>2]|128}j=1;break a}Ba(k,1,q,0)}Ba(k,1,3702,0);j=0}a=j;na=s+80|0;return a|0}function Sb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,L=0,M=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{switch(H[a+84>>2]){case 0:k:{b=H[a+52>>2];c=H[a+196>>2];if(b>>>0>>0){d=H[a+64>>2];if(d>>>0>>0){break k}}Ba(H[a+236>>2],1,8491,0);break b}if(!H[a+44>>2]){p=H[a+36>>2];c=0;break i}H[a+44>>2]=0;q=H[a+68>>2];c=1;break i;case 1:l:{b=H[a+52>>2];c=H[a+196>>2];if(b>>>0>>0){d=H[a+64>>2];if(d>>>0>>0){break l}}Ba(H[a+236>>2],1,8536,0);break b}if(!H[a+44>>2]){n=H[a+36>>2];c=0;break e}H[a+44>>2]=0;w=H[a+48>>2];c=1;break e;case 2:m:{B=H[a+52>>2];z=H[a+196>>2];if(B>>>0>>0){P=H[a+64>>2];if(P>>>0>>0){break m}}Ba(H[a+236>>2],1,8671,0);break b}if(!H[a+44>>2]){p=H[a+40>>2];break f}H[a+228>>2]=0;H[a+232>>2]=0;H[a+44>>2]=0;i=H[a+200>>2];while(1){O=i+(w<<4)|0;j=H[O+8>>2];if(j){v=H[O+12>>2];c=0;while(1){f=j+(c^-1)|0;d=v+(c<<4)|0;s=f+H[d>>2]|0;n:{if(s>>>0>31){break n}b=H[O>>2];if(b>>>0>-1>>>s>>>0){break n}b=b<>>0>n>>>0?n:b:b;H[a+228>>2]=n}f=f+H[d+4>>2]|0;o:{if(f>>>0>31){break o}b=H[O+4>>2];if(b>>>0>-1>>>f>>>0){break o}b=b<>>0>t>>>0?t:b:b;H[a+232>>2]=t}c=c+1|0;if((j|0)!=(c|0)){continue}break}}w=w+1|0;if((z|0)!=(w|0)){continue}break};if(!n|!t){break d}if(!I[a|0]){H[a+108>>2]=H[a+208>>2];H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}n=H[a+48>>2];c=1;break f;case 3:p:{B=H[a+52>>2];j=H[a+196>>2];if(B>>>0>>0){P=H[a+64>>2];if(P>>>0>>0){break p}}Ba(H[a+236>>2],1,8626,0);break b}if(!H[a+44>>2]){w=H[a+200>>2];e=H[a+28>>2];Q=w+(e<<4)|0;E=H[a+40>>2];break g}H[a+228>>2]=0;H[a+232>>2]=0;H[a+44>>2]=0;w=H[a+200>>2];while(1){z=(q<<4)+w|0;s=H[z+8>>2];if(s){v=H[z+12>>2];c=0;while(1){f=s+(c^-1)|0;d=v+(c<<4)|0;i=f+H[d>>2]|0;q:{if(i>>>0>31){break q}b=H[z>>2];if(b>>>0>-1>>>i>>>0){break q}b=b<>>0>p>>>0?p:b:b;H[a+228>>2]=p}f=f+H[d+4>>2]|0;r:{if(f>>>0>31){break r}b=H[z+4>>2];if(b>>>0>-1>>>f>>>0){break r}b=b<>>0>n>>>0?n:b:b;H[a+232>>2]=n}c=c+1|0;if((s|0)!=(c|0)){continue}break}}q=q+1|0;if((j|0)!=(q|0)){continue}break};if(!p|!n){break d}s:{if(I[a|0]){n=H[a+108>>2];break s}n=H[a+208>>2];H[a+108>>2]=n;H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}c=1;break g;case 4:break j;default:break d}}t:{q=H[a+52>>2];b=H[a+196>>2];if(q>>>0>>0){k=H[a+64>>2];if(k>>>0>>0){break t}}Ba(H[a+236>>2],1,8581,0);break d}if(!H[a+44>>2]){q=H[a+28>>2];r=H[a+200>>2]+(q<<4)|0;t=H[a+40>>2];break h}H[a+28>>2]=q;H[a+44>>2]=0;e=1;break h}u:while(1){v:{w:{if(!c){p=p+1|0;break w}H[a+40>>2]=q;if(K[a+56>>2]<=q>>>0){break b}n=H[a+48>>2];c=0;break v}c=1}x:while(1){y:{z:{A:{B:{if(!c){H[a+32>>2]=n;if(K[a+60>>2]<=n>>>0){break B}H[a+28>>2]=b;c=b;e=0;break y}H[a+36>>2]=p;if(K[a+76>>2]<=p>>>0){c=H[a+28>>2];e=1;break y}c=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],H[a+40>>2])|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],p)|0;if(c>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(c<<1)|0;if(J[c>>1]){break A}break a}q=H[a+40>>2]+1|0;break z}c=0;continue u}c=1;continue u}while(1){C:{D:{E:{if(!e){if(c>>>0>=d>>>0){break E}e=H[a+32>>2];f=H[a+200>>2]+(c<<4)|0;if(e>>>0>=K[f+8>>2]){break C}if(!I[a|0]){c=H[f+12>>2]+(e<<4)|0;H[a+76>>2]=N(H[c+12>>2],H[c+8>>2])}p=H[a+72>>2];c=1;continue x}c=c+1|0;H[a+28>>2]=c;break D}n=H[a+32>>2]+1|0;c=0;continue x}e=0;continue}e=1;continue}}}}F:while(1){G:{H:{if(!e){t=t+1|0;H[a+40>>2]=t;break H}if(k>>>0<=q>>>0){break b}H[a+228>>2]=0;H[a+232>>2]=0;r=H[a+200>>2]+(q<<4)|0;s=H[r+8>>2];if(!s){break b}v=H[r+12>>2];p=0;n=0;e=0;while(1){f=s+(e^-1)|0;d=v+(e<<4)|0;i=f+H[d>>2]|0;I:{if(i>>>0>31){break I}b=H[r>>2];if(b>>>0>-1>>>i>>>0){break I}b=b<>>0>n>>>0?n:b:b;H[a+228>>2]=n}f=f+H[d+4>>2]|0;J:{if(f>>>0>31){break J}b=H[r+4>>2];if(b>>>0>-1>>>f>>>0){break J}b=b<>>0>p>>>0?p:b:b;H[a+232>>2]=p}e=e+1|0;if((s|0)!=(e|0)){continue}break}if(!n|!p){break d}K:{if(I[a|0]){w=H[a+108>>2];break K}w=H[a+208>>2];H[a+108>>2]=w;H[a+100>>2]=H[a+204>>2];H[a+112>>2]=H[a+216>>2];H[a+104>>2]=H[a+212>>2]}e=0;break G}e=1}L:while(1){M:{N:{O:{P:{if(!e){H[a+224>>2]=w;if(K[a+112>>2]<=w>>>0){break P}Q=H[a+100>>2];e=0;break M}if(K[a+56>>2]<=t>>>0){c=H[a+32>>2];e=1;break M}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],t)|0)+N(H[a+20>>2],q)|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}b=H[a+4>>2]+(b<<1)|0;if(J[b>>1]){break O}G[b>>1]=1;return 1}q=q+1|0;H[a+28>>2]=q;break N}e=0;continue F}e=1;continue F}while(1){Q:{R:{S:{T:{if(!e){H[a+220>>2]=Q;if(K[a+104>>2]<=Q>>>0){break S}c=H[a+48>>2];break T}c=c+1|0}H[a+32>>2]=c;b=H[a+60>>2];d=H[r+8>>2];if((b>>>0>>0?b:d)>>>0>c>>>0){f=H[r>>2];e=f;o=d+(c^-1)|0;l=o;d=l&31;if((l&63)>>>0>=32){b=f<>>32-d;x=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break Q}b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}e=H[r+4>>2];if((b&e)!=(e|0)){break Q}d=l&31;if((l&63)>>>0>=32){b=e<>>32-d;y=e<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,y,b);b=h;L=H[a+208>>2];d=F+L|0;b=L>>>0>d>>>0?b+1|0:b;s=fe(d,b,y,C);B=x-1|0;i=H[a+212>>2];j=B+i|0;d=g-!x|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,x,g);A=H[a+204>>2];i=A+B|0;b=A>>>0>i>>>0?b+1|0:b;i=fe(i,b,x,g);D=H[r+12>>2]+(c<<4)|0;M=H[D>>2];u=M+o|0;b=u&31;if((u&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break Q}h=e;P=H[D+4>>2];o=P+o|0;m=o&31;if((o&63)>>>0>=32){b=e<>>32-m;m=e<>>0>=32){e=b>>>j|0}else{e=((1<>>j}if((h|0)!=(e|0)){break Q}j=H[a+224>>2];m=!!(ge(j,m,b)|qa);b=o&31;if((o&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=o<>>32-m|b<>2];if((u&63)>>>0>=32){b=f<>>32-o;f=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=i<>>32-u|b<>2];if(!o|(!H[D+12>>2]|(i|0)==(z|0))){break Q}if((s|0)==(O|0)){break Q}t=H[a+68>>2];H[a+40>>2]=t;b=d;e=e+B|0;b=e>>>0>>0?b+1|0:b;f=(fe(e,b,x,g)>>>M)-(i>>>M)|0;b=v;e=j+F|0;b=e>>>0>>0?b+1|0:b;T=a,U=N(o,(fe(e,b,y,C)>>>P)-(s>>>P)|0)+f|0,H[T+36>>2]=U;e=1;continue L}e=H[a+220>>2];b=H[a+228>>2];Q=e+b-(e>>>0)%(b>>>0)|0;break R}e=H[a+224>>2];b=H[a+232>>2];w=e+b-(e>>>0)%(b>>>0)|0;e=0;continue L}e=0;continue}e=1;continue}}}}U:while(1){V:{W:{if(!c){E=E+1|0;H[a+40>>2]=E;break W}H[a+224>>2]=n;if(K[a+112>>2]<=n>>>0){break b}x=H[a+100>>2];c=0;break V}c=1}X:while(1){Y:{Z:{_:{$:{if(!c){H[a+220>>2]=x;if(K[a+104>>2]<=x>>>0){break $}H[a+28>>2]=B;e=B;c=0;break Y}if(K[a+56>>2]<=E>>>0){t=H[a+32>>2];c=1;break Y}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],E)|0)+N(H[a+20>>2],e)|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(b<<1)|0;if(J[c>>1]){break _}break a}c=H[a+224>>2];b=H[a+232>>2];n=c+b-(c>>>0)%(b>>>0)|0;break Z}c=0;continue U}c=1;continue U}while(1){aa:{ba:{ca:{da:{if(!c){if(e>>>0>=P>>>0){break ca}t=H[a+48>>2];H[a+32>>2]=t;Q=(e<<4)+w|0;break da}t=t+1|0;H[a+32>>2]=t}b=H[a+60>>2];d=H[Q+8>>2];if((b>>>0>>0?b:d)>>>0>t>>>0){f=H[Q>>2];c=f;g=d+(t^-1)|0;m=g;d=g&31;if((g&63)>>>0>=32){b=c<>>32-d;q=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break aa}b=m&31;if((m&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}c=H[Q+4>>2];if((b&c)!=(c|0)){break aa}d=m&31;if((m&63)>>>0>=32){b=c<>>32-d;r=c<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,r,b);b=h;y=H[a+208>>2];d=y+F|0;b=y>>>0>d>>>0?b+1|0:b;s=fe(d,b,r,o);C=q-1|0;i=H[a+212>>2];j=C+i|0;d=u-!q|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,q,u);L=H[a+204>>2];i=C+L|0;b=L>>>0>i>>>0?b+1|0:b;i=fe(i,b,q,u);A=H[Q+12>>2]+(t<<4)|0;D=H[A>>2];l=D+g|0;b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break aa}h=c;M=H[A+4>>2];g=M+g|0;k=g&31;if((g&63)>>>0>=32){b=c<>>32-k;k=c<>>0>=32){c=b>>>j|0}else{c=((1<>>j}if((h|0)!=(c|0)){break aa}j=H[a+224>>2];k=!!(ge(j,k,b)|qa);b=g&31;if((g&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-k|b<>2];if((l&63)>>>0>=32){b=f<>>32-g;g=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-l|b<>2];if(!g|(!H[A+12>>2]|(i|0)==(z|0))){break aa}if((s|0)==(O|0)){break aa}E=H[a+68>>2];H[a+40>>2]=E;b=d;c=c+C|0;b=c>>>0>>0?b+1|0:b;f=(fe(c,b,q,u)>>>D)-(i>>>D)|0;b=v;c=j+F|0;b=c>>>0>>0?b+1|0:b;T=a,U=N(g,(fe(c,b,r,o)>>>M)-(s>>>M)|0)+f|0,H[T+36>>2]=U;c=1;continue X}e=e+1|0;H[a+28>>2]=e;break ba}c=H[a+220>>2];b=H[a+228>>2];x=c+b-(c>>>0)%(b>>>0)|0;c=0;continue X}c=0;continue}c=1;continue}}}}ea:while(1){fa:{ga:{if(!c){p=p+1|0;H[a+40>>2]=p;break ga}H[a+32>>2]=n;if(K[a+60>>2]<=n>>>0){break b}q=H[a+108>>2];c=0;break fa}c=1}ha:while(1){ia:{ja:{ka:{la:{if(!c){H[a+224>>2]=q;if(K[a+112>>2]<=q>>>0){break la}E=H[a+100>>2];c=0;break ia}if(K[a+56>>2]<=p>>>0){e=H[a+28>>2];c=1;break ia}b=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],p)|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],H[a+36>>2])|0;if(b>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(b<<1)|0;if(J[c>>1]){break ka}break a}n=H[a+32>>2]+1|0;break ja}c=0;continue ea}c=1;continue ea}while(1){ma:{na:{oa:{pa:{if(!c){H[a+220>>2]=E;if(K[a+104>>2]<=E>>>0){break oa}H[a+28>>2]=B;e=B;break pa}e=e+1|0;H[a+28>>2]=e}if(e>>>0

      >>0){l=H[a+32>>2];k=H[a+200>>2]+(e<<4)|0;b=H[k+8>>2];if(l>>>0>=b>>>0){break ma}f=H[k>>2];c=f;g=b+(l^-1)|0;m=g;d=g&31;if((g&63)>>>0>=32){b=c<>>32-d;r=f<>>0>=32){b=b>>>d|0}else{b=((1<>>d}if((v|0)!=(b|0)){break ma}b=m&31;if((m&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}c=H[k+4>>2];if((b&c)!=(c|0)){break ma}d=m&31;if((m&63)>>>0>=32){b=c<>>32-d;x=c<>2];i=F+d|0;O=fe(i,d>>>0>i>>>0?h+1|0:h,x,b);b=h;y=H[a+208>>2];d=y+F|0;b=y>>>0>d>>>0?b+1|0:b;s=fe(d,b,x,o);C=r-1|0;i=H[a+212>>2];j=C+i|0;d=u-!r|0;b=d;z=fe(j,j>>>0>>0?b+1|0:b,r,u);L=H[a+204>>2];i=C+L|0;b=L>>>0>i>>>0?b+1|0:b;i=fe(i,b,r,u);A=H[k+12>>2]+(l<<4)|0;D=H[A>>2];l=D+g|0;b=l&31;if((l&63)>>>0>=32){b=-1>>>b|0}else{b=(1<>>b}if((f|0)!=(b&f)){break ma}h=c;M=H[A+4>>2];g=M+g|0;k=g&31;if((g&63)>>>0>=32){b=c<>>32-k;k=c<>>0>=32){c=b>>>j|0}else{c=((1<>>j}if((h|0)!=(c|0)){break ma}j=H[a+224>>2];k=!!(ge(j,k,b)|qa);b=g&31;if((g&63)>>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-k|b<>2];if((l&63)>>>0>=32){b=f<>>32-g;g=f<>>0>=32){h=-1<>>32-b;b=-1<>>0>=32){h=g<>>32-l|b<>2];if(!g|(!H[A+12>>2]|(i|0)==(z|0))){break ma}if((s|0)==(O|0)){break ma}p=H[a+68>>2];H[a+40>>2]=p;b=d;c=c+C|0;b=c>>>0>>0?b+1|0:b;f=(fe(c,b,r,u)>>>D)-(i>>>D)|0;b=v;c=j+F|0;b=c>>>0>>0?b+1|0:b;T=a,U=N(g,(fe(c,b,x,o)>>>M)-(s>>>M)|0)+f|0,H[T+36>>2]=U;c=1;continue ha}c=H[a+220>>2];b=H[a+228>>2];E=c+b-(c>>>0)%(b>>>0)|0;break na}c=H[a+224>>2];b=H[a+232>>2];q=c+b-(c>>>0)%(b>>>0)|0;c=0;continue ha}c=0;continue}c=1;continue}}}}qa:while(1){ra:{sa:{if(!c){n=n+1|0;break sa}H[a+32>>2]=w;if(K[a+60>>2]<=w>>>0){break b}p=H[a+68>>2];c=0;break ra}c=1}ta:while(1){ua:{va:{wa:{xa:{if(!c){H[a+40>>2]=p;if(K[a+56>>2]<=p>>>0){break xa}H[a+28>>2]=b;c=b;e=0;break ua}H[a+36>>2]=n;if(K[a+76>>2]<=n>>>0){c=H[a+28>>2];e=1;break ua}c=((N(H[a+16>>2],H[a+32>>2])+N(H[a+12>>2],H[a+40>>2])|0)+N(H[a+20>>2],H[a+28>>2])|0)+N(H[a+24>>2],n)|0;if(c>>>0>=K[a+8>>2]){break c}c=H[a+4>>2]+(c<<1)|0;if(J[c>>1]){break wa}break a}w=H[a+32>>2]+1|0;break va}c=0;continue qa}c=1;continue qa}while(1){ya:{za:{Aa:{if(!e){if(c>>>0>=d>>>0){break Aa}e=H[a+32>>2];f=H[a+200>>2]+(c<<4)|0;if(e>>>0>=K[f+8>>2]){break ya}if(!I[a|0]){c=H[f+12>>2]+(e<<4)|0;H[a+76>>2]=N(H[c+12>>2],H[c+8>>2])}n=H[a+72>>2];c=1;continue ta}c=c+1|0;H[a+28>>2]=c;break za}p=H[a+40>>2]+1|0;c=0;continue ta}e=0;continue}e=1;continue}}}}return 0}Ba(H[a+236>>2],1,1343,0)}return 0}G[c>>1]=1;return 1}function gd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=O(0),w=0,x=0,z=0,A=O(0),C=O(0),D=0,E=0,G=0,J=0,K=0,L=O(0),M=O(0),P=0,Q=0,R=0;n=na-8320|0;na=n;H[n+64>>2]=0;j=2;g=H[a>>2];a:{b:{if((g|0)==176622093){break b}if((g|0)!=1375686655){if(!((g|0)!=201326592|H[a+4>>2]!=538988650)&H[a+8>>2]==176622093){break b}Y(1101);j=1;break a}j=0}g=Ea(1,96);h=0;c:{if(!g){break c}H[g+76>>2]=1;d:{e:{f:{switch(j|0){case 0:H[g+88>>2]=60;H[g+84>>2]=61;H[g+80>>2]=62;H[g+16>>2]=63;H[g+4>>2]=64;H[g+28>>2]=65;H[g+24>>2]=66;H[g+20>>2]=67;H[g>>2]=68;H[g+92>>2]=69;H[g+44>>2]=70;H[g+40>>2]=71;H[g+36>>2]=72;H[g+32>>2]=73;H[g+12>>2]=74;H[g+8>>2]=75;h=Gb();H[g+48>>2]=h;if(h){break e}break d;case 2:break f;default:break d}}H[g+88>>2]=76;H[g+84>>2]=77;H[g+80>>2]=78;H[g+16>>2]=79;H[g+4>>2]=80;H[g+92>>2]=81;H[g+44>>2]=82;H[g+40>>2]=83;H[g+36>>2]=84;H[g+32>>2]=85;H[g+28>>2]=86;H[g+24>>2]=87;H[g+20>>2]=88;H[g+12>>2]=89;H[g+8>>2]=90;H[g>>2]=91;h=Ea(1,136);g:{if(h){k=Gb();H[h>>2]=k;h:{if(!k){break h}F[h+124|0]=0;H[h+116>>2]=0;H[h+120>>2]=0;H[h+108>>2]=0;H[h+112>>2]=0;k=pb();H[h+4>>2]=k;if(!k){break h}k=pb();H[h+8>>2]=k;if(!k){break h}break g}xc(h)}h=0}H[g+48>>2]=h;if(!h){break d}}H[g+72>>2]=1;H[g+64>>2]=1;H[g+60>>2]=0;H[g+52>>2]=0;H[g+56>>2]=0;H[g+68>>2]=1;h=g;break c}Ca(g);h=0}g=h;if(g){H[g+60>>2]=0;H[g+72>>2]=92}if(g){H[g+56>>2]=0;H[g+68>>2]=93}if(g){H[g+52>>2]=0;H[g+64>>2]=94}h=n+68|0;if(h){y(h,0,8248);H[h+8248>>2]=0;H[h+8200>>2]=-1;H[h+8204>>2]=-1}if(d){H[n+8316>>2]=H[n+8316>>2]|1}H[n+60>>2]=b;H[n+56>>2]=a;H[n+52>>2]=a;j=1;b=0;h=n+52|0;i:{if(!h){break i}a=Ea(1,72);if(a){j:{H[a+64>>2]=1048576;k=Fa(1048576);H[a+32>>2]=k;if(!k){Ca(a);a=0;break j}H[a+36>>2]=k;H[a+28>>2]=2;H[a+24>>2]=3;H[a+20>>2]=4;H[a+16>>2]=5;H[a+44>>2]=6;H[a+40>>2]=8;H[a+68>>2]=H[a+68>>2]|2}}else{a=0}if(!a){break i}if(a){H[a+4>>2]=0;H[a>>2]=h}if(a){H[a+8>>2]=H[h+8>>2];H[a+12>>2]=0}if(I[a+68|0]&2){H[a+16>>2]=56}if(a){H[a+24>>2]=58}if(a){H[a+28>>2]=59}b=a}a=n+68|0;if(!g|!a){a=0}else{k:{if(!H[g+76>>2]){Ba(g+52|0,1,9902,0);a=0;break k}ra[H[g+24>>2]](H[g+48>>2],a);a=1}}if(!a){Y(1153);eb(b);gb(g);break a}if(!b|!g){a=0}else{l:{if(!H[g+76>>2]){Ba(g+52|0,1,9983,0);a=0;break l}a=ra[H[g>>2]](b,H[g+48>>2],n- -64|0,g+52|0)|0}}if(!a){Y(1181);eb(b);gb(g);Ta(H[n+64>>2]);break a}m:{if(!f){break m}if(g){a=ra[H[g+40>>2]](H[g+48>>2],f,g+52|0)|0}else{a=0}if(a){break m}Y(1116);eb(b);gb(g);Ta(H[n+64>>2]);break a}a=H[n+64>>2];n:{if(!H[g+76>>2]|(!g|!b)){a=0}else{a=ra[H[g+4>>2]](H[g+48>>2],b,a,g+52|0)|0}if(a){if(!H[g+76>>2]|(!g|!b)){a=0}else{a=ra[H[g+16>>2]](H[g+48>>2],b,g+52|0)|0}if(a){break n}}Y(1316);gb(g);eb(b);Ta(H[n+64>>2]);break a}eb(b);gb(g);l=H[n+64>>2];a=H[l+28>>2];if(a){Ca(a);l=H[n+64>>2];H[l+28>>2]=0;H[l+32>>2]=0}u=H[l+16>>2];o:{p:{if(!c){if(!(!e|(u|0)!=4)){p=1;u=4;break o}q:{b=H[l+20>>2];if(!((b|0)==3|(u|0)!=3)){a=H[l+24>>2];if(H[a>>2]!=H[a+4>>2]|H[a+52>>2]==1){break q}H[l+20>>2]=3;break p}if(u>>>0>2){break q}H[l+20>>2]=2;break o}r:{switch(b-3|0){case 2:s:{t:{if(u>>>0<4){break t}b=H[l+24>>2];a=H[b>>2];if((a|0)!=H[b+52>>2]|(a|0)!=H[b+104>>2]|(a|0)!=H[b+156>>2]){break t}a=H[b+4>>2];if((a|0)!=H[b+56>>2]|(a|0)!=H[b+108>>2]){break t}if((a|0)==H[b+160>>2]){break s}}H[n+20>>2]=1053;H[n+16>>2]=1373;Bb(8179,n+16|0);break o}f=N(H[b+12>>2],H[b+8>>2]);A=O(O(1)/O((-1<>2]^-1)>>>0));C=O(O(1)/O((-1<>2]^-1)>>>0));L=O(O(1)/O((-1<>2]^-1)>>>0));M=O(O(1)/O((-1<>2]^-1)>>>0));a=0;while(1){if((a|0)!=(f|0)){c=a<<2;g=c+H[b+148>>2]|0;h=H[g>>2];k=c+H[b+96>>2]|0;j=H[k>>2];m=c+H[b+44>>2]|0;v=O(O(1)-O(A*O(H[c+H[b+200>>2]>>2])));H[m>>2]=~~O(O(O(O(1)-O(M*O(H[m>>2])))*O(255))*v);H[k>>2]=~~O(O(O(O(1)-O(L*O(j|0)))*O(255))*v);H[g>>2]=~~O(O(O(O(1)-O(C*O(h|0)))*O(255))*v);a=a+1|0;continue}break};Ca(H[b+200>>2]);a=H[l+24>>2];H[a+128>>2]=8;H[a+76>>2]=8;H[a+24>>2]=8;H[a+200>>2]=0;H[l+20>>2]=1;a=H[l+16>>2]-1|0;H[l+16>>2]=a;i=3;while(1){if(a>>>0<=i>>>0){break o}a=H[l+24>>2]+N(i,52)|0;B(a,a+52|0,52);i=i+1|0;a=H[l+16>>2];continue};case 0:break p;case 1:break r;default:break o}}b=H[l+24>>2];a=H[b>>2];u:{v:{if((a|0)!=H[b+52>>2]|(a|0)!=H[b+104>>2]){break v}a=H[b+4>>2];if((a|0)!=H[b+56>>2]){break v}if((a|0)==H[b+108>>2]){break u}}H[n+36>>2]=1115;H[n+32>>2]=1373;Bb(8221,n+32|0);break o}a=H[b+24>>2];c=-1<>2]?0:a;h=H[b+84>>2]?0:a;k=N(H[b+12>>2],H[b+8>>2]);a=0;while(1){if((a|0)!=(k|0)){f=a<<2;j=f+H[b+44>>2]|0;m=f+H[b+148>>2]|0;v=O(H[m>>2]-g|0);i=f+H[b+96>>2]|0;A=O(H[i>>2]-h|0);C=O(H[j>>2]);f=~~O(O(O(v*O(1.4019900560379028))+O(O(A*O(-3680000008898787e-20))+C))+O(.5));H[j>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;f=~~O(O(O(v*O(-.7141128182411194))+O(O(C*O(1.0003000497817993))+O(A*O(-.34412500262260437))))+O(.5));H[i>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;f=~~O(O(O(v*O(-7999999979801942e-21))+O(O(C*O(.9998229742050171))+O(A*O(1.7720400094985962))))+O(.5));H[m>>2]=(c|0)<(f|0)?c:(f|0)>0?f:0;a=a+1|0;continue}break}H[l+20>>2]=1;break o}u=c>>>0>u>>>0?u:c;p=1;break o}w:{x:{c=H[l+24>>2];if(H[c>>2]!=1){break x}y:{switch(H[c+52>>2]-1|0){case 1:if(H[c+104>>2]!=2){break x}if(!(H[c+4>>2]!=1|H[c+56>>2]!=2|H[c+108>>2]!=2)){b=H[c+24>>2];i=H[c+148>>2];a=H[c+96>>2];j=H[c+44>>2];D=H[c+60>>2];q=H[c+8>>2];f=H[c+12>>2];c=N(q,f)<<2;g=Ia(c);h=Ia(c);k=Ia(c);if(!(!g|!h|!k)){m=-1<>2]&1;J=f-b|0;G=H[l>>2]&1;w=q-G|0;if(!b){c=g;f=h;b=k;break w}c=g;f=h;b=k;while(1){if((p|0)==(q|0)){break w}Ka(o,m,H[j>>2],0,0,c,f,b);p=p+1|0;b=b+4|0;f=f+4|0;c=c+4|0;j=j+4|0;continue}}Ca(g);Ca(h);Ca(k);break o}if(H[c+4>>2]!=1|H[c+56>>2]!=1|H[c+108>>2]!=1){break x}a=H[c+24>>2];f=H[c+148>>2];b=H[c+96>>2];i=H[c+44>>2];s=H[c+60>>2];g=H[c+8>>2];w=H[c+12>>2];c=N(g,w)<<2;h=Ia(c);k=Ia(c);m=Ia(c);if(!(!h|!k|!m)){o=-1<>2]&1;a=g-x|0;t=a>>>1|0;D=a&1;E=a&-2;a=h;j=k;c=m;while(1){if((q|0)!=(w|0)){if(x){Ka(r,o,H[i>>2],0,0,a,j,c);c=c+4|0;j=j+4|0;i=i+4|0;a=a+4|0}g=0;while(1){if(g>>>0>>0){Ka(r,o,H[i>>2],H[b>>2],H[f>>2],a,j,c);Ka(r,o,H[i+4>>2],H[b>>2],H[f>>2],a+4|0,j+4|0,c+4|0);g=g+2|0;f=f+4|0;b=b+4|0;c=c+8|0;j=j+8|0;a=a+8|0;i=i+8|0;continue}break}z:{if(!D){break z}g=H[i>>2];A:{if((s|0)==(t|0)){Ka(r,o,g,0,0,a,j,c);break A}Ka(r,o,g,H[b>>2],H[f>>2],a,j,c)}c=c+4|0;j=j+4|0;a=a+4|0;i=i+4|0;if(s>>>0<=t>>>0){break z}f=f+4|0;b=b+4|0}q=q+1|0;continue}break}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=h;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=k;Ca(H[a+148>>2]);a=H[l+24>>2];H[a+148>>2]=m;b=H[a+8>>2];H[a+112>>2]=b;H[a+60>>2]=b;b=H[a+12>>2];H[a+116>>2]=b;H[a+64>>2]=b;b=H[a>>2];H[a+104>>2]=b;H[a+52>>2]=b;b=H[a+4>>2];H[a+108>>2]=b;H[a+56>>2]=b;H[l+20>>2]=1;break o}Ca(h);Ca(k);Ca(m);break o;case 0:break y;default:break x}}if(H[c+104>>2]!=1|H[c+4>>2]!=1|(H[c+56>>2]!=1|H[c+108>>2]!=1)){break x}b=H[c+24>>2];i=H[c+148>>2];a=H[c+96>>2];j=H[c+44>>2];o=N(H[c+12>>2],H[c+8>>2]);c=o<<2;h=Ia(c);k=Ia(c);m=Ia(c);if(!(!h|!k|!m)){q=-1<>2],H[a>>2],H[i>>2],f,b,g);c=c+1|0;g=g+4|0;b=b+4|0;f=f+4|0;i=i+4|0;a=a+4|0;j=j+4|0;continue}break}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=h;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=k;Ca(H[a+148>>2]);H[H[l+24>>2]+148>>2]=m;H[l+20>>2]=1;break o}Ca(h);Ca(k);Ca(m);break o}H[n+4>>2]=463;H[n>>2]=1373;Bb(8264,n);break o}E=w>>>1|0;x=w&-2;P=J&-2;t=q<<2;while(1){if(K>>>0

      >>0){p=b+t|0;r=f+t|0;q=c+t|0;s=j+t|0;if(G){Ka(o,m,H[j>>2],0,0,c,f,b);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p);p=p+4|0;r=r+4|0;q=q+4|0;s=s+4|0;f=f+4|0;c=c+4|0;j=j+4|0;b=b+4|0}z=0;while(1){if(x>>>0>z>>>0){Ka(o,m,H[j>>2],H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[j+4>>2],H[a>>2],H[i>>2],c+4|0,f+4|0,b+4|0);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p);Ka(o,m,H[s+4>>2],H[a>>2],H[i>>2],q+4|0,r+4|0,p+4|0);z=z+2|0;i=i+4|0;a=a+4|0;p=p+8|0;r=r+8|0;q=q+8|0;s=s+8|0;b=b+8|0;f=f+8|0;c=c+8|0;j=j+8|0;continue}break}B:{if((w|0)==(x|0)){break B}z=H[j>>2];C:{if((D|0)==(E|0)){Ka(o,m,z,0,0,c,f,b);Ka(o,m,H[s>>2],0,0,q,r,p);break C}Ka(o,m,z,H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[s>>2],H[a>>2],H[i>>2],q,r,p)}b=b+4|0;f=f+4|0;c=c+4|0;j=j+4|0;if(D>>>0<=E>>>0){break B}i=i+4|0;a=a+4|0}K=K+2|0;b=b+t|0;f=f+t|0;c=c+t|0;j=j+t|0;continue}break}D:{if(!(J&1)){break D}if(G){Ka(o,m,H[j>>2],0,0,c,f,b);f=f+4|0;c=c+4|0;j=j+4|0;b=b+4|0}p=0;while(1){if(p>>>0>>0){Ka(o,m,H[j>>2],H[a>>2],H[i>>2],c,f,b);Ka(o,m,H[j+4>>2],H[a>>2],H[i>>2],c+4|0,f+4|0,b+4|0);p=p+2|0;i=i+4|0;a=a+4|0;b=b+8|0;f=f+8|0;c=c+8|0;j=j+8|0;continue}break}if((w|0)==(x|0)){break D}j=H[j>>2];if((D|0)==(E|0)){Ka(o,m,j,0,0,c,f,b);break D}Ka(o,m,j,H[a>>2],H[i>>2],c,f,b)}Ca(H[H[l+24>>2]+44>>2]);a=H[l+24>>2];H[a+44>>2]=g;Ca(H[a+96>>2]);a=H[l+24>>2];H[a+96>>2]=h;Ca(H[a+148>>2]);a=H[l+24>>2];H[a+148>>2]=k;b=H[a+8>>2];H[a+112>>2]=b;H[a+60>>2]=b;b=H[a+12>>2];H[a+116>>2]=b;H[a+64>>2]=b;b=H[a>>2];H[a+104>>2]=b;H[a+52>>2]=b;b=H[a+4>>2];H[a+108>>2]=b;H[a+56>>2]=b;H[l+20>>2]=1;p=0}c=H[n+64>>2];if(!d){b=0;while(1){if((b|0)!=(u|0)){a=H[c+24>>2]+N(b,52)|0;d=H[a+24>>2];if((d|0)!=8){E:{if(d>>>0<=7){f=N(H[a+12>>2],H[a+8>>2]);g=H[a+44>>2];if(H[a+32>>2]){h=1<>2];k=d>>31<<7|d>>>25;Q=m,R=ee(d<<7,k,h,0),H[Q>>2]=R;i=i+1|0;continue}}d=-1<>2],0,255),qa,d,0);H[h>>2]=k;i=i+1|0;continue}}d=d-8|0;f=N(H[a+12>>2],H[a+8>>2]);g=H[a+44>>2];i=0;if(H[a+32>>2]){while(1){if((f|0)==(i|0)){break E}h=g+(i<<2)|0;H[h>>2]=H[h>>2]>>d;i=i+1|0;continue}}while(1){if((f|0)==(i|0)){break E}h=g+(i<<2)|0;H[h>>2]=H[h>>2]>>>d;i=i+1|0;continue}}H[a+24>>2]=8}b=b+1|0;continue}break}c=H[n+64>>2]}a=H[c+24>>2];b=N(H[a+12>>2],H[a+8>>2]);F:{if(!p){if(H[c+20>>2]==2){if(H[c+16>>2]==1){ma(H[a+44>>2],b|0);break F}if(!e){break F}da(H[a+44>>2],H[a+96>>2],b|0);break F}ca(H[a+44>>2],H[a+96>>2],H[a+148>>2],b|0);break F}G:{switch(u-1|0){case 0:ba(H[a+44>>2],b|0);break F;case 2:aa(H[a+44>>2],H[a+96>>2],H[a+148>>2],b|0);break F;case 3:break G;default:break F}}$(H[a+44>>2],H[a+96>>2],H[a+148>>2],H[a+200>>2],b|0)}Ta(H[n+64>>2]);j=0}na=n+8320|0;return j|0}function Zb(a,b,c,d,e,f,g,h,i){var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,F=0,G=0,I=0,J=0,L=0,M=0,O=0;j=H[a>>2];a:{if(j>>>0>>0|b>>>0>=d>>>0|b>>>0>=j>>>0){break a}j=H[a+4>>2];if(j>>>0>>0|c>>>0>=e>>>0|c>>>0>=j>>>0){break a}C=(c>>>0)/K[a+12>>2]|0;s=H[a+8>>2];E=(b>>>0)/(s>>>0)|0;J=(N(s,E)-b|0)+s|0;F=(g|0)!=8;w=c;while(1){k=H[a+12>>2];j=k;j=(c|0)==(w|0)?j-((c>>>0)%(j>>>0)|0)|0:j;m=e-w|0;r=j>>>0>>0?j:m;x=r&-4;t=r&3;L=r&-8;G=r&7;v=r-1|0;M=(g|0)==2&(r|0)==1;I=N(k-j|0,s);z=(N(w-c|0,h)<<2)+f|0;D=E;u=b;while(1){j=(b|0)==(u|0)?J:s;k=d-u|0;p=j>>>0>>0?j:k;k=s-j|0;m=D<<2;j=H[m+(H[a+24>>2]+(N(H[a+16>>2],C)<<2)|0)>>2];b:{c:{d:{e:{f:{g:{h:{i:{j:{k:{l:{m:{n:{o:{p:{q:{r:{s:{if(i){if(j){l=((I<<2)+j|0)+(k<<2)|0;j=u-b|0;if((g|0)==1){break s}m=(N(g,j)<<2)+z|0;if((p|0)==1){break r}if(M){break q}if(p>>>0<=7|F){break p}if(!r){break b}o=p&-4;k=0;while(1){j=0;while(1){H[(j<<5)+m>>2]=H[(j<<2)+l>>2];n=j|1;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];n=j|2;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];n=j|3;H[(n<<5)+m>>2]=H[(n<<2)+l>>2];j=j+4|0;if(o>>>0>j>>>0){continue}break}if(j>>>0

      >>0){while(1){H[(j<<5)+m>>2]=H[(j<<2)+l>>2];j=j+1|0;if((p|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;k=k+1|0;if((r|0)!=(k|0)){continue}break}break b}if((g|0)==1){break o}if(!r|!p){break b}q=p&-4;n=p&3;l=(N(u-b|0,g)<<2)+z|0;o=0;A=p-1>>>0<3;while(1){j=0;k=0;m=0;t:{if(!A){while(1){H[(N(g,j)<<2)+l>>2]=0;H[(N(j|1,g)<<2)+l>>2]=0;H[(N(j|2,g)<<2)+l>>2]=0;H[(N(j|3,g)<<2)+l>>2]=0;j=j+4|0;k=k+4|0;if((q|0)!=(k|0)){continue}break}m=j;if(!n){break t}}j=0;while(1){H[(N(g,m)<<2)+l>>2]=0;m=m+1|0;j=j+1|0;if((n|0)!=(j|0)){continue}break}}l=(h<<2)+l|0;o=o+1|0;if((r|0)!=(o|0)){continue}break}break b}if(!j){j=Ea(1,N(H[a+8>>2],H[a+12>>2])<<2);if(!j){return 0}H[m+(H[a+24>>2]+(N(H[a+16>>2],C)<<2)|0)>>2]=j}l=((I<<2)+j|0)+(k<<2)|0;j=u-b|0;u:{v:{if((g|0)!=1){m=(N(g,j)<<2)+z|0;if((p|0)==1){break v}if(p>>>0<=7|F){break u}if(!r){break b}o=p&-4;k=0;while(1){j=0;while(1){H[(j<<2)+l>>2]=H[(j<<5)+m>>2];n=j|1;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];n=j|2;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];n=j|3;H[(n<<2)+l>>2]=H[(n<<5)+m>>2];j=j+4|0;if(o>>>0>j>>>0){continue}break}if(j>>>0

      >>0){while(1){H[(j<<2)+l>>2]=H[(j<<5)+m>>2];j=j+1|0;if((p|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;k=k+1|0;if((r|0)!=(k|0)){continue}break}break b}j=(j<<2)+z|0;if((p|0)==4){break m}if(!r){break b}m=p<<2;o=0;if(v>>>0>=3){break h}break g}if(!r){break b}o=0;if(v>>>0>=3){while(1){H[l>>2]=H[m>>2];j=s<<2;l=j+l|0;k=h<<2;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;H[l>>2]=H[m>>2];l=j+l|0;m=k+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}j=0;while(1){H[l>>2]=H[m>>2];l=(s<<2)+l|0;m=(h<<2)+m|0;j=j+1|0;if((t|0)!=(j|0)){continue}break}break b}if(!r|!p){break b}A=p&-4;q=p&3;n=0;break k}j=(j<<2)+z|0;if((p|0)==4){break n}if(!r){break b}m=p<<2;o=0;if(v>>>0>=3){break d}break c}if(!r){break b}k=0;if(v>>>0>=3){while(1){H[m>>2]=H[l>>2];j=h<<2;o=j+m|0;m=s<<2;l=m+l|0;H[o>>2]=H[l>>2];o=j+o|0;l=l+m|0;H[o>>2]=H[l>>2];o=j+o|0;l=l+m|0;H[o>>2]=H[l>>2];l=l+m|0;m=j+o|0;k=k+4|0;if((x|0)!=(k|0)){continue}break}if(!t){break b}}j=0;while(1){H[m>>2]=H[l>>2];l=(s<<2)+l|0;m=(h<<2)+m|0;j=j+1|0;if((t|0)!=(j|0)){continue}break}break b}j=0;k=p&-4;if(k){while(1){H[(j<<3)+m>>2]=H[(j<<2)+l>>2];o=j|1;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];o=j|2;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];o=j|3;H[(o<<3)+m>>2]=H[(o<<2)+l>>2];j=j+4|0;if(k>>>0>j>>>0){continue}break}}if(j>>>0>=p>>>0){break b}o=0;k=j;n=p-j&3;if(n){while(1){H[(k<<3)+m>>2]=H[(k<<2)+l>>2];k=k+1|0;o=o+1|0;if((n|0)!=(o|0)){continue}break}}if(j-p>>>0>4294967292){break b}while(1){H[(k<<3)+m>>2]=H[(k<<2)+l>>2];j=k+1|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];j=k+2|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];j=k+3|0;H[(j<<3)+m>>2]=H[(j<<2)+l>>2];k=k+4|0;if((p|0)!=(k|0)){continue}break}break b}if(!r|!p){break b}A=p&-4;q=p&3;n=0;O=p-1>>>0<3;break l}if(!r){break b}m=p<<2;k=(u-b<<2)+z|0;o=0;if(v>>>0>=7){break f}break e}if(!r){break b}o=0;if(v>>>0>=3){while(1){k=H[l+12>>2];H[j+8>>2]=H[l+8>>2];H[j+12>>2]=k;k=H[l+4>>2];H[j>>2]=H[l>>2];H[j+4>>2]=k;k=l;l=s<<2;k=k+l|0;n=H[k+12>>2];m=h<<2;j=m+j|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;k=l+k|0;n=H[k+12>>2];j=j+m|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;k=l+k|0;n=H[k+12>>2];j=j+m|0;H[j+8>>2]=H[k+8>>2];H[j+12>>2]=n;n=H[k+4>>2];H[j>>2]=H[k>>2];H[j+4>>2]=n;l=l+k|0;j=j+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}m=0;while(1){k=H[l+12>>2];H[j+8>>2]=H[l+8>>2];H[j+12>>2]=k;k=H[l+4>>2];H[j>>2]=H[l>>2];H[j+4>>2]=k;l=(s<<2)+l|0;j=(h<<2)+j|0;m=m+1|0;if((t|0)!=(m|0)){continue}break}break b}if(!r){break b}o=0;if(v>>>0>=3){break j}break i}while(1){j=0;o=0;k=0;w:{if(!O){while(1){H[(N(g,j)<<2)+m>>2]=H[(j<<2)+l>>2];k=j|1;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];k=j|2;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];k=j|3;H[(N(k,g)<<2)+m>>2]=H[(k<<2)+l>>2];j=j+4|0;o=o+4|0;if((A|0)!=(o|0)){continue}break}k=j;if(!q){break w}}j=0;while(1){H[(N(g,k)<<2)+m>>2]=H[(k<<2)+l>>2];k=k+1|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;n=n+1|0;if((r|0)!=(n|0)){continue}break}break b}while(1){j=0;o=0;k=0;x:{if(p>>>0>=4){while(1){H[(j<<2)+l>>2]=H[(N(g,j)<<2)+m>>2];k=j|1;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=j|2;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=j|3;H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];j=j+4|0;o=o+4|0;if((A|0)!=(o|0)){continue}break}k=j;if(!q){break x}}j=0;while(1){H[(k<<2)+l>>2]=H[(N(g,k)<<2)+m>>2];k=k+1|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}l=(s<<2)+l|0;m=(h<<2)+m|0;n=n+1|0;if((r|0)!=(n|0)){continue}break}break b}while(1){k=H[j+12>>2];H[l+8>>2]=H[j+8>>2];H[l+12>>2]=k;k=H[j+4>>2];H[l>>2]=H[j>>2];H[l+4>>2]=k;m=s<<2;k=m+l|0;l=h<<2;j=l+j|0;n=H[j+12>>2];H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;n=H[j+12>>2];k=k+m|0;H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;n=H[j+12>>2];k=k+m|0;H[k+8>>2]=H[j+8>>2];H[k+12>>2]=n;n=H[j+4>>2];H[k>>2]=H[j>>2];H[k+4>>2]=n;j=j+l|0;l=k+m|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}m=0;while(1){k=H[j+12>>2];H[l+8>>2]=H[j+8>>2];H[l+12>>2]=k;k=H[j+4>>2];H[l>>2]=H[j>>2];H[l+4>>2]=k;j=(h<<2)+j|0;l=(s<<2)+l|0;m=m+1|0;if((t|0)!=(m|0)){continue}break}break b}while(1){k=!m;if(!k){B(l,j,m)}q=j;j=h<<2;n=q+j|0;q=l;l=s<<2;q=q+l|0;if(!k){B(q,n,m)}n=j+n|0;q=l+q|0;if(!k){B(q,n,m)}n=j+n|0;q=l+q|0;if(!k){B(q,n,m)}j=j+n|0;l=l+q|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}k=0;while(1){if(m){B(l,j,m)}j=(h<<2)+j|0;l=(s<<2)+l|0;k=k+1|0;if((t|0)!=(k|0)){continue}break}break b}while(1){j=!m;if(!j){y(k,0,m)}l=k;k=h<<2;l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}l=l+k|0;if(!j){y(l,0,m)}k=l+k|0;o=o+8|0;if((L|0)!=(o|0)){continue}break}if(!G){break b}}j=0;while(1){if(m){y(k,0,m)}k=(h<<2)+k|0;j=j+1|0;if((G|0)!=(j|0)){continue}break}break b}while(1){k=!m;if(!k){B(j,l,m)}q=l;l=s<<2;n=q+l|0;q=j;j=h<<2;q=q+j|0;if(!k){B(q,n,m)}n=l+n|0;q=j+q|0;if(!k){B(q,n,m)}n=l+n|0;q=j+q|0;if(!k){B(q,n,m)}l=l+n|0;j=j+q|0;o=o+4|0;if((x|0)!=(o|0)){continue}break}if(!t){break b}}k=0;while(1){if(m){B(j,l,m)}l=(s<<2)+l|0;j=(h<<2)+j|0;k=k+1|0;if((t|0)!=(k|0)){continue}break}}D=D+1|0;u=p+u|0;if(u>>>0>>0){continue}break}C=C+1|0;w=r+w|0;if(w>>>0>>0){continue}break}}return 1}function Gd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0;if(!c){return 0}if(!Fb(H[a>>2],b,c,d)){Ba(d,1,6210,0);return 0}n=c;g=na-240|0;na=g;q=1;o=a;a:{if(H[H[a>>2]+60>>2]|H[a+128>>2]){break a}b:{c=H[a+116>>2];c:{if(!c){a=H[a+120>>2];break c}b=H[n+16>>2];a=H[o+120>>2];if(!(!a|!H[a+12>>2])){b=I[a+18|0]}d:{f=J[c+4>>1];if(f){c=H[c>>2];while(1){h=c+N(e,6)|0;i=J[h>>1];if(i>>>0>=b>>>0){H[g+180>>2]=b;H[g+176>>2]=i;Ba(d,1,13715,g+176|0);q=0;break a}e:{h=J[h+4>>1];if((h+1&65535)>>>0<2){break e}h=h-1|0;if(h>>>0>>0){break e}H[g+164>>2]=b;H[g+160>>2]=h;Ba(d,1,13715,g+160|0);q=0;break a}e=e+1|0;if((f|0)!=(e|0)){continue}break}break d}if(b){break b}break c}while(1){b=b-1|0;e=0;while(1){if(J[c+N(e,6)>>1]!=(b|0)){e=e+1|0;if((f|0)!=(e|0)){continue}break b}break}if(b){continue}break}}f:{if(!a){break f}c=H[a+12>>2];if(!c){break f}g:{b=I[a+18|0];h:{if(b){e=0;i=1;while(1){a=H[n+16>>2];f=J[c+(e<<2)>>1];if(a>>>0<=f>>>0){H[g+148>>2]=a;H[g+144>>2]=f;Ba(d,1,13715,g+144|0);i=0}e=e+1|0;if((b|0)!=(e|0)){continue}break}a=Ea(b,4);if(!a){break h}e=0;while(1){f=c+(e<<2)|0;h=I[f+2|0];i:{if(h>>>0>=2){H[g+68>>2]=h;H[g+64>>2]=e;Ba(d,1,12094,g- -64|0);i=0;break i}f=I[f+3|0];if(f>>>0>=b>>>0){H[g+128>>2]=f;Ba(d,1,12038,g+128|0);i=0;break i}j=(h|0)!=1;m=(f<<2)+a|0;if(!(j|!H[m>>2])){H[g+80>>2]=f;Ba(d,1,11527,g+80|0);i=0;break i}if(!(h|!f)){H[g+100>>2]=f;H[g+96>>2]=e;Ba(d,1,11901,g+96|0);i=0;break i}if(!(j|(e|0)==(f|0))){H[g+120>>2]=f;H[g+116>>2]=e;H[g+112>>2]=e;Ba(d,1,11937,g+112|0);i=0;break i}H[m>>2]=1}e=e+1|0;if((b|0)!=(e|0)){continue}break}i=!i;e=0;while(1){j:{f=e<<2;if(I[(c+f|0)+2|0]?H[f+a>>2]:1){e=e+1|0;if((b|0)!=(e|0)){continue}if(i&1){break j}if(H[n+16>>2]!=1){break g}e=0;while(1){if(H[(e<<2)+a>>2]){e=e+1|0;if((b|0)!=(e|0)){continue}break g}break}h=0;Ba(d,2,9253,0);f=b&3;e=0;if(b>>>0>=4){i=b&252;b=0;while(1){j=c+(e<<2)|0;F[j+3|0]=e;F[j+2|0]=1;j=e|1;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;j=e|2;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;j=e|3;m=c+(j<<2)|0;F[m+3|0]=j;F[m+2|0]=1;e=e+4|0;b=b+4|0;if((i|0)!=(b|0)){continue}break}if(!f){break g}}while(1){b=c+(e<<2)|0;F[b+3|0]=e;F[b+2|0]=1;e=e+1|0;h=h+1|0;if((f|0)!=(h|0)){continue}break}break g}H[g+48>>2]=e;i=1;Ba(d,1,11101,g+48|0);e=e+1|0;if((b|0)!=(e|0)){continue}}break}Ca(a);q=0;break a}a=Ea(b,4);if(a){break g}}q=0;Ba(d,1,12285,0);break a}Ca(a)}a=H[o+120>>2];k:{if(!a){break k}l=H[a+12>>2];if(!l){Ca(H[a+4>>2]);Ca(H[H[o+120>>2]+8>>2]);Ca(H[H[o+120>>2]>>2]);a=H[o+120>>2];b=H[a+12>>2];if(b){Ca(b);a=H[o+120>>2]}Ca(a);H[o+120>>2]=0;break k}m=H[n+24>>2];l:{i=I[a+18|0];m:{if(i){u=H[a>>2];h=H[a+4>>2];j=H[a+8>>2];e=0;n:{while(1){if(H[(m+N(J[l+(e<<2)>>1],52)|0)+44>>2]){e=e+1|0;if((i|0)!=(e|0)){continue}break n}break}H[g+32>>2]=e;Ba(d,1,13877,g+32|0);q=0;break a}f=Fa(N(i,52));if(!f){break m}b=0;while(1){a=l+(b<<2)|0;e=J[a>>1];c=N(I[a+2|0]?I[a+3|0]:b,52)+f|0;a=m+N(e,52)|0;H[c+48>>2]=H[a+48>>2];e=H[a+44>>2];H[c+40>>2]=H[a+40>>2];H[c+44>>2]=e;e=H[a+36>>2];H[c+32>>2]=H[a+32>>2];H[c+36>>2]=e;e=H[a+28>>2];H[c+24>>2]=H[a+24>>2];H[c+28>>2]=e;e=H[a+20>>2];H[c+16>>2]=H[a+16>>2];H[c+20>>2]=e;e=H[a+12>>2];H[c+8>>2]=H[a+8>>2];H[c+12>>2]=e;e=H[a+4>>2];H[c>>2]=H[a>>2];H[c+4>>2]=e;c=N(b,52)+f|0;a=Ia(N(H[a+8>>2],H[a+12>>2])<<2);H[c+44>>2]=a;if(!a){if(b){while(1){Ca(H[(N(b,52)+f|0)-8>>2]);b=b-1|0;if(b){continue}break}}Ca(f);q=0;Ba(d,1,13825,0);break a}H[c+24>>2]=I[b+j|0];H[c+32>>2]=I[b+h|0];b=b+1|0;if((i|0)!=(b|0)){continue}break}j=J[H[o+120>>2]+16>>1]-1|0;while(1){a=N(p,52)+f|0;c=N(H[a+12>>2],H[a+8>>2]);b=l+(p<<2)|0;e=H[(m+N(J[b>>1],52)|0)+44>>2];o:{if(!I[b+2|0]){if(!c){break o}h=H[a+44>>2];r=c&3;a=0;b=0;if(c>>>0>=4){t=c&-4;k=0;while(1){c=b<<2;H[c+h>>2]=H[c+e>>2];s=c|4;H[s+h>>2]=H[e+s>>2];s=c|8;H[s+h>>2]=H[e+s>>2];c=c|12;H[c+h>>2]=H[c+e>>2];b=b+4|0;k=k+4|0;if((t|0)!=(k|0)){continue}break}if(!r){break o}}while(1){c=b<<2;H[c+h>>2]=H[c+e>>2];b=b+1|0;a=a+1|0;if((r|0)!=(a|0)){continue}break}break o}if(!c){break o}b=I[b+3|0];a=(b<<2)+u|0;r=H[(N(b,52)+f|0)+44>>2];b=0;if((c|0)!=1){t=c&1;s=c&-2;h=0;while(1){k=b<<2;c=H[k+e>>2];H[k+r>>2]=H[a+(N(i,(c|0)>=0?(c|0)<(j|0)?c:j:0)<<2)>>2];k=k|4;c=H[k+e>>2];H[k+r>>2]=H[a+(N(i,(c|0)>=0?(c|0)<(j|0)?c:j:0)<<2)>>2];b=b+2|0;h=h+2|0;if((s|0)!=(h|0)){continue}break}if(!t){break o}}c=b<<2;b=H[c+e>>2];H[c+r>>2]=H[a+(N(i,(b|0)>=0?(b|0)<(j|0)?b:j:0)<<2)>>2]}p=p+1|0;if((i|0)!=(p|0)){continue}break}break l}f=Fa(N(i,52));if(f){break l}}q=0;Ba(d,1,13825,0);break a}a=H[n+16>>2];if(a){e=0;while(1){b=H[(m+N(e,52)|0)+44>>2];if(b){Ca(b)}e=e+1|0;if((a|0)!=(e|0)){continue}break}}Ca(m);H[n+16>>2]=i;H[n+24>>2]=f}e=H[o+116>>2];if(!e){break a}i=H[e>>2];m=J[e+4>>1];if(m){e=0;r=m-2&65535;h=1;while(1){a=H[n+16>>2];p=N(e,6)+i|0;b=J[p>>1];p:{if(a>>>0<=b>>>0){H[g+20>>2]=a;H[g+16>>2]=b;Ba(d,2,7334,g+16|0);break p}c=J[p+4>>1];if((c+1&65535)>>>0<=1){G[(H[n+24>>2]+N(b,52)|0)+48>>1]=J[p+2>>1];break p}c=c-1|0;j=c&65535;if(j>>>0>=a>>>0){H[g+4>>2]=a;H[g>>2]=j;Ba(d,2,7293,g);break p}q:{if(J[p+2>>1]|(b|0)==(j|0)){break q}f=H[n+24>>2];a=f+N(b,52)|0;H[g+232>>2]=H[a+48>>2];l=H[a+44>>2];H[g+224>>2]=H[a+40>>2];H[g+228>>2]=l;l=H[a+36>>2];H[g+216>>2]=H[a+32>>2];H[g+220>>2]=l;l=H[a+28>>2];H[g+208>>2]=H[a+24>>2];H[g+212>>2]=l;l=H[a+20>>2];H[g+200>>2]=H[a+16>>2];H[g+204>>2]=l;l=H[a+12>>2];H[g+192>>2]=H[a+8>>2];H[g+196>>2]=l;l=H[a+4>>2];H[g+184>>2]=H[a>>2];H[g+188>>2]=l;l=N(j,52);f=l+f|0;H[a+48>>2]=H[f+48>>2];k=H[f+44>>2];H[a+40>>2]=H[f+40>>2];H[a+44>>2]=k;k=H[f+36>>2];H[a+32>>2]=H[f+32>>2];H[a+36>>2]=k;k=H[f+28>>2];H[a+24>>2]=H[f+24>>2];H[a+28>>2]=k;k=H[f+20>>2];H[a+16>>2]=H[f+16>>2];H[a+20>>2]=k;k=H[f+12>>2];H[a+8>>2]=H[f+8>>2];H[a+12>>2]=k;k=H[f+4>>2];H[a>>2]=H[f>>2];H[a+4>>2]=k;a=l+H[n+24>>2]|0;H[a+48>>2]=H[g+232>>2];f=H[g+228>>2];H[a+40>>2]=H[g+224>>2];H[a+44>>2]=f;f=H[g+220>>2];H[a+32>>2]=H[g+216>>2];H[a+36>>2]=f;f=H[g+212>>2];H[a+24>>2]=H[g+208>>2];H[a+28>>2]=f;f=H[g+204>>2];H[a+16>>2]=H[g+200>>2];H[a+20>>2]=f;f=H[g+196>>2];H[a+8>>2]=H[g+192>>2];H[a+12>>2]=f;f=H[g+188>>2];H[a>>2]=H[g+184>>2];H[a+4>>2]=f;if(m>>>0<=e+1>>>0){break q}f=h;if(!(e-m&1)){a=c;f=N(f,6)+i|0;l=J[f>>1];r:{if((l|0)!=(b|0)){a=b;if((j|0)!=(l|0)){break r}}G[f>>1]=a}f=h+1|0}if((r|0)==(e&65535)){break q}while(1){a=c;l=N(f,6)+i|0;k=J[l>>1];s:{if((k|0)!=(b|0)){a=b;if((j|0)!=(k|0)){break s}}G[l>>1]=a}a=c;k=J[l+6>>1];t:{if((k|0)!=(b|0)){a=b;if((j|0)!=(k|0)){break t}}G[l+6>>1]=a}f=f+2|0;if((m|0)!=(f&65535)){continue}break}}G[(H[n+24>>2]+N(b,52)|0)+48>>1]=J[p+2>>1]}h=h+1|0;e=e+1|0;if((m|0)!=(e|0)){continue}break}e=H[o+116>>2];i=H[e>>2]}if(i){Ca(i);e=H[o+116>>2]}Ca(e);H[o+116>>2]=0;break a}q=0;Ba(d,1,9499,0)}na=g+240|0;return q|0}function Mc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=na-48|0;na=k;H[a+8>>2]=1;a:{b:{d=k+40|0;c:{if((Ja(b,d,2,c)|0)!=2){break c}Da(d,k+44|0,2);if(H[k+44>>2]!=65359){break c}H[a+8>>2]=2;d=H[b+56>>2];e=d-2|0;d=H[b+60>>2]-(d>>>0<2)|0;g=H[a+224>>2];H[g>>2]=e;H[g+4>>2]=d;H[k+16>>2]=e;H[k+20>>2]=d;Ba(c,4,12769,k+16|0);i=H[a+224>>2];f=H[i>>2];e=H[i+24>>2];d=e+1|0;g=H[i+32>>2];if(d>>>0<=g>>>0){g=H[i+28>>2];break b}d=~~O(O(g>>>0)+O(100))>>>0;H[i+32>>2]=d;g=Ga(H[i+28>>2],N(d,24));if(g){H[i+28>>2]=g;e=H[i+24>>2];d=e+1|0;break b}Ca(H[i+28>>2]);H[i+32>>2]=0;H[i+24>>2]=0;H[i+28>>2]=0;Ba(c,1,3899,0)}Ba(c,1,15656,0);a=0;break a}e=N(e,24)+g|0;H[e+16>>2]=2;H[e+8>>2]=f;H[e+12>>2]=f>>31;G[e>>1]=65359;H[i+24>>2]=d;if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}Da(H[a+16>>2],k+40|0,2);d:{e:{g=H[k+40>>2];if((g|0)!=65424){while(1){e=24912;if(g>>>0<=65279){H[k>>2]=g;Ba(c,1,2268,k);a=0;break a}while(1){d=e;f=H[d>>2];if(f){e=d+12|0;if((f|0)!=(g|0)){continue}}break}f:{g:{if(f){break g}i=2;Ba(c,2,3847,0);e=2472;h:{i:{if((Ja(b,H[a+16>>2],2,c)|0)!=2){break i}while(1){Da(H[a+16>>2],k+44|0,2);f=24912;g=H[k+44>>2];if(g>>>0>=65280){while(1){d=f;j=H[d>>2];if(j){f=d+12|0;if((g|0)!=(j|0)){continue}}break}if(!(H[d+4>>2]&H[a+8>>2])){e=5397;break i}if(j){if((j|0)==65424){H[k+40>>2]=65424;break f}f=H[b+56>>2];h=H[a+224>>2];g=H[h+24>>2];e=g+1|0;d=H[h+32>>2];if(e>>>0<=d>>>0){d=H[h+28>>2];break h}d=~~O(O(d>>>0)+O(100))>>>0;H[h+32>>2]=d;d=Ga(H[h+28>>2],N(d,24));if(d){H[h+28>>2]=d;g=H[h+24>>2];e=g+1|0;break h}Ca(H[h+28>>2]);H[h+32>>2]=0;H[h+24>>2]=0;H[h+28>>2]=0;e=3899;break i}i=i+2|0}if((Ja(b,H[a+16>>2],2,c)|0)==2){continue}break}}Ba(c,1,e,0);Ba(c,1,9847,0);a=0;break a}d=N(g,24)+d|0;H[d+16>>2]=i;g=f-i|0;H[d+8>>2]=g;H[d+12>>2]=g>>31;G[d>>1]=0;H[h+24>>2]=e;H[k+40>>2]=j;g=24912;while(1){d=g;f=H[d>>2];if(!f){break g}g=d+12|0;if((f|0)!=(j|0)){continue}break}}if(!(H[d+4>>2]&H[a+8>>2])){Ba(c,1,5397,0);a=0;break a}if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}Da(H[a+16>>2],k+36|0,2);e=H[k+36>>2];if(e>>>0<=1){Ba(c,1,6074,0);a=0;break a}e=e-2|0;H[k+36>>2]=e;g=H[a+16>>2];if(K[a+20>>2]>>0){g=Ga(g,e);if(!g){Ca(H[a+16>>2]);H[a+16>>2]=0;H[a+20>>2]=0;Ba(c,1,4973,0);a=0;break a}H[a+16>>2]=g;e=H[k+36>>2];H[a+20>>2]=e}e=Ja(b,g,e,c);if((e|0)!=H[k+36>>2]){Ba(c,1,2472,0);a=0;break a}if(!(ra[H[d+8>>2]](a,H[a+16>>2],e,c)|0)){Ba(c,1,2490,0);a=0;break a}d=H[b+56>>2];j=H[k+36>>2];h=H[a+224>>2];g=H[h+24>>2];i=g+1|0;e=H[h+32>>2];j:{if(i>>>0<=e>>>0){e=H[h+28>>2];break j}e=~~O(O(e>>>0)+O(100))>>>0;H[h+32>>2]=e;e=Ga(H[h+28>>2],N(e,24));if(!e){break d}H[h+28>>2]=e;g=H[h+24>>2];i=g+1|0}e=N(g,24)+e|0;H[e+16>>2]=j+4;d=(d-j|0)-4|0;H[e+8>>2]=d;H[e+12>>2]=d>>31;G[e>>1]=f;H[h+24>>2]=i;if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);a=0;break a}n=(f|0)==65372?1:n;l=(f|0)==65362?1:l;m=(f|0)==65361?1:m;Da(H[a+16>>2],k+40|0,2);g=H[k+40>>2];if((g|0)!=65424){continue}}break}if(m){break e}}Ba(c,1,4785,0);a=0;break a}if(!l){Ba(c,1,4831,0);a=0;break a}if(!n){Ba(c,1,4877,0);a=0;break a}d=0;e=0;l=0;i=na-16|0;na=i;n=1;k:{if(!(F[a+212|0]&1)){break k}l:{f=H[a+136>>2];if(!f){break l}m:{while(1){j=H[a+140>>2]+(l<<3)|0;if(H[j>>2]){h=H[j+4>>2];g=d-h|0;g=d>>>0>=g>>>0?g:0;if(d>>>0>>0){h=h-d|0;f=H[j>>2]+d|0;while(1){if(h>>>0<4){d=5671;break m}Da(f,i+12|0,4);d=H[i+12>>2];if((d^-1)>>>0>>0){d=5645;break m}j=h-4|0;m=j>>>0>>0;g=m?d-j|0:g;e=d+e|0;h=j-d|0;f=((m?0:d)+f|0)+4|0;if(d>>>0>>0){continue}break}f=H[a+136>>2]}d=g}l=l+1|0;if(l>>>0>>0){continue}break}if(!d){break l}n=0;Ba(c,1,3067,0);break k}n=0;Ba(c,1,d,0);break k}d=Fa(e);H[a+160>>2]=d;if(!d){n=0;Ba(c,1,4337,0);break k}H[a+148>>2]=e;h=H[a+140>>2];f=H[a+136>>2];if(f){e=0;d=0;g=0;while(1){l=g<<3;j=l+h|0;if(H[j>>2]){m=H[a+160>>2]+d|0;h=H[j>>2];f=H[j+4>>2];n:{if(f>>>0<=e>>>0){if(f){B(m,h,f)}d=d+f|0;e=e-f|0;break n}if(e){B(m,h,e)}d=d+e|0;f=f-e|0;e=e+h|0;while(1){if(f>>>0<=3){n=0;Ba(c,1,5671,0);break k}Da(e,i+8|0,4);e=e+4|0;h=H[a+160>>2]+d|0;f=f-4|0;j=H[i+8>>2];if(f>>>0>>0){if(f){B(h,e,f)}d=d+f|0;e=H[i+8>>2]-f|0;break n}if(j){B(h,e,j)}h=H[i+8>>2];d=h+d|0;e=e+h|0;f=f-h|0;if(f){continue}break}e=0}Ca(H[l+H[a+140>>2]>>2]);h=H[a+140>>2];f=l+h|0;H[f>>2]=0;H[f+4>>2]=0;f=H[a+136>>2]}g=g+1|0;if(g>>>0>>0){continue}break}e=H[a+148>>2];d=H[a+160>>2]}H[a+168>>2]=e;H[a+144>>2]=d;H[a+136>>2]=0;Ca(h);H[a+140>>2]=0}na=i+16|0;if(!n){Ba(c,1,8085,0);a=0;break a}Ba(c,4,11754,0);d=H[a+224>>2];e=H[b+56>>2];e=e-2|0;H[d+8>>2]=e;H[d+12>>2]=0;b=0;i=0;n=na-16|0;na=n;g=H[a+68>>2];o:{if(!g){H[a+76>>2]=1;break o}if(H[a+76>>2]){break o}d=H[a+72>>2];h=H[a+224>>2];e=H[h+40>>2];p:{if((g|0)!=1){l=g&1;j=g&-2;while(1){m=(b<<3)+d|0;o=J[m>>1];f=e+N(o,40)|0;H[f>>2]=o;H[f+8>>2]=H[f+8>>2]+1;m=J[m+8>>1];f=e+N(m,40)|0;H[f>>2]=m;H[f+8>>2]=H[f+8>>2]+1;b=b+2|0;i=i+2|0;if((j|0)!=(i|0)){continue}break}if(!l){break p}}f=J[(b<<3)+d>>1];b=e+N(f,40)|0;H[b>>2]=f;H[b+8>>2]=H[b+8>>2]+1}f=H[h+36>>2];q:{if(f){b=0;while(1){if(!H[(e+N(b,40)|0)+8>>2]){H[n>>2]=b;Ba(c,1,9304,n);break q}b=b+1|0;if((f|0)!=(b|0)){continue}break}}f=H[h+8>>2];b=H[h+12>>2];e=0;while(1){r:{l=e<<3;h=H[H[a+224>>2]+40>>2]+N(J[l+d>>1],40)|0;i=H[h+16>>2];if(!i){i=Ea(H[h+8>>2],24);H[h+16>>2]=i;if(!i){break r}g=H[a+68>>2];d=H[a+72>>2]}j=H[h+4>>2];i=N(j,24)+i|0;H[i>>2]=f;H[i+4>>2]=b;l=H[(d+l|0)+4>>2];f=l+f|0;H[i+16>>2]=f;b=f>>>0>>0?b+1|0:b;H[i+20>>2]=b;H[h+4>>2]=j+1;e=e+1|0;if(g>>>0>e>>>0){continue}break o}break}Ba(c,1,6882,0)}H[a+76>>2]=1;if(!H[a+68>>2]){break o}d=H[H[a+224>>2]+40>>2];b=0;while(1){c=N(J[H[a+72>>2]+(b<<3)>>1],40);d=c+d|0;H[d+8>>2]=0;Ca(H[d+16>>2]);d=H[H[a+224>>2]+40>>2];H[(c+d|0)+16>>2]=0;b=b+1|0;if(b>>>0>2]){continue}break}}na=n+16|0;H[a+8>>2]=8;a=1;break a}Ca(H[h+28>>2]);H[h+32>>2]=0;H[h+24>>2]=0;H[h+28>>2]=0;Ba(c,1,3899,0);a=0}na=k+48|0;return a|0}function Sd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0;g=na-160|0;na=g;a:{if(c>>>0<=35){b=0;Ba(d,1,6095,0);break a}h=H[a+96>>2];c=c-36|0;f=(c>>>0)/3|0;if((N(f,3)|0)!=(c|0)){b=0;Ba(d,1,6095,0);break a}c=g+156|0;Da(b,c,2);G[a+104>>1]=H[g+156>>2];Da(b+2|0,h+8|0,4);Da(b+6|0,h+12|0,4);Da(b+10|0,h,4);Da(b+14|0,h+4|0,4);Da(b+18|0,a+116|0,4);Da(b+22|0,a+120|0,4);Da(b+26|0,a+108|0,4);Da(b+30|0,a+112|0,4);Da(b+34|0,c,2);b:{c:{d:{e:{c=H[g+156>>2];if(c>>>0<=16384){H[h+16>>2]=c;if((c|0)!=(f|0)){H[g+132>>2]=f;H[g+128>>2]=c;Ba(d,1,14980,g+128|0);break b}c=H[h+4>>2];j=H[h+12>>2];k=H[h+8>>2];e=H[h>>2];if(!(c>>>0>>0&k>>>0>e>>>0)){H[g+120>>2]=j-c;H[g+124>>2]=0-(c>>>0>j>>>0);H[g+112>>2]=k-e;H[g+116>>2]=0-(e>>>0>k>>>0);Ba(d,1,14542,g+112|0);break b}i=H[a+116>>2];l=H[a+120>>2];if(!(l?i:0)){H[g+4>>2]=l;H[g>>2]=i;Ba(d,1,15094,g);break b}f:{o=H[a+108>>2];g:{if(o>>>0>e>>>0){break g}i=i+o|0;if(e>>>0>=(i>>>0>>0?-1:i)>>>0){break g}i=H[a+112>>2];if(i>>>0>c>>>0){break g}l=i+l|0;if(c>>>0<(i>>>0>l>>>0?-1:l)>>>0){break f}}b=0;Ba(d,1,2792,0);break a}h:{if(H[a+248>>2]){break h}i=H[a+240>>2];if(!i){break h}l=H[a+244>>2];if(!l){break h}e=k-e|0;c=j-c|0;if((e|0)==(i|0)&(c|0)==(l|0)){break h}H[g+108>>2]=c;H[g+104>>2]=e;H[g+100>>2]=l;H[g+96>>2]=i;Ba(d,1,14006,g+96|0);break b}c=Ea(f,52);H[h+24>>2]=c;if(!c){break e}i:{if(!H[h+16>>2]){break i}e=g+152|0;Da(b+36|0,e,1);f=H[g+152>>2];i=f>>>7|0;H[c+32>>2]=i;l=(f&127)+1|0;H[c+24>>2]=l;j=H[a+248>>2];Da(b+37|0,e,1);H[c>>2]=H[g+152>>2];Da(b+38|0,e,1);f=H[g+152>>2];H[c+4>>2]=f;e=0;k=H[c>>2];if(k-256>>>0<4294967041|f-256>>>0<4294967041){break c}f=H[c+24>>2];if(f>>>0>31){break d}H[c+36>>2]=0;H[c+40>>2]=H[a+184>>2];e=1;if(K[h+16>>2]<=1){break i}i=j?0:i;j=j?0:l;b=b+39|0;while(1){Da(b,g+152|0,1);k=H[g+152>>2];f=k>>>7|0;H[c+84>>2]=f;k=(k&127)+1|0;H[c+76>>2]=k;if(!(H[a+248>>2]|(I[a+212|0]&4|(j|0)==(k|0)&(f|0)==(i|0)))){H[g+84>>2]=f;H[g+80>>2]=k;H[g+76>>2]=e;H[g+72>>2]=i;H[g+68>>2]=j;H[g+64>>2]=e;Ba(d,2,14778,g- -64|0)}f=g+152|0;Da(b+1|0,f,1);H[c+52>>2]=H[g+152>>2];Da(b+2|0,f,1);f=H[g+152>>2];H[c+56>>2]=f;k=H[c+52>>2];if(!(f-256>>>0>4294967040&k-256>>>0>=4294967041)){break c}f=H[c+76>>2];if(f>>>0>=32){break d}b=b+3|0;H[c+88>>2]=0;H[c+92>>2]=H[a+184>>2];c=c+52|0;e=e+1|0;if(e>>>0>2]){continue}break}}b=0;f=H[a+116>>2];if(!f){break a}j=H[a+120>>2];if(!j){break a}k=0;c=k;p=H[a+108>>2];l=H[h+8>>2]-p|0;i=f-1|0;e=l+i|0;c=l>>>0>e>>>0?c+1|0:c;e=fe(e,c,f,0);H[a+128>>2]=e;o=0;c=o;q=H[a+112>>2];n=H[h+12>>2]-q|0;m=n;l=j-1|0;n=n+l|0;c=m>>>0>n>>>0?c+1|0:c;c=fe(n,c,j,0);H[a+132>>2]=c;j:{if(!(!e|!c)){if(e>>>0<=65535/(c>>>0)>>>0){break j}}H[g+20>>2]=c;H[g+16>>2]=e;Ba(d,1,14120,g+16|0);break a}n=N(c,e);k:{if(I[a+92|0]&2){H[a+28>>2]=(H[a+28>>2]-p>>>0)/(f>>>0);H[a+32>>2]=(H[a+32>>2]-q>>>0)/(j>>>0);c=k;e=H[a+36>>2]-p|0;m=e;e=e+i|0;c=m>>>0>e>>>0?c+1|0:c;v=a,w=fe(e,c,f,0),H[v+36>>2]=w;c=o;e=H[a+40>>2]-q|0;m=e;e=e+l|0;c=m>>>0>e>>>0?c+1|0:c;v=a,w=fe(e,c,j,0),H[v+40>>2]=w;break k}H[a+40>>2]=c;H[a+36>>2]=e;H[a+28>>2]=0;H[a+32>>2]=0}c=Ea(n,5644);H[a+180>>2]=c;if(!c){Ba(d,1,3935,0);break a}c=Ea(H[h+16>>2],1080);H[H[a+12>>2]+5584>>2]=c;if(!H[H[a+12>>2]+5584>>2]){Ba(d,1,3935,0);break a}c=Ea(10,20);H[H[a+12>>2]+5616>>2]=c;c=H[a+12>>2];if(!H[c+5616>>2]){Ba(d,1,3935,0);break a}H[c+5624>>2]=10;c=Ea(10,20);H[H[a+12>>2]+5628>>2]=c;c=H[a+12>>2];if(!H[c+5628>>2]){Ba(d,1,3935,0);break a}H[c+5636>>2]=10;e=H[h+16>>2];l:{if(!e){break l}f=H[h+24>>2];c=0;if((e|0)!=1){j=e&1;k=e&-2;e=0;while(1){i=f+N(c,52)|0;if(!H[i+32>>2]){H[(H[H[a+12>>2]+5584>>2]+N(c,1080)|0)+1076>>2]=1<>2]-1}i=c|1;l=f+N(i,52)|0;if(!H[l+32>>2]){H[(H[H[a+12>>2]+5584>>2]+N(i,1080)|0)+1076>>2]=1<>2]-1}c=c+2|0;e=e+2|0;if((k|0)!=(e|0)){continue}break}if(!j){break l}}e=f+N(c,52)|0;if(H[e+32>>2]){break l}H[(H[H[a+12>>2]+5584>>2]+N(c,1080)|0)+1076>>2]=1<>2]-1}if(n){c=H[a+180>>2];e=0;while(1){f=Ea(H[h+16>>2],1080);H[c+5584>>2]=f;if(!f){Ba(d,1,3935,0);break a}c=c+5644|0;e=e+1|0;if(n>>>0>e>>>0){continue}break}}c=N(H[a+132>>2],H[a+128>>2]);H[H[a+224>>2]+36>>2]=c;c=Ea(c,40);d=H[a+224>>2];H[d+40>>2]=c;e=0;m:{if(!c){break m}e=1;if(!H[d+36>>2]){break m}d=0;while(1){n:{e=0;j=N(d,40);c=j+c|0;H[c+20>>2]=0;H[c+28>>2]=100;f=Ea(100,24);k=H[a+224>>2];c=H[k+40>>2];H[(j+c|0)+24>>2]=f;if(!f){break n}e=1;d=d+1|0;if(d>>>0>2]){continue}}break}}if(!e){break a}H[a+8>>2]=4;r=H[h+16>>2];if(r){b=H[a+112>>2];d=H[a+120>>2];c=b+N(d,H[a+132>>2]-1|0)|0;d=c+d|0;c=c>>>0>d>>>0?-1:d;d=H[h+12>>2];c=c>>>0>>0?c:d;k=c-1|0;l=0-!c|0;c=H[a+108>>2];d=H[a+116>>2];a=c+N(d,H[a+128>>2]-1|0)|0;d=a+d|0;a=a>>>0>d>>>0?-1:d;d=H[h+8>>2];a=a>>>0>>0?a:d;i=a-1|0;o=0-!a|0;a=H[h+4>>2];b=a>>>0>>0?b:a;n=b-1|0;p=0-!b|0;a=H[h>>2];b=a>>>0>>0?c:a;q=b-1|0;u=0-!b|0;a=H[h+24>>2];b=0;while(1){c=p;e=H[a+4>>2];d=e+n|0;h=fe(d,d>>>0>>0?c+1|0:c,e,0);H[a+20>>2]=h;c=u;f=H[a>>2];d=f+q|0;s=fe(d,d>>>0>>0?c+1|0:c,f,0);H[a+16>>2]=s;d=H[a+40>>2];j=d&31;if((d&63)>>>0>=32){c=-1<>>32-j}j=m^-1;c=c^-1;m=c;c=l;t=e+k|0;c=t>>>0>>0?c+1|0:c;e=fe(t,c,e,0)-h|0;c=m;h=e;e=e+j|0;c=h>>>0>e>>>0?c+1|0:c;h=e;e=d&31;if((d&63)>>>0>=32){c=c>>>e|0}else{c=((1<>>e}H[a+12>>2]=c;c=o;e=f+i|0;c=e>>>0>>0?c+1|0:c;e=fe(e,c,f,0)-s|0;c=m;e=e+j|0;c=e>>>0>>0?c+1|0:c;f=e;e=d&31;if((d&63)>>>0>=32){c=c>>>e|0}else{c=((1<>>e}H[a+8>>2]=c;a=a+52|0;b=b+1|0;if((r|0)!=(b|0)){continue}break}}b=1;break a}H[g+144>>2]=c;Ba(d,1,7932,g+144|0);break b}b=0;H[h+16>>2]=0;Ba(d,1,3935,0);break a}H[g+52>>2]=f;H[g+48>>2]=e;Ba(d,1,15402,g+48|0);break b}H[g+40>>2]=f;H[g+36>>2]=k;H[g+32>>2]=e;Ba(d,1,14340,g+32|0)}b=0}na=g+160|0;return b|0}function nc(a,b,c,d,e,f,g){var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;j=na+-64|0;na=j;H[j+60>>2]=b;y=j+41|0;z=j+39|0;u=j+40|0;a:{b:{c:{d:{e:while(1){b=0;f:while(1){if((o^2147483647)<(b|0)){break d}o=b+o|0;g:{h:{i:{j:{k=H[j+60>>2];b=k;i=I[b|0];if(i){while(1){k:{h=i&255;l:{if(!h){n=b;break l}if((h|0)!=37){break k}i=b;while(1){if(I[i+1|0]!=37){n=i;break l}b=b+1|0;h=I[i+2|0];n=i+2|0;i=n;if((h|0)==37){continue}break}}H[j+60>>2]=n;b=b-k|0;x=o^2147483647;if((b|0)>(x|0)){break d}if(a){La(a,k,b)}if(b){continue f}b=n+1|0;s=-1;h=F[n+1|0]-48|0;if(!(I[n+2|0]!=36|h>>>0>9)){v=1;s=h;b=n+3|0}H[j+60>>2]=b;p=0;i=F[b|0];m=i-32|0;m:{if(m>>>0>31){h=b;break m}h=b;n=1<>2]=h;p=n|p;i=F[b+1|0];m=i-32|0;if(m>>>0>=32){break m}b=h;n=1<>>0>9)){p:{if(!a){H[(b<<2)+e>>2]=10;q=0;break p}q=H[(b<<3)+d>>2]}b=h+3|0;v=1;break o}if(v){break j}b=h+1|0;if(!a){H[j+60>>2]=b;v=0;q=0;break n}h=H[c>>2];H[c>>2]=h+4;q=H[h>>2];v=0}H[j+60>>2]=b;if((q|0)>=0){break n}q=0-q|0;p=p|8192;break n}q=mc(j+60|0);if((q|0)<0){break d}b=H[j+60>>2]}h=0;l=-1;i=0;q:{if(I[b|0]!=46){break q}if(I[b+1|0]==42){i=F[b+2|0]-48|0;r:{if(!(I[b+3|0]!=36|i>>>0>9)){b=b+4|0;s:{if(!a){H[(i<<2)+e>>2]=10;l=0;break s}l=H[(i<<3)+d>>2]}break r}if(v){break j}b=b+2|0;l=0;if(!a){break r}i=H[c>>2];H[c>>2]=i+4;l=H[i>>2]}H[j+60>>2]=b;i=(l|0)>=0;break q}H[j+60>>2]=b+1;l=mc(j+60|0);b=H[j+60>>2];i=1}w=i;while(1){m=h;n=28;r=b;i=F[b|0];if(i-123>>>0<4294967238){break c}b=b+1|0;h=I[(i+N(h,58)|0)+25551|0];if((h-1&255)>>>0<8){continue}break}H[j+60>>2]=b;t:{if((h|0)!=27){if(!h){break c}if((s|0)>=0){if(!a){H[(s<<2)+e>>2]=h;continue e}b=(s<<3)+d|0;h=H[b+4>>2];H[j+48>>2]=H[b>>2];H[j+52>>2]=h;break t}if(!a){break g}lc(j+48|0,h,c,g);break t}if((s|0)>=0){break c}b=0;if(!a){continue f}}if(I[a|0]&32){break b}i=p&-65537;h=p&8192?i:p;p=0;s=1072;n=u;u:{v:{w:{x:{y:{z:{A:{B:{C:{D:{E:{F:{G:{H:{I:{J:{K:{r=I[r|0];b=r<<24>>24;b=m?(r&15)==3?b&-45:b:b;switch(b-88|0){case 0:case 32:break G;case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 10:case 16:case 18:case 19:case 20:case 21:case 25:case 26:case 28:case 30:case 31:break h;case 9:case 13:case 14:case 15:break u;case 11:break B;case 12:case 17:break E;case 22:break I;case 23:break F;case 24:break H;case 27:break A;case 29:break J;default:break K}}L:{switch(b-65|0){case 1:case 3:break h;case 0:case 4:case 5:case 6:break u;case 2:break z;default:break L}}if((b|0)==83){break y}break h}m=H[j+48>>2];r=H[j+52>>2];s=1072;break D}b=0;M:{switch(m|0){case 0:H[H[j+48>>2]>>2]=o;continue f;case 1:H[H[j+48>>2]>>2]=o;continue f;case 2:h=H[j+48>>2];H[h>>2]=o;H[h+4>>2]=o>>31;continue f;case 3:G[H[j+48>>2]>>1]=o;continue f;case 4:F[H[j+48>>2]]=o;continue f;case 6:H[H[j+48>>2]>>2]=o;continue f;case 7:break M;default:continue f}}h=H[j+48>>2];H[h>>2]=o;H[h+4>>2]=o>>31;continue f}l=l>>>0<=8?8:l;h=h|8;b=120}i=u;k=H[j+52>>2];r=k;m=H[j+48>>2];t=m;if(k|m){A=b&32;while(1){i=i-1|0;F[i|0]=A|I[(t&15)+26080|0];t=(k&15)<<28|t>>>4;k=k>>>4|0;if(t|k){continue}break}}k=i;if(!(h&8)|!(m|r)){break C}s=(b>>>4|0)+1072|0;p=2;break C}b=u;k=H[j+52>>2];r=k;m=H[j+48>>2];t=m;if(k|m){while(1){b=b-1|0;F[b|0]=t&7|48;t=(k&7)<<29|t>>>3;k=k>>>3|0;if(t|k){continue}break}}k=b;if(!(h&8)){break C}b=y-b|0;l=(b|0)<(l|0)?l:b;break C}m=H[j+48>>2];b=H[j+52>>2];r=b;if((b|0)<0){k=0-(b+((m|0)!=0)|0)|0;r=k;m=0-m|0;H[j+48>>2]=m;H[j+52>>2]=k;p=1;s=1072;break D}if(h&2048){p=1;s=1073;break D}p=h&1;s=p?1074:1072}k=_a(m,r,u)}if((l|0)<0&w){break d}h=w?h&-65537:h;if(!((m|r)!=0|l)){k=u;l=0;break h}b=!(m|r)+(u-k|0)|0;l=(b|0)<(l|0)?l:b;break h}b=I[j+48|0];break i}m=l>>>0>=2147483647?2147483647:l;h=m;n=(h|0)!=0;b=H[j+48>>2];k=b?b:1686;b=k;N:{O:{P:{Q:{if(!(b&3)|!h){break Q}while(1){if(!I[b|0]){break P}h=h-1|0;n=(h|0)!=0;b=b+1|0;if(!(b&3)){break Q}if(h){continue}break}}if(!n){break O}if(!(!I[b|0]|h>>>0<4)){while(1){n=H[b>>2];if(((16843008-n|n)&-2139062144)!=-2139062144){break P}b=b+4|0;h=h-4|0;if(h>>>0>3){continue}break}}if(!h){break O}}while(1){if(!I[b|0]){break N}b=b+1|0;h=h-1|0;if(h){continue}break}}b=0}b=b?b-k|0:m;n=b+k|0;if((l|0)>=0){h=i;l=b;break h}h=i;l=b;if(I[n|0]){break d}break h}b=H[j+48>>2];if(b|H[j+52>>2]){break x}b=0;break i}if(l){i=H[j+48>>2];break w}b=0;Na(a,32,q,0,h);break v}H[j+12>>2]=0;H[j+8>>2]=b;i=j+8|0;H[j+48>>2]=i;l=-1}b=0;while(1){R:{k=H[i>>2];if(!k){break R}k=jc(j+4|0,k);if((k|0)<0){break b}if(k>>>0>l-b>>>0){break R}i=i+4|0;b=b+k|0;if(l>>>0>b>>>0){continue}}break}n=61;if((b|0)<0){break c}Na(a,32,q,b,h);if(!b){b=0;break v}n=0;i=H[j+48>>2];while(1){k=H[i>>2];if(!k){break v}m=j+4|0;k=jc(m,k);n=k+n|0;if(n>>>0>b>>>0){break v}La(a,m,k);i=i+4|0;if(b>>>0>n>>>0){continue}break}}Na(a,32,q,b,h^8192);b=(b|0)<(q|0)?q:b;continue f}if((l|0)<0&w){break d}n=61;b=ra[f|0](a,M[j+48>>3],q,l,h,b,m)|0;if((b|0)>=0){continue f}break c}i=I[b+1|0];b=b+1|0;continue}}if(a){break a}if(!v){break g}b=1;while(1){a=H[(b<<2)+e>>2];if(a){lc((b<<3)+d|0,a,c,g);o=1;b=b+1|0;if((b|0)!=10){continue}break a}break}while(1){if(H[(b<<2)+e>>2]){break j}o=1;b=b+1|0;if((b|0)!=10){continue}break}break a}n=28;break c}F[j+39|0]=b;l=1;k=z;h=i}m=n-k|0;l=(l|0)>(m|0)?l:m;if((l|0)>(p^2147483647)){break d}n=61;i=l+p|0;b=(i|0)<(q|0)?q:i;if(x>>>0>>0){break c}Na(a,32,b,i,h);La(a,s,p);Na(a,48,b,i,h^65536);Na(a,48,l,m,0);La(a,k,m);Na(a,32,b,i,h^8192);continue}break}break}o=0;break a}n=61}H[6605]=n}o=-1}na=j- -64|0;return o}function _c(a,b,c,d,e,f,g){a=a|0;b=+b;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,t=0,u=0,v=0,w=0,y=0,z=0,A=0,B=0,C=0;m=na-560|0;na=m;H[m+556>>2]=0;x(+b);i=s(1)|0;s(0)|0;a:{if((i|0)<0){q=1;z=1082;b=-b;x(+b);i=s(1)|0;s(0)|0;break a}if(e&2048){q=1;z=1085;break a}q=e&1;z=q?1088:1083;A=!q}b:{if((i&2146435072)==2146435072){g=q+3|0;Na(a,32,c,g,e&-65537);La(a,z,q);d=f&32;La(a,b!=b?d?1207:1435:d?1312:1476,3);Na(a,32,c,g,e^8192);k=(c|0)>(g|0)?c:g;break b}w=m+528|0;c:{d:{e:{b=kc(b,m+556|0);b=b+b;if(b!=0){g=H[m+556>>2];H[m+556>>2]=g-1;y=f|32;if((y|0)!=97){break e}break c}y=f|32;if((y|0)==97){break c}k=H[m+556>>2];break d}k=g-29|0;H[m+556>>2]=k;b=b*268435456}l=(d|0)<0?6:d;o=((k|0)>=0?488:0)+m|0;h=o;while(1){d=~~b>>>0;H[h>>2]=d;h=h+4|0;b=(b-+(d>>>0))*1e9;if(b!=0){continue}break}f:{if((k|0)<=0){i=k;j=o;g=h;break f}j=o;i=k;while(1){u=i>>>0>=29?29:i;g=h-4|0;g:{if(j>>>0>g>>>0){break g}d=0;i=0;while(1){p=H[g>>2];v=u&31;if((u&63)>>>0>=32){n=p<>>32-v;p=p<>>0>p>>>0?i+1|0:i;d=fe(p,n,1e9,0);i=qa;B=g,C=p+be(d,i,-1e9)|0,H[B>>2]=C;g=g-4|0;if(j>>>0<=g>>>0){continue}break}if(!n&p>>>0<1e9){break g}j=j-4|0;H[j>>2]=d}while(1){g=h;if(j>>>0>>0){h=g-4|0;if(!H[h>>2]){continue}}break}i=H[m+556>>2]-u|0;H[m+556>>2]=i;h=g;if((i|0)>0){continue}break}}if((i|0)<0){r=((l+25>>>0)/9|0)+1|0;u=(y|0)==102;while(1){d=0-i|0;v=d>>>0>=9?9:d;h:{if(g>>>0<=j>>>0){h=H[j>>2]?0:4;break h}p=1e9>>>v|0;n=-1<>2];H[h>>2]=i+(d>>>v|0);i=N(p,d&n);h=h+4|0;if(h>>>0>>0){continue}break}h=H[j>>2]?0:4;if(!i){break h}H[g>>2]=i;g=g+4|0}i=H[m+556>>2]+v|0;H[m+556>>2]=i;j=h+j|0;d=u?o:j;g=g-d>>2>(r|0)?d+(r<<2)|0:g;if((i|0)<0){continue}break}}i=0;i:{if(g>>>0<=j>>>0){break i}i=N(o-j>>2,9);h=10;d=H[j>>2];if(d>>>0<10){break i}while(1){i=i+1|0;h=N(h,10);if(d>>>0>=h>>>0){continue}break}}d=(l-((y|0)!=102?i:0)|0)-((y|0)==103&(l|0)!=0)|0;if((d|0)<(N(g-o>>2,9)-9|0)){n=d+9216|0;d=(n|0)/9|0;k=(((k|0)<0?-4092:-3604)+m|0)+(d<<2)|0;h=10;d=n+N(d,-9)|0;if((d|0)<=7){while(1){h=N(h,10);d=d+1|0;if((d|0)!=8){continue}break}}n=H[k>>2];r=(n>>>0)/(h>>>0)|0;p=N(r,h);d=k+4|0;j:{if((n|0)==(p|0)&(d|0)==(g|0)){break j}n=n-p|0;k:{if(!(r&1)){b=9007199254740992;if(!(F[k-4|0]&1)|((h|0)!=1e9|j>>>0>=k>>>0)){break k}}b=9007199254740994}t=(d|0)==(g|0)?1:1.5;d=h>>>1|0;t=d>>>0>n>>>0?.5:(d|0)==(n|0)?t:1.5;if(!(I[z|0]!=45|A)){t=-t;b=-b}H[k>>2]=p;if(b+t==b){break j}d=h+p|0;H[k>>2]=d;if(d>>>0>=1e9){while(1){H[k>>2]=0;k=k-4|0;if(k>>>0>>0){j=j-4|0;H[j>>2]=0}d=H[k>>2]+1|0;H[k>>2]=d;if(d>>>0>999999999){continue}break}}i=N(o-j>>2,9);h=10;d=H[j>>2];if(d>>>0<10){break j}while(1){i=i+1|0;h=N(h,10);if(d>>>0>=h>>>0){continue}break}}d=k+4|0;g=d>>>0>>0?d:g}while(1){n=g;h=g>>>0<=j>>>0;if(!h){g=g-4|0;if(!H[g>>2]){continue}}break}l:{if((y|0)!=103){d=e&8;break l}g=l?l:1;d=(g|0)>(i|0)&(i|0)>-5;l=(d?i^-1:-1)+g|0;f=(d?-1:-2)+f|0;d=e&8;if(d){break l}g=-9;m:{if(h){break m}k=H[n-4>>2];if(!k){break m}d=10;g=0;if((k>>>0)%10|0){break m}while(1){h=g;g=g+1|0;d=N(d,10);if(!((k>>>0)%(d>>>0)|0)){continue}break}g=h^-1}h=N(n-o>>2,9);if((f&-33)==70){d=0;g=(g+h|0)-9|0;g=(g|0)>0?g:0;l=(g|0)>(l|0)?l:g;break l}d=0;g=((h+i|0)+g|0)-9|0;g=(g|0)>0?g:0;l=(g|0)>(l|0)?l:g}k=-1;p=d|l;if(((p?2147483645:2147483646)|0)<(l|0)){break b}u=(((p|0)!=0)+l|0)+1|0;h=f&-33;n:{if((h|0)==70){if((u^2147483647)<(i|0)){break b}g=(i|0)>0?i:0;break n}g=i>>31;g=_a((g^i)-g|0,0,w);if((w-g|0)<=1){while(1){g=g-1|0;F[g|0]=48;if((w-g|0)<2){continue}break}}r=g-2|0;F[r|0]=f;F[g-1|0]=(i|0)<0?45:43;g=w-r|0;if((g|0)>(u^2147483647)){break b}}f=g+u|0;if((f|0)>(q^2147483647)){break b}k=f+q|0;Na(a,32,c,k,e);La(a,z,q);Na(a,48,c,k,e^65536);o:{p:{q:{if((h|0)==70){f=m+528|9;d=j>>>0>o>>>0?o:j;j=d;while(1){g=_a(H[j>>2],0,f);r:{if((d|0)!=(j|0)){if(m+528>>>0>=g>>>0){break r}while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}break r}if((f|0)!=(g|0)){break r}g=g-1|0;F[g|0]=48}La(a,g,f-g|0);j=j+4|0;if(o>>>0>=j>>>0){continue}break}if(p){La(a,1684,1)}if((l|0)<=0|j>>>0>=n>>>0){break q}while(1){g=_a(H[j>>2],0,f);if(g>>>0>m+528>>>0){while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}}La(a,g,(l|0)>=9?9:l);g=l-9|0;j=j+4|0;if(n>>>0<=j>>>0){break p}d=(l|0)>9;l=g;if(d){continue}break}break p}s:{if((l|0)<0){break s}f=j>>>0>>0?n:j+4|0;o=m+528|9;h=j;while(1){g=_a(H[h>>2],0,o);if((o|0)==(g|0)){g=g-1|0;F[g|0]=48}t:{if((h|0)!=(j|0)){if(m+528>>>0>=g>>>0){break t}while(1){g=g-1|0;F[g|0]=48;if(m+528>>>0>>0){continue}break}break t}La(a,g,1);g=g+1|0;if(!(d|l)){break t}La(a,1684,1)}i=g;g=o-g|0;La(a,i,(g|0)<(l|0)?g:l);l=l-g|0;h=h+4|0;if(f>>>0<=h>>>0){break s}if((l|0)>=0){continue}break}}Na(a,48,l+18|0,18,0);La(a,r,w-r|0);break o}g=l}Na(a,48,g+9|0,9,0)}Na(a,32,c,k,e^8192);k=(c|0)>(k|0)?c:k;break b}i=(f<<26>>31&9)+z|0;u:{if(d>>>0>12){break u}g=I[i|0];t=tc(52-(d<<2)|0);if((g|0)==45){b=-(t+(-b-t));break u}b=b+t-t}h=H[m+556>>2];g=h>>31;g=_a((g^h)-g|0,0,w);if((w|0)==(g|0)){g=g-1|0;F[g|0]=48;h=H[m+556>>2]}o=f&32;l=g-2|0;F[l|0]=f+15;F[g-1|0]=(h|0)<0?45:43;j=!(e&8)&(d|0)<=0;h=m+528|0;while(1){f=h;g=~~b;F[h|0]=o|I[g+26080|0];b=(b-+(g|0))*16;h=h+1|0;if(!(j&b==0|(h-(m+528|0)|0)!=1)){F[f+1|0]=46;h=f+2|0}if(b!=0){continue}break}k=-1;j=w-l|0;if((2147483643-(j+q|0)|0)<(d|0)){break b}g=m+528|0;o=h-g|0;f=d?(o-2|0)<(d|0)?d+2|0:o:o;d=q|2;h=f+(d+j|0)|0;Na(a,32,c,h,e);La(a,i,d);Na(a,48,c,h,e^65536);La(a,g,o);Na(a,48,f-o|0,0,0);La(a,l,j);Na(a,32,c,h,e^8192);k=(c|0)>(h|0)?c:h}na=m+560|0;return k|0}function Hc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;i=na-80|0;na=i;H[i+76>>2]=1;a:{b:{if(H[a+128>>2]!=1|H[a+132>>2]!=1|(H[a+108>>2]|H[a+112>>2])){break b}j=H[a+100>>2];if(H[j>>2]|H[j+4>>2]|(H[j+8>>2]!=H[a+116>>2]|H[j+12>>2]!=H[a+120>>2])){break b}if(!bb(a,i+72|0,0,i+68|0,i- -64|0,i+60|0,i+56|0,i+52|0,i+76|0,b,c)){break a}c:{d:{if(!H[i+76>>2]){break d}if(!hb(a,H[i+72>>2],0,0,b,c)){break d}b=H[a+100>>2];if(H[b+16>>2]){break c}e=1;break a}Ba(c,1,8776,0);break a}f=H[b+24>>2];while(1){b=N(h,52);Ca(H[(b+f|0)+44>>2]);c=H[a+100>>2];f=H[c+24>>2];j=b+f|0;l=H[a+232>>2];d=H[H[H[l+20>>2]>>2]+20>>2]+N(h,76)|0;H[j+44>>2]=H[d+36>>2];H[j+36>>2]=H[(b+H[H[l+24>>2]+24>>2]|0)+36>>2];H[d+36>>2]=0;e=1;h=h+1|0;if(h>>>0>2]){continue}break}break a}H[a+80>>2]=0;H[a+84>>2]=0;Ca(H[a+88>>2]);H[a+88>>2]=0;e:{if(!(H[a+28>>2]|H[a+32>>2]|H[a+36>>2]!=H[a+128>>2])){j=2;if(H[a+40>>2]==H[a+132>>2]){break e}}j=2;if(H[a+76>>2]){break e}if(!zb(b)){break e}q=H[a+128>>2];j=N(q,H[a+132>>2]);if(j){e=H[H[a+224>>2]+40>>2];f:{g:{if((j|0)==1){j=0;break g}k=j&1;m=j&-2;j=0;while(1){d=e+N(h,40)|0;g=H[d+4>>2];if(g){n=(H[d+16>>2]+N(g,24)|0)-8|0;g=H[n>>2];o=g;p=g>>>0>j>>>0;g=H[n+4>>2];n=p&(g|0)>=(l|0)|(g|0)>(l|0);j=n?o:j;l=n?g:l}g=H[d+44>>2];if(g){g=(H[d+56>>2]+N(g,24)|0)-8|0;d=H[g>>2];o=d;n=d>>>0>j>>>0;d=H[g+4>>2];g=n&(d|0)>=(l|0)|(d|0)>(l|0);j=g?o:j;l=g?d:l}h=h+2|0;f=f+2|0;if((m|0)!=(f|0)){continue}break}if(!k){break f}}d=e+N(h,40)|0;e=H[d+4>>2];if(!e){break f}e=(H[d+16>>2]+N(e,24)|0)-8|0;d=H[e>>2];g=d;o=d>>>0>j>>>0;d=H[e+4>>2];e=o&(d|0)>=(l|0)|(d|0)>(l|0);j=e?g:j;l=e?d:l}j=j+2|0;l=j>>>0<2?l+1|0:l}else{j=2;l=0}h=0;m=H[a+32>>2];p=H[a+40>>2];h:{if(m>>>0>=p>>>0){break h}d=H[a+28>>2];g=H[a+36>>2];if(d>>>0>=g>>>0){break h}k=g-d&3;s=H[H[a+224>>2]+40>>2];t=d-g>>>0>4294967292;while(1){n=s+N(N(m,q),40)|0;e=d;f=0;if(k){while(1){h=H[(n+N(e,40)|0)+4>>2]+h|0;e=e+1|0;f=f+1|0;if((k|0)!=(f|0)){continue}break}}if(!t){while(1){f=n+N(e,40)|0;h=H[f+124>>2]+(H[f+84>>2]+(H[f+44>>2]+(H[f+4>>2]+h|0)|0)|0)|0;e=e+4|0;if((g|0)!=(e|0)){continue}break}}m=m+1|0;if((p|0)!=(m|0)){continue}break}}g=Fa(h<<3);H[a+88>>2]=g;if(!h|!g){break e}h=0;e=H[a+40>>2];n=H[a+32>>2];i:{if(e>>>0<=n>>>0){break i}f=H[a+36>>2];if(f>>>0<=K[a+28>>2]){break i}while(1){g=H[a+28>>2];if(f>>>0>g>>>0){s=H[H[a+224>>2]+40>>2]+N(N(H[a+128>>2],n),40)|0;while(1){e=s+N(g,40)|0;d=H[e+4>>2];if(d){q=d&3;p=H[e+16>>2];k=0;j:{k:{if(d>>>0<4){e=0;break k}t=d&-4;e=0;m=0;while(1){d=p+N(e,24)|0;r=H[d+4>>2];f=h<<3;o=f+H[a+88>>2]|0;H[o>>2]=H[d>>2];H[o+4>>2]=r;r=H[d+28>>2];o=f+H[a+88>>2]|0;H[o+8>>2]=H[d+24>>2];H[o+12>>2]=r;r=H[d+52>>2];o=f+H[a+88>>2]|0;H[o+16>>2]=H[d+48>>2];H[o+20>>2]=r;o=H[d+76>>2];f=f+H[a+88>>2]|0;H[f+24>>2]=H[d+72>>2];H[f+28>>2]=o;e=e+4|0;h=h+4|0;m=m+4|0;if((t|0)!=(m|0)){continue}break}if(!q){break j}}while(1){f=p+N(e,24)|0;m=H[f+4>>2];d=H[a+88>>2]+(h<<3)|0;H[d>>2]=H[f>>2];H[d+4>>2]=m;e=e+1|0;h=h+1|0;k=k+1|0;if((q|0)!=(k|0)){continue}break}}f=H[a+36>>2]}g=g+1|0;if(f>>>0>g>>>0){continue}break}e=H[a+40>>2]}n=n+1|0;if(n>>>0>>0){continue}break}g=H[a+88>>2]}H[a+84>>2]=h;f=na-208|0;na=f;H[f+8>>2]=1;H[f+12>>2]=0;e=h<<3;l:{if(!e){break l}H[f+16>>2]=8;H[f+20>>2]=8;d=2;while(1){h=(f+16|0)+(d<<2)|0;k=H[h-4>>2]+(H[h-8>>2]+8|0)|0;H[h>>2]=k;d=d+1|0;if(e>>>0>k>>>0){continue}break}d=e-8|0;m:{if((d|0)<=0){k=1;d=1;e=0;break m}e=d+g|0;k=1;d=1;while(1){n:{if((k&3)==3){Ab(g,d,f+16|0);tb(f+8|0,2);d=d+2|0;break n}m=f+16|0;h=d-1|0;o:{if(K[m+(h<<2)>>2]>=e-g>>>0){sb(g,k,H[f+12>>2],d,0,m);break o}Ab(g,d,f+16|0)}if((d|0)==1){rb(f+8|0,1);d=0;break n}rb(f+8|0,h);d=1}k=H[f+8>>2]|1;H[f+8>>2]=k;g=g+8|0;if(e>>>0>g>>>0){continue}break}e=H[f+12>>2]}sb(g,k,e,d,0,f+16|0);e=H[f+12>>2];k=H[f+8>>2];if(!(e|((d|0)!=1|(k|0)!=1))){break l}while(1){p:{if((d|0)<=1){e=qc(k,e);tb(f+8|0,e);d=d+e|0;break p}h=f+8|0;rb(h,2);H[f+8>>2]=H[f+8>>2]^7;tb(h,1);m=g-8|0;k=f+16|0;e=d-2|0;sb(m-H[k+(e<<2)>>2]|0,H[f+8>>2],H[f+12>>2],d-1|0,1,k);rb(h,1);d=H[f+8>>2]|1;H[f+8>>2]=d;sb(m,d,H[f+12>>2],e,1,k);d=e}g=g-8|0;e=H[f+12>>2];k=H[f+8>>2];if(e|((d|0)!=1|(k|0)!=1)){continue}break}}na=f+208|0}e=H[a+128>>2];f=0;q:{while(1){r:{if(!(!H[H[a+180>>2]+5596>>2]|((e|0)!=1|H[a+132>>2]!=1))){H[i+72>>2]=0;H[a+228>>2]=0;H[a+8>>2]=H[a+8>>2]|128;d=0;break r}e=0;if(!bb(a,i+72|0,0,i+68|0,i- -64|0,i+60|0,i+56|0,i+52|0,i+76|0,b,c)){break a}if(!H[i+76>>2]){break q}d=H[i+72>>2]}e=d+1|0;g=hb(a,d,0,0,b,c);h=N(H[a+128>>2],H[a+132>>2]);if(!g){H[i+4>>2]=h;H[i>>2]=e;Ba(c,1,7537,i);e=0;break a}H[i+36>>2]=h;H[i+32>>2]=e;Ba(c,4,11795,i+32|0);if(!Ac(H[a+232>>2],H[H[a+100>>2]+24>>2])){e=0;break a}s:{if(!(H[a+128>>2]!=1|H[a+132>>2]!=1)){h=H[a+100>>2];g=H[a+96>>2];if(H[h>>2]!=H[g>>2]|H[h+4>>2]!=H[g+4>>2]|(H[h+8>>2]!=H[g+8>>2]|H[h+12>>2]!=H[g+12>>2])){break s}}d=H[a+180>>2]+N(d,5644)|0;h=H[d+5596>>2];if(!h){break s}Ca(h);H[d+5596>>2]=0;H[d+5600>>2]=0}H[i+16>>2]=e;Ba(c,4,16601,i+16|0);if(!(Pa(b)|qa)&H[a+8>>2]==64){break q}f=f+1|0;e=H[a+128>>2];if((f|0)==(N(e,H[a+132>>2])|0)){break q}d=H[a+84>>2];if(!d|(d|0)!=H[a+80>>2]){continue}break}hc(b,j,l,c)}e=zc(a,c)}na=i+80|0;return e|0}function Xa(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;d=H[a+32>>2];a:{if(d){break a}b:{h=H[a+16>>2];if((h|0)>=6){b=H[a+8>>2];f=H[a+12>>2];d=h;break b}b=H[a+20>>2];c:{d:{if((b|0)>=5){c=H[a>>2];d=H[c>>2];H[a>>2]=c+4;g=b-4|0;break d}if((b|0)<=0){d=-1;break c}c=H[a>>2];e:{if((b|0)==1){e=-1;b=0;break e}d=0;f:{g:{if((b|0)==2){e=-1;i=b;break g}f=b-1|0;k=f&1;j=f&-2;e=-1;i=b;while(1){f=c;H[a>>2]=c+1;l=I[c|0];c=c+2|0;H[a>>2]=c;H[a+20>>2]=i-1;f=I[f+1|0];i=i-2|0;H[a+20>>2]=i;e=((255<>2]=f;c=I[c|0];H[a+20>>2]=i-1;e=(255<>2]=c+1;d=(255<>2]=g}b=H[a+24>>2];c=d>>>24|0;H[a+24>>2]=(c|0)==255;g=d>>>16&255;k=(g|0)==255;f=d&255;e=(f|0)==255;j=b+e|0;b=d>>>8&255;i=(b|0)==255;j=k+(j+i|0)|0;d=(h-j|0)+32|0;H[a+16>>2]=d;l=H[a+12>>2];b=c|(g|(b|f<<(e?7:8))<<(i?7:8))<<(k?7:8);f=(j-h|0)+32|0;c=f&31;if((f&63)>>>0>=32){i=b<>>32-c;g=b<>2];c=i|l;f=c;H[a+8>>2]=b;H[a+12>>2]=c;if((d|0)>=6){break b}d=0;break a}e=H[a+28>>2];i=H[(e<<2)+20752>>2];h:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break h}g=b;h=63-i|0;c=h&31;if((h&63)>>>0>=32){g=f>>>c|0}else{g=((1<>>c}c=(g&(-1<>2]=d;H[a+28>>2]=e;g=b;h=i&31;if((i&63)>>>0>=32){b=b<>>32-h|f<>2]=g;H[a+12>>2]=b;i=H[a+44>>2]|c>>31;j=H[a+40>>2]&-64|c;H[a+40>>2]=j;H[a+44>>2]=i;if((d|0)<6){d=1;break a}b=H[(e<<2)+20752>>2];i:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break i}k=g;h=63-b|0;c=h&31;if((h&63)>>>0>=32){k=f>>>c|0}else{k=((1<>>c}c=(k&(-1<>2]=d;H[a+28>>2]=e;k=g;h=b&31;if((b&63)>>>0>=32){b=g<>>32-h|f<>2]=k;H[a+12>>2]=b;b=c>>31<<7|c>>>25|i;h=b;j=j&-8065|c<<7;H[a+40>>2]=j;H[a+44>>2]=b;if((d|0)<6){d=2;break a}b=H[(e<<2)+20752>>2];j:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break j}g=k;i=63-b|0;c=i&31;if((i&63)>>>0>=32){g=f>>>c|0}else{g=((1<>>c}c=(g&(-1<>2]=d;H[a+28>>2]=e;l=k;g=b&31;if((b&63)>>>0>=32){i=k<>>32-g|f<>2]=g;f=i;H[a+12>>2]=f;b=c>>31<<14|c>>>18|h;i=b;k=j&-1032193|c<<14;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=3;break a}b=H[(e<<2)+20752>>2];k:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:e)+1|0;break k}j=g;h=63-b|0;c=h&31;if((h&63)>>>0>=32){j=f>>>c|0}else{j=((1<>>c}c=(j&(-1<>2]=d;H[a+28>>2]=e;j=g;h=b&31;if((b&63)>>>0>=32){b=g<>>32-h|f<>2]=g;H[a+12>>2]=b;b=c>>31<<21|c>>>11|i;j=b;k=k&-132120577|c<<21;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=4;break a}b=H[(e<<2)+20752>>2];l:{if((f|0)<0){c=(-1<=11?11:e)+1|0;d=d-1|0;break l}h=g;i=63-b|0;c=i&31;if((i&63)>>>0>=32){i=f>>>c|0}else{i=((1<>>c}c=(i&(-1<>2]=d;H[a+28>>2]=h;i=g;e=b&31;if((b&63)>>>0>=32){b=g<>>32-e|f<>2]=g;f=b;H[a+12>>2]=b;b=j&-4|(c>>31<<28|c>>>4);j=b;k=k&268435455|c<<28;H[a+40>>2]=k;H[a+44>>2]=b;if((d|0)<6){d=5;break a}b=H[(h<<2)+20752>>2];m:{if((f|0)<0){e=(-1<=11?11:h)+1|0;i=d-1|0;break m}i=g;e=63-b|0;c=e&31;if((e&63)>>>0>=32){i=f>>>c|0}else{i=((1<>>c}e=(i&(-1<>2]=i;H[a+28>>2]=h;d=g;c=b&31;if((b&63)>>>0>=32){b=d<>>32-c|f<>2]=g;H[a+12>>2]=b;b=j&-505|e<<3;l=b;H[a+40>>2]=k;H[a+44>>2]=b;d=6;if((i|0)<6){break a}b=H[(h<<2)+20752>>2];n:{if((c|0)<0){e=(-1<=11?11:h)+1|0;d=i-1|0;break n}d=g;e=63-b|0;f=e&31;if((e&63)>>>0>=32){f=c>>>f|0}else{f=((1<>>f}e=(f&(-1<>2]=d;H[a+28>>2]=h;j=g;f=b&31;if((b&63)>>>0>=32){i=g<>>32-f|c<>2]=g;f=i;H[a+12>>2]=f;i=k;b=l&-64513|e<<10;k=b;H[a+40>>2]=i;H[a+44>>2]=b;if((d|0)<6){d=7;break a}b=H[(h<<2)+20752>>2];o:{if((f|0)<0){d=d-1|0;c=(-1<=11?11:h)+1|0;break o}j=g;e=63-b|0;c=e&31;if((e&63)>>>0>=32){j=f>>>c|0}else{j=((1<>>c}c=(j&(-1<>2]=d;H[a+28>>2]=e;d=g;e=b&31;if((b&63)>>>0>=32){b=d<>>32-e|f<>2]=g;H[a+12>>2]=b;H[a+40>>2]=i;H[a+44>>2]=k&-8257537|c<<17;d=8}H[a+32>>2]=d-1;f=H[a+44>>2];b=f>>>7|0;c=H[a+40>>2];H[a+40>>2]=(f&127)<<25|c>>>7;H[a+44>>2]=b;return c&127}function Rb(a,b,c,d,e,f,g,h,i){var j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0;p=na-32|0;na=p;H[p+24>>2]=f;r=H[(N(H[d+28>>2],76)+b|0)+28>>2]+N(H[d+32>>2],152)|0;a:{if(!(H[d+40>>2]|!H[r+24>>2])){k=r+28|0;while(1){b:{if(Nb(k)){break b}b=H[d+36>>2];if(b>>>0>=K[k+24>>2]/40>>>0){Ba(i,1,2836,0);break a}b=H[k+20>>2]+N(b,40)|0;cc(H[b+32>>2]);cc(H[b+36>>2]);o=N(H[b+20>>2],H[b+16>>2]);if(!o){break b}q=o&7;b=H[b+24>>2];if(o>>>0>=8){o=o&-8;j=0;while(1){H[b+516>>2]=0;H[b+520>>2]=0;H[b+448>>2]=0;H[b+452>>2]=0;H[b+380>>2]=0;H[b+384>>2]=0;H[b+312>>2]=0;H[b+316>>2]=0;H[b+244>>2]=0;H[b+248>>2]=0;H[b+176>>2]=0;H[b+180>>2]=0;H[b+108>>2]=0;H[b+112>>2]=0;H[b+40>>2]=0;H[b+44>>2]=0;b=b+544|0;j=j+8|0;if((o|0)!=(j|0)){continue}break}if(!q){break b}}j=0;while(1){H[b+40>>2]=0;H[b+44>>2]=0;b=b+68|0;j=j+1|0;if((q|0)!=(j|0)){continue}break}}k=k+36|0;n=n+1|0;if(n>>>0>2]){continue}break}}q=f;c:{if(!(I[c|0]&2)){break c}if(h>>>0<=5){Ba(i,2,4196,0);break c}if(!(I[f|0]==255&I[f+1|0]==145)){Ba(i,2,4238,0);break c}q=f+6|0;H[p+24>>2]=q}l=Fa(20);if(!l){break a}d:{if(F[a+108|0]&1){q=H[a+40>>2];o=a+44|0;h=a+40|0;break d}if(I[c+5640|0]&2){q=H[c+5168>>2];o=c+5180|0;h=c+5168|0;break d}H[p+28>>2]=(f+h|0)-q;o=p+28|0;h=p+24|0}a=H[o>>2];H[l+12>>2]=0;H[l+16>>2]=0;H[l+8>>2]=q;H[l>>2]=q;H[l+4>>2]=a+q;if(!Qa(l,1)){ec(l);a=fc(l);cb(l);a=a+q|0;b=H[h>>2];d=H[o>>2];if(I[c|0]&4){if(b+(d-a|0)>>>0<=1){Ba(i,1,4422,0);break a}if(!(I[a|0]==255&I[a+1|0]==146)){Ba(i,1,4401,0);break a}a=a+2|0}a=a-b|0;H[o>>2]=d-a;H[h>>2]=a+b;H[e>>2]=0;H[g>>2]=H[p+24>>2]-f;x=1;break a}if(H[r+24>>2]){t=r+28|0;while(1){a=H[d+36>>2];b=H[t+20>>2];e:{if(Nb(t)){break e}u=b+N(a,40)|0;z=N(H[u+20>>2],H[u+16>>2]);if(!z){break e}k=H[u+24>>2];v=0;while(1){f:{g:{if(!H[k+40>>2]){a=ac(l,H[u+32>>2],v,H[d+40>>2]+1|0);break g}a=Qa(l,1)}if(!a){H[k+36>>2]=0;break f}if(!H[k+40>>2]){b=0;while(1){a=b;b=b+1|0;if(!ac(l,H[u+36>>2],v,a)){continue}break}b=H[t+28>>2];H[k+32>>2]=3;H[k+24>>2]=b;H[k+28>>2]=(b-a|0)+1}a=1;h:{if(!Qa(l,1)){break h}a=2;if(!Qa(l,1)){break h}a=Qa(l,2);if((a|0)!=3){a=a+3|0;break h}a=Qa(l,5);if((a|0)!=31){a=a+6|0;break h}a=Qa(l,7)+37|0}H[k+36>>2]=a;b=0;while(1){a=b;b=b+1|0;if(Qa(l,1)){continue}break}H[k+32>>2]=a+H[k+32>>2];i:{a=H[k+40>>2];j:{k:{if(!a){a=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];if(!H[k+48>>2]){b=Ga(H[k>>2],240);if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=10}j=H[k>>2];jb(j);b=a&4?1:a&1?10:109;a=0;break k}b=H[k>>2];n=a-1|0;j=b+N(n,24)|0;if(H[j+4>>2]!=H[j+12>>2]){break j}n=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];j=H[k+48>>2];if(j>>>0>>0){j=j+10|0;b=Ga(b,N(j,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=j;b=H[k>>2]}j=N(a,24)+b|0;jb(j);b=1;l:{if(n&4){break l}b=109;if(!(n&1)){break l}b=H[j-12>>2];b=(b|0)==1?2:(b|0)==10?2:1}}n=a;H[j+12>>2]=b}a=H[k+36>>2];if(I[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16|0]&64){while(1){m=N(n,24);s=n?a:1;H[(m+H[k>>2]|0)+16>>2]=s;w=H[k+32>>2];j=0;b=a;if(s>>>0>=2){while(1){j=j+1|0;s=b>>>0>3;b=b>>>1|0;if(s){continue}break}}b=j+w|0;if(b>>>0>=33){H[p+16>>2]=b;Ba(i,1,15535,p+16|0);break i}j=Qa(l,b);b=H[k>>2];m=m+b|0;H[m+20>>2]=j;a=a-H[m+16>>2]|0;if((a|0)<=0){break f}j=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];m=H[k+48>>2];if(m>>>0>>0){m=m+10|0;b=Ga(b,N(m,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=m;b=H[k>>2]}n=n+1|0;b=b+N(n,24)|0;jb(b);if(j&4){H[b+12>>2]=1;continue}if(j&1){j=b;b=H[b-12>>2];H[j+12>>2]=(b|0)==1?2:(b|0)==10?2:1}else{H[b+12>>2]=109}continue}}while(1){m=N(n,24);j=m+H[k>>2]|0;b=H[j+12>>2]-H[j+4>>2]|0;b=(a|0)>(b|0)?b:a;H[j+16>>2]=b;s=H[k+32>>2];j=0;if(b>>>0>=2){while(1){j=j+1|0;w=b>>>0>3;b=b>>>1|0;if(w){continue}break}}b=j+s|0;if(b>>>0>=33){H[p>>2]=b;Ba(i,1,15535,p);break i}j=Qa(l,b);b=H[k>>2];m=m+b|0;H[m+20>>2]=j;a=a-H[m+16>>2]|0;if((a|0)<=0){break f}j=H[(H[c+5584>>2]+N(H[d+28>>2],1080)|0)+16>>2];m=H[k+48>>2];if(m>>>0>>0){m=m+10|0;b=Ga(b,N(m,24));if(!b){break i}H[k>>2]=b;y(b+N(H[k+48>>2],24)|0,0,240);H[k+48>>2]=m;b=H[k>>2]}n=n+1|0;b=b+N(n,24)|0;jb(b);if(j&4){H[b+12>>2]=1;continue}if(j&1){j=b;b=H[b-12>>2];H[j+12>>2]=(b|0)==1?2:(b|0)==10?2:1}else{H[b+12>>2]=109}continue}}cb(l);break a}k=k+68|0;v=v+1|0;if((z|0)!=(v|0)){continue}break}}t=t+36|0;A=A+1|0;if(A>>>0>2]){continue}break}}if(!ec(l)){cb(l);break a}a=fc(l);cb(l);b=a+q|0;a=H[h>>2];if(I[c|0]&4){if(a+(H[o>>2]-b|0)>>>0<=1){Ba(i,1,4422,0);break a}if(!(I[b|0]==255&I[b+1|0]==146)){Ba(i,1,4401,0);break a}b=b+2|0}if((a|0)==(b|0)){break a}H[o>>2]=H[o>>2]+(a-b|0);H[h>>2]=b;x=1;H[e>>2]=1;H[g>>2]=H[p+24>>2]-f}na=p+32|0;return x}function Fb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0;a:{if(!c){break a}b:{e=H[a+184>>2];if(!e){break b}f=H[a+96>>2];if(!f|!H[f+16>>2]|(e|0)!=H[H[f+24>>2]+40>>2]){break b}f=H[c+16>>2];if(!f){break b}n=H[c+24>>2];if(H[n+40>>2]|H[n+44>>2]){break b}i=f&7;c:{if(f>>>0>=8){p=f&-8;while(1){f=N(h,52)+n|0;H[f+404>>2]=e;H[f+352>>2]=e;H[f+300>>2]=e;H[f+248>>2]=e;H[f+196>>2]=e;H[f+144>>2]=e;H[f+92>>2]=e;H[f+40>>2]=e;h=h+8|0;g=g+8|0;if((g|0)!=(p|0)){continue}break}if(!i){break c}}while(1){H[(N(h,52)+n|0)+40>>2]=e;h=h+1|0;j=j+1|0;if((i|0)!=(j|0)){continue}break}}e=0;p=0;n=na-32|0;na=n;v=H[c+16>>2];d:{if(!v){e=1;break d}h=H[c>>2];f=h>>31;q=f;e:{if((f|0)<0){break e}i=H[c+4>>2];f=i>>31;m=f;if((f|0)<0){break e}g=H[c+8>>2];f=g>>31;s=f;if((f|0)<0){break e}j=H[c+12>>2];r=j>>31;if((r|0)<0){break e}f=H[c+24>>2];y=h-1|0;w=q-!h|0;z=i-1|0;x=m-!i|0;A=g-1|0;B=s-!g|0;C=j-1|0;D=r-!j|0;while(1){e=H[f>>2];h=e+y|0;i=e>>>0>h>>>0?w+1|0:w;j=fe(h,i,e,0);H[f+16>>2]=j;i=H[f+4>>2];h=i+z|0;g=i>>>0>h>>>0?x+1|0:x;r=fe(h,g,i,0);H[f+20>>2]=r;h=H[f+40>>2];g=h&31;if((h&63)>>>0>=32){q=1<>>32-g}s=m-1|0;g=q-!m|0;u=g;t=e>>31;l=t+B|0;o=l+1|0;g=l;l=e+A|0;g=ee(l,l>>>0>>0?o:g,e,t);e=u+(g>>31)|0;o=e+1|0;l=e;e=g+s|0;t=g>>>0>e>>>0?o:l;g=h&31;l=(j>>31)+u|0;E=l+1|0;o=l;l=j+s|0;j=j>>>0>l>>>0?E:o;if((h&63)>>>0>=32){g=t>>g}else{g=((1<>>g}e=h&31;if((h&63)>>>0>=32){e=j>>e}else{e=((1<>>e}e=g-e|0;if((e|0)<0){H[n+4>>2]=e;H[n>>2]=p;Ba(d,1,13510,n);e=0;break d}H[f+8>>2]=e;e=i>>31;g=e+D|0;o=g+1|0;l=g;g=i+C|0;j=g>>>0>>0?o:l;i=ee(g,j,i,e);e=(i>>31)+u|0;g=e+1|0;l=e;e=i+s|0;g=i>>>0>e>>>0?g:l;i=h&31;j=q+(r>>31)|0;o=j+1|0;l=j;j=m+r|0;q=j>>>0>>0?o:l;m=j-1|0;if((h&63)>>>0>=32){l=g>>i}else{l=((1<>>i}i=q-!j|0;e=h&31;if((h&63)>>>0>=32){m=i>>e}else{m=((1<>>e}e=l-m|0;if((e|0)<0){H[n+20>>2]=e;H[n+16>>2]=p;Ba(d,1,13579,n+16|0);e=0;break d}H[f+12>>2]=e;f=f+52|0;e=1;p=p+1|0;if((v|0)!=(p|0)){continue}break}break d}Ba(d,1,6720,0)}na=n+32|0;if(e){break b}return 0}e=H[a+100>>2];if(!e){e=Db();H[a+100>>2]=e;if(!e){break a}}wc(c,e);if(!Va(H[a+216>>2],22,d)){break a}h=H[a+216>>2];f=H[h>>2];e=H[h+8>>2];f:{if(f){k=1;g:{if((f|0)!=1){n=f&1;i=f&-2;f=0;while(1){m=0;h:{if(!k){break h}m=0;if(!(ra[H[e>>2]](a,b,d)|0)){break h}m=(ra[H[e+4>>2]](a,b,d)|0)!=0}k=m;e=e+8|0;f=f+2|0;if((i|0)!=(f|0)){continue}break}if(!n){break g}}if(!k){k=0;break g}k=(ra[H[e>>2]](a,b,d)|0)!=0}Oa(h);if(k){break f}Ta(H[a+96>>2]);H[a+96>>2]=0;return 0}Oa(h)}f=0;b=0;d=0;i:{j:{k:{e=H[a+60>>2];if(!e){if(H[c+16>>2]){break k}k=1;break i}h=Fa(N(e,52));if(!h){break j}e=0;if(H[c+16>>2]){b=H[c+24>>2];while(1){e=N(d,52);Ca(H[(e+b|0)+44>>2]);b=H[c+24>>2];H[(e+b|0)+44>>2]=0;d=d+1|0;e=H[c+16>>2];if(d>>>0>>0){continue}break}}if(H[a+60>>2]){d=H[H[a+100>>2]+24>>2];e=0;while(1){b=h+N(e,52)|0;f=N(H[H[a+64>>2]+(e<<2)>>2],52);d=f+d|0;H[b+48>>2]=H[d+48>>2];k=H[d+44>>2];H[b+40>>2]=H[d+40>>2];H[b+44>>2]=k;k=H[d+36>>2];H[b+32>>2]=H[d+32>>2];H[b+36>>2]=k;k=H[d+28>>2];H[b+24>>2]=H[d+24>>2];H[b+28>>2]=k;k=H[d+20>>2];H[b+16>>2]=H[d+16>>2];H[b+20>>2]=k;k=H[d+12>>2];H[b+8>>2]=H[d+8>>2];H[b+12>>2]=k;k=H[d+4>>2];H[b>>2]=H[d>>2];H[b+4>>2]=k;d=H[H[a+100>>2]+24>>2];f=f+d|0;H[b+36>>2]=H[f+36>>2];H[b+44>>2]=H[f+44>>2];H[f+44>>2]=0;e=e+1|0;f=H[a+60>>2];if(e>>>0>>0){continue}break}e=H[c+16>>2]}if(e){b=H[H[a+100>>2]+24>>2];d=0;while(1){e=N(d,52);Ca(H[(e+b|0)+44>>2]);b=H[H[a+100>>2]+24>>2];H[(e+b|0)+44>>2]=0;d=d+1|0;if(d>>>0>2]){continue}break}f=H[a+60>>2]}H[c+16>>2]=f;Ca(H[c+24>>2]);H[c+24>>2]=h;k=1;break i}e=H[c+24>>2];d=H[H[a+100>>2]+24>>2];while(1){f=N(b,52);e=f+e|0;H[e+36>>2]=H[(d+f|0)+36>>2];Ca(H[e+44>>2]);e=H[c+24>>2];m=f+e|0;d=H[H[a+100>>2]+24>>2];f=f+d|0;H[m+44>>2]=H[f+44>>2];H[f+44>>2]=0;b=b+1|0;if(b>>>0>2]){continue}break}k=1;break i}Ta(H[a+96>>2]);H[a+96>>2]=0;k=0}}return k|0}function yb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;j=(c<<2)+b|0;e=H[a>>2];f=H[a+12>>2]<<5;i=e+f|0;l=e-f|0;e=H[a+16>>2];k=H[a+28>>2];h=H[a+20>>2];m=H[a+8>>2];a:{b:{if(i&15|(b&15|d>>>0<8)){if(e>>>0>=h>>>0){break a}c:{switch(d-1|0){case 1:f=e+1|0;if(h-e&1){j=i+(e<<6)|0;e=(e<<2)+b|0;L[j>>2]=L[e>>2];L[j+4>>2]=L[e+(c<<2)>>2];e=f}if((f|0)==(h|0)){break a}while(1){f=i+(e<<6)|0;j=(e<<2)+b|0;L[f>>2]=L[j>>2];g=f;f=c<<2;L[g+4>>2]=L[f+j>>2];j=e+1|0;g=i+(j<<6)|0;j=(j<<2)+b|0;L[g>>2]=L[j>>2];L[g+4>>2]=L[f+j>>2];e=e+2|0;if((h|0)!=(e|0)){continue}break};break a;case 0:break c;default:break b}}f=e;g=h-e&3;if(g){j=0;while(1){L[i+(f<<6)>>2]=L[(f<<2)+b>>2];f=f+1|0;j=j+1|0;if((g|0)!=(j|0)){continue}break}}if(e-h>>>0>4294967292){break a}while(1){L[i+(f<<6)>>2]=L[(f<<2)+b>>2];e=f+1|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];e=f+2|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];e=f+3|0;L[i+(e<<6)>>2]=L[(e<<2)+b>>2];f=f+4|0;if((h|0)!=(f|0)){continue}break}break a}if(e>>>0>=h>>>0){break a}while(1){f=i+(e<<6)|0;L[f>>2]=L[(e<<2)+b>>2];g=c+e|0;L[f+4>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+8>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+12>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+16>>2]=L[(g<<2)+b>>2];g=c+g<<2;L[f+20>>2]=L[g+b>>2];g=g+j|0;L[f+24>>2]=L[g>>2];L[f+28>>2]=L[g+(c<<2)>>2];e=e+1|0;if((h|0)!=(e|0)){continue}break}break a}while(1){f=i+(e<<6)|0;L[f>>2]=L[(e<<2)+b>>2];g=c+e|0;L[f+4>>2]=L[(g<<2)+b>>2];g=c+g|0;L[f+8>>2]=L[(g<<2)+b>>2];d:{if((d|0)==3){break d}g=c+g|0;L[f+12>>2]=L[(g<<2)+b>>2];if((d|0)==4){break d}g=c+g|0;L[f+16>>2]=L[(g<<2)+b>>2];if((d|0)==5){break d}g=c+g|0;L[f+20>>2]=L[(g<<2)+b>>2];if((d|0)==6){break d}g=j+(g<<2)|0;L[f+24>>2]=L[g>>2];if((d|0)==7){break d}L[f+28>>2]=L[g+(c<<2)>>2]}e=e+1|0;if((h|0)!=(e|0)){continue}break}}b=(m<<2)+b|0;f=b+(c<<2)|0;e=H[a+24>>2];i=l+32|0;e:{if(i&15|(b&15|d>>>0<8)){if(e>>>0>=k>>>0){break e}f:{switch(d-1|0){case 1:a=e+1|0;if(k-e&1){d=i+(e<<6)|0;e=b+(e<<2)|0;L[d>>2]=L[e>>2];L[d+4>>2]=L[e+(c<<2)>>2];e=a}if((a|0)==(k|0)){break e}while(1){a=i+(e<<6)|0;d=b+(e<<2)|0;L[a>>2]=L[d>>2];f=a;a=c<<2;L[f+4>>2]=L[a+d>>2];d=e+1|0;f=i+(d<<6)|0;d=b+(d<<2)|0;L[f>>2]=L[d>>2];L[f+4>>2]=L[a+d>>2];e=e+2|0;if((k|0)!=(e|0)){continue}break};break e;case 0:c=e;a=k-e&3;if(a){f=0;while(1){L[i+(c<<6)>>2]=L[b+(c<<2)>>2];c=c+1|0;f=f+1|0;if((a|0)!=(f|0)){continue}break}}if(e-k>>>0>4294967292){break e}while(1){L[i+(c<<6)>>2]=L[b+(c<<2)>>2];a=c+1|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];a=c+2|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];a=c+3|0;L[i+(a<<6)>>2]=L[b+(a<<2)>>2];c=c+4|0;if((k|0)!=(c|0)){continue}break};break e;default:break f}}while(1){a=i+(e<<6)|0;L[a>>2]=L[b+(e<<2)>>2];h=c+e|0;L[a+4>>2]=L[b+(h<<2)>>2];h=c+h|0;L[a+8>>2]=L[b+(h<<2)>>2];g:{if((d|0)==3){break g}h=c+h|0;L[a+12>>2]=L[b+(h<<2)>>2];if((d|0)==4){break g}h=c+h|0;L[a+16>>2]=L[b+(h<<2)>>2];if((d|0)==5){break g}h=c+h|0;L[a+20>>2]=L[b+(h<<2)>>2];if((d|0)==6){break g}h=f+(h<<2)|0;L[a+24>>2]=L[h>>2];if((d|0)==7){break g}L[a+28>>2]=L[h+(c<<2)>>2]}e=e+1|0;if((k|0)!=(e|0)){continue}break}break e}if(e>>>0>=k>>>0){break e}while(1){a=i+(e<<6)|0;L[a>>2]=L[b+(e<<2)>>2];d=c+e|0;L[a+4>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+8>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+12>>2]=L[b+(d<<2)>>2];d=c+d|0;L[a+16>>2]=L[b+(d<<2)>>2];d=c+d<<2;L[a+20>>2]=L[d+b>>2];d=d+f|0;L[a+24>>2]=L[d>>2];L[a+28>>2]=L[d+(c<<2)>>2];e=e+1|0;if((k|0)!=(e|0)){continue}break}}}function Hd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=na-16|0;na=i;if(H[a+8>>2]==16){k=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{k=H[a+12>>2]}a:{if(!c){Ba(d,1,4259,0);break a}j=H[a+96>>2];e=1;Da(b,i,1);g=H[i>>2];if(g>>>0>=2){Ba(d,2,9792,0);break a}if((g+1|0)!=(c|0)){e=0;Ba(d,2,4259,0);break a}b:{if((g|0)!=1){break b}d=H[j+16>>2];if(!d){break b}j=d&7;c=H[k+5584>>2];if(d>>>0>=8){d=d&-8;e=0;while(1){H[c+8636>>2]=0;H[c+7556>>2]=0;H[c+6476>>2]=0;H[c+5396>>2]=0;H[c+4316>>2]=0;H[c+3236>>2]=0;H[c+2156>>2]=0;H[c+1076>>2]=0;c=c+8640|0;e=e+8|0;if((d|0)!=(e|0)){continue}break}if(!j){break b}}e=0;while(1){H[c+1076>>2]=0;c=c+1080|0;e=e+1|0;if((j|0)!=(e|0)){continue}break}}c=H[k+5608>>2];if(c){Ca(c);H[k+5608>>2]=0;g=H[i>>2]}if(!g){e=1;break a}while(1){b=b+1|0;Da(b,i+4|0,1);c:{if(!H[k+5632>>2]){break c}j=H[k+5628>>2];if(H[j>>2]!=H[i+4>>2]){break c}c=H[j+4>>2];d=H[a+96>>2];if((c|0)!=H[d+16>>2]){break c}g=H[j+8>>2];d:{if(!g){break d}e=0;h=N(c,c);if(H[g+16>>2]!=(N(h,H[(H[g>>2]<<2)+24896>>2])|0)){break a}c=Fa(h<<2);H[k+5608>>2]=c;if(!c){break a}e=H[g+12>>2];e:{switch(H[g>>2]-1|0){default:f=0;g=na-16|0;na=g;if(h){while(1){Da(e,g+12|0,2);L[c>>2]=K[g+12>>2];c=c+4|0;e=e+2|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}na=g+16|0;break d;case 0:f=0;g=na-16|0;na=g;if(h){while(1){Da(e,g+12|0,4);L[c>>2]=K[g+12>>2];c=c+4|0;e=e+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}na=g+16|0;break d;case 1:f=0;if(!h){break d}while(1){Fc(e,i+8|0);L[c>>2]=L[i+8>>2];c=c+4|0;e=e+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break d;case 2:break e}}f=0;if(!h){break d}while(1){Hb(e,i+8|0);L[c>>2]=M[i+8>>3];c=c+4|0;e=e+8|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}j=H[j+12>>2];if(!j){break c}e=0;h=H[d+16>>2];if(H[j+16>>2]!=(N(h,H[(H[j>>2]<<2)+24896>>2])|0)){break a}g=Fa(h<<2);if(!g){break a}c=H[j+12>>2];f:{g:{switch(H[j>>2]-1|0){default:f=0;e=g;if(!h){break f}while(1){Da(c,i+8|0,2);H[e>>2]=H[i+8>>2];e=e+4|0;c=c+2|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 0:f=0;e=g;if(!h){break f}while(1){Da(c,i+8|0,4);H[e>>2]=H[i+8>>2];e=e+4|0;c=c+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 1:f=0;e=g;if(!h){break f}while(1){Fc(c,i+8|0);H[e>>2]=~~L[i+8>>2];e=e+4|0;c=c+4|0;f=f+1|0;if((h|0)!=(f|0)){continue}break};break f;case 2:break g}}f=0;e=g;if(!h){break f}while(1){Hb(c,i+8|0);H[e>>2]=~~M[i+8>>3];e=e+4|0;c=c+8|0;f=f+1|0;if((h|0)!=(f|0)){continue}break}}d=H[d+16>>2];h:{if(!d){break h}h=d&7;c=H[k+5584>>2];i:{if(d>>>0<8){e=g;break i}j=d&-8;d=0;e=g;while(1){H[c+1076>>2]=H[e>>2];H[c+2156>>2]=H[e+4>>2];H[c+3236>>2]=H[e+8>>2];H[c+4316>>2]=H[e+12>>2];H[c+5396>>2]=H[e+16>>2];H[c+6476>>2]=H[e+20>>2];H[c+7556>>2]=H[e+24>>2];H[c+8636>>2]=H[e+28>>2];c=c+8640|0;e=e+32|0;d=d+8|0;if((j|0)!=(d|0)){continue}break}if(!h){break h}}d=0;while(1){H[c+1076>>2]=H[e>>2];c=c+1080|0;e=e+4|0;d=d+1|0;if((h|0)!=(d|0)){continue}break}}Ca(g)}e=1;l=l+1|0;if(l>>>0>2]){continue}break}}na=i+16|0;return e|0}function _d(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=na-128|0;na=e;H[e+120>>2]=0;a:{if((c|0)!=8){Ba(d,1,4047,0);Ba(d,1,4047,0);break a}Da(b,a+228|0,2);Da(b+2|0,e+124|0,4);Da(b+6|0,e+116|0,1);Da(b+7|0,e+120|0,1);c=H[a+228>>2];i=H[a+128>>2];if(c>>>0>=N(i,H[a+132>>2])>>>0){H[e+112>>2]=c;Ba(d,1,7843,e+112|0);break a}f=H[a+180>>2]+N(c,5644)|0;h=(c>>>0)/(i>>>0)|0;b=H[e+116>>2];b:{g=H[a+44>>2];if((g|0)>=0&(c|0)!=(g|0)){break b}g=H[f+5588>>2]+1|0;if((g|0)==(b|0)){break b}H[e+104>>2]=g;H[e+100>>2]=b;H[e+96>>2]=c;Ba(d,1,7867,e+96|0);f=0;break a}H[f+5588>>2]=b;c:{b=H[e+124>>2];if(b-1>>>0<=12){if((b|0)!=12){break c}H[e+64>>2]=12;Ba(d,2,11864,e- -64|0);b=H[e+124>>2]}if(!b){Ba(d,4,10695,0);H[a+56>>2]=1}d:{e:{f:{g:{b=H[f+5592>>2];if(b){g=H[e+116>>2];if(g>>>0>>0){break g}H[e+52>>2]=b;H[e+48>>2]=g;Ba(d,1,5150,e+48|0);H[a+56>>2]=1;f=0;break a}g=H[e+120>>2];if(g){break f}break d}g=H[e+120>>2];if(!g){break e}}b=(I[a+92|0]>>>4&1)+g|0;H[e+120>>2]=b;g=H[e+116>>2];j=H[f+5592>>2];if(g>>>0>j-1>>>0){H[e+20>>2]=j;H[e+16>>2]=g;Ba(d,1,5051,e+16|0);H[a+56>>2]=1;f=0;break a}if(b>>>0<=g>>>0){H[e+36>>2]=b;H[e+32>>2]=g;Ba(d,1,5250,e+32|0);H[a+56>>2]=1;f=0;break a}H[f+5592>>2]=b}if((H[e+116>>2]+1|0)!=(b|0)){break d}F[a+92|0]=I[a+92|0]|1}b=H[e+124>>2];H[a+8>>2]=16;H[a+24>>2]=H[a+56>>2]?0:b-12|0;f=H[a+44>>2];h:{if((f|0)==-1){f=4;b=c-N(h,i)|0;if(!(b>>>0>2]|b>>>0>=K[a+36>>2]|h>>>0>2])){f=h>>>0>=K[a+40>>2]?4:0}F[a+92|0]=I[a+92|0]&251|f;b=H[a+228>>2];break h}b=H[a+228>>2];F[a+92|0]=I[a+92|0]&251|((f|0)!=(b|0)?4:0)}c=H[H[a+224>>2]+40>>2]+N(b,40)|0;H[c>>2]=b;H[c+12>>2]=H[e+116>>2];f=H[e+120>>2];if(!H[a+76>>2]){if(K[c+4>>2]>=f>>>0){f=1;break a}H[e>>2]=b;Ba(d,2,1612,e);H[a+76>>2]=1;f=H[e+120>>2]}c=H[H[a+224>>2]+40>>2];b=H[a+228>>2];h=c+N(b,40)|0;if(f){H[h+4>>2]=f;b=H[e+120>>2];H[h+8>>2]=b;c=H[h+16>>2];if(!c){b=Ea(b,24);H[(H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0)+16>>2]=b;if(b){f=1;break a}f=0;Ba(d,1,6947,0);break a}b=Ga(c,N(b,24));c=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;if(!b){Ca(H[c+16>>2]);f=0;H[(H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0)+16>>2]=0;Ba(d,1,6947,0);break a}H[c+16>>2]=b;f=1;break a}i:{g=H[h+16>>2];if(g){break i}H[h+8>>2]=10;g=Ea(10,24);c=H[H[a+224>>2]+40>>2];b=H[a+228>>2];h=c+N(b,40)|0;H[h+16>>2]=g;if(g){break i}f=0;H[h+8>>2]=0;Ba(d,1,6947,0);break a}f=1;b=N(b,40)+c|0;c=H[e+116>>2];if(K[b+8>>2]>c>>>0){break a}h=b;b=c+1|0;H[h+8>>2]=b;b=Ga(g,N(b,24));c=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;if(!b){Ca(H[c+16>>2]);f=0;a=H[H[a+224>>2]+40>>2]+N(H[a+228>>2],40)|0;H[a+8>>2]=0;H[a+16>>2]=0;Ba(d,1,6947,0);break a}H[c+16>>2]=b;break a}H[e+80>>2]=b;Ba(d,1,12133,e+80|0);f=0}na=e+128|0;return f|0}function mb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;f=H[a+8>>2];e=f+H[a+4>>2]|0;a:{if(!H[a+12>>2]){if((e|0)<2|(d|0)<=0){break a}q=e&2147483644;m=e&3;r=e&1;s=f+1|0;g=H[a>>2];o=g+(e<<2)|0;t=e-4>>>1|0;a=e-1|0;u=g+(a<<2)|0;v=N(c,f)<<2;l=e>>>0<4;w=N(a>>>1|0,c)<<2;while(1){f=H[b+v>>2];e=H[b>>2]-(f+1>>1)|0;a=0;h=0;if(!l){while(1){j=a+1|0;x=H[(N(j,c)<<2)+b>>2];i=H[(N(a+s|0,c)<<2)+b>>2];p=g+(h<<2)|0;H[p>>2]=e;k=e;e=x-((f+i|0)+2>>2)|0;H[p+4>>2]=(k+e>>1)+f;h=h+2|0;k=(a|0)!=(t|0);f=i;a=j;if(k){continue}break}}H[g+(h<<2)>>2]=e;if(r){a=H[b+w>>2]-(f+1>>1)|0;H[u>>2]=a;e=a+e>>1;a=-8}else{a=-4}H[a+o>>2]=e+f;f=0;a=0;h=0;e=0;b:{if(!l){while(1){H[(N(a,c)<<2)+b>>2]=H[g+(a<<2)>>2];e=a|1;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|2;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|3;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];a=a+4|0;h=h+4|0;if((q|0)!=(h|0)){continue}break}e=a;if(!m){break b}}while(1){H[(N(c,e)<<2)+b>>2]=H[g+(e<<2)>>2];e=e+1|0;f=f+1|0;if((m|0)!=(f|0)){continue}break}}b=b+4|0;n=n+1|0;if((n|0)!=(d|0)){continue}break}break a}c:{switch(e-1|0){case 0:if((d|0)<=0){break a}a=d&3;if(d>>>0>=4){d=d&2147483644;c=0;while(1){H[b>>2]=H[b>>2]/2;H[b+4>>2]=H[b+4>>2]/2;H[b+8>>2]=H[b+8>>2]/2;H[b+12>>2]=H[b+12>>2]/2;b=b+16|0;c=c+4|0;if((d|0)!=(c|0)){continue}break}if(!a){break a}}c=0;while(1){H[b>>2]=H[b>>2]/2;b=b+4|0;c=c+1|0;if((a|0)!=(c|0)){continue}break};break a;case 1:if((d|0)<=0){break a}a=H[a>>2];e=0;f=N(c,f)<<2;while(1){i=b+f|0;j=H[b>>2]-(H[i>>2]+1>>1)|0;H[a+4>>2]=j;i=j+H[i>>2]|0;H[a>>2]=i;H[b>>2]=i;H[(c<<2)+b>>2]=H[a+4>>2];b=b+4|0;e=e+1|0;if((e|0)!=(d|0)){continue}break};break a;default:break c}}if((e|0)<3|(d|0)<=0){break a}q=e&2147483644;m=e&3;g=H[a>>2];r=(g+(e<<2)|0)-4|0;a=e-2|0;s=g+(a<<2)|0;o=e&1;i=!o;t=((e-i|0)-4>>>1|0)+1|0;u=N(c,f)<<2;v=a-i>>>0<2;w=N((e>>>1|0)-1|0,c)<<2;x=e-1>>>0<3;while(1){l=b+u|0;f=H[l+(c<<2)>>2];a=H[l>>2];e=H[b>>2]-((f+a|0)+2>>2)|0;H[g>>2]=e+a;a=1;h=1;if(!v){while(1){p=H[(N(a,c)<<2)+b>>2];j=a+1|0;i=H[l+(N(j,c)<<2)>>2];y=g+(h<<2)|0;H[y>>2]=e;k=e;e=p-((f+i|0)+2>>2)|0;H[y+4>>2]=(k+e>>1)+f;h=h+2|0;k=(a|0)!=(t|0);f=i;a=j;if(k){continue}break}}H[g+(h<<2)>>2]=e;d:{if(!o){a=H[b+w>>2]-(f+1>>1)|0;H[s>>2]=(e+a>>1)+f;break d}a=e+f|0}H[r>>2]=a;f=0;a=0;h=0;e=0;e:{if(!x){while(1){H[(N(a,c)<<2)+b>>2]=H[g+(a<<2)>>2];e=a|1;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|2;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];e=a|3;H[(N(e,c)<<2)+b>>2]=H[g+(e<<2)>>2];a=a+4|0;h=h+4|0;if((q|0)!=(h|0)){continue}break}e=a;if(!m){break e}}while(1){H[(N(c,e)<<2)+b>>2]=H[g+(e<<2)>>2];e=e+1|0;f=f+1|0;if((m|0)!=(f|0)){continue}break}}b=b+4|0;n=n+1|0;if((n|0)!=(d|0)){continue}break}}}function Ub(a,b,c,d,e,f,g){var h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;a:{n=N(e,3);h=H[b>>2]>>>n|0;if(h&2097168){break a}h=h&495;if(!h){break a}o=a+28|0;l=o+(I[h+H[a+108>>2]|0]<<2)|0;H[a+104>>2]=l;k=H[l>>2];i=H[k>>2];h=H[a+4>>2]-i|0;H[a+4>>2]=h;j=H[a>>2];b:{if(j>>>16>>>0>>0){m=H[k+4>>2];H[a+4>>2]=i;h=h>>>0>>0;H[l>>2]=H[k+(h?8:12)>>2];k=h?m:!m;h=H[a+8>>2];while(1){c:{if(h){break c}h=H[a+16>>2];m=h+1|0;l=I[h+1|0];if(I[h|0]==255){if(l>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;h=8;break c}H[a+16>>2]=m;j=(l<<9)+j|0;h=7;break c}H[a+16>>2]=m;h=8;j=(l<<8)+j|0}h=h-1|0;H[a+8>>2]=h;j=j<<1;H[a>>2]=j;i=i<<1;H[a+4>>2]=i;if(i>>>0<32768){continue}break}h=i;break b}j=j-(i<<16)|0;H[a>>2]=j;if(!(h&32768)){m=H[k+4>>2];i=h>>>0>>0;H[l>>2]=H[k+(i?12:8)>>2];k=i?!m:m;i=H[a+8>>2];while(1){d:{if(i){break d}i=H[a+16>>2];m=i+1|0;l=I[i+1|0];if(I[i|0]==255){if(l>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;i=8;break d}H[a+16>>2]=m;j=(l<<9)+j|0;i=7;break d}H[a+16>>2]=m;i=8;j=(l<<8)+j|0}i=i-1|0;H[a+8>>2]=i;j=j<<1;H[a>>2]=j;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break b}k=H[k+4>>2]}e:{if(!k){break e}p=b-4|0;i=H[b>>2];k=H[b+4>>2]>>>n+17&4|(H[p>>2]>>>n+19&1|(i>>>n+16&64|i>>>n&170|i>>>(e?n+12|0:14)&16));m=o+(I[k+24384|0]<<2)|0;H[a+104>>2]=m;l=H[m>>2];i=H[l>>2];h=h-i|0;H[a+4>>2]=h;o=I[k+24640|0];f:{if(j>>>16>>>0>>0){k=H[l+4>>2];H[a+4>>2]=i;h=h>>>0>>0;H[m>>2]=H[l+(h?8:12)>>2];l=h?k:!k;h=H[a+8>>2];while(1){g:{if(h){break g}h=H[a+16>>2];m=h+1|0;k=I[h+1|0];if(I[h|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;h=8;break g}H[a+16>>2]=m;j=(k<<9)+j|0;h=7;break g}H[a+16>>2]=m;h=8;j=(k<<8)+j|0}h=h-1|0;H[a+8>>2]=h;j=j<<1;H[a>>2]=j;i=i<<1;H[a+4>>2]=i;if(i>>>0<32768){continue}break}break f}k=j-(i<<16)|0;H[a>>2]=k;if(!(h&32768)){j=H[l+4>>2];i=h>>>0>>0;H[m>>2]=H[l+(i?12:8)>>2];l=i?!j:j;j=H[a+8>>2];while(1){h:{if(j){break h}j=H[a+16>>2];m=j+1|0;i=I[j+1|0];if(I[j|0]==255){if(i>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;k=k+65280|0;j=8;break h}H[a+16>>2]=m;k=(i<<9)+k|0;j=7;break h}H[a+16>>2]=m;j=8;k=(i<<8)+k|0}j=j-1|0;H[a+8>>2]=j;k=k<<1;H[a>>2]=k;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break f}l=H[l+4>>2]}H[c>>2]=(l|0)==(o|0)?d:0-d|0;H[p>>2]=H[p>>2]|32<>2]=H[b>>2]|(c<<19|16)<>2]=H[b+4>>2]|8<>2]=H[a+4>>2]|32768;H[a>>2]=H[a>>2]|c<<31|65536;a=a-4|0;H[a>>2]=H[a>>2]|131072}if((e|0)!=3){break e}a=(f<<2)+b|0;H[a+4>>2]=H[a+4>>2]|1;H[a>>2]=H[a>>2]|c<<18|2;a=a-4|0;H[a>>2]=H[a>>2]|4}H[b>>2]=H[b>>2]|2097152<>2]>>>m|0;if(g&2097168){break a}n=a+28|0;k=n+(I[H[a+108>>2]+(g&495)|0]<<2)|0;H[a+104>>2]=k;j=H[k>>2];h=H[j>>2];g=H[a+4>>2]-h|0;H[a+4>>2]=g;i=H[a>>2];b:{if(i>>>16>>>0>>0){l=H[j+4>>2];H[a+4>>2]=h;g=g>>>0>>0;H[k>>2]=H[j+(g?8:12)>>2];j=g?l:!l;g=H[a+8>>2];while(1){c:{if(g){break c}g=H[a+16>>2];l=g+1|0;k=I[g+1|0];if(I[g|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;g=8;break c}H[a+16>>2]=l;i=(k<<9)+i|0;g=7;break c}H[a+16>>2]=l;g=8;i=(k<<8)+i|0}g=g-1|0;H[a+8>>2]=g;i=i<<1;H[a>>2]=i;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}g=h;break b}i=i-(h<<16)|0;H[a>>2]=i;if(!(g&32768)){l=H[j+4>>2];h=g>>>0>>0;H[k>>2]=H[j+(h?12:8)>>2];j=h?!l:l;h=H[a+8>>2];while(1){d:{if(h){break d}h=H[a+16>>2];l=h+1|0;k=I[h+1|0];if(I[h|0]==255){if(k>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;h=8;break d}H[a+16>>2]=l;i=(k<<9)+i|0;h=7;break d}H[a+16>>2]=l;h=8;i=(k<<8)+i|0}h=h-1|0;H[a+8>>2]=h;i=i<<1;H[a>>2]=i;g=g<<1;H[a+4>>2]=g;if(g>>>0<32768){continue}break}break b}j=H[j+4>>2]}if(!j){break a}j=n;n=b-4|0;h=H[b>>2];o=H[b+4>>2]>>>m+17&4|(H[n>>2]>>>m+19&1|(h>>>m+16&64|h>>>m&170|h>>>(e?m+12|0:14)&16));l=j+(I[o+24384|0]<<2)|0;H[a+104>>2]=l;k=H[l>>2];h=H[k>>2];g=g-h|0;H[a+4>>2]=g;e:{if(i>>>16>>>0>>0){j=H[k+4>>2];H[a+4>>2]=h;g=g>>>0>>0;H[l>>2]=H[k+(g?8:12)>>2];k=g?j:!j;g=H[a+8>>2];while(1){f:{if(g){break f}g=H[a+16>>2];l=g+1|0;j=I[g+1|0];if(I[g|0]==255){if(j>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;i=i+65280|0;g=8;break f}H[a+16>>2]=l;i=(j<<9)+i|0;g=7;break f}H[a+16>>2]=l;g=8;i=(j<<8)+i|0}g=g-1|0;H[a+8>>2]=g;i=i<<1;H[a>>2]=i;h=h<<1;H[a+4>>2]=h;if(h>>>0<32768){continue}break}break e}j=i-(h<<16)|0;H[a>>2]=j;if(!(g&32768)){i=H[k+4>>2];h=g>>>0>>0;H[l>>2]=H[k+(h?12:8)>>2];k=h?!i:i;i=H[a+8>>2];while(1){g:{if(i){break g}i=H[a+16>>2];l=i+1|0;h=I[i+1|0];if(I[i|0]==255){if(h>>>0>=144){H[a+12>>2]=H[a+12>>2]+1;j=j+65280|0;i=8;break g}H[a+16>>2]=l;j=(h<<9)+j|0;i=7;break g}H[a+16>>2]=l;i=8;j=(h<<8)+j|0}i=i-1|0;H[a+8>>2]=i;j=j<<1;H[a>>2]=j;g=g<<1;H[a+4>>2]=g;if(g>>>0<32768){continue}break}break e}k=H[k+4>>2]}g=c;c=I[o+24640|0];H[g>>2]=(c|0)==(k|0)?d:0-d|0;H[n>>2]=H[n>>2]|32<>2]=H[b>>2]|(d<<19|16)<>2]=H[b+4>>2]|8<>2]<<2)+b|0;H[c+4>>2]=H[c+4>>2]|32768;H[c>>2]=H[c>>2]|d<<31|65536;c=c-4|0;H[c>>2]=H[c>>2]|131072}if((e|0)!=3){break a}a=(H[a+124>>2]<<2)+b|0;H[a+4>>2]=H[a+4>>2]|4;H[a+12>>2]=H[a+12>>2]|1;H[a+8>>2]=H[a+8>>2]|d<<18|2}}function Cd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;e=na-112|0;na=e;j=1024;a:{b:{h=Ea(1,1024);if(h){l=e+92|0;k=e+108|0;while(1){c:{d:{d=e+104|0;e:{if((Ja(b,d,8,c)|0)!=8){break e}Da(d,e+88|0,4);Da(k,l,4);f=8;f:{g:{h:{i:{switch(H[e+88>>2]){case 0:d=Pa(b);g=qa;if((g|0)<0){g=1}else{g=d>>>0<4294967288&(g|0)<=0}if(g){break h}Ba(c,1,8449,0);break e;case 1:break i;default:break f}}d=e+104|0;if((Ja(b,d,8,c)|0)!=8){break e}Da(d,e+100|0,4);if(!H[e+100>>2]){break g}Ba(c,1,8449,0);break e}H[e+88>>2]=d+8;break f}Da(k,e+88|0,4);f=16}d=H[e+92>>2];if((d|0)==1785737827){b=H[a+100>>2];if(b&4){H[a+100>>2]=b|8;break e}Ba(c,1,5702,0);Ca(h);a=0;break a}i=H[e+88>>2];if(!i){Ba(c,1,3268,0);Ca(h);a=0;break a}if(f>>>0>i>>>0){H[e+4>>2]=d;H[e>>2]=i;Ba(c,1,13933,e);break b}j:{k:{l:{m:{n:{o:{p:{q:{r:{s:{if((d|0)<=1668246641){if((d|0)==1651532643){break r}if((d|0)==1667523942){break p}if((d|0)!=1668112752){break s}g=25264;break n}if((d|0)<=1783635999){if((d|0)==1668246642){break o}g=25232;if((d|0)==1768449138){break n}if((d|0)!=1718909296){break s}g=25208;break l}if((d|0)==1885564018){break q}if((d|0)==1783636e3){break m}g=25216;if((d|0)==1785737832){break l}}d=H[a+100>>2];if(d&1){break j}Ba(c,1,2062,0);Ca(h);a=0;break a}g=25248;break n}g=25256;break n}g=25272;break n}g=25240}H[e+76>>2]=d&255;H[e+64>>2]=d>>>24;H[e+72>>2]=d>>>8&255;H[e+68>>2]=d>>>16&255;Ba(c,2,2011,e- -64|0);f=i-f|0;if(I[a+100|0]&4){break k}d=H[e+92>>2];H[e+48>>2]=d>>>24;H[e+60>>2]=d&255;H[e+52>>2]=d>>>16&255;H[e+56>>2]=d>>>8&255;Ba(c,2,6771,e+48|0);H[a+100>>2]=H[a+100>>2]|2147483647;d=qb(b,f,c);if(!qa&(d|0)==(f|0)){continue}Ba(c,1,3748,0);Ca(h);a=0;break a}g=25200}f=i-f|0}d=f;f=Pa(b);i=qa;if((i|0)<0){f=1}else{f=(i|0)<=0&d>>>0>f>>>0}if(f){f=H[e+88>>2];a=H[e+92>>2];m=e,n=Pa(b),H[m+40>>2]=n;H[e+36>>2]=d;H[e+32>>2]=a&255;H[e+20>>2]=a>>>24;H[e+16>>2]=f;H[e+28>>2]=a>>>8&255;H[e+24>>2]=a>>>16&255;Ba(c,1,15680,e+16|0);break b}if(d>>>0<=j>>>0){f=h;break c}j=d;f=Ga(h,d);if(f){break c}Ca(h);Ba(c,1,2193,0);a=0;break a}if(!(d&2)){Ba(c,1,2132,0);Ca(h);a=0;break a}H[a+100>>2]=d|2147483647;d=i-f|0;f=qb(b,d,c);if(!qa&(d|0)==(f|0)){continue}if(!(I[a+100|0]&8)){break d}Ba(c,2,3748,0)}Ca(h);a=1;break a}Ba(c,1,3748,0);Ca(h);a=0;break a}if((Ja(b,f,d,c)|0)!=(d|0)){Ba(c,1,3798,0);Ca(f);a=0;break a}h=f;if(ra[H[g+4>>2]](a,f,d,c)|0){continue}break}Ca(f);a=0;break a}Ba(c,1,4923,0);a=0;break a}Ca(h);a=0}na=e+112|0;return a|0}function Id(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;f=na-16|0;na=f;if(H[a+8>>2]==16){i=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{i=H[a+12>>2]}a:{if(c>>>0<=1){Ba(d,1,4721,0);a=0;break a}Da(b,f+12|0,2);if(H[f+12>>2]){Ba(d,2,5897,0);a=1;break a}if(c>>>0<=6){Ba(d,1,4721,0);a=0;break a}Da(b+2|0,f+8|0,1);e=H[i+5628>>2];h=H[i+5632>>2];b:{c:{if(!h){break c}j=H[f+8>>2];a=e;while(1){if((j|0)!=H[a>>2]){a=a+20|0;g=g+1|0;if((h|0)!=(g|0)){continue}break c}break}j=0;if((g|0)!=(h|0)){break b}}if(H[i+5636>>2]==(h|0)){a=h+10|0;H[i+5636>>2]=a;a=Ga(e,N(a,20));if(!a){Ca(H[i+5628>>2]);H[i+5636>>2]=0;H[i+5628>>2]=0;H[i+5632>>2]=0;Ba(d,1,4747,0);a=0;break a}H[i+5628>>2]=a;e=H[i+5632>>2];j=N(H[i+5636>>2]-e|0,20);if(j){y(a+N(e,20)|0,0,j)}h=H[i+5632>>2];e=H[i+5628>>2]}a=e+N(h,20)|0;j=1}H[a>>2]=H[f+8>>2];Da(b+3|0,f+12|0,2);if(H[f+12>>2]){Ba(d,2,5897,0);a=1;break a}Da(b+5|0,f+4|0,2);e=H[f+4>>2];if(e>>>0>=2){Ba(d,2,3130,0);a=1;break a}h=c-7|0;if(e){c=b+7|0;while(1){if(h>>>0<=2){Ba(d,1,4721,0);a=0;break a}Da(c,f+12|0,1);if(H[f+12>>2]!=1){Ba(d,2,5579,0);a=1;break a}Da(c+1|0,f,2);e=H[f>>2];b=e&32767;H[a+4>>2]=b;h=h-3|0;e=(e>>>15|0)+1|0;k=N(e,b)+2|0;if(h>>>0>>0){Ba(d,1,4721,0);a=0;break a}c=c+3|0;g=0;if(b){while(1){Da(c,f+12|0,e);if(H[f+12>>2]!=(g|0)){Ba(d,2,6259,0);a=1;break a}c=c+e|0;g=g+1|0;if(g>>>0>2]){continue}break}}Da(c,f,2);e=H[f>>2];b=e&32767;H[f>>2]=b;if((b|0)!=H[a+4>>2]){Ba(d,2,3306,0);a=1;break a}e=(e>>>15|0)+1|0;l=N(e,b)+3|0;k=h-k|0;if(l>>>0>k>>>0){Ba(d,1,4721,0);a=0;break a}c=c+2|0;g=0;if(b){while(1){Da(c,f+12|0,e);if(H[f+12>>2]!=(g|0)){Ba(d,2,6259,0);a=1;break a}c=c+e|0;g=g+1|0;if(g>>>0>2]){continue}break}}Da(c,f+12|0,3);e=H[f+12>>2];H[a+8>>2]=0;H[a+12>>2]=0;F[a+16|0]=!(e&65536)|I[a+16|0]&254;h=e&255;H[f+8>>2]=h;d:{if(!h){break d}m=H[i+5620>>2];if(m){g=H[i+5616>>2];b=0;while(1){if((h|0)==H[g+8>>2]){H[a+8>>2]=g;break d}g=g+20|0;b=b+1|0;if((m|0)!=(b|0)){continue}break}}Ba(d,1,4721,0);a=0;break a}e=e>>>8&255;H[f+8>>2]=e;e:{if(!e){break e}h=H[i+5620>>2];if(h){g=H[i+5616>>2];b=0;while(1){if((e|0)==H[g+8>>2]){H[a+12>>2]=g;break e}g=g+20|0;b=b+1|0;if((h|0)!=(b|0)){continue}break}}Ba(d,1,4721,0);a=0;break a}h=k-l|0;c=c+3|0;n=n+1|0;if(n>>>0>2]){continue}break}}if(h){Ba(d,1,4721,0);a=0;break a}a=1;if(!j){break a}H[i+5632>>2]=H[i+5632>>2]+1;a=1}na=f+16|0;return a|0}function Tc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;if(K[a+44>>2]>=8){i=H[a+40>>2];l=8;while(1){k=H[a+12>>2]<<5;e=H[a>>2];g=H[a+36>>2];b=H[a+16>>2];h=H[a+20>>2];a:{if(b>>>0>=h>>>0){break a}j=e+k|0;d=b+1|0;if(h-b&1){c=j+(b<<6)|0;b=(N(b,g)<<2)+i|0;f=H[b+28>>2];H[c+24>>2]=H[b+24>>2];H[c+28>>2]=f;f=H[b+20>>2];H[c+16>>2]=H[b+16>>2];H[c+20>>2]=f;f=H[b+12>>2];H[c+8>>2]=H[b+8>>2];H[c+12>>2]=f;f=H[b+4>>2];H[c>>2]=H[b>>2];H[c+4>>2]=f;b=d}if((d|0)==(h|0)){break a}while(1){d=(N(b,g)<<2)+i|0;f=H[d+28>>2];c=j+(b<<6)|0;H[c+24>>2]=H[d+24>>2];H[c+28>>2]=f;f=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=f;f=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=f;f=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=f;d=b+1|0;c=j+(d<<6)|0;d=(N(d,g)<<2)+i|0;f=H[d+28>>2];H[c+24>>2]=H[d+24>>2];H[c+28>>2]=f;f=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=f;f=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=f;f=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=f;b=b+2|0;if((h|0)!=(b|0)){continue}break}}b=H[a+24>>2];h=H[a+28>>2];b:{if(b>>>0>=h>>>0){break b}j=(e-k|0)+32|0;k=(N(g,H[a+8>>2])<<2)+i|0;d=b+1|0;if(h-b&1){c=j+(b<<6)|0;b=k+(N(b,g)<<2)|0;e=H[b+28>>2];H[c+24>>2]=H[b+24>>2];H[c+28>>2]=e;e=H[b+20>>2];H[c+16>>2]=H[b+16>>2];H[c+20>>2]=e;e=H[b+12>>2];H[c+8>>2]=H[b+8>>2];H[c+12>>2]=e;e=H[b+4>>2];H[c>>2]=H[b>>2];H[c+4>>2]=e;b=d}if((d|0)==(h|0)){break b}while(1){d=k+(N(b,g)<<2)|0;e=H[d+28>>2];c=j+(b<<6)|0;H[c+24>>2]=H[d+24>>2];H[c+28>>2]=e;e=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=e;e=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=e;e=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=e;d=b+1|0;c=j+(d<<6)|0;d=k+(N(d,g)<<2)|0;e=H[d+28>>2];H[c+24>>2]=H[d+24>>2];H[c+28>>2]=e;e=H[d+20>>2];H[c+16>>2]=H[d+16>>2];H[c+20>>2]=e;e=H[d+12>>2];H[c+8>>2]=H[d+8>>2];H[c+12>>2]=e;e=H[d+4>>2];H[c>>2]=H[d>>2];H[c+4>>2]=e;b=b+2|0;if((h|0)!=(b|0)){continue}break}}Sa(a);b=0;if(H[a+32>>2]){while(1){d=H[a>>2]+(b<<5)|0;c=H[d+28>>2];g=(N(H[a+36>>2],b)<<2)+i|0;H[g+24>>2]=H[d+24>>2];H[g+28>>2]=c;c=H[d+20>>2];H[g+16>>2]=H[d+16>>2];H[g+20>>2]=c;c=H[d+12>>2];H[g+8>>2]=H[d+8>>2];H[g+12>>2]=c;c=H[d+4>>2];H[g>>2]=H[d>>2];H[g+4>>2]=c;b=b+1|0;if(b>>>0>2]){continue}break}}i=i+32|0;l=l+8|0;if(l>>>0<=K[a+44>>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function Zc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0;d=b;b=H[b>>2]+7&-8;H[d>>2]=b+16;o=a;f=H[b>>2];a=H[b+4>>2];d=H[b+8>>2];c=H[b+12>>2];p=c;h=na-32|0;na=h;b=c&65535;e=c;c=d;d=e>>>16&32767;g=d;a:{if(d-15361>>>0<=2045){b=b<<4|c>>>28;d=c<<4|a>>>28;e=g-15360|0;a=a&268435455;b:{if((a|0)==134217728&(f|0)!=0|a>>>0>134217728){c=b;d=d+1|0;b=d?c:c+1|0;break b}if(f|(a|0)!=134217728){break b}a=d;d=d+(d&1)|0;b=a>>>0>d>>>0?b+1|0:b}c=b>>>0>1048575;f=c?0:d;a=c?0:b;b=0;c=c+e|0;b=c>>>0>>0?1:b;break a}if(!(!(c|f|(a|b))|(d|0)!=32767)){e=b<<4|c>>>28;f=c<<4|a>>>28;a=e|524288;c=2047;b=0;break a}if(g>>>0>17406){f=0;a=0;c=2047;b=0;break a}e=!(d|k);l=e?15360:15361;k=l-g|0;if((k|0)>112){f=0;a=0;c=0;b=0;break a}d=c;b=e?b:b|65536;if((g|0)!=(l|0)){m=f;c=a;i=d;e=b;l=128-k|0;c:{if(l&64){g=f;e=l+-64|0;c=e&31;if((e&63)>>>0>=32){e=f<>>32-c|a<>>0>=32){g=i<>>32-j|e<>>0>=32){e=0;i=c>>>i|0}else{e=c>>>i|0;i=((1<>>i}i=n|i;e=e|g;n=m;j=l&31;if((l&63)>>>0>=32){g=m<>>32-j|c<>2]=m;H[h+20>>2]=c;H[h+24>>2]=i;H[h+28>>2]=e;m=(H[h+16>>2]|H[h+24>>2]|(H[h+20>>2]|H[h+28>>2]))!=0}d:{if(k&64){c=d;f=k+-64|0;a=f&31;if((f&63)>>>0>=32){e=0;f=b>>>a|0}else{e=b>>>a|0;f=((1<>>a}a=e;d=0;b=0;break d}if(!k){break d}g=d;c=64-k|0;e=c&31;if((c&63)>>>0>=32){c=d<>>32-e|b<>>0>=32){g=0;a=a>>>f|0}else{g=a>>>f|0;a=((1<>>f}f=i|a;a=c|g;e=d;d=k&31;if((k&63)>>>0>=32){c=0;d=b>>>d|0}else{c=b>>>d|0;d=((1<>>d}b=c}H[h>>2]=f;H[h+4>>2]=a;H[h+8>>2]=d;H[h+12>>2]=b;a=H[h+8>>2];d=H[h+4>>2];f=a<<4|d>>>28;a=H[h+12>>2]<<4|a>>>28;c=d&268435455;b=H[h>>2]|m;e:{if((c|0)==134217728&(b|0)!=0|c>>>0>134217728){f=f+1|0;a=f?a:a+1|0;break e}if(b|(c|0)!=134217728){break e}b=a;a=f;f=f+(f&1)|0;a=a>>>0>f>>>0?b+1|0:b}c=a>>>0>1048575;a=c?a^1048576:a;b=0}na=h+32|0;u(0,f|0);u(1,a|(p&-2147483648|c<<20));q=o,r=+w(),M[q>>3]=r}function Ac(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0;q=H[a+24>>2];if(!H[q+16>>2]){return 1}r=H[q+24>>2];o=H[H[H[a+20>>2]>>2]+20>>2];while(1){d=H[r+36>>2];H[b+36>>2]=d;f=H[o+28>>2];c=f+N(d,152)|0;t=H[a+64>>2];a:{if(t){d=f+N(H[o+24>>2],152)|0;p=H[d-144>>2]-H[d-152>>2]|0;f=c+12|0;e=c+4|0;d=H[c+8>>2];g=H[c>>2];c=36;break a}f=c+148|0;e=c+140|0;d=H[c+144>>2];g=H[c+136>>2];p=d-g|0;c=52}u=H[c+o>>2];b:{c:{if(!u){break c}k=H[e>>2];l=H[f>>2];f=d-g|0;e=H[b+40>>2];c=e&31;if((e&63)>>>0>=32){c=-1<>>32-c}m=h^-1;j=H[b+20>>2];h=m+j|0;i=c^-1;c=i;j=h>>>0>>0?c+1|0:c;c=e&31;if((e&63)>>>0>=32){h=j>>>c|0}else{h=((1<>>c}c=H[b+16>>2];j=c+m|0;m=c>>>0>j>>>0?i+1|0:i;i=e&31;c=H[b+8>>2];if((e&63)>>>0>=32){i=m>>>i|0}else{i=((1<>>i}e=c+i|0;d:{if(g>>>0>i>>>0){m=g-i|0;i=0;if(e>>>0>=d>>>0){d=f;j=0;break d}d=e-g|0;j=f-d|0;break d}i=i-g|0;if(e>>>0>=d>>>0){d=f-i|0;j=0;m=0;break d}j=d-e|0;m=0;d=c}f=l-k|0;e=H[b+12>>2];g=e+h|0;e:{if(h>>>0>>0){s=k-h|0;h=0;n=0;if(g>>>0>=l>>>0){break e}n=f;f=g-k|0;n=n-f|0;break e}h=h-k|0;if(g>>>0>=l>>>0){f=f-h|0;s=0;n=0;break e}s=0;f=e;n=l-g|0}k=n;g=0;if((h|i|(j|k)|(d|f))<0){break b}k=N(h,p)+i|0;l=H[b+44>>2];h=N(c,s)+m|0;f:{g:{if(!(k|l|(h|(c|0)!=(p|0))|(c|0)!=(d|0))){if((e|0)!=(f|0)){break g}d=(t?36:52)+o|0;H[b+44>>2]=H[d>>2];H[d>>2]=0;break c}if(l){break f}}be(e,0,c);if(qa|!e){break b}c=N(c,e);if(c>>>0>1073741823){break b}c=Ia(c<<2);H[b+44>>2]=c;if(!c){break b}e=H[b+8>>2];g=H[b+12>>2];if((e|0)==(d|0)&(g|0)==(f|0)){break f}e=N(e,g)<<2;if(!e){break f}y(c,0,e)}if(!f){break c}d=d<<2;g=H[b+44>>2]+(h<<2)|0;c=(k<<2)+u|0;if((f|0)!=1){k=f&1;l=f&2147483646;f=0;while(1){h=!d;if(!h){B(g,c,d)}i=p<<2;c=i+c|0;e=(H[b+8>>2]<<2)+g|0;if(!h){B(e,c,d)}c=c+i|0;g=e+(H[b+8>>2]<<2)|0;f=f+2|0;if((l|0)!=(f|0)){continue}break}if(!k){break c}}if(!d){break c}B(g,c,d)}o=o+76|0;r=r+52|0;b=b+52|0;g=1;v=v+1|0;if(v>>>0>2]){continue}}break}return g}function vb(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0;if(a){a:{if(H[a>>2]){b=H[a+12>>2];if(b){ib(b);Ca(H[a+12>>2]);H[a+12>>2]=0}b=H[a+16>>2];if(b){Ca(b);H[a+16>>2]=0;H[a+20>>2]=0}Ca(H[a+64>>2]);H[a+60>>2]=0;H[a+64>>2]=0;Ca(H[a+72>>2]);H[a+72>>2]=0;Ca(H[a+88>>2]);H[a+88>>2]=0;break a}b=H[a+44>>2];if(b){Ca(b);H[a+44>>2]=0}b=H[a+32>>2];if(b){Ca(b);H[a+32>>2]=0;H[a+36>>2]=0}b=H[a+52>>2];if(!b){break a}Ca(b);H[a+52>>2]=0;H[a+56>>2]=0}Qb(H[a+232>>2]);b=H[a+180>>2];if(b){e=N(H[a+128>>2],H[a+132>>2]);if(e){while(1){ib(b);b=b+5644|0;c=c+1|0;if((e|0)!=(c|0)){continue}break}b=H[a+180>>2]}Ca(b);H[a+180>>2]=0}b=H[a+140>>2];if(b){c=H[a+136>>2];if(c){b=0;while(1){e=H[H[a+140>>2]+(b<<3)>>2];if(e){Ca(e);c=H[a+136>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+140>>2]}H[a+136>>2]=0;Ca(b);H[a+140>>2]=0}Ca(H[a+160>>2]);H[a+144>>2]=0;H[a+160>>2]=0;Ca(H[a+124>>2]);H[a+124>>2]=0;if(!(I[a+212|0]&2)){Ca(H[a+192>>2])}y(a+104|0,0,112);ob(H[a+216>>2]);H[a+216>>2]=0;ob(H[a+220>>2]);H[a+216>>2]=0;d=H[a+224>>2];if(d){b=H[d+28>>2];if(b){Ca(b);H[d+28>>2]=0}c=H[d+40>>2];if(c){if(H[d+36>>2]){while(1){e=N(g,40);b=H[(e+c|0)+36>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+36>>2]=0}b=H[(c+e|0)+16>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+16>>2]=0}b=H[(c+e|0)+24>>2];if(b){Ca(b);c=H[d+40>>2];H[(e+c|0)+24>>2]=0}g=g+1|0;if(g>>>0>2]){continue}break}}Ca(c);H[d+40>>2]=0}Ca(d)}H[a+224>>2]=0;Ta(H[a+96>>2]);H[a+96>>2]=0;Ta(H[a+100>>2]);H[a+100>>2]=0;f=H[a+236>>2];if(f){b:{if(!H[f+8>>2]){break b}if(H[f+12>>2]){H[f+40>>2]=0;while(1){if(H[f+24>>2]>0){continue}break}}H[f+16>>2]=1;Ca(H[f>>2]);c=H[f+28>>2];if(!c){break b}while(1){b=H[c+4>>2];Ca(c);H[f+28>>2]=b;c=b;if(b){continue}break}}d=H[f+36>>2];if(d){g=H[d+4>>2];if((g|0)>0){b=0;while(1){e=H[d>>2]+N(b,12)|0;c=H[e+8>>2];if(c){ra[c|0](H[e+4>>2]);g=H[d+4>>2]}b=b+1|0;if((g|0)>(b|0)){continue}break}}Ca(H[d>>2]);Ca(d)}Ca(f)}H[a+236>>2]=0;Ca(a)}}function bc(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;k=na-256|0;na=k;a:{if(!a){a=0;break a}if(!(H[a>>2]==(b|0)&H[a+4>>2]==(c|0))){H[a+4>>2]=c;H[a>>2]=b;H[k>>2]=c;H[k+128>>2]=b;e=c;g=b;while(1){o=h;h=h+1|0;j=h<<2;l=(e+1|0)/2|0;H[j+k>>2]=l;n=j+(k+128|0)|0;j=(g+1|0)/2|0;H[n>>2]=j;i=N(e,g);f=i+f|0;e=l;g=j;if(i>>>0>1){continue}break}H[a+8>>2]=f;b:{c:{d:{if(!f){b=H[a+12>>2];if(!b){break d}Ca(b);H[a+12>>2]=0;break d}f=f<<4;if(f>>>0<=K[a+16>>2]){break b}b=Ga(H[a+12>>2],f);if(b){break c}Ba(d,1,6451,0);b=H[a+12>>2];if(!b){break d}Ca(b);H[a+12>>2]=0}Ca(a);a=0;break a}H[a+12>>2]=b;c=H[a+16>>2];d=f-c|0;if(d){y(b+c|0,0,d)}H[a+16>>2]=f;c=H[a+4>>2];b=H[a>>2]}g=H[a+12>>2];if(o){l=0;f=(N(b,c)<<4)+g|0;e=f;while(1){b=l<<2;h=H[b+k>>2];e:{if((h|0)<=0){break e}j=h-1|0;d=0;f:{g:{b=H[b+(k+128|0)>>2];if((b|0)<=0){i=h&3;if(h>>>0>=4){break g}h=0;break f}while(1){c=f;f=b;while(1){h:{H[g>>2]=e;if((f|0)==1){g=g+16|0;e=e+16|0;break h}H[g+16>>2]=e;e=e+16|0;g=g+32|0;i=(f|0)>2;f=f-2|0;if(i){continue}}break}i=((d|0)==(j|0)|d)&1;f=i?e:c+(b<<4)|0;e=i?e:c;d=d+1|0;if((h|0)!=(d|0)){continue}break}break e}m=h&2147483644;h=0;c=0;while(1){n=(h|0)==(j|0);h=h+4|0;f=n?e:f;e=f;c=c+4|0;if((m|0)!=(c|0)){continue}break}if(i){break f}break e}while(1){c=f;m=((h|0)==(j|0)|h)&1;f=m?e:(b<<4)+f|0;e=m?e:c;h=h+1|0;d=d+1|0;if((i|0)!=(d|0)){continue}break}}l=l+1|0;if((o|0)!=(l|0)){continue}break}}H[g>>2]=0}b=H[a+8>>2];if(!b){break a}c=b&3;e=H[a+12>>2];if(b>>>0>=4){b=b&-4;g=0;while(1){H[e+60>>2]=0;H[e+52>>2]=999;H[e+56>>2]=0;H[e+44>>2]=0;H[e+36>>2]=999;H[e+40>>2]=0;H[e+28>>2]=0;H[e+20>>2]=999;H[e+24>>2]=0;H[e+12>>2]=0;H[e+4>>2]=999;H[e+8>>2]=0;e=e- -64|0;g=g+4|0;if((b|0)!=(g|0)){continue}break}if(!c){break a}}g=0;while(1){H[e+12>>2]=0;H[e+4>>2]=999;H[e+8>>2]=0;e=e+16|0;g=g+1|0;if((c|0)!=(g|0)){continue}break}}na=k+256|0;return a}function fb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;i=a>>>0<=8?8:a;l=i+48|0;a:{b:{while(1){if(a-1&a|b>>>0>4294967239){break b}c=H[6867];d=c;g=H[6866];b=b>>>0<=8?8:b+3&-4;c:{if(b>>>0<=127){e=(b>>>3|0)-1|0;break c}f=Q(b);e=((b>>>29-f^4)-(f<<2)|0)+110|0;if(b>>>0<=4095){break c}f=((b>>>30-f^2)-(f<<1)|0)+71|0;e=f>>>0>=63?63:f}h=e;e=h&31;if((h&63)>>>0>=32){f=0;c=c>>>e|0}else{f=c>>>e|0;c=((1<>>e}if(c|f){while(1){e=f;d:{if(e|c){d=e-1|0;f=d+1|0;g=d;d=c-1|0;g=(d|0)!=-1?f:g;f=Q(e^g);f=(f|0)==32?Q(c^d)+32|0:f;d=63-f|0;qa=0-(f>>>0>63)|0;break d}qa=0;d=64}g=d;d=g&31;if((g&63)>>>0>=32){f=0;j=e>>>d|0}else{f=e>>>d|0;j=((1<>>d}h=g+h|0;c=h<<4;e=H[c+26440>>2];d=c+26432|0;e:{if((e|0)!=(d|0)){c=Cb(e,i,b);if(c){break a}c=H[e+4>>2];g=H[e+8>>2];H[c+8>>2]=g;H[g+4>>2]=c;H[e+8>>2]=d;H[e+4>>2]=H[d+4>>2];H[d+4>>2]=e;H[H[e+4>>2]+8>>2]=e;h=h+1|0;c=(f&1)<<31|j>>>1;f=f>>>1|0;break e}m=H[6867];g=h&63;c=g;e=c&31;if(c>>>0>=32){c=0;d=-1>>>e|0}else{c=-1>>>e|0;d=c|(1<>>0>=32){c=d<>>32-e|c<>>0>=32){c=-1<>>32-c}n=d&-2;d=g&31;if(g>>>0>=32){g=0;d=c>>>d|0}else{g=c>>>d|0;d=((1<>>d}c=d|k;qa=e|g;H[6866]=H[6866]&c;H[6867]=qa&m;c=j^1}if(c|f){continue}break}g=H[6866];d=H[6867]}f=Q(d);e=63-((f|0)==32?Q(g)+32|0:f)|0;f:{if(!(d|g)){h=0;break f}c=e<<4;h=H[c+26440>>2];if(!d&g>>>0<1073741824){break f}f=98;d=c+26432|0;if((d|0)==(h|0)){break f}while(1){c=Cb(h,i,b);if(c){break a}h=H[h+8>>2];if((d|0)==(h|0)){break f}c=f;f=c-1|0;if(c){continue}break}}f=a>>>0>8;a=i;if(sc((f?l:48)+b|0)){continue}break}if(!h){break b}a=(e<<4)+26432|0;if((a|0)==(h|0)){break b}while(1){c=Cb(h,i,b);if(c){break a}h=H[h+8>>2];if((a|0)!=(h|0)){continue}break}}c=0}return c}function wb(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;m=H[H[a+24>>2]+24>>2]+N(b,52)|0;l=H[m+4>>2];k=l-1|0;o=H[a+60>>2];j=k+o|0;p=0-!l|0;i=p;r=H[H[H[a+20>>2]>>2]+20>>2]+N(b,76)|0;n=H[r+12>>2];i=fe(j,j>>>0>>0?i+1|0:i,l,0);q=i>>>0>n>>>0?n:i;j=H[m>>2];m=j-1|0;s=H[a+56>>2];n=m+s|0;o=0-!j|0;i=o;t=H[r+8>>2];i=fe(n,n>>>0>>0?i+1|0:i,j,0);n=i>>>0>t>>>0?t:i;i=p;t=H[r+4>>2];s=H[a+52>>2];k=s+k|0;i=fe(k,k>>>0>>0?i+1|0:i,l,0);k=i>>>0>>0?t:i;i=o;p=H[r>>2];l=m;m=H[a+48>>2];l=l+m|0;i=fe(l,l>>>0>>0?i+1|0:i,j,0);i=i>>>0

      >>0?p:i;l=0;p=H[(H[H[a+32>>2]+5584>>2]+N(b,1080)|0)+20>>2];c=H[r+20>>2]+(c?0-c|0:-1)|0;a:{if(!c){a=n;l=i;b=k;break a}m=c-1|0;j=(d&1)<>>0>>0){a=c&31;l=i-j|0;if((c&63)>>>0>=32){i=-1<>>32-a;a=-1<>>0>>0?i+1|0:i;b=a;a=c&31;if((c&63)>>>0>=32){l=i>>>a|0}else{l=((1<>>a}}a=0;b=0;d=d>>>1<>>0>>0){b=c&31;o=k-d|0;if((c&63)>>>0>=32){i=-1<>>32-b;b=-1<>>0>>0?i+1|0:i;k=b;b=c&31;if((c&63)>>>0>=32){b=i>>>b|0}else{b=((1<>>b}}if(j>>>0>>0){a=c&31;k=n-j|0;if((c&63)>>>0>=32){i=-1<>>32-a;a=-1<>>0>>0?i+1|0:i;j=a;a=c&31;if((c&63)>>>0>=32){a=i>>>a|0}else{a=((1<>>a}}if(d>>>0>=q>>>0){q=0;break a}k=q-d|0;d=c&31;if((c&63)>>>0>=32){i=-1<>>32-d;d=-1<>>0>>0?i+1|0:i;j=d;d=c&31;if((c&63)>>>0>=32){q=i>>>d|0}else{q=((1<>>d}}c=(p|0)==1?2:3;d=c+a|0;d=(a>>>0>d>>>0?-1:d)>>>0>e>>>0;a=c+q|0;d=d&(a>>>0>>0?-1:a)>>>0>f>>>0;a=l-c|0;d=d&(a>>>0<=l>>>0?a:0)>>>0>>0;a=b-c|0;return d&(a>>>0<=b>>>0?a:0)>>>0>>0}function kb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;g=H[a+16>>2];if(g>>>0>=32){return H[a+8>>2]}b=H[a+20>>2];a:{if((b|0)>=4){d=H[a>>2];c=H[d-3>>2];e=b-4|0;H[a+20>>2]=e;H[a>>2]=d-4;break a}if((b|0)<=0){e=b;break a}f=H[a>>2];d=24;b:{if((b|0)==1){break b}l=b&1;k=b&2147483646;while(1){h=f-1|0;H[a>>2]=h;i=I[f|0];f=f-2|0;H[a>>2]=f;H[a+20>>2]=b-1;h=I[h|0];b=b-2|0;H[a+20>>2]=b;c=i<>2]=f-1;f=I[f|0];H[a+20>>2]=b-1;c=f<>2];k=c&255;H[a+24>>2]=k>>>0>143;b=b?(c&2130706432)==2130706432?7:8:8;h=b+(c>>>0<=2415919103?8:(c&8323072)==8323072?7:8)|0;f=c>>>16&255;i=h+(f>>>0<=143?8:(c&32512)==32512?7:8)|0;d=c>>>8&255;l=i+(g+(d>>>0<=143?8:(c&127)==127?7:8)|0)|0;H[a+16>>2]=l;j=H[a+12>>2];b=f<>>24|d<>>0>=32){d=b<>>32-c;b=b<>2];b=d|j;h=b;H[a+8>>2]=g;H[a+12>>2]=b;if(l>>>0<=31){c:{if((e|0)>=4){b=H[a>>2];c=H[b-3>>2];H[a+20>>2]=e-4;H[a>>2]=b-4;break c}if((e|0)<=0){c=0;break c}b=H[a>>2];d:{if((e|0)==1){d=24;c=0;break d}i=e&1;j=e&2147483646;d=24;c=0;f=0;while(1){m=b-1|0;H[a>>2]=m;n=I[b|0];b=b-2|0;H[a>>2]=b;H[a+20>>2]=e-1;m=I[m|0];e=e-2|0;H[a+20>>2]=e;c=n<>2]=b-1;b=I[b|0];H[a+20>>2]=e-1;c=b<>2]=e>>>0>143;k=k>>>0<=143?8:(c&2130706432)==2130706432?7:8;i=k+(c>>>0<=2415919103?8:(c&8323072)==8323072?7:8)|0;f=c>>>16&255;j=i+(f>>>0<=143?8:(c&32512)==32512?7:8)|0;d=c>>>8&255;H[a+16>>2]=j+(l+(d>>>0<=143?8:(c&127)==127?7:8)|0);b=a;a=f<>>24|d<>>0>=32){c=a<>>32-e;a=a<>2]=g;H[b+12>>2]=c|h}return g}function Lc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;j=H[a+96>>2];l=N(H[a+128>>2],H[a+132>>2]);a:{if(l){b=H[j+16>>2];m=N(b,1080);k=N(b,b)<<2;e=H[a+12>>2];b=H[a+180>>2];while(1){n=H[b+5584>>2];B(b,e,5644);H[b+5608>>2]=0;H[b+5588>>2]=-1;H[b+5168>>2]=0;H[b+5636>>2]=0;H[b+5616>>2]=0;H[b+5624>>2]=0;H[b+5628>>2]=0;H[b+5584>>2]=n;F[b+5640|0]=I[b+5640|0]&252;b:{if(!H[e+5608>>2]){break b}d=Fa(k);H[b+5608>>2]=d;if(!d){return 0}if(!k){break b}B(d,H[e+5608>>2],k)}d=N(H[e+5624>>2],20);f=Fa(d);H[b+5616>>2]=f;i=0;if(!f){break a}if(d){B(f,H[e+5616>>2],d)}g=H[e+5620>>2];if(g){d=H[e+5616>>2];f=H[b+5616>>2];h=0;while(1){if(H[d+12>>2]){g=Fa(H[d+16>>2]);H[f+12>>2]=g;if(!g){return 0}o=H[d+16>>2];if(o){B(g,H[d+12>>2],o)}g=H[e+5620>>2]}H[b+5624>>2]=H[b+5624>>2]+1;f=f+20|0;d=d+20|0;h=h+1|0;if(h>>>0>>0){continue}break}}d=N(H[e+5636>>2],20);f=Fa(d);H[b+5628>>2]=f;if(!f){break a}if(d){B(f,H[e+5628>>2],d)}i=H[e+5636>>2];H[b+5636>>2]=i;if(i){d=H[e+5628>>2];f=H[b+5628>>2];h=0;while(1){g=H[d+8>>2];if(g){H[f+8>>2]=H[b+5616>>2]+(g-H[e+5616>>2]|0)}g=H[d+12>>2];if(g){H[f+12>>2]=H[b+5616>>2]+(g-H[e+5616>>2]|0)}f=f+20|0;d=d+20|0;h=h+1|0;if((i|0)!=(h|0)){continue}break}}if(m){B(n,H[e+5584>>2],m)}b=b+5644|0;p=p+1|0;if((p|0)!=(l|0)){continue}break}}i=1;e=Ea(1,72);b=0;c:{if(!e){break c}F[e+40|0]=I[e+40|0]&254|1;d=Ea(1,4);H[e+20>>2]=d;b=e;if(d){break c}Ca(b);b=0}H[a+232>>2]=b;if(!b){return 0}f=H[a+236>>2];e=0;H[b+28>>2]=a+104;H[b+24>>2]=j;d=Ea(1,848);H[H[b+20>>2]>>2]=d;d:{if(!d){break d}d=Ea(H[j+16>>2],76);h=H[H[b+20>>2]>>2];H[h+20>>2]=d;if(!d){break d}H[h+16>>2]=H[j+16>>2];e=H[a+188>>2];H[b+44>>2]=f;H[b>>2]=e;e=1}if(e){break a}Qb(H[a+232>>2]);i=0;H[a+232>>2]=0;Ba(c,1,3668,0)}return i|0}function $d(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;f=na-80|0;na=f;H[f+76>>2]=1;j=H[a+44>>2];d=H[a+224>>2];e=H[d+40>>2];a:{b:{if(!e|!H[e+16>>2]){break b}c:{e=e+N(j,40)|0;if(!H[e+4>>2]){e=H[d+8>>2];d=H[d+12>>2];e=e+2|0;d=e>>>0<2?d+1|0:d;if(ab(b,e,d,c)){break c}d=0;Ba(c,1,5440,0);break a}d=H[e+16>>2];if(!ab(b,H[d>>2],H[d+4>>2],c)){Ba(c,1,5440,0);d=0;break a}if((Ja(b,H[a+16>>2],2,c)|0)!=2){Ba(c,1,2472,0);d=0;break a}Da(H[a+16>>2],f+72|0,2);if(H[f+72>>2]==65424){break c}Ba(c,1,4073,0);d=0;break a}if(H[a+8>>2]!=256){break b}H[a+8>>2]=8}e=N(H[a+132>>2],H[a+128>>2]);d:{if(!e){break d}i=e&7;h=H[a+180>>2];d=0;if(e>>>0>=8){k=e&-8;while(1){e=h+N(g,5644)|0;H[e+45096>>2]=-1;H[e+39452>>2]=-1;H[e+33808>>2]=-1;H[e+28164>>2]=-1;H[e+22520>>2]=-1;H[e+16876>>2]=-1;H[e+11232>>2]=-1;H[e+5588>>2]=-1;g=g+8|0;l=l+8|0;if((k|0)!=(l|0)){continue}break}if(!i){break d}}while(1){H[(h+N(g,5644)|0)+5588>>2]=-1;g=g+1|0;d=d+1|0;if((i|0)!=(d|0)){continue}break}}d=0;if(!bb(a,f+72|0,0,f+68|0,f- -64|0,f+60|0,f+56|0,f+52|0,f+76|0,b,c)){break a}i=j+1|0;while(1){e:{if(!H[f+76>>2]){break e}e=H[f+72>>2];if(!hb(a,e,0,0,b,c)){break a}h=H[a+128>>2];k=H[a+132>>2];g=e+1|0;H[f+32>>2]=g;H[f+36>>2]=N(h,k);Ba(c,4,11795,f+32|0);if(!Ac(H[a+232>>2],H[H[a+100>>2]+24>>2])){break a}d=H[a+180>>2]+N(e,5644)|0;h=H[d+5596>>2];if(h){Ca(h);H[d+5596>>2]=0;H[d+5600>>2]=0}H[f+16>>2]=g;Ba(c,4,16601,f+16|0);if((e|0)==(j|0)){d=H[a+224>>2];e=H[d+8>>2];d=H[d+12>>2];e=e+2|0;d=e>>>0<2?d+1|0:d;if(ab(b,e,d,c)){break e}d=0;Ba(c,1,5440,0);break a}H[f+4>>2]=i;H[f>>2]=g;Ba(c,2,13648,f);d=0;if(bb(a,f+72|0,0,f+68|0,f- -64|0,f+60|0,f+56|0,f+52|0,f+76|0,b,c)){continue}break a}break}d=zc(a,c)}na=f+80|0;return d|0}function Ma(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0;h=H[a+16>>2];if(h>>>0>=32){return H[a+8>>2]}c=H[a+24>>2];a:{if((c|0)>=4){b=H[a>>2];d=H[b>>2];e=c-4|0;H[a+24>>2]=e;H[a>>2]=b+4;break a}d=H[a+28>>2]?-1:0;if((c|0)<=0){e=c;break a}b=H[a>>2];if((c|0)!=1){j=c&1;i=c&2147483646;while(1){e=b;H[a>>2]=b+1;k=I[b|0];b=b+2|0;H[a>>2]=b;H[a+24>>2]=c-1;e=I[e+1|0];c=c-2|0;H[a+24>>2]=c;d=((255<>2]=b+1;b=I[b|0];H[a+24>>2]=c-1;d=(255<>2];i=d>>>24|0;H[a+20>>2]=(i|0)==255;c=d>>>16&255;f=d>>>8&255;b=b?7:8;d=d&255;g=b+((d|0)==255?7:8)|0;k=((f|0)==255?7:8)+g|0;j=(h+((c|0)==255?7:8)|0)+k|0;H[a+16>>2]=j;l=H[a+12>>2];b=d|(f<>>0>=32){d=b<>>32-c;b=b<>2];b=d|l;k=b;H[a+8>>2]=h;H[a+12>>2]=b;if(j>>>0<=31){b:{if((e|0)>=4){b=H[a>>2];d=H[b>>2];H[a+24>>2]=e-4;H[a>>2]=b+4;break b}f=0;d=H[a+28>>2]?-1:0;if((e|0)<=0){break b}b=H[a>>2];if((e|0)!=1){l=e&1;m=e&2147483646;g=0;while(1){c=b;H[a>>2]=b+1;n=I[b|0];b=b+2|0;H[a>>2]=b;H[a+24>>2]=e-1;c=I[c+1|0];e=e-2|0;H[a+24>>2]=e;d=((255<>2]=b+1;b=I[b|0];H[a+24>>2]=e-1;d=(255<>>24|0;H[a+20>>2]=(e|0)==255;c=d>>>16&255;f=d>>>8&255;g=(i|0)==255?7:8;d=d&255;i=g+((d|0)==255?7:8)|0;l=((f|0)==255?7:8)+i|0;H[a+16>>2]=(((c|0)==255?7:8)+j|0)+l;b=a;a=d|(f<>>0>=32){c=a<>>32-e;a=a<>2]=h;H[b+12>>2]=c|k}return h}function Dc(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0;h=na-32|0;na=h;if(H[a+8>>2]==16){f=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{f=H[a+12>>2]}a:{if(K[d>>2]<=4){a=0;Ba(e,1,2607,0);break a}f=H[f+5584>>2]+N(b,1080)|0;Da(c,f+4|0,1);g=H[f+4>>2]+1|0;H[f+4>>2]=g;if(g>>>0>=34){H[h+4>>2]=33;H[h>>2]=g;Ba(e,1,7635,h);a=0;break a}j=H[a+184>>2];if(j>>>0>=g>>>0){H[h+24>>2]=g;H[h+20>>2]=j;H[h+16>>2]=b;Ba(e,1,16423,h+16|0);H[a+8>>2]=H[a+8>>2]|32768;a=0;break a}Da(c+1|0,f+8|0,1);H[f+8>>2]=H[f+8>>2]+2;Da(c+2|0,f+12|0,1);a=H[f+12>>2]+2|0;H[f+12>>2]=a;b=H[f+8>>2];if(!(!(b>>>0>10|a>>>0>10)&a+b>>>0<13)){a=0;Ba(e,1,5468,0);break a}Da(c+3|0,f+16|0,1);if(I[f+16|0]&128){a=0;Ba(e,1,6564,0);break a}Da(c+4|0,f+20|0,1);if(K[f+20>>2]>=2){a=0;Ba(e,1,6499,0);break a}b=H[d>>2]-5|0;H[d>>2]=b;a=1;g=H[f+4>>2];if(!(F[f|0]&1)){if(!g){break a}j=g&3;d=f+944|0;e=f+812|0;b=0;c=0;if(g>>>0>=4){k=g&-4;g=0;while(1){f=c<<2;H[f+e>>2]=15;H[d+f>>2]=15;i=f|4;H[i+e>>2]=15;H[d+i>>2]=15;i=f|8;H[i+e>>2]=15;H[d+i>>2]=15;f=f|12;H[f+e>>2]=15;H[d+f>>2]=15;c=c+4|0;g=g+4|0;if((k|0)!=(g|0)){continue}break}if(!j){break a}}while(1){a=c<<2;H[a+e>>2]=15;H[a+d>>2]=15;a=1;c=c+1|0;b=b+1|0;if((j|0)!=(b|0)){continue}break}break a}if(b>>>0>=g>>>0){b:{if(!g){g=0;break b}Da(c+5|0,h+28|0,1);a=H[h+28>>2];H[f+944>>2]=a>>>4;H[f+812>>2]=a&15;g=H[f+4>>2];if(g>>>0>=2){j=f+944|0;k=f+812|0;a=c+6|0;c=1;while(1){Da(a,h+28|0,1);c:{b=H[h+28>>2];if(b>>>0>=16){g=b&15;if(g){break c}}a=0;Ba(e,1,6025,0);break a}i=c<<2;H[i+k>>2]=g;H[j+i>>2]=b>>>4;a=a+1|0;c=c+1|0;g=H[f+4>>2];if(c>>>0>>0){continue}break}}b=H[d>>2]}H[d>>2]=b-g;a=1;break a}a=0;Ba(e,1,2607,0)}na=h+32|0;return a}function dc(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0;j=na-256|0;na=j;g=Ea(1,20);a:{if(!g){Ba(c,1,6413,0);g=0;break a}H[g+4>>2]=b;H[g>>2]=a;H[j>>2]=b;H[j+128>>2]=a;while(1){p=h;h=h+1|0;e=h<<2;d=(b+1|0)/2|0;H[e+j>>2]=d;i=e+(j+128|0)|0;e=(a+1|0)/2|0;H[i>>2]=e;i=N(a,b);f=i+f|0;b=d;a=e;if(i>>>0>1){continue}break}H[g+8>>2]=f;if(!f){Ca(g);g=0;break a}d=Ea(f,16);H[g+12>>2]=d;if(!d){Ba(c,1,3564,0);Ca(g);g=0;break a}l=H[g+8>>2];H[g+16>>2]=l<<4;a=d;if(p){f=(N(H[g+4>>2],H[g>>2])<<4)+d|0;b=f;while(1){c=n<<2;e=H[c+j>>2];b:{if((e|0)<=0){break b}o=e-1|0;i=0;c:{d:{c=H[c+(j+128|0)>>2];if((c|0)<=0){k=e&3;if(e>>>0>=4){break d}h=0;break c}while(1){h=f;f=c;while(1){e:{H[a>>2]=b;if((f|0)==1){a=a+16|0;b=b+16|0;break e}H[a+16>>2]=b;b=b+16|0;a=a+32|0;k=(f|0)>2;f=f-2|0;if(k){continue}}break}k=((i|0)==(o|0)|i)&1;f=k?b:h+(c<<4)|0;b=k?b:h;i=i+1|0;if((e|0)!=(i|0)){continue}break}break b}m=e&2147483644;h=0;e=0;while(1){q=(h|0)==(o|0);h=h+4|0;f=q?b:f;b=f;e=e+4|0;if((m|0)!=(e|0)){continue}break}if(k){break c}break b}while(1){e=f;m=((h|0)==(o|0)|h)&1;f=m?b:e+(c<<4)|0;b=m?b:e;h=h+1|0;i=i+1|0;if((k|0)!=(i|0)){continue}break}}n=n+1|0;if((n|0)!=(p|0)){continue}break}}H[a>>2]=0;f:{if(!l){break f}a=l&3;if(l>>>0>=4){c=l&-4;b=0;while(1){H[d+60>>2]=0;H[d+52>>2]=999;H[d+56>>2]=0;H[d+44>>2]=0;H[d+36>>2]=999;H[d+40>>2]=0;H[d+28>>2]=0;H[d+20>>2]=999;H[d+24>>2]=0;H[d+12>>2]=0;H[d+4>>2]=999;H[d+8>>2]=0;d=d- -64|0;b=b+4|0;if((c|0)!=(b|0)){continue}break}if(!a){break f}}b=0;while(1){H[d+12>>2]=0;H[d+4>>2]=999;H[d+8>>2]=0;d=d+16|0;b=b+1|0;if((a|0)!=(b|0)){continue}break}}}na=j+256|0;return g}function Wb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;H[a+8>>2]=0;H[a+12>>2]=0;H[a>>2]=b;H[a+16>>2]=0;H[a+20>>2]=0;H[a+28>>2]=d;h=c-1|0;H[a+24>>2]=h;n=b&3;a:{if((c|0)<=0){e=b;b=d;break a}e=b+1|0;H[a>>2]=e;b=I[b|0]}g=b;i=8;H[a+16>>2]=8;j=(g|0)==255;H[a+20>>2]=j;H[a+8>>2]=g;H[a+12>>2]=0;b:{if((n|0)==3){break b}k=c-2|0;H[a+24>>2]=k;c:{if((c|0)<2){b=e;e=d;break c}b=e+1|0;H[a>>2]=b;e=I[e|0]}j=(e|0)==255;H[a+20>>2]=j;i=(g|0)==255?15:16;H[a+16>>2]=i;g=g|e<<8;H[a+8>>2]=g;H[a+12>>2]=0;if((n|0)==2){e=b;c=h;h=k;break b}o=c-3|0;H[a+24>>2]=o;d:{if((c|0)<3){f=b;b=d;break d}f=b+1|0;H[a>>2]=f;b=I[b|0]}j=(b|0)==255;H[a+20>>2]=j;l=((e|0)==255?7:8)+i|0;H[a+16>>2]=l;e=i&31;if((i&63)>>>0>=32){m=b<>>32-e;e=b<>2]=g;H[a+12>>2]=m;if((n|0)==1){e=f;i=l;c=k;h=o;break b}h=c-4|0;H[a+24>>2]=h;e:{if((c|0)<4){e=f;c=d;break e}e=f+1|0;H[a>>2]=e;c=I[f|0]}j=(c|0)==255;H[a+20>>2]=j;i=l+((b|0)==255?7:8)|0;H[a+16>>2]=i;b=l&31;if((l&63)>>>0>=32){f=c<>>32-b;b=c<>2]=g;H[a+12>>2]=b;c=o}f:{if((c|0)>=5){d=H[e>>2];H[a+24>>2]=c-5;H[a>>2]=e+4;break f}b=0;d=d?-1:0;if((c|0)<2){break f}while(1){c=e+1|0;H[a>>2]=c;e=I[e|0];f=h-1|0;H[a+24>>2]=f;d=(255<>>0>1;e=c;h=f;if(k){continue}break}}b=d>>>24|0;H[a+20>>2]=(b|0)==255;c=d>>>16&255;e=d>>>8&255;h=j?7:8;d=d&255;f=h+((d|0)==255?7:8)|0;k=((e|0)==255?7:8)+f|0;H[a+16>>2]=(((c|0)==255?7:8)+i|0)+k;b=d|(e<>>0>=32){d=a<>>32-b;a=a<>2]=a|g;H[c+12>>2]=d|m}function Ga(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;if(!b){return 0}a:{if(!a){a=fb(8,b);break a}b:{if(b>>>0>4294967239){break b}h=b>>>0<=8?8:b+3&-4;d=h+8|0;j=a-4|0;f=j;e=H[f>>2];b=e+f|0;c=H[b>>2];c:{d:{e:{if((c|0)!=H[(b+c|0)-4>>2]){c=c+e|0;if(c>>>0>=d+16>>>0){e=H[b+4>>2];b=H[b+8>>2];H[e+8>>2]=b;H[b+4>>2]=e;b=d+f|0;c=c-d|0;H[b>>2]=c;H[(b+(c&-4)|0)-4>>2]=c|1;e=H[b>>2]-8|0;f:{if(e>>>0<=127){g=(e>>>3|0)-1|0;break f}c=Q(e);g=((e>>>29-c^4)-(c<<2)|0)+110|0;if(e>>>0<=4095){break f}c=((e>>>30-c^2)-(c<<1)|0)+71|0;g=c>>>0>=63?63:c}c=g;e=c<<4;H[b+4>>2]=e+26432;e=e+26440|0;H[b+8>>2]=H[e>>2];H[e>>2]=b;H[H[b+8>>2]+4>>2]=b;e=H[6867];b=c&31;if((c&63)>>>0>=32){c=1<>>32-b}H[6866]=g|H[6866];H[6867]=c|e;H[f>>2]=d;H[(f+(d&-4)|0)-4>>2]=d;c=1;break c}if(c>>>0>>0){break e}d=H[b+4>>2];b=H[b+8>>2];H[d+8>>2]=b;H[b+4>>2]=d;H[f>>2]=c;H[(f+(c&-4)|0)-4>>2]=c;c=1;break c}if(e>>>0>=d+16>>>0){H[f>>2]=d;H[(f+(d&-4)|0)-4>>2]=d;b=d+f|0;c=e-d|0;H[b>>2]=c;H[(b+(c&-4)|0)-4>>2]=c|1;d=H[b>>2]-8|0;g:{if(d>>>0<=127){c=(d>>>3|0)-1|0;break g}f=Q(d);c=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break g}c=((d>>>30-f^2)-(f<<1)|0)+71|0;c=c>>>0>=63?63:c}d=c<<4;H[b+4>>2]=d+26432;d=d+26440|0;H[b+8>>2]=H[d>>2];H[d>>2]=b;H[H[b+8>>2]+4>>2]=b;d=H[6867];b=c&31;if((c&63)>>>0>=32){c=1<>>32-b;b=e}H[6866]=b|H[6866];H[6867]=c|d;c=1;break c}c=1;if(d>>>0<=e>>>0){break d}}c=0}}if(c){break a}b=fb(8,h);if(!b){break b}i=H[j>>2]-8|0;$a(b,a,h>>>0>>0?h:i);Ca(a);i=b}a=i}return a}function Zd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;h=na-16|0;na=h;k=H[a+96>>2];if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}F[e+5640|0]=I[e+5640|0]|1;a:{if(c>>>0<=4){b=0;Ba(d,1,4565,0);break a}Da(b,e,1);if(K[e>>2]>=8){b=0;Ba(d,1,4531,0);break a}Da(b+1|0,h+8|0,1);f=H[h+8>>2];H[e+4>>2]=f;if((f|0)>=5){Ba(d,1,4490,0);H[e+4>>2]=-1}Da(b+2|0,e+8|0,2);g=H[e+8>>2];if(g-65536>>>0<=4294901760){H[h>>2]=g;Ba(d,1,8111,h);b=0;break a}f=H[a+188>>2];H[e+12>>2]=f?f:g;Da(b+4|0,e+16|0,1);if(K[e+16>>2]>=2){b=0;Ba(d,1,5536,0);break a}f=b+5|0;H[h+12>>2]=c-5;b=H[k+16>>2];b:{if(!b){break b}k=b&7;i=H[e>>2]&1;e=H[e+5584>>2];c=0;if(b>>>0>=8){b=b&-8;g=0;while(1){j=e+N(c,1080)|0;H[j+7560>>2]=i;H[j+6480>>2]=i;H[j+5400>>2]=i;H[j+4320>>2]=i;H[j+3240>>2]=i;H[j+2160>>2]=i;H[j+1080>>2]=i;H[j>>2]=i;c=c+8|0;g=g+8|0;if((b|0)!=(g|0)){continue}break}if(!k){break b}}while(1){H[e+N(c,1080)>>2]=i;c=c+1|0;l=l+1|0;if((k|0)!=(l|0)){continue}break}}b=0;if(!Dc(a,0,f,h+12|0,d)){Ba(d,1,4565,0);break a}if(H[h+12>>2]){Ba(d,1,4565,0);break a}if(H[a+8>>2]==16){b=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{b=H[a+12>>2]}if(K[H[a+96>>2]+16>>2]>=2){b=H[b+5584>>2];f=H[b+4>>2]<<2;l=b+944|0;e=b+812|0;c=b;g=1;while(1){H[c+1084>>2]=H[b+4>>2];H[c+1088>>2]=H[b+8>>2];H[c+1092>>2]=H[b+12>>2];H[c+1096>>2]=H[b+16>>2];H[c+1100>>2]=H[b+20>>2];d=!f;if(!d){B(c+1892|0,e,f)}if(!d){B(c+2024|0,l,f)}c=c+1080|0;g=g+1|0;if(g>>>0>2]+16>>2]){continue}break}}b=1}na=h+16|0;return b|0}function sd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=na-80|0;na=e;a:{if(c>>>0<=2){Ba(d,1,14478,0);break a}if(I[a+124|0]){Ba(d,4,11193,0);g=1;break a}g=1;Da(b,a+40|0,1);Da(b+1|0,a+52|0,1);Da(b+2|0,a+44|0,1);f=b+3|0;b:{c:{d:{e:{f:{h=H[a+40>>2];switch(h-1|0){case 0:break f;case 1:break e;default:break d}}if(c>>>0<=6){H[e+16>>2]=c;Ba(d,1,15155,e+16|0);g=0;break a}if(!((c|0)==7|H[a+48>>2]==14)){H[e+48>>2]=c;Ba(d,2,15155,e+48|0)}Da(f,a+48|0,4);if(H[a+48>>2]!=14){break b}f=Fa(36);if(!f){g=0;Ba(d,1,7993,0);break a}H[f>>2]=14;H[e+64>>2]=0;H[e+56>>2]=0;H[e+72>>2]=0;H[e+60>>2]=0;H[e+68>>2]=0;H[e+76>>2]=0;g=4470064;H[e+52>>2]=4470064;H[f+4>>2]=1145390592;g:{if((c|0)!=7){if((c|0)==35){Da(b+7|0,e+76|0,4);Da(b+11|0,e+72|0,4);Da(b+15|0,e+68|0,4);Da(b+19|0,e- -64|0,4);Da(b+23|0,e+60|0,4);Da(b+27|0,e+56|0,4);Da(b+31|0,e+52|0,4);H[f+4>>2]=0;g=H[e+52>>2];c=H[e+56>>2];d=H[e+64>>2];i=H[e+68>>2];j=H[e+76>>2];h=H[e+72>>2];b=H[e+60>>2];break g}H[e+32>>2]=c;Ba(d,2,15191,e+32|0)}c=0;d=0;h=0;b=0}H[f+24>>2]=b;H[f+16>>2]=i;H[f+8>>2]=j;H[f+32>>2]=g;H[f+28>>2]=c;H[f+20>>2]=d;H[f+12>>2]=h;H[a+112>>2]=0;H[a+108>>2]=f;break b}b=c-3|0;H[a+112>>2]=b;d=Ea(1,b);H[a+108>>2]=d;if(!d){break c}if((c|0)<=3){break b}c=0;while(1){Da(f,e+76|0,1);F[H[a+108>>2]+c|0]=H[e+76>>2];f=f+1|0;c=c+1|0;if((b|0)!=(c|0)){continue}break}break b}if(h>>>0<3){break a}H[e>>2]=h;Ba(d,4,15950,e);break a}g=0;H[a+112>>2]=0;break a}g=1;F[a+124|0]=1}na=e+80|0;return g|0}function Ja(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0;h=H[a+48>>2];if(h>>>0>=c>>>0){if(c){B(b,H[a+36>>2],c)}H[a+36>>2]=H[a+36>>2]+c;H[a+48>>2]=H[a+48>>2]-c;b=H[a+60>>2];d=H[a+56>>2]+c|0;b=d>>>0>>0?b+1|0:b;H[a+56>>2]=d;H[a+60>>2]=b;return c}if(I[a+68|0]&4){if(h){B(b,H[a+36>>2],h)}b=H[a+48>>2];H[a+48>>2]=0;H[a+36>>2]=b+H[a+36>>2];g=H[a+60>>2];c=b;b=H[a+56>>2]+b|0;g=c>>>0>b>>>0?g+1|0:g;H[a+56>>2]=b;H[a+60>>2]=g;return h?h:-1}a:{if(h){if(h){B(b,H[a+36>>2],h)}i=H[a+32>>2];H[a+36>>2]=i;e=H[a+48>>2];H[a+48>>2]=0;f=H[a+60>>2];g=H[a+56>>2]+e|0;f=g>>>0>>0?f+1|0:f;H[a+56>>2]=g;H[a+60>>2]=f;c=c-e|0;b=b+e|0;break a}i=H[a+32>>2];H[a+36>>2]=i}b:{while(1){c:{e=H[a>>2];f=H[a+16>>2];g=H[a+64>>2];d:{if(g>>>0>c>>>0){f=ra[f|0](i,g,e)|0;H[a+48>>2]=f;if((f|0)==-1){break b}if(c>>>0>f>>>0){if(f){B(b,H[a+36>>2],f)}i=H[a+32>>2];H[a+36>>2]=i;e=H[a+48>>2];break d}if(c){B(b,H[a+36>>2],c)}H[a+36>>2]=H[a+36>>2]+c;H[a+48>>2]=H[a+48>>2]-c;b=H[a+60>>2];d=H[a+56>>2]+c|0;b=d>>>0>>0?b+1|0:b;H[a+56>>2]=d;H[a+60>>2]=b;return c+h|0}e=ra[f|0](b,c,e)|0;H[a+48>>2]=e;if((e|0)==-1){break b}if(c>>>0<=e>>>0){break c}i=H[a+32>>2];H[a+36>>2]=i;f=e}H[a+48>>2]=0;g=H[a+60>>2];j=H[a+56>>2]+e|0;g=j>>>0>>0?g+1|0:g;H[a+56>>2]=j;H[a+60>>2]=g;b=b+e|0;c=c-e|0;h=f+h|0;continue}break}H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];f=H[a+60>>2];b=H[a+56>>2]+e|0;f=b>>>0>>0?f+1|0:f;H[a+56>>2]=b;H[a+60>>2]=f;return e+h|0}Ba(d,4,15630,0);H[a+48>>2]=0;H[a+68>>2]=H[a+68>>2]|4;return h?h:-1}function Gb(){var a=0,b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;a:{c=Ea(1,256);if(c){H[c>>2]=1;H[c+208>>2]=1;F[c+212|0]=I[c+212|0]|6;a=Ea(1,5644);H[c+12>>2]=a;if(!a){break a}a=Ea(1,1e3);H[c+16>>2]=a;if(!a){break a}H[c+48>>2]=0;H[c+52>>2]=0;H[c+44>>2]=-1;H[c+20>>2]=1e3;b:{a=Ea(1,48);if(a){H[a+24>>2]=0;H[a+32>>2]=100;b=Ea(100,24);H[a+28>>2]=b;if(b){break b}Ca(a)}H[c+224>>2]=0;break a}H[a+40>>2]=0;H[c+224>>2]=a;a=pb();H[c+220>>2]=a;if(!a){break a}a=pb();H[c+216>>2]=a;if(!a){break a}a=1419;c:{while(1){b=I[a|0];if(!b|(b|0)==61){break c}a=a+1|0;if(a&3){continue}break}d:{e:{d=H[a>>2];if(((d|16843008-d)&-2139062144)!=-2139062144){break e}while(1){b=d^1027423549;if(((16843008-b|b)&-2139062144)!=-2139062144){break e}d=H[a+4>>2];b=a+4|0;a=b;if(((16843008-d|d)&-2139062144)==-2139062144){continue}break}break d}b=a}while(1){a=b;e=I[a|0];if(!e){break c}b=a+1|0;if((e|0)!=61){continue}break}}b=0;f:{if((a|0)==1419){break f}e=a-1419|0;g:{if(I[e+1419|0]){break g}h=H[6870];if(!h){break g}a=H[h>>2];if(!a){break g}while(1){h:{d=1419;b=a;i=e;g=0;i:{if(!e){break i}f=I[1419];if(f){j:{while(1){g=I[b|0];if((g|0)!=(f|0)|!g){break j}i=i-1|0;if(!i){break j}b=b+1|0;f=I[d+1|0];d=d+1|0;if(f){continue}break}f=0}}else{f=0}g=f-I[b|0]|0}if(!g){a=a+e|0;if(I[a|0]==61){break h}}a=H[h+4>>2];h=h+4|0;if(a){continue}break g}break}j=a+1|0}b=j}k:{if(!b){break k}}a=gc();H[c+236>>2]=a;if(!a){a=gc();H[c+236>>2]=a;if(!a){break a}}}else{c=0}return c}vb(c);return 0}function id(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=H[a+48>>2];if(e>>>0>=b>>>0){H[a+48>>2]=e-b;H[a+36>>2]=H[a+36>>2]+b;e=c+H[a+60>>2]|0;d=b+H[a+56>>2]|0;e=d>>>0>>0?e+1|0:e;H[a+56>>2]=d;H[a+60>>2]=e;qa=c;return b|0}if(I[a+68|0]&4){H[a+48>>2]=0;H[a+36>>2]=e+H[a+36>>2];g=H[a+60>>2];c=H[a+56>>2];b=c+e|0;H[a+56>>2]=b;H[a+60>>2]=b>>>0>>0?g+1|0:g;qa=e?0:-1;return(e?e:-1)|0}if(e){H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];f=e;b=b-e|0}a:{if((c|0)>0){i=1}else{i=!!b&(c|0)>=0}if(i){while(1){i=H[a+12>>2];e=c+g|0;h=b+f|0;e=H[a+60>>2]+(h>>>0>>0?e+1|0:e)|0;j=h;h=h+H[a+56>>2]|0;e=j>>>0>h>>>0?e+1|0:e;if((e|0)==(i|0)&h>>>0>K[a+8>>2]|e>>>0>i>>>0){Ba(d,4,15630,0);H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];b=g+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;d=H[a+8>>2];f=d-c|0;e=H[a+12>>2];g=e-((c>>>0>d>>>0)+b|0)|0;i=ra[H[a+28>>2]](d,e,H[a>>2])|0;h=H[a+68>>2];if(i){H[a+56>>2]=d;H[a+60>>2]=e}H[a+68>>2]=h|4;a=(c|0)==(d|0)&(b|0)==(e|0);b=a?-1:f;break a}e=ra[H[a+24>>2]](b,c,H[a>>2])|0;i=qa;h=i;if((e&h)==-1){Ba(d,4,15630,0);H[a+68>>2]=H[a+68>>2]|4;e=g+H[a+60>>2]|0;b=f+H[a+56>>2]|0;e=b>>>0>>0?e+1|0:e;H[a+56>>2]=b;H[a+60>>2]=e;a=!(g|f);b=a?-1:f;break a}g=g+h|0;f=e+f|0;g=f>>>0>>0?g+1|0:g;i=b;b=b-e|0;c=c-((e>>>0>i>>>0)+h|0)|0;if(!!b&(c|0)>=0|(c|0)>0){continue}break}}b=g+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;qa=g;return f|0}qa=a?-1:g;return b|0}function Sa(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;a:{b:{if(!H[a+12>>2]){k=1;if(H[a+4>>2]>0|H[a+8>>2]>1){break b}break a}e=1;if(H[a+8>>2]>0){break b}if(H[a+4>>2]<2){break a}}b=H[a>>2];f=b+(e<<5)|0;g=H[a+16>>2];h=H[a+20>>2];if(g>>>0>>0){d=g;while(1){c=(d<<6)+f|0;L[c>>2]=L[c>>2]*O(1.2301740646362305);L[c+4>>2]=L[c+4>>2]*O(1.2301740646362305);L[c+8>>2]=L[c+8>>2]*O(1.2301740646362305);L[c+12>>2]=L[c+12>>2]*O(1.2301740646362305);L[c+16>>2]=L[c+16>>2]*O(1.2301740646362305);L[c+20>>2]=L[c+20>>2]*O(1.2301740646362305);L[c+24>>2]=L[c+24>>2]*O(1.2301740646362305);L[c+28>>2]=L[c+28>>2]*O(1.2301740646362305);d=d+1|0;if((h|0)!=(d|0)){continue}break}}i=b+(k<<5)|0;j=H[a+28>>2];c=H[a+24>>2];if(j>>>0>c>>>0){d=c;while(1){b=(d<<6)+i|0;L[b>>2]=L[b>>2]*O(1.625732421875);L[b+4>>2]=L[b+4>>2]*O(1.625732421875);L[b+8>>2]=L[b+8>>2]*O(1.625732421875);L[b+12>>2]=L[b+12>>2]*O(1.625732421875);L[b+16>>2]=L[b+16>>2]*O(1.625732421875);L[b+20>>2]=L[b+20>>2]*O(1.625732421875);L[b+24>>2]=L[b+24>>2]*O(1.625732421875);L[b+28>>2]=L[b+28>>2]*O(1.625732421875);d=d+1|0;if((j|0)!=(d|0)){continue}break}}b=f+32|0;d=H[a+8>>2];a=H[a+4>>2];e=a-e|0;e=(d|0)<(e|0)?d:e;lb(i,b,g,h,e,O(-.4435068666934967));l=i+32|0;d=d-k|0;a=(a|0)<(d|0)?a:d;lb(f,l,c,j,a,O(-.8829110860824585));lb(i,b,g,h,e,O(.05298011749982834));lb(f,l,c,j,a,O(1.5861343145370483))}}function Xb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;e=H[a+28>>2]+N(b,152)|0;c=H[e-144>>2]-H[e-152>>2]|0;d=H[e-140>>2]-H[e-148>>2]|0;f=d>>>0>=64?64:d;g=c>>>0>=64?64:c;a:{if(!(!c|!d|(!g|!f)|g>>>0>4294967295/(f>>>0)>>>2>>>0)){e=Ea(1,28);H[e+12>>2]=f;H[e+8>>2]=g;H[e+4>>2]=d;H[e>>2]=c;j=d;d=d+f|0;i=j>>>0>d>>>0;d=fe(d-1|0,i-!d|0,f,0);H[e+20>>2]=d;j=c;c=c+g|0;f=j>>>0>c>>>0;c=fe(c-1|0,f-!c|0,g,0);H[e+16>>2]=c;be(d,0,c);b:{if(qa){break b}c=Ea(4,N(c,d));H[e+24>>2]=c;if(!c){break b}break a}Ca(e)}e=0}if(!e){return 0}c:{if(b){while(1){q=N(p,152);g=q+H[a+28>>2]|0;c=H[g+24>>2];if(c){j=g+28|0;d=H[g+20>>2];f=H[g+16>>2];n=0;while(1){if(N(d,f)){i=N(n,36)+j|0;o=0;while(1){k=H[i+20>>2]+N(o,40)|0;c=H[k+20>>2];h=H[k+16>>2];if(N(c,h)){f=0;while(1){l=H[k+24>>2]+N(f,68)|0;r=H[l+60>>2];if(r){h=H[l+8>>2];d=h-H[i>>2]|0;m=H[i+16>>2];if(m&1){c=H[a+28>>2]+q|0;d=(H[c-144>>2]+d|0)-H[c-152>>2]|0}s=H[l+12>>2];c=s-H[i+4>>2]|0;if(m&2){m=H[a+28>>2]+q|0;c=(c+H[m-140>>2]|0)-H[m-148>>2]|0}h=H[l+16>>2]-h|0;if(!Ya(e,d,c,d+h|0,(H[l+20>>2]-s|0)+c|0,r,1,h)){break c}h=H[k+16>>2];c=H[k+20>>2]}f=f+1|0;if(f>>>0>>0){continue}break}f=H[g+16>>2];d=H[g+20>>2]}o=o+1|0;if(o>>>0>>0){continue}break}c=H[g+24>>2]}n=n+1|0;if(n>>>0>>0){continue}break}}p=p+1|0;if((p|0)!=(b|0)){continue}break}}return e}Ua(e);return 0}function Ld(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;h=na-16|0;na=h;if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}a:{if(c>>>0<=1){Ba(d,1,4132,0);a=0;break a}Da(b,h+12|0,2);b:{if(H[h+12>>2]){Ba(d,2,3608,0);break b}if(c>>>0<=6){Ba(d,1,4132,0);a=0;break a}Da(b+2|0,h+12|0,2);g=H[e+5616>>2];k=I[h+12|0];f=H[e+5620>>2];c:{d:{if(!f){break d}a=g;while(1){if(H[a+8>>2]!=(k|0)){a=a+20|0;i=i+1|0;if((i|0)!=(f|0)){continue}break d}break}if((f|0)!=(i|0)){break c}}if(H[e+5624>>2]==(f|0)){a=f+10|0;H[e+5624>>2]=a;a=Ga(g,N(a,20));g=H[e+5616>>2];if(!a){Ca(g);H[e+5624>>2]=0;H[e+5616>>2]=0;H[e+5620>>2]=0;Ba(d,1,4158,0);a=0;break a}e:{if((a|0)==(g|0)){break e}l=H[e+5632>>2];if(!l){break e}m=H[e+5628>>2];i=0;while(1){f=N(i,20)+m|0;j=H[f+8>>2];if(j){H[f+8>>2]=a+(j-g|0)}j=H[f+12>>2];if(j){H[f+12>>2]=a+(j-g|0)}i=i+1|0;if((l|0)!=(i|0)){continue}break}}H[e+5616>>2]=a;g=H[e+5620>>2];f=N(H[e+5624>>2]-g|0,20);if(f){y(a+N(g,20)|0,0,f)}f=H[e+5620>>2];g=H[e+5616>>2]}H[e+5620>>2]=f+1;a=N(f,20)+g|0}e=H[a+12>>2];if(e){Ca(e);H[a+12>>2]=0;H[a+16>>2]=0}H[a+8>>2]=k;e=H[h+12>>2];H[a>>2]=e>>>10&3;H[a+4>>2]=e>>>8&3;Da(b+4|0,h+12|0,2);if(H[h+12>>2]){Ba(d,2,3023,0);break b}c=c-6|0;e=Fa(c);H[a+12>>2]=e;if(!e){Ba(d,1,4132,0);a=0;break a}if(c){B(e,b+6|0,c)}H[a+16>>2]=c}a=1}na=h+16|0;return a|0}function Qb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;if(a){b=H[a+20>>2];if(b){g=H[b>>2];if(g){d=H[g+20>>2];if(H[g+16>>2]){i=F[a+40|0]&1?16:17;while(1){c=H[d+28>>2];if(c){b=H[d+32>>2];l=(b>>>0)/152|0;j=0;if(b>>>0>=152){while(1){b=H[c+48>>2];if(b){f=H[c+52>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+48>>2]}Ca(b);H[c+48>>2]=0}b=H[c+84>>2];if(b){f=H[c+88>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+84>>2]}Ca(b);H[c+84>>2]=0}b=H[c+120>>2];if(b){f=H[c+124>>2];h=(f>>>0)/40|0;e=0;if(f>>>0>=40){while(1){Za(H[b+32>>2]);H[b+32>>2]=0;Za(H[b+36>>2]);H[b+36>>2]=0;ra[i|0](b);b=b+40|0;e=e+1|0;if((h|0)!=(e|0)){continue}break}b=H[c+120>>2]}Ca(b);H[c+120>>2]=0}c=c+152|0;j=j+1|0;if((l|0)!=(j|0)){continue}break}c=H[d+28>>2]}Ca(c);H[d+28>>2]=0}a:{if(!H[d+40>>2]){break a}b=H[d+36>>2];if(!b){break a}Ca(b);H[d+44>>2]=0;H[d+48>>2]=0;H[d+36>>2]=0;H[d+40>>2]=0}Ca(H[d+52>>2]);d=d+76|0;k=k+1|0;if(k>>>0>2]){continue}break}d=H[g+20>>2]}Ca(d);H[g+20>>2]=0;Ca(H[H[a+20>>2]>>2]);b=H[a+20>>2];H[b>>2]=0}Ca(b);H[a+20>>2]=0}Ca(H[a+68>>2]);Ca(a)}}function Ob(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;e=H[a+24>>2];j=H[e+16>>2];if(!j){return 0}f=H[e+24>>2];e=H[H[H[a+20>>2]>>2]+20>>2];a:{b:{if(!b){b=0;while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-140>>2];g=H[a-144>>2]-H[a-152>>2]|0;a=H[a-148>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}break b}b=0;if(!H[a+64>>2]){while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-4>>2];g=H[a-8>>2]-H[a-16>>2]|0;a=H[a-12>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}break b}while(1){c=H[f+24>>2];a=H[e+28>>2]+N(H[e+24>>2],152)|0;d=H[a-140>>2];g=H[a-144>>2]-H[a-152>>2]|0;a=H[a-148>>2];h=d-a|0;be(g,0,h);if(!(!qa|(a|0)==(d|0))){break a}a=(c>>>3|0)+((c&7)!=0)|0;c=(a|0)==3?4:a;a=!c;d=N(g,h);be(c,0,d);if(!(!qa|a)){break a}a=-1;c=N(c,d);if(c>>>0>(b^-1)>>>0){break b}e=e+76|0;f=f+52|0;b=b+c|0;a=b;i=i+1|0;if((j|0)!=(i|0)){continue}break}}return a}return-1}function Yb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0;c=H[a+8>>2];e=c+H[a+4>>2]|0;a:{if(!H[a+12>>2]){if((e|0)<2){break a}h=(c<<2)+b|0;d=H[h>>2];c=H[b>>2]-(d+1>>1)|0;i=H[a>>2];if(e>>>0>=4){k=(e-4>>>1|0)+1|0;a=1;while(1){f=d;d=a<<2;m=H[d+b>>2];d=H[d+h>>2];j=c;l=i+(g<<2)|0;H[l>>2]=c;c=m-((d+f|0)+2>>2)|0;H[l+4>>2]=f+(j+c>>1);g=g+2|0;f=(a|0)!=(k|0);a=a+1|0;if(f){continue}break}}H[i+(g<<2)>>2]=c;if(e&1){f=e-1|0;a=H[(f<<1)+b>>2]-(d+1>>1)|0;H[i+(f<<2)>>2]=a;c=a+c>>1;j=-8}else{j=-4}a=e<<2;H[j+(a+i|0)>>2]=c+d;if(!a){break a}B(b,i,a);return}b:{switch(e-1|0){case 0:H[b>>2]=H[b>>2]/2;return;case 1:a=H[a>>2];c=(c<<2)+b|0;d=H[b>>2]-(H[c>>2]+1>>1)|0;H[a+4>>2]=d;H[a>>2]=d+H[c>>2];c=H[a+4>>2];H[b>>2]=H[a>>2];H[b+4>>2]=c;return;default:break b}}if((e|0)<3){break a}h=H[a>>2];k=(c<<2)+b|0;d=H[k+4>>2];a=H[k>>2];c=H[b>>2]-((d+a|0)+2>>2)|0;H[h>>2]=c+a;g=1;m=e-2|0;l=e&1;a=!l;if(m-a>>>0>=2){o=((e-a|0)-4>>>1|0)+1|0;a=1;while(1){f=d;p=H[(a<<2)+b>>2];j=a+1|0;d=H[k+(j<<2)>>2];i=c;n=h+(g<<2)|0;H[n>>2]=c;c=p-((d+f|0)+2>>2)|0;H[n+4>>2]=f+(i+c>>1);g=g+2|0;f=(a|0)!=(o|0);a=j;if(f){continue}break}}H[h+(g<<2)>>2]=c;c:{if(!l){g=H[((e<<1)+b|0)-4>>2]-(d+1>>1)|0;H[h+(m<<2)>>2]=(g+c>>1)+d;break c}g=c+d|0}a=e<<2;H[(a+h|0)-4>>2]=g;if(!a){break a}B(b,h,a)}}function ce(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{e=b;if(e){if(!c){break j}if(!d){break i}e=Q(d)-Q(e)|0;if(e>>>0<=31){break h}break b}if((d|0)==1|d>>>0>1){break b}b=(a>>>0)/(c>>>0)|0;oa=a-N(b,c)|0;pa=0;qa=0;return b}if(!a){break g}if(!d){break f}f=d-1|0;if(f&d){break f}oa=a;pa=e&f;a=e>>>de(d)|0;qa=0;return a}f=c-1|0;if(!(f&c)){break e}k=(Q(c)+33|0)-Q(e)|0;g=0-k|0;break c}k=e+1|0;g=63-e|0;break c}oa=0;a=(e>>>0)/(d>>>0)|0;pa=e-N(a,d)|0;qa=0;return a}e=Q(d)-Q(e)|0;if(e>>>0<31){break d}break b}oa=a&f;pa=0;if((c|0)==1){break a}c=de(c);d=c&31;if((c&63)>>>0>=32){e=0;a=b>>>d|0}else{e=b>>>d|0;a=((1<>>d}qa=e;return a}k=e+1|0;g=63-e|0}f=a;e=k&63;h=e&31;if((e&63)>>>0>=32){e=0;f=b>>>h|0}else{e=b>>>h|0;f=((1<>>h}h=g&63;g=a;i=h&31;if((h&63)>>>0>=32){j=a<>>32-i|b<>>31;f=f<<1|b>>>31;l=e;i=g-(e+(f>>>0>h>>>0)|0)|0;m=i>>31;j=m;e=f;i=c&j;f=e-i|0;e=l-((d&j)+(e>>>0>>0)|0)|0;j=b<<1|a>>>31;a=n|a<<1;b=j|o;l=m&1;n=l;k=k-1|0;if(k){continue}break}}oa=f;pa=e;j=b<<1|a>>>31;a=l|a<<1;qa=j|o;return a}oa=a;pa=b;a=0;b=0}qa=b;return a}function Cc(a,b,c,d,e){var f=0,g=0,h=0,i=0;h=na-16|0;na=h;if(H[a+8>>2]==16){a=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{a=H[a+12>>2]}f=H[d>>2];a:{if(!f){d=0;Ba(e,1,2642,0);break a}a=H[a+5584>>2];H[d>>2]=f-1;Da(c,h+12|0,1);g=N(b,1080)+a|0;a=H[h+12>>2];H[g+804>>2]=a>>>5;b=a&31;H[g+24>>2]=b;a=c+1|0;b:{c:{d:{e:{f:{switch(b|0){case 0:f=H[d>>2];break e;case 1:break d;default:break f}}f=H[d>>2]>>>1|0}if(f>>>0>=98){H[h+4>>2]=97;H[h+8>>2]=97;H[h>>2]=f;Ba(e,2,16056,h);b=H[g+24>>2]}if(b){b=f;if(b){break d}a=0;break c}if(f){b=g+28|0;c=0;while(1){Da(a,h+12|0,1);if(c>>>0<=96){e=H[h+12>>2];i=b+(c<<3)|0;H[i+4>>2]=0;H[i>>2]=e>>>3}a=a+1|0;c=c+1|0;if((f|0)!=(c|0)){continue}break}}a=H[d>>2];if(a>>>0>>0){d=0;break a}a=a-f|0;break b}e=g+28|0;c=0;while(1){Da(a,h+12|0,2);if(c>>>0<=96){f=e+(c<<3)|0;i=H[h+12>>2];H[f+4>>2]=i&2047;H[f>>2]=i>>>11}a=a+2|0;c=c+1|0;if((c|0)!=(b|0)){continue}break}a=b<<1}b=H[d>>2];if(a>>>0>b>>>0){d=0;break a}a=b-a|0}H[d>>2]=a;d=1;if(H[g+24>>2]!=1){break a}f=g+28|0;c=H[g+32>>2];e=H[g+28>>2];a=1;while(1){b=f+(a<<3)|0;H[b+12>>2]=c;H[b+4>>2]=c;g=e-((a>>>0)/3|0)|0;H[b+8>>2]=(g|0)>0?g:0;g=b;b=e-((a-1>>>0)/3|0)|0;H[g>>2]=(b|0)>0?b:0;a=a+2|0;if((a|0)!=97){continue}break}}na=h+16|0;return d}function ud(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;h=na-16|0;na=h;a:{if(!(I[a+100|0]&2)){Ba(d,1,11356,0);a=0;break a}H[a+104>>2]=0;b:{c:{d:{if(c){while(1){if(c>>>0<=7){Ba(d,1,3403,0);break b}g=h+12|0;Da(b,g,4);e=H[h+12>>2];Da(b+4|0,g,4);f=8;g=H[h+12>>2];e:{f:{g:{switch(e|0){case 1:if(c>>>0<16){e=3443;break c}Da(b+8|0,h+8|0,4);if(H[h+8>>2]){e=8449;break c}Da(b+12|0,h+12|0,4);e=H[h+12>>2];if(e){break f}e=3268;break c;case 0:break g;default:break e}}Ba(d,1,3268,0);break b}f=16}if(e>>>0>>0){Ba(d,1,9148,0);break b}if(c>>>0>>0){Ba(d,1,9076,0);a=0;break a}h:{i:{j=b+f|0;k=e-f|0;j:{k:{l:{m:{if((g|0)<=1668246641){if((g|0)==1651532643){break m}if((g|0)==1667523942){break k}if((g|0)!=1668112752){break i}f=25264;break j}if((g|0)==1885564018){break l}f=25232;if((g|0)==1768449138){break j}if((g|0)!=1668246642){break i}f=25240;break j}f=25248;break j}f=25256;break j}f=25272}if(ra[H[f+4>>2]](a,j,k,d)|0){break h}a=0;break a}H[a+104>>2]=H[a+104>>2]|2147483647}i=(g|0)==1768449138?1:i;b=b+e|0;c=c-e|0;if(c){continue}break}if(i){break d}}Ba(d,1,8976,0);a=0;break a}F[a+132|0]=1;H[a+100>>2]=H[a+100>>2]|4;a=1;break a}Ba(d,1,e,0)}Ba(d,1,1968,0);a=0}na=h+16|0;return a|0}function Rd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0;e=na-32|0;na=e;f=1;a:{if(c>>>0<=1){f=0;Ba(d,1,10062,0);break a}if(H[a+76>>2]){break a}Da(b,e+28|0,1);Da(b+1|0,e+24|0,1);h=H[e+24>>2];g=h>>>4&3;if((g|0)==3){H[a+76>>2]=1;Ba(d,2,11558,0);break a}c=c-2|0;h=(h>>>5&2)+2|0;i=h+g|0;j=(c>>>0)/(i>>>0)|0;if((N(j,i)|0)!=(c|0)){H[a+76>>2]=1;Ba(d,2,11139,0);break a}if(c>>>0>>0){break a}b:{c=H[a+68>>2];if(c>>>0<=(j^-1)>>>0){c=c+j|0;if(c>>>0<536870912){break b}}H[a+76>>2]=1;Ba(d,2,9400,0);break a}i=Ga(H[a+72>>2],c<<3);if(!i){H[a+76>>2]=1;Ba(d,2,9443,0);break a}c=b+2|0;H[a+72>>2]=i;c:{if(g){while(1){Da(c,e+20|0,g);b=H[e+20>>2];if(b>>>0>=N(H[a+132>>2],H[a+128>>2])>>>0){break c}b=c+g|0;Da(b,e+16|0,h);c=H[a+68>>2];f=i+(c<<3)|0;G[f>>1]=H[e+20>>2];H[f+4>>2]=H[e+16>>2];f=1;H[a+68>>2]=c+1;c=b+h|0;k=k+1|0;if((k|0)!=(j|0)){continue}break}break a}b=H[a+68>>2];g=0;while(1){H[e+20>>2]=b;if(N(H[a+132>>2],H[a+128>>2])>>>0<=b>>>0){break c}Da(c,e+16|0,h);k=H[a+68>>2];f=i+(k<<3)|0;G[f>>1]=b;H[f+4>>2]=H[e+16>>2];f=1;b=k+1|0;H[a+68>>2]=b;c=c+h|0;g=g+1|0;if((j|0)!=(g|0)){continue}break}break a}H[a+76>>2]=1;H[e>>2]=b;Ba(d,2,7799,e)}na=e+32|0;return f|0}function Bd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;a:{if(!Va(H[b+8>>2],46,d)){break a}i=H[b+4>>2];e=H[i>>2];g=H[i+8>>2];b:{if(e){f=1;c:{if((e|0)!=1){k=e&1;e=e&-2;while(1){h=0;d:{if(!f){break d}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break d}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}f=h;g=g+8|0;j=j+2|0;if((e|0)!=(j|0)){continue}break}if(!k){break c}}if(!f){f=0;break c}f=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(f){break b}break a}Oa(i)}i=H[b+8>>2];e=H[i>>2];g=H[i+8>>2];e:{if(e){f=1;f:{if((e|0)!=1){k=e&1;e=e&-2;j=0;while(1){h=0;g:{if(!f){break g}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break g}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}f=h;g=g+8|0;j=j+2|0;if((e|0)!=(j|0)){continue}break}if(!k){break f}}if(!f){f=0;break f}f=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(!f){break a}break e}Oa(i)}if(!I[b+132|0]){Ba(d,1,11696,0);return 0}if(!I[b+133|0]){Ba(d,1,11667,0);return 0}l=Jb(a,H[b>>2],c,d);if(!c|!l){break a}a=H[c>>2];if(!a){break a}c=H[b+48>>2]-12|0;if(c>>>0<=12){c=H[(c<<2)+25280>>2]}else{c=-1}H[a+20>>2]=c;c=H[b+108>>2];if(!c){break a}H[a+28>>2]=c;H[a+32>>2]=H[b+112>>2];H[b+108>>2]=0}return l|0}function Jb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;e=Db();H[b+96>>2]=e;a:{b:{if(!e){break b}c:{if(Va(H[b+220>>2],18,d)){if(Va(H[b+220>>2],19,d)){break c}}break a}i=H[b+220>>2];f=H[i>>2];g=H[i+8>>2];d:{if(f){e=1;e:{if((f|0)!=1){k=f&1;f=f&-2;while(1){h=0;f:{if(!e){break f}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break f}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}e=h;g=g+8|0;j=j+2|0;if((f|0)!=(j|0)){continue}break}if(!k){break e}}if(!e){e=0;break e}e=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(e){break d}break a}Oa(i)}g:{if(Va(H[b+216>>2],20,d)){if(Va(H[b+216>>2],21,d)){break g}}break a}i=H[b+216>>2];f=H[i>>2];g=H[i+8>>2];h:{if(f){e=1;i:{if((f|0)!=1){k=f&1;f=f&-2;j=0;while(1){h=0;j:{if(!e){break j}h=0;if(!(ra[H[g>>2]](b,a,d)|0)){break j}h=(ra[H[g+4>>2]](b,a,d)|0)!=0}e=h;g=g+8|0;j=j+2|0;if((f|0)!=(j|0)){continue}break}if(!k){break i}}if(!e){e=0;break i}e=(ra[H[g>>2]](b,a,d)|0)!=0}Oa(i);if(e){break h}break a}Oa(i)}a=Db();H[c>>2]=a;if(!a){break b}wc(H[b+96>>2],a);l=1}return l|0}Ta(H[b+96>>2]);H[b+96>>2]=0;return 0}function sc(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0;b=H[6524];c=a+7&-8;a=c+7|0;d=a>>>0<7?1:d;e=a&-8;a=e+b|0;a:{b:{if(!(a>>>0>>0?d+1|0:d)){if(a>>>0<=sa()<<16>>>0){break b}if(ga(a|0)|0){break b}}H[6605]=48;b=-1;break a}H[6524]=a}d=b;if((d|0)!=-1){a=d+c|0;H[a-4>>2]=16;f=a-16|0;H[f>>2]=16;b=H[6864];if(b){e=H[b+8>>2]}else{e=0}c:{d:{if((e|0)==(d|0)){e=H[d-4>>2]&-2;g=d-e|0;h=H[g-4>>2];H[b+8>>2]=a;b=h&-2;a=g-b|0;if(H[(a+H[a>>2]|0)-4>>2]&1){f=H[a+4>>2];g=H[a+8>>2];H[f+8>>2]=g;H[g+4>>2]=f;b=(b+(c+e|0)|0)-16|0;H[a>>2]=b;break c}a=d-16|0;break d}H[d>>2]=16;H[d+8>>2]=a;H[d+4>>2]=b;H[d+12>>2]=16;H[6864]=d;a=d+16|0}b=f-a|0;H[a>>2]=b}H[((b&-4)+a|0)-4>>2]=b|1;c=H[a>>2]-8|0;e:{if(c>>>0<=127){b=(c>>>3|0)-1|0;break e}e=Q(c);b=((c>>>29-e^4)-(e<<2)|0)+110|0;if(c>>>0<=4095){break e}b=((c>>>30-e^2)-(e<<1)|0)+71|0;b=b>>>0>=63?63:b}c=b<<4;H[a+4>>2]=c+26432;c=c+26440|0;H[a+8>>2]=H[c>>2];H[c>>2]=a;H[H[a+8>>2]+4>>2]=a;c=H[6867];a=b&31;if((b&63)>>>0>=32){b=1<>>32-a}H[6866]=e|H[6866];H[6867]=b|c}return(d|0)!=-1}function wc(a,b){var c=0,d=0,e=0,f=0,g=0;H[b>>2]=H[a>>2];H[b+4>>2]=H[a+4>>2];H[b+8>>2]=H[a+8>>2];H[b+12>>2]=H[a+12>>2];c=H[b+24>>2];if(c){d=H[b+16>>2];if(d){c=0;while(1){f=H[(H[b+24>>2]+N(c,52)|0)+44>>2];if(f){Ca(f);d=H[b+16>>2]}c=c+1|0;if(d>>>0>c>>>0){continue}break}c=H[b+24>>2]}Ca(c);H[b+24>>2]=0}c=H[a+16>>2];H[b+16>>2]=c;c=Fa(N(c,52));H[b+24>>2]=c;if(c){if(H[b+16>>2]){f=0;while(1){g=N(f,52);d=g+c|0;c=H[a+24>>2]+g|0;H[d+48>>2]=H[c+48>>2];e=H[c+44>>2];H[d+40>>2]=H[c+40>>2];H[d+44>>2]=e;e=H[c+36>>2];H[d+32>>2]=H[c+32>>2];H[d+36>>2]=e;e=H[c+28>>2];H[d+24>>2]=H[c+24>>2];H[d+28>>2]=e;e=H[c+20>>2];H[d+16>>2]=H[c+16>>2];H[d+20>>2]=e;e=H[c+12>>2];H[d+8>>2]=H[c+8>>2];H[d+12>>2]=e;e=H[c+4>>2];H[d>>2]=H[c>>2];H[d+4>>2]=e;c=H[b+24>>2];H[(g+c|0)+44>>2]=0;f=f+1|0;if(f>>>0>2]){continue}break}}H[b+20>>2]=H[a+20>>2];c=H[a+32>>2];H[b+32>>2]=c;a:{if(c){c=Fa(c);H[b+28>>2]=c;if(!c){H[b+28>>2]=0;H[b+32>>2]=0;return}b=H[a+32>>2];if(!b){break a}B(c,H[a+28>>2],b);return}H[b+28>>2]=0}return}H[b+16>>2]=0;H[b+24>>2]=0}function lb(a,b,c,d,e,f){var g=0,h=O(0),i=0,j=O(0);g=(c<<6)+b|0;b=c?g+-64|0:a;i=d>>>0>>0?d:e;if(i>>>0>c>>>0){h=L[b>>2];while(1){a=b;b=g;g=b-32|0;j=h;h=L[b>>2];L[g>>2]=O(O(j+h)*f)+L[g>>2];g=b-28|0;L[g>>2]=O(O(L[a+4>>2]+L[b+4>>2])*f)+L[g>>2];g=b-24|0;L[g>>2]=O(O(L[a+8>>2]+L[b+8>>2])*f)+L[g>>2];g=b-20|0;L[g>>2]=O(O(L[a+12>>2]+L[b+12>>2])*f)+L[g>>2];g=b-16|0;L[g>>2]=O(O(L[a+16>>2]+L[b+16>>2])*f)+L[g>>2];g=b-12|0;L[g>>2]=O(O(L[a+20>>2]+L[b+20>>2])*f)+L[g>>2];g=b-8|0;L[g>>2]=O(O(L[a+24>>2]+L[b+24>>2])*f)+L[g>>2];g=b-4|0;L[g>>2]=O(O(L[a+28>>2]+L[b+28>>2])*f)+L[g>>2];g=b- -64|0;c=c+1|0;if((i|0)!=(c|0)){continue}break}}if(d>>>0>e>>>0){a=g-32|0;f=O(f+f);L[a>>2]=O(L[b>>2]*f)+L[a>>2];a=g-28|0;L[a>>2]=O(L[b+4>>2]*f)+L[a>>2];a=g-24|0;L[a>>2]=O(L[b+8>>2]*f)+L[a>>2];a=g-20|0;L[a>>2]=O(L[b+12>>2]*f)+L[a>>2];a=g-16|0;L[a>>2]=O(L[b+16>>2]*f)+L[a>>2];a=g-12|0;L[a>>2]=O(L[b+20>>2]*f)+L[a>>2];a=g-8|0;L[a>>2]=O(L[b+24>>2]*f)+L[a>>2];a=g-4|0;L[a>>2]=O(L[b+28>>2]*f)+L[a>>2]}}function Uc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;if(K[a+44>>2]>=8){e=H[a+36>>2];n=e<<5;o=N(e,28);p=N(e,24);q=N(e,20);r=e<<4;i=N(e,12);j=e<<3;g=H[a+40>>2];k=8;while(1){yb(a,g,H[a+36>>2],8);Sa(a);h=H[a+32>>2];if(h){f=H[a>>2];b=0;a:{if((h|0)!=1){s=h&1;t=h&-2;l=0;while(1){c=(b<<2)+g|0;d=f+(b<<5)|0;L[c>>2]=L[d>>2];m=e<<2;L[c+m>>2]=L[d+4>>2];L[c+j>>2]=L[d+8>>2];L[c+i>>2]=L[d+12>>2];d=b|1;c=(d<<2)+g|0;d=f+(d<<5)|0;L[c>>2]=L[d>>2];L[c+m>>2]=L[d+4>>2];L[c+j>>2]=L[d+8>>2];L[c+i>>2]=L[d+12>>2];b=b+2|0;l=l+2|0;if((t|0)!=(l|0)){continue}break}if(!s){break a}}c=(b<<2)+g|0;b=f+(b<<5)|0;L[c>>2]=L[b>>2];L[c+(e<<2)>>2]=L[b+4>>2];L[c+j>>2]=L[b+8>>2];L[c+i>>2]=L[b+12>>2]}d=H[a>>2];b=0;while(1){f=(b<<2)+g|0;c=d+(b<<5)|0;L[f+r>>2]=L[c+16>>2];L[f+q>>2]=L[c+20>>2];L[f+p>>2]=L[c+24>>2];L[f+o>>2]=L[c+28>>2];b=b+1|0;if((h|0)!=(b|0)){continue}break}}g=g+n|0;k=k+8|0;if(k>>>0<=K[a+44>>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function pd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;f=na-16|0;na=f;a:{if(H[a+120>>2]|c>>>0<3){break a}Da(b,f+12|0,2);k=J[f+12>>1];if(k-1025>>>0<=4294966271){H[f>>2]=k;Ba(d,1,3526,f);break a}Da(b+2|0,f+12|0,1);i=J[f+12>>1];if(!i){Ba(d,1,3174,0);break a}if(i+3>>>0>c>>>0){break a}h=Fa(N(i,k)<<2);if(!h){break a}j=Fa(i);if(!j){Ca(h);break a}l=Fa(i);if(!l){Ca(h);Ca(j);break a}g=Fa(20);if(!g){Ca(h);Ca(j);Ca(l);break a}d=b+3|0;H[g+8>>2]=j;H[g+4>>2]=l;G[g+16>>1]=k;H[g>>2]=h;m=H[f+12>>2];H[g+12>>2]=0;F[g+18|0]=m;H[a+120>>2]=g;while(1){Da(d,f+12|0,1);F[e+j|0]=(I[f+12|0]&127)+1;F[e+l|0]=(H[f+12>>2]&128)>>>7;d=d+1|0;e=e+1|0;if((i|0)!=(e|0)){continue}break}g=0;while(1){e=0;a=0;while(1){e=I[e+j|0]+7>>>3|0;e=e>>>0>=4?4:e;if((e+(d-b|0)|0)>(c|0)){e=0;break a}Da(d,f+12|0,e);H[h>>2]=H[f+12>>2];h=h+4|0;d=d+e|0;a=a+1|0;e=a&65535;if(i>>>0>e>>>0){continue}break}e=1;g=g+1|0;if((g&65535)>>>0>>0){continue}break}}na=f+16|0;return e|0}function hd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;f=-1;e=-1;if(!(I[a+68|0]&8)){f=H[a+32>>2];H[a+36>>2]=f;a:{b:{c:{e=H[a+48>>2];if(e){while(1){e=ra[H[a+20>>2]](f,e,H[a>>2])|0;if((e|0)==-1){break c}f=e+H[a+36>>2]|0;H[a+36>>2]=f;e=H[a+48>>2]-e|0;H[a+48>>2]=e;if(e){continue}break}f=H[a+32>>2]}H[a+36>>2]=f;if(!!b&(c|0)>=0|(c|0)>0){break b}f=0;e=0;break a}H[a+68>>2]=H[a+68>>2]|8;Ba(d,4,15604,0);H[a+48>>2]=0;H[a+68>>2]=H[a+68>>2]|8;qa=-1;return-1}f=0;e=0;while(1){g=ra[H[a+24>>2]](b,c,H[a>>2])|0;h=qa;i=h;if((g&h)==-1){Ba(d,4,15589,0);H[a+68>>2]=H[a+68>>2]|8;b=e+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b;a=!(e|f);b=a?-1:f;qa=a?-1:e;return b|0}e=e+i|0;f=f+g|0;e=f>>>0>>0?e+1|0:e;h=b;b=b-g|0;c=c-(i+(g>>>0>h>>>0)|0)|0;if(!!b&(c|0)>=0|(c|0)>0){continue}break}}b=e+H[a+60>>2]|0;c=f+H[a+56>>2]|0;b=c>>>0>>0?b+1|0:b;H[a+56>>2]=c;H[a+60>>2]=b}qa=e;return f|0}function Nd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;g=na-16|0;na=g;a:{if(c>>>0<=1){Ba(d,1,3983,0);a=0;break a}if(F[a+212|0]&1){Ba(d,1,12668,0);a=0;break a}a=H[a+180>>2]+N(H[a+228>>2],5644)|0;F[a+5640|0]=I[a+5640|0]|2;Da(b,g+12|0,1);e=H[a+5164>>2];b:{if(!e){f=H[g+12>>2]+1|0;e=Ea(f,8);H[a+5164>>2]=e;if(!e){Ba(d,1,4009,0);a=0;break a}H[a+5160>>2]=f;break b}f=H[g+12>>2];if(f>>>0>2]){break b}h=e;e=f+1|0;f=Ga(h,e<<3);if(!f){Ba(d,1,4009,0);a=0;break a}H[a+5164>>2]=f;h=H[a+5160>>2];i=e-h<<3;if(i){y(f+(h<<3)|0,0,i)}H[a+5160>>2]=e;e=H[a+5164>>2]}h=e;e=H[g+12>>2];if(H[h+(e<<3)>>2]){H[g>>2]=e;Ba(d,1,7063,g);a=0;break a}c=c-1|0;e=Fa(c);a=H[a+5164>>2];f=a+(H[g+12>>2]<<3)|0;H[f>>2]=e;if(!e){Ba(d,1,4009,0);a=0;break a}H[f+4>>2]=c;if(c){B(H[a+(H[g+12>>2]<<3)>>2],b+1|0,c)}a=1}na=g+16|0;return a|0}function Cb(a,b,c){var d=0,e=0,f=0,g=0;e=a+4|0;d=(e+b|0)-1&0-b;b=H[a>>2];if(d+c>>>0<=(b+a|0)-4>>>0){f=H[a+4>>2];g=H[a+8>>2];H[f+8>>2]=g;H[g+4>>2]=f;if((d|0)!=(e|0)){d=d-e|0;f=a-(H[a-4>>2]&-2)|0;e=d+H[f>>2]|0;H[f>>2]=e;H[(f+(e&-4)|0)-4>>2]=e;a=a+d|0;b=b-d|0;H[a>>2]=b}a:{if(c+24>>>0<=b>>>0){e=a+c|0;b=(b-c|0)-8|0;H[e+8>>2]=b;g=e+8|0;H[(g+(b&-4)|0)-4>>2]=b|1;d=H[e+8>>2]-8|0;b:{if(d>>>0<=127){b=(d>>>3|0)-1|0;break b}f=Q(d);b=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break b}b=((d>>>30-f^2)-(f<<1)|0)+71|0;b=b>>>0>=63?63:b}d=b<<4;H[e+12>>2]=d+26432;d=d+26440|0;H[e+16>>2]=H[d>>2];H[d>>2]=g;H[H[e+16>>2]+4>>2]=g;d=H[6866];f=H[6867];e=b&31;if((b&63)>>>0>=32){b=1<>>32-e}H[6866]=g|d;H[6867]=b|f;b=c+8|0;H[a>>2]=b;c=(b&-4)+a|0;break a}c=a+b|0}H[c-4>>2]=b;a=a+4|0}else{a=0}return a}function Td(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;g=na-16|0;na=g;i=H[H[a+96>>2]+16>>2];h=i>>>0<257?1:2;e=(h<<1)+5|0;f=(c>>>0)/(e>>>0)|0;a:{if(!((N(e,f)|0)==(c|0)&c>>>0>=e>>>0)){Ba(d,1,4643,0);a=0;break a}if(H[a+8>>2]==16){e=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{e=H[a+12>>2]}a=0;c=I[e+5640|0];a=c&4?H[e+420>>2]+1|0:a;f=f+a|0;if(f>>>0>=32){H[g>>2]=f;Ba(d,1,7781,g);a=0;break a}F[e+5640|0]=c|4;if(a>>>0>>0){c=(e+N(a,148)|0)+424|0;while(1){Da(b,c,1);b=b+1|0;Da(b,c+4|0,h);b=b+h|0;Da(b,c+8|0,2);d=H[c+8>>2];j=H[e+8>>2];H[c+8>>2]=d>>>0>>0?d:j;Da(b+2|0,c+12|0,1);b=b+3|0;Da(b,c+16|0,h);b=b+h|0;Da(b,g+12|0,1);H[c+36>>2]=H[g+12>>2];d=H[c+16>>2];H[c+16>>2]=d>>>0>>0?d:i;c=c+148|0;b=b+1|0;a=a+1|0;if((f|0)!=(a|0)){continue}break}}H[e+420>>2]=f-1;a=1}na=g+16|0;return a|0}function ib(a){var b=0,c=0,d=0,e=0;a:{if(!a){break a}b=H[a+5164>>2];if(b){c=H[a+5160>>2];if(c){b=0;while(1){d=H[H[a+5164>>2]+(b<<3)>>2];if(d){Ca(d);c=H[a+5160>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+5164>>2]}H[a+5160>>2]=0;Ca(b);H[a+5164>>2]=0}b=H[a+5172>>2];if(b){Ca(b);H[a+5172>>2]=0}b=H[a+5584>>2];if(b){Ca(b);H[a+5584>>2]=0}b=H[a+5612>>2];if(b){Ca(b);H[a+5612>>2]=0}b=H[a+5608>>2];if(b){Ca(b);H[a+5608>>2]=0}b=H[a+5628>>2];if(b){Ca(b);H[a+5636>>2]=0;H[a+5628>>2]=0;H[a+5632>>2]=0}b=H[a+5616>>2];if(b){e=H[a+5620>>2];if(e){c=0;while(1){d=H[b+12>>2];if(d){Ca(d);H[b+12>>2]=0;e=H[a+5620>>2]}b=b+20|0;c=c+1|0;if(e>>>0>c>>>0){continue}break}b=H[a+5616>>2]}Ca(b);H[a+5616>>2]=0}b=H[a+5604>>2];if(b){Ca(b);H[a+5604>>2]=0}b=H[a+5596>>2];if(!b){break a}Ca(b);H[a+5596>>2]=0;H[a+5600>>2]=0}}function td(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-32|0;na=e;a:{if(H[a+72>>2]){Ba(d,2,7015,0);c=1;break a}if((c|0)!=14){c=0;Ba(d,1,14445,0);break a}Da(b,a+16|0,4);Da(b+4|0,a+12|0,4);Da(b+8|0,a+20|0,2);f=H[a+12>>2];b:{g=H[a+16>>2];c=H[a+20>>2];c:{if(!g){break c}c=H[a+20>>2];if(!f){break c}if(c){break b}c=0}H[e+8>>2]=c;H[e+4>>2]=g;H[e>>2]=f;Ba(d,1,14289,e);c=0;break a}if(c>>>0>=16385){c=0;Ba(d,1,14203,0);break a}c=Ea(c,12);H[a+72>>2]=c;if(!c){c=0;Ba(d,1,14240,0);break a}c=1;Da(b+10|0,a+24|0,1);Da(b+11|0,a+28|0,1);f=H[a+28>>2];if((f|0)!=7){H[e+16>>2]=f;Ba(d,4,16272,e+16|0)}Da(b+12|0,a+32|0,1);Da(b+13|0,a+36|0,1);b=H[a>>2];F[b+212|0]=I[b+212|0]&251|(H[a+24>>2]==255?4:0);b=H[a>>2];H[b+240>>2]=H[a+12>>2];H[b+244>>2]=H[a+16>>2];F[a+133|0]=1}na=e+32|0;return c|0}function lc(a,b,c,d){a:{switch(b-9|0){case 0:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=H[b>>2];return;case 6:b=H[c>>2];H[c>>2]=b+4;b=G[b>>1];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 7:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=J[b>>1];H[a+4>>2]=0;return;case 8:b=H[c>>2];H[c>>2]=b+4;b=F[b|0];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 9:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=I[b|0];H[a+4>>2]=0;return;case 16:b=H[c>>2]+7&-8;H[c>>2]=b+8;M[a>>3]=M[b>>3];return;case 17:ra[d|0](a,c);default:return;case 1:case 4:case 14:b=H[c>>2];H[c>>2]=b+4;b=H[b>>2];H[a>>2]=b;H[a+4>>2]=b>>31;return;case 2:case 5:case 11:case 15:b=H[c>>2];H[c>>2]=b+4;H[a>>2]=H[b>>2];H[a+4>>2]=0;return;case 3:case 10:case 12:case 13:break a}}b=H[c>>2]+7&-8;H[c>>2]=b+8;c=H[b+4>>2];H[a>>2]=H[b>>2];H[a+4>>2]=c}function Od(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;g=na-16|0;na=g;a:{if(c>>>0<=1){Ba(d,1,4311,0);a=0;break a}F[a+212|0]=I[a+212|0]|1;Da(b,g+12|0,1);e=H[a+140>>2];b:{if(!e){f=H[g+12>>2]+1|0;e=Ea(f,8);H[a+140>>2]=e;if(!e){Ba(d,1,4337,0);a=0;break a}H[a+136>>2]=f;break b}f=H[g+12>>2];if(f>>>0>2]){break b}h=e;e=f+1|0;f=Ga(h,e<<3);if(!f){Ba(d,1,4337,0);a=0;break a}H[a+140>>2]=f;h=H[a+136>>2];i=e-h<<3;if(i){y(f+(h<<3)|0,0,i)}H[a+136>>2]=e;e=H[a+140>>2]}h=e;e=H[g+12>>2];if(H[h+(e<<3)>>2]){H[g>>2]=e;Ba(d,1,7085,g);a=0;break a}c=c-1|0;e=Fa(c);a=H[a+140>>2];f=a+(H[g+12>>2]<<3)|0;H[f>>2]=e;if(!e){Ba(d,1,4337,0);a=0;break a}H[f+4>>2]=c;if(c){B(H[a+(H[g+12>>2]<<3)>>2],b+1|0,c)}a=1}na=g+16|0;return a|0}function cd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0;e=na-32|0;na=e;d=H[a+28>>2];H[e+16>>2]=d;g=H[a+20>>2];H[e+28>>2]=c;H[e+24>>2]=b;b=(d|0)==(g|0);i=b?1:2;f=e+16|0;b=b?f|8:f;d=g-d|0;H[e+20>>2]=d;g=c+d|0;a:{while(1){b:{c:{d:{if(!rc(Z(H[a+60>>2],b|0,i|0,e+12|0)|0)){f=H[e+12>>2];if((f|0)==(g|0)){break d}if((f|0)>=0){break c}break b}if((g|0)!=-1){break b}}b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;H[a+16>>2]=b+H[a+48>>2];a=c;break a}h=H[b+4>>2];j=h>>>0>>0;d=(j?8:0)+b|0;h=f-(j?h:0)|0;H[d>>2]=h+H[d>>2];b=(j?12:4)+b|0;H[b>>2]=H[b>>2]-h;g=g-f|0;i=i-j|0;b=d;continue}break}H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0;H[a>>2]=H[a>>2]|32;a=0;if((i|0)==2){break a}a=c-H[b+4>>2]|0}na=e+32|0;return a|0}function Ca(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;if(a){b=a-4|0;f=H[b>>2];c=f;d=b;e=H[a-8>>2];a=e&-2;if((a|0)!=(e|0)){d=b-a|0;c=H[d+4>>2];e=H[d+8>>2];H[c+8>>2]=e;H[e+4>>2]=c;c=a+f|0}a=b+f|0;b=H[a>>2];if((b|0)!=H[(a+b|0)-4>>2]){f=H[a+4>>2];a=H[a+8>>2];H[f+8>>2]=a;H[a+4>>2]=f;c=b+c|0}H[d>>2]=c;H[((c&-4)+d|0)-4>>2]=c|1;b=H[d>>2]-8|0;a:{if(b>>>0<=127){a=(b>>>3|0)-1|0;break a}c=Q(b);a=((b>>>29-c^4)-(c<<2)|0)+110|0;if(b>>>0<=4095){break a}a=((b>>>30-c^2)-(c<<1)|0)+71|0;a=a>>>0>=63?63:a}b=a<<4;H[d+4>>2]=b+26432;b=b+26440|0;H[d+8>>2]=H[b>>2];H[b>>2]=d;H[H[d+8>>2]+4>>2]=d;b=H[6866];c=H[6867];d=a&31;if((a&63)>>>0>=32){a=1<>>32-d}H[6866]=e|b;H[6867]=a|c}}function Xc(a,b){a=a|0;b=+b;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;c=a<<3;h=c+28672|0;e=M[c+28704>>3];if(e!=0){g=M[h>>3];b=S(g,b);d=b-g;i=~~d>>>0;if(P(d)>=1){c=~~(d>0?R(T(d*2.3283064365386963e-10),4294967295):U((d-+(~~d>>>0>>>0))*2.3283064365386963e-10))>>>0}else{c=0}j=~~e>>>0;if(P(e)>=1){f=~~(e>0?R(T(e*2.3283064365386963e-10),4294967295):U((e-+(~~e>>>0>>>0))*2.3283064365386963e-10))>>>0}else{f=0}c=fe(i,c,j,f);f=qa;c=c+1|0;f=c?f:f+1|0;d=(+(c>>>0)+ +(f>>>0)*4294967296)*e+g;g=d-b}M[h>>3]=d;fa(a|0,+g)|0;c=(a|0)==2?27:(a|0)==1?26:14;a=c-1|0;a:{if(H[6901]>>>a&1){H[6903]=H[6903]|1<>2];if(a){ra[a|0](c)}}}function nd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=na-16|0;na=e;a:{if(H[a+116>>2]){break a}if(c>>>0<=1){Ba(d,1,8882,0);break a}Da(b,e+12|0,2);f=H[e+12>>2];h=f&65535;if(!h){Ba(d,1,8915,0);break a}if(N(h,6)+2>>>0>c>>>0){Ba(d,1,8882,0);break a}d=Fa(N(f,6));if(!d){break a}c=Fa(8);H[a+116>>2]=c;if(!c){Ca(d);break a}H[c>>2]=d;f=c;c=J[e+12>>1];G[f+4>>1]=c;if(!c){g=1;break a}c=0;while(1){g=e+12|0;Da(b+2|0,g,2);f=d+N(c,6)|0;G[f>>1]=H[e+12>>2];Da(b+4|0,g,2);G[f+2>>1]=H[e+12>>2];b=b+6|0;Da(b,g,2);G[f+4>>1]=H[e+12>>2];g=1;c=c+1|0;if(c>>>0>2]+4>>1]){continue}break}}na=e+16|0;return g|0}function Ib(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;g=na-32|0;na=g;f=H[a+96>>2];a:{if(!f){Ba(d,1,13752,0);e=0;break a}f=Ea(4,H[f+16>>2]);e=0;if(!f){break a}if(b){j=H[a+96>>2];while(1){b:{e=H[(h<<2)+c>>2];c:{if(e>>>0>=K[j+16>>2]){H[g+16>>2]=e;Ba(d,1,2443,g+16|0);break c}i=f+(e<<2)|0;if(!H[i>>2]){break b}H[g>>2]=e;Ba(d,1,3487,g)}Ca(f);e=0;break a}H[i>>2]=1;h=h+1|0;if((h|0)!=(b|0)){continue}break}}Ca(f);Ca(H[a+64>>2]);d:{if(b){d=b<<2;e=Fa(d);H[a+64>>2]=e;if(!e){H[a+60>>2]=0;e=0;break a}if(!d){break d}B(e,c,d);break d}H[a+64>>2]=0}H[a+60>>2]=b;e=1}na=g+32|0;return e|0}function xc(a){a=a|0;var b=0,c=0;if(a){vb(H[a>>2]);H[a>>2]=0;b=H[a+72>>2];if(b){Ca(b);H[a+72>>2]=0}b=H[a+68>>2];if(b){Ca(b);H[a+68>>2]=0}b=H[a+108>>2];if(b){Ca(b);H[a+108>>2]=0}b=H[a+116>>2];if(b){c=H[b>>2];if(c){Ca(c);b=H[a+116>>2];H[b>>2]=0}Ca(b);H[a+116>>2]=0}b=H[a+120>>2];if(b){c=H[b+12>>2];if(c){Ca(c);b=H[a+120>>2];H[b+12>>2]=0}c=H[b+4>>2];if(c){Ca(c);b=H[a+120>>2];H[b+4>>2]=0}c=H[b+8>>2];if(c){Ca(c);b=H[a+120>>2];H[b+8>>2]=0}c=H[b>>2];if(c){Ca(c);b=H[a+120>>2];H[b>>2]=0}Ca(b);H[a+120>>2]=0}b=H[a+4>>2];if(b){ob(b);H[a+4>>2]=0}b=H[a+8>>2];if(b){ob(b);H[a+8>>2]=0}Ca(a)}}function sb(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0;g=na-272|0;na=g;H[g+268>>2]=c;H[g+264>>2]=b;H[g>>2]=a;k=!e;a:{b:{c:{d:{if(!(!c&(b|0)==1)){h=1;e=a;while(1){i=(d<<2)+f|0;a=e-H[i>>2]|0;if((Bc(a,H[g>>2])|0)>0){j=k^-1;k=1;if(!(((d|0)<2|j)&1)){i=H[i-8>>2];j=e-8|0;if((Bc(j,a)|0)>=0){break d}if((Bc(j-i|0,a)|0)>=0){break d}}H[((h&63)<<2)+g>>2]=a;b=qc(b,c);tb(g+264|0,b);h=h+1|0;d=b+d|0;e=a;c=H[g+268>>2];b=H[g+264>>2];if(c|(b|0)!=1){continue}break c}break}if(!k){break a}a=e;b=h&63;break b}if(e){break a}b=1;break b}a=e}b=h&63}pc(g,b);Ab(a,d,f)}na=g+272|0}function Yd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;e=na-16|0;na=e;if(H[a+8>>2]==16){g=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{g=H[a+12>>2]}h=H[a+96>>2];f=K[h+16>>2]<257?1:2;a:{if(f>>>0>=c>>>0){c=0;Ba(d,1,4669,0);break a}H[e+12>>2]=(f^-1)+c;Da(b,e+8|0,f);i=H[e+8>>2];if(i>>>0>=K[h+16>>2]){c=0;Ba(d,1,14067,0);break a}c=1;b=b+f|0;Da(b,H[g+5584>>2]+N(i,1080)|0,1);if(!Dc(a,H[e+8>>2],b+1|0,e+12|0,d)){c=0;Ba(d,1,4669,0);break a}if(!H[e+12>>2]){break a}c=0;Ba(d,1,4669,0)}na=e+16|0;return c|0}function zc(a,b){var c=0,d=0,e=0,f=0,g=0;f=na-32|0;na=f;c=H[a+60>>2];a:{b:{if(c){g=1;while(1){e=H[H[a+64>>2]+(d<<2)>>2];if(!H[(H[H[a+100>>2]+24>>2]+N(e,52)|0)+44>>2]){H[f+16>>2]=e;Ba(b,2,7604,f+16|0);g=0;c=H[a+60>>2]}d=d+1|0;if(c>>>0>d>>>0){continue}break}break b}g=1;c=H[a+100>>2];e=1;if(!H[c+16>>2]){break a}while(1){if(!H[(H[c+24>>2]+N(d,52)|0)+44>>2]){H[f>>2]=d;Ba(b,2,7604,f);g=0;c=H[a+100>>2]}d=d+1|0;if(d>>>0>2]){continue}break}}e=1;if(g){break a}Ba(b,1,2897,0);e=0}na=f+32|0;return e}function od(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;f=na-16|0;na=f;e=H[a+120>>2];a:{if(!e){Ba(d,1,8836,0);c=0;break a}if(H[e+12>>2]){Ba(d,1,11598,0);c=0;break a}e=I[e+18|0];g=e<<2;if(g>>>0>c>>>0){Ba(d,1,8803,0);c=0;break a}g=Fa(g);c=0;if(!g){break a}if(e){d=0;while(1){c=f+12|0;Da(b,c,2);h=g+(d<<2)|0;G[h>>1]=H[f+12>>2];Da(b+2|0,c,1);F[h+2|0]=H[f+12>>2];Da(b+3|0,c,1);F[h+3|0]=H[f+12>>2];b=b+4|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}}H[H[a+120>>2]+12>>2]=g;c=1}na=f+16|0;return c|0}function Jd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0;e=na-16|0;na=e;g=H[H[a+96>>2]+16>>2];a:{if((g+2|0)!=(c|0)){Ba(d,1,4617,0);break a}Da(b,e+12|0,2);if(H[e+12>>2]!=(g|0)){Ba(d,1,4617,0);break a}if(!g){f=1;break a}c=b+2|0;a=H[H[a+96>>2]+24>>2];b=0;while(1){Da(c,e+8|0,1);f=H[e+8>>2];h=f&127;i=h+1|0;H[a+24>>2]=i;H[a+32>>2]=f>>>7&1;if(h>>>0>=31){H[e+4>>2]=i;H[e>>2]=b;Ba(d,1,15402,e);f=0;break a}a=a+52|0;f=1;c=c+1|0;b=b+1|0;if((g|0)!=(b|0)){continue}break}}na=e+16|0;return f|0}function oc(a,b,c,d,e){var f=0,g=0;f=na-208|0;na=f;H[f+204>>2]=c;c=f+160|0;y(c,0,40);H[f+200>>2]=H[f+204>>2];if((nc(0,b,f+200|0,f+80|0,c,d,e)|0)>=0){c=H[a>>2];H[a>>2]=c&-33;a:{b:{c:{if(!H[a+48>>2]){H[a+48>>2]=80;H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0;g=H[a+44>>2];H[a+44>>2]=f;break c}if(H[a+16>>2]){break b}}if(uc(a)){break a}}nc(a,b,f+200|0,f+80|0,f+160|0,d,e)}if(g){ra[H[a+36>>2]](a,0,0)|0;H[a+48>>2]=0;H[a+44>>2]=g;H[a+28>>2]=0;H[a+16>>2]=0;H[a+20>>2]=0}H[a>>2]=H[a>>2]|c&32}na=f+208|0}function Vd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0;e=na-16|0;na=e;a:{b:{h=e+8|0;c:{if(K[H[a+96>>2]+16>>2]<=256){if(c){f=-1;g=1;break c}Ba(d,1,4695,0);a=0;break a}if(c>>>0<=1){break b}f=-2;g=2}Da(b,h,g);H[e+12>>2]=c+f;c=H[e+8>>2];f=H[H[a+96>>2]+16>>2];if(c>>>0>=f>>>0){H[e+4>>2]=f;H[e>>2]=c;Ba(d,1,7712,e);a=0;break a}if(!Cc(a,c,b+g|0,e+12|0,d)){Ba(d,1,4695,0);a=0;break a}a=1;if(!H[e+12>>2]){break a}Ba(d,1,4695,0);a=0;break a}Ba(d,1,4695,0);a=0}na=e+16|0;return a|0}function qd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=H[a+32>>2];H[a+36>>2]=f;a:{e=H[a+48>>2];if(e){while(1){e=ra[H[a+20>>2]](f,e,H[a>>2])|0;if((e|0)==-1){break a}f=e+H[a+36>>2]|0;H[a+36>>2]=f;e=H[a+48>>2]-e|0;H[a+48>>2]=e;if(e){continue}break}f=H[a+32>>2]}H[a+48>>2]=0;H[a+36>>2]=f;if(!(ra[H[a+28>>2]](b,c,H[a>>2])|0)){H[a+68>>2]=H[a+68>>2]|8;return 0}H[a+56>>2]=b;H[a+60>>2]=c;return 1}H[a+68>>2]=H[a+68>>2]|8;Ba(d,4,15604,0);H[a+68>>2]=H[a+68>>2]|8;return 0}function Ba(a,b,c,d){var e=0,f=0;e=na-528|0;na=e;a:{if(!a){break a}b:{c:{switch(b-1|0){case 0:b=a+12|0;break b;case 1:b=a+16|0;a=a+4|0;break b;case 3:break c;default:break a}}b=a+20|0;a=a+8|0}b=H[b>>2];if(!b|!c){break a}f=H[a>>2];y(e,0,512);H[e+524>>2]=d;a=na-160|0;na=a;H[a+148>>2]=e;H[a+152>>2]=511;y(a,0,144);H[a+76>>2]=-1;H[a+36>>2]=97;H[a+80>>2]=-1;H[a+44>>2]=a+159;H[a+84>>2]=a+148;F[e|0]=0;oc(a,c,d,95,96);na=a+160|0;F[e+511|0]=0;ra[b|0](e,f)}na=e+528|0}function cc(a){var b=0,c=0,d=0;a:{if(!a){break a}b=H[a+8>>2];if(!b){break a}c=b&3;a=H[a+12>>2];if(b>>>0>=4){d=b&-4;b=0;while(1){H[a+60>>2]=0;H[a+52>>2]=999;H[a+56>>2]=0;H[a+44>>2]=0;H[a+36>>2]=999;H[a+40>>2]=0;H[a+28>>2]=0;H[a+20>>2]=999;H[a+24>>2]=0;H[a+12>>2]=0;H[a+4>>2]=999;H[a+8>>2]=0;a=a- -64|0;b=b+4|0;if((d|0)!=(b|0)){continue}break}if(!c){break a}}b=0;while(1){H[a+12>>2]=0;H[a+4>>2]=999;H[a+8>>2]=0;a=a+16|0;b=b+1|0;if((c|0)!=(b|0)){continue}break}}}function vd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;if(H[a+100>>2]!=1){Ba(d,1,11401,0);return 0}a:{if(c>>>0<=7){break a}Da(b,a+56|0,4);Da(b+4|0,a+60|0,4);if(c&3){break a}c=c-8|0;e=c>>>2|0;H[a+64>>2]=e;b:{if(!c){break b}c=Ea(e,4);H[a+68>>2]=c;if(!c){Ba(d,1,2235,0);return 0}if(!H[a+64>>2]){break b}d=b+8|0;c=0;while(1){Da(d,H[a+68>>2]+(c<<2)|0,4);d=d+4|0;c=c+1|0;if(c>>>0>2]){continue}break}}H[a+100>>2]=H[a+100>>2]|2;return 1}Ba(d,1,5955,0);return 0}function Wd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;H[e+12>>2]=c;a:{if(!(!Cc(a,0,b,e+12|0,d)|H[e+12>>2])){if(H[a+8>>2]==16){b=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{b=H[a+12>>2]}f=1;if(K[H[a+96>>2]+16>>2]<2){break a}c=H[b+5584>>2];g=c+28|0;d=c;b=1;while(1){H[d+1104>>2]=H[c+24>>2];H[d+1884>>2]=H[c+804>>2];B(d+1108|0,g,776);d=d+1080|0;b=b+1|0;if(b>>>0>2]+16>>2]){continue}break}break a}Ba(d,1,4591,0)}na=e+16|0;return f|0}function ac(a,b,c,d){var e=0,f=0,g=0;f=na-128|0;na=f;e=f;b=H[b+12>>2]+(c<<4)|0;c=H[b>>2];if(c){while(1){H[e>>2]=b;e=e+4|0;b=c;c=H[c>>2];if(c){continue}break}}while(1){c=H[b+8>>2];if((g|0)>(c|0)){H[b+8>>2]=g;c=g}a:{if((c|0)>=(d|0)){break a}while(1){if(H[b+4>>2]<=(c|0)){break a}b:{if(Qa(a,1)){H[b+4>>2]=c;break b}c=c+1|0}if((c|0)<(d|0)){continue}break}}H[b+8>>2]=c;if((e|0)!=(f|0)){e=e-4|0;b=H[e>>2];g=c;continue}break}na=f+128|0;return H[b+4>>2]<(d|0)}function Dd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;a:{if(!Va(H[a+8>>2],46,c)){break a}h=H[a+8>>2];d=H[h>>2];f=H[h+8>>2];b:{if(d){e=1;c:{if((d|0)!=1){j=d&1;d=d&-2;while(1){g=0;d:{if(!e){break d}g=0;if(!(ra[H[f>>2]](a,b,c)|0)){break d}g=(ra[H[f+4>>2]](a,b,c)|0)!=0}e=g;f=f+8|0;i=i+2|0;if((d|0)!=(i|0)){continue}break}if(!j){break c}}if(!e){e=0;break c}e=(ra[H[f>>2]](a,b,c)|0)!=0}Oa(h);if(!e){break a}break b}Oa(h)}k=1}return k|0}function Xd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;g=H[H[a+96>>2]+16>>2];f=g>>>0<257?1:2;a:{if((f+2|0)!=(c|0)){a=0;Ba(d,1,4285,0);break a}if(H[a+8>>2]==16){c=H[a+180>>2]+N(H[a+228>>2],5644)|0}else{c=H[a+12>>2]}Da(b,e+12|0,f);a=1;b=b+f|0;Da(b,e+8|0,1);f=H[e+12>>2];if(f>>>0>=g>>>0){H[e+4>>2]=g;H[e>>2]=f;Ba(d,1,14923,e);a=0;break a}Da(b+1|0,(H[c+5584>>2]+N(f,1080)|0)+808|0,1)}na=e+16|0;return a|0}function ae(){var a=0,b=0,c=0;while(1){b=a<<4;c=b+26432|0;H[b+26436>>2]=c;H[b+26440>>2]=c;a=a+1|0;if((a|0)!=64){continue}break}sc(48);a=na-16|0;na=a;a:{if(la(a+12|0,a+8|0)|0){break a}b=ub((H[a+12>>2]<<2)+4|0);H[6870]=b;if(!b){break a}b=ub(H[a+8>>2]);if(b){c=H[6870];H[c+(H[a+12>>2]<<2)>>2]=0;if(!(ka(c|0,b|0)|0)){break a}}H[6870]=0}na=a+16|0;H[6885]=8192;H[6883]=94272;H[6877]=42;H[6884]=65536}function db(a,b,c){var d=0;if(!H[a+12>>2]){ra[b|0](c,H[a+36>>2]);return}d=Fa(8);a:{if(!d){break a}H[d+4>>2]=c;H[d>>2]=b;b=Fa(8);if(!b){Ca(d);return}H[b>>2]=d;c=N(H[a+4>>2],100);H[a+40>>2]=c;while(1){if((c|0)>2]){continue}break}H[b+4>>2]=H[a+20>>2];H[a+20>>2]=b;H[a+24>>2]=H[a+24>>2]+1;b=H[a+28>>2];if(!b){break a}H[H[b>>2]+8>>2]=0;H[a+28>>2]=H[b+4>>2];H[a+32>>2]=H[a+32>>2]-1;Ca(b)}}function La(a,b,c){var d=0,e=0,f=0;if(!(I[a|0]&32)){a:{d=H[a+16>>2];if(!d){if(uc(a)){break a}d=H[a+16>>2]}e=H[a+20>>2];if(d-e>>>0>>0){ra[H[a+36>>2]](a,b,c)|0;break a}b:{c:{if(!c|H[a+80>>2]<0){break c}d=c;while(1){f=b+d|0;if(I[f-1|0]!=10){d=d-1|0;if(d){continue}break c}break}if(ra[H[a+36>>2]](a,b,d)>>>0>>0){break a}c=c-d|0;e=H[a+20>>2];break b}f=b}$a(e,f,c);H[a+20>>2]=H[a+20>>2]+c}}}function Ec(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;H[a+184>>2]=b;d=H[a+96>>2];a:{if(!d){break a}f=H[d+24>>2];if(!f){break a}e=H[a+12>>2];if(!e|!H[e+5584>>2]){break a}e=H[d+16>>2];if(!e){return 1}d=0;while(1){if(K[(H[H[a+12>>2]+5584>>2]+N(d,1080)|0)+4>>2]<=b>>>0){Ba(c,1,9177,0);return 0}H[(N(d,52)+f|0)+40>>2]=b;g=1;d=d+1|0;if((e|0)!=(d|0)){continue}break}}return g|0}function xb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;if(a){c=H[a+4>>2];if(c){Ca(c);H[a+4>>2]=0}if(b){c=a;while(1){d=H[c+200>>2];if(d){e=0;f=H[c+196>>2];if(f){while(1){g=H[d+12>>2];if(g){Ca(g);H[d+12>>2]=0;f=H[c+196>>2]}d=d+16|0;e=e+1|0;if(e>>>0>>0){continue}break}d=H[c+200>>2]}Ca(d);H[c+200>>2]=0}c=c+240|0;h=h+1|0;if((h|0)!=(b|0)){continue}break}}Ca(a)}}function Yc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=H[a+84>>2];f=H[e>>2];d=H[e+4>>2];h=H[a+28>>2];g=H[a+20>>2]-h|0;g=d>>>0>>0?d:g;if(g){$a(f,h,g);f=g+H[e>>2]|0;H[e>>2]=f;d=H[e+4>>2]-g|0;H[e+4>>2]=d}d=c>>>0>d>>>0?d:c;if(d){$a(f,b,d);f=d+H[e>>2]|0;H[e>>2]=f;H[e+4>>2]=H[e+4>>2]-d}F[f|0]=0;b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;return c|0}function ld(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=H[c+8>>2];d=e>>>0<=1?1:e;f=H[c+4>>2];g=f-H[c>>2]|0;while(1){h=d;d=d<<1;if(h-g>>>0>>0){continue}break}if((e|0)!=(h|0)){d=Fa(h);if(!d){return-1}e=H[c>>2];if(e){if(g){B(d,e,g)}Ca(H[c>>2])}H[c+8>>2]=h;H[c>>2]=d;f=d+g|0;H[c+4>>2]=f}if(b){B(f,a,b)}H[c+4>>2]=H[c+4>>2]+b;return b|0}function Vb(a){H[a+100>>2]=20832;H[a+96>>2]=20832;H[a+92>>2]=20832;H[a+88>>2]=20832;H[a+84>>2]=20832;H[a+80>>2]=20832;H[a+76>>2]=20832;H[a+72>>2]=20832;H[a+68>>2]=20832;H[a+64>>2]=20832;H[a+60>>2]=20832;H[a+56>>2]=20832;H[a+52>>2]=20832;H[a+48>>2]=20832;H[a+44>>2]=20832;H[a+40>>2]=20832;H[a+36>>2]=20832;H[a+32>>2]=20832;H[a+28>>2]=20832}function Qa(a,b){var c=0,d=0,e=0,f=0;if((b|0)<=0){return 0}c=H[a+12>>2];d=H[a+16>>2];while(1){e=b;a:{if(d){break a}c=c<<8&65280;H[a+12>>2]=c;d=(c|0)==65280?7:8;H[a+16>>2]=d;b=H[a+8>>2];if(b>>>0>=K[a+4>>2]){break a}H[a+8>>2]=b+1;c=I[b|0]|c;H[a+12>>2]=c}d=d-1|0;H[a+16>>2]=d;b=e-1|0;f=(c>>>d&1)<>>0>1){continue}break}return f}function rd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=na-16|0;na=f;e=H[a+24>>2];if((e|0)!=255){H[f>>2]=e;Ba(d,2,2678,f)}a:{b:{if(H[a+20>>2]==(c|0)){if(c){break b}e=1;break a}e=0;Ba(d,1,14510,0);break a}c=0;while(1){e=1;Da(b,(H[a+72>>2]+N(c,12)|0)+8|0,1);b=b+1|0;c=c+1|0;if(c>>>0>2]){continue}break}}na=f+16|0;return e|0}function Da(a,b,c){var d=0,e=0;H[b>>2]=0;a:{if(!c){break a}d=c&3;b=b+c|0;if(c>>>0>=4){e=c&-4;c=0;while(1){F[b-1|0]=I[a|0];F[b-2|0]=I[a+1|0];F[b-3|0]=I[a+2|0];b=b-4|0;F[b|0]=I[a+3|0];a=a+4|0;c=c+4|0;if((e|0)!=(c|0)){continue}break}if(!d){break a}}c=0;while(1){b=b-1|0;F[b|0]=I[a|0];a=a+1|0;c=c+1|0;if((d|0)!=(c|0)){continue}break}}}function Pd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;e=na-16|0;na=e;a:{if(!c){Ba(d,1,4106,0);a=0;break a}Da(b,e+12|0,1);f=c-1|0;a=1;if(!f){break a}a=0;c=0;while(1){b=b+1|0;Da(b,e+8|0,1);g=H[e+8>>2];c=g<<24>>31&(g&127|c)<<7;a=a+1|0;if((f|0)!=(a|0)){continue}break}a=1;if(!c){break a}Ba(d,1,4106,0);a=0}na=e+16|0;return a|0}function Ab(a,b,c){var d=0,e=0,f=0,g=0,h=0;d=na-256|0;na=d;H[d>>2]=a;f=1;if((b|0)>=2){while(1){a:{g=H[d>>2];a=a-8|0;h=b-2|0;e=a-H[(h<<2)+c>>2]|0;if((Bc(g,e)|0)>=0){if((Bc(g,a)|0)>=0){break a}}g=e;e=(Bc(e,a)|0)>=0;a=e?g:a;H[((f&63)<<2)+d>>2]=a;f=f+1|0;b=e?b-1|0:h;if((b|0)>1){continue}}break}a=f&63}else{a=1}pc(d,a);na=d+256|0}function _b(a,b,c,d){var e=0,f=0,g=O(0),h=0,i=O(0),j=0,k=O(0);if(d){while(1){e=f<<2;h=e+b|0;i=L[h>>2];j=a+e|0;g=L[j>>2];e=c+e|0;k=L[e>>2];L[j>>2]=O(k*O(1.4019999504089355))+g;L[h>>2]=O(g+O(i*O(-.3441300094127655)))+O(k*O(-.714139997959137));L[e>>2]=g+O(i*O(1.7719999551773071));f=f+1|0;if((f|0)!=(d|0)){continue}break}}}function tc(a){var b=0;b=1;a:{if((a|0)>=1024){b=898846567431158e293;if(a>>>0<2047){a=a-1023|0;break a}b=Infinity;a=(a>>>0>=3069?3069:a)-2046|0;break a}if((a|0)>-1023){break a}b=2004168360008973e-307;if(a>>>0>4294965304){a=a+969|0;break a}b=0;a=(a>>>0<=4294964336?-2960:a)+1938|0}u(0,0);u(1,a+1023<<20);return b*+w()}function pc(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;c=8;f=na-256|0;na=f;if(b>>>0>=2){h=(b<<2)+a|0;H[h>>2]=f;while(1){e=c>>>0>=256?256:c;$a(H[h>>2],H[a>>2],e);d=0;while(1){g=(d<<2)+a|0;d=d+1|0;$a(H[g>>2],H[(d<<2)+a>>2],e);H[g>>2]=H[g>>2]+e;if((b|0)!=(d|0)){continue}break}c=c-e|0;if(c){continue}break}}na=f+256|0}function _a(a,b,c){var d=0,e=0,f=0,g=0;if((b|0)==1|b>>>0>1){while(1){d=a;e=b;c=c-1|0;a=fe(a,b,10,0);b=qa;f=c,g=be(a,b,246)+d|48,F[f|0]=g;if(e>>>0>9){continue}break}}if(!b&a>>>0>=10|b){while(1){c=c-1|0;b=a;a=(b>>>0)/10|0;F[c|0]=b+N(a,246)|48;if(b>>>0>99){continue}break}}if(a){c=c-1|0;F[c|0]=a|48}return c}function Pc(a){a=a|0;var b=0,c=0,d=0,e=0;b=H[a+24>>2];if(b){c=H[a+28>>2];e=(c>>>0)/52|0;if(c>>>0>=52){while(1){c=H[b>>2];if(c){Ca(c-1|0);H[b>>2]=0}c=H[b+4>>2];if(c){Ca(c);H[b+4>>2]=0}c=H[b+8>>2];if(c){Ca(c);H[b+8>>2]=0}b=b+52|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}b=H[a+24>>2]}Ca(b);H[a+24>>2]=0}}function Qc(a){a=a|0;var b=0,c=0,d=0,e=0;b=H[a+24>>2];if(b){c=H[a+28>>2];e=(c>>>0)/68|0;if(c>>>0>=68){while(1){c=H[b>>2];if(c){Ca(c);H[b>>2]=0}c=H[b+4>>2];if(c){Ca(c);H[b+4>>2]=0}Ca(H[b+60>>2]);H[b+60>>2]=0;b=b+68|0;d=d+1|0;if((e|0)!=(d|0)){continue}break}b=H[a+24>>2]}Ca(b);H[a+24>>2]=0}}function Vc(a,b){a=a|0;b=b|0;var c=0,d=0;c=H[a+32>>2];b=H[a+28>>2];d=b+8|0;if(c>>>0>=d>>>0){while(1){mb(a,H[a+24>>2]+(b<<2)|0,H[a+20>>2],8);c=H[a+32>>2];b=d;d=b+8|0;if(c>>>0>=d>>>0){continue}break}}if(b>>>0>>0){mb(a,H[a+24>>2]+(b<<2)|0,H[a+20>>2],c-b|0)}Ca(H[a>>2]);Ca(a)}function wd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=na-16|0;na=e;a:{if(H[a+100>>2]){Ba(d,1,11302,0);a=0;break a}if((c|0)!=4){Ba(d,1,5991,0);a=0;break a}Da(b,e+12|0,4);if(H[e+12>>2]!=218793738){Ba(d,1,5007,0);a=0;break a}H[a+100>>2]=H[a+100>>2]|1;a=1}na=e+16|0;return a|0}function Va(a,b,c){var d=0,e=0;a:{d=H[a>>2];e=H[a+4>>2];b:{if((d|0)!=(e|0)){e=H[a+8>>2];break b}d=e+10|0;H[a+4>>2]=d;e=Ga(H[a+8>>2],d<<2);if(!e){break a}H[a+8>>2]=e;d=H[a>>2]}H[(d<<2)+e>>2]=b;H[a>>2]=d+1;return 1}Ca(H[a+8>>2]);H[a>>2]=0;H[a+4>>2]=0;Ba(c,1,6123,0);return 0}function mc(a){var b=0,c=0,d=0,e=0,f=0;d=H[a>>2];b=F[d|0]-48|0;if(b>>>0>9){return 0}while(1){e=-1;if(c>>>0<=214748364){c=N(c,10);e=(c^2147483647)>>>0>>0?-1:c+b|0}b=d+1|0;H[a>>2]=b;f=F[d+1|0];c=e;d=b;b=f-48|0;if(b>>>0<10){continue}break}return c} +function kc(a,b){var c=0,d=0,e=0;x(+a);d=s(1)|0;e=s(0)|0;c=d>>>20&2047;if((c|0)!=2047){if(!c){if(a==0){c=0}else{a=kc(a*0x10000000000000000,b);c=H[b>>2]+-64|0}H[b>>2]=c;return a}H[b>>2]=c-1022;u(0,e|0);u(1,d&-2146435073|1071644672);a=+w()}return a}function Ta(a){var b=0,c=0,d=0;if(a){b=H[a+24>>2];if(b){c=H[a+16>>2];if(c){b=0;while(1){d=H[(H[a+24>>2]+N(b,52)|0)+44>>2];if(d){Ca(d);c=H[a+16>>2]}b=b+1|0;if(c>>>0>b>>>0){continue}break}b=H[a+24>>2]}Ca(b)}b=H[a+28>>2];if(b){Ca(b)}Ca(a)}}function Ka(a,b,c,d,e,f,g,h){var i=0,j=0;i=+O(e-a|0);e=~~(i*1.402)+c|0;H[f>>2]=(e|0)>=0?(b|0)>(e|0)?e:b:0;j=+O(d-a|0);a=c-~~(j*.344+i*.714)|0;H[g>>2]=(a|0)>=0?(a|0)<(b|0)?a:b:0;a=~~(j*1.772)+c|0;H[h>>2]=(a|0)>=0?(a|0)<(b|0)?a:b:0}function kd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=H[c+4>>2];e=H[c>>2]+H[c+8>>2]|0;if((d|0)==(e|0)){qa=-1;return-1}H[c+4>>2]=a+d;f=a;c=e-d|0;d=c;e=a>>>0>>0;a=c>>31;c=e&(a|0)>=(b|0)|(a|0)>(b|0);d=c?f:d;qa=c?b:a;return d|0}function ee(a,b,c,d){var e=0,f=0,g=0,h=0;f=b^d;g=f>>31;e=b>>31;a=a^e;h=a-e|0;e=(b^e)-((a>>>0>>0)+e|0)|0;a=d>>31;b=c^a;f=f>>31;a=fe(h,e,b-a|0,(a^d)-((a>>>0>b>>>0)+a|0)|0)^f;b=a-f|0;qa=(g^qa)-((a>>>0>>0)+g|0)|0;return b}function Ua(a){var b=0,c=0,d=0,e=0;if(a){b=H[a+20>>2];c=H[a+16>>2];if(N(b,c)){while(1){e=H[H[a+24>>2]+(d<<2)>>2];if(e){Ca(e);c=H[a+16>>2];b=H[a+20>>2]}d=d+1|0;if(d>>>0>>0){continue}break}}Ca(H[a+24>>2]);Ca(a)}}function $b(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0;if(d){while(1){e=f<<2;g=e+a|0;h=c+e|0;i=H[h>>2];j=b+e|0;k=H[j>>2];e=H[g>>2]-(i+k>>2)|0;H[g>>2]=e+i;H[j>>2]=e;H[h>>2]=e+k;f=f+1|0;if((f|0)!=(d|0)){continue}break}}}function ab(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;H[a+48>>2]=0;H[a+36>>2]=H[a+32>>2];e=ra[H[a+28>>2]](b,c,H[a>>2])|0;d=H[a+68>>2];if(!e){H[a+68>>2]=d|4;return 0}H[a+56>>2]=b;H[a+60>>2]=c;H[a+68>>2]=d&-5;return 1}function Na(a,b,c,d,e){var f=0;f=na-256|0;na=f;if(!(e&73728|(c|0)<=(d|0))){d=c-d|0;c=d>>>0<256;vc(f,b,c?d:256);if(!c){while(1){La(a,f,256);d=d-256|0;if(d>>>0>255){continue}break}}La(a,f,d)}na=f+256|0}function be(a,b,c){var d=0,e=0,f=0,g=0,h=0;e=c>>>16|0;d=a>>>16|0;h=N(e,d);f=c&65535;a=a&65535;g=N(f,a);d=(g>>>16|0)+N(d,f)|0;a=(d&65535)+N(a,e)|0;qa=h+N(b,c)+(d>>>16)+(a>>>16)|0;return g&65535|a<<16}function uc(a){var b=0;b=H[a+72>>2];H[a+72>>2]=b-1|b;b=H[a>>2];if(b&8){H[a>>2]=b|32;return-1}H[a+4>>2]=0;H[a+8>>2]=0;b=H[a+44>>2];H[a+28>>2]=b;H[a+20>>2]=b;H[a+16>>2]=b+H[a+48>>2];return 0}function ec(a){var b=0,c=0;a:{if(I[a+12|0]==255){H[a+12>>2]=65280;H[a+16>>2]=7;b=H[a+8>>2];c=0;if(b>>>0>=K[a+4>>2]){break a}H[a+8>>2]=b+1;H[a+12>>2]=I[b|0]|65280}H[a+16>>2]=0;c=1}return c}function md(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=H[c+4>>2];d=H[c>>2]+H[c+8>>2]|0;if((e|0)==(d|0)){return-1}d=d-e|0;b=b>>>0>d>>>0?d:b;if(b){B(a,e,b)}H[c+4>>2]=b+H[c+4>>2];return b|0}function Wc(a,b){a=a|0;b=b|0;b=H[a+28>>2];if(b>>>0>2]){while(1){Yb(a,H[a+24>>2]+(N(H[a+20>>2],b)<<2)|0);b=b+1|0;if(b>>>0>2]){continue}break}}Ca(H[a>>2]);Ca(a)}function rb(a,b){var c=0,d=0;a:{b:{if(b>>>0<=31){d=H[a>>2];c=H[a+4>>2];break b}c=H[a>>2];H[a+4>>2]=c;H[a>>2]=0;b=b-32|0;if(!b){break a}}H[a>>2]=d<>2]=c<>>32-b}}function Bc(a,b){a=a|0;b=b|0;var c=0,d=0;c=H[a>>2];d=H[b>>2];a=H[a+4>>2];b=H[b+4>>2];return(c>>>0>d>>>0&(a|0)>=(b|0)|(a|0)>(b|0))-(c>>>0>>0&(a|0)<=(b|0)|(a|0)<(b|0))|0}function tb(a,b){var c=0,d=0;c=H[a+4>>2];a:{b:{if(b>>>0<=31){d=c;c=H[a>>2];break b}H[a+4>>2]=0;H[a>>2]=c;b=b-32|0;if(!b){break a}}H[a+4>>2]=d>>>b;H[a>>2]=d<<32-b|c>>>b}}function dd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=na-16|0;na=e;a=rc(ea(H[a+60>>2],b|0,c|0,d&255,e+8|0)|0);na=e+16|0;qa=a?-1:H[e+12>>2];return(a?-1:H[e+8>>2])|0}function Hb(a,b){F[b+7|0]=I[a|0];F[b+6|0]=I[a+1|0];F[b+5|0]=I[a+2|0];F[b+4|0]=I[a+3|0];F[b+3|0]=I[a+4|0];F[b+2|0]=I[a+5|0];F[b+1|0]=I[a+6|0];F[b|0]=I[a+7|0]}function Pa(a){var b=0,c=0,d=0,e=0;b=H[a+12>>2];e=b;c=H[a+8>>2];if(!(b|c)){qa=0;return 0}d=H[a+56>>2];b=c-d|0;qa=e-(H[a+60>>2]+(c>>>0>>0)|0)|0;return b}function ic(a,b){var c=0;c=na-16|0;na=c;if(a){if(b&3){a=28}else{a=fb(b,a);H[c+12>>2]=a;a=a?0:48}a=a?0:H[c+12>>2]}else{a=0}na=c+16|0;return a}function Rc(a){a=a|0;var b=0;if(a){b=H[a+116>>2];if(b){Ca(b);H[a+116>>2]=0}b=H[a+120>>2];if(b){Ca(b);H[a+120>>2]=0}Ca(H[a+148>>2]);Ca(a)}} +function Md(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(H[H[a+96>>2]+16>>2]<<2!=(c|0)){Ba(d,1,4464,0);a=0}else{a=1}return a|0}function gc(){var a=0,b=0;a=Ea(1,44);a:{if(a){H[a+16>>2]=0;b=Ea(1,8);H[a+36>>2]=b;if(b){break a}Ca(a)}a=0}return a}function Mb(a,b){a=a|0;b=b|0;if(!(!a|!b)){H[a+188>>2]=H[b+4>>2];H[a+184>>2]=H[b>>2];H[a+248>>2]=H[b+8248>>2]&2}}function pb(){var a=0,b=0;a=Ea(1,12);if(a){H[a+4>>2]=10;b=Ea(10,4);H[a+8>>2]=b;if(b){return a}Ca(a)}return 0}function Ad(a,b,c,d,e,f,g,h,i,j,k){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;k=k|0;D()}function eb(a){var b=0;if(a){b=H[a+4>>2];if(b){ra[b|0](H[a>>2])}Ca(H[a+32>>2]);H[a+32>>2]=0;Ca(a)}}function Lb(a,b){a=a|0;b=b|0;a:{if(!a){break a}H[a+208>>2]=b;if(!b){break a}F[a+92|0]=I[a+92|0]|8}}function jd(a,b,c){a=a|0;b=b|0;c=c|0;b=H[c+8>>2];H[c+4>>2]=H[c>>2]+(a>>>0>b>>>0?b:a);return 1}function Qd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(c){a=1}else{Ba(d,1,4375,0);a=0}return a|0}function jc(a,b){if(!((b&-128)==57216|b>>>0<=127)){H[6605]=25;return-1}F[a|0]=b;return 1}function jb(a){H[a+16>>2]=0;H[a+20>>2]=0;H[a+8>>2]=0;H[a+12>>2]=0;H[a>>2]=0;H[a+4>>2]=0}function Ra(a){if(H[a+12>>2]){H[a+40>>2]=0;while(1){if(H[a+24>>2]>0){continue}break}}}function Nc(a,b,c){a=a|0;b=b|0;c=c|0;return!H[a+8>>2]&(H[a+216>>2]!=0&H[a+220>>2]!=0)}function Fc(a,b){F[b+3|0]=I[a|0];F[b+2|0]=I[a+1|0];F[b+1|0]=I[a+2|0];F[b|0]=I[a+3|0]}function gb(a){if(a){ra[H[(H[a+76>>2]?20:16)+a>>2]](H[a+48>>2]);H[a+48>>2]=0;Ca(a)}}function Fd(a,b){a=a|0;b=b|0;Mb(H[a>>2],b);F[a+124|0]=0;H[a+128>>2]=H[b+8248>>2]&1}function Ea(a,b){if(!a|!b){a=0}else{b=N(a,b);a=fb(8,b);if(a){vc(a,0,b)}}return a}function Bb(a,b){var c=0;c=na-16|0;na=c;H[c+12>>2]=b;oc(26104,a,b,0,0);na=c+16|0}function qc(a,b){if((a|0)!=1){return de(a-1|0)}if(!b){return 0}return de(b)|32}function he(a){var b=0;while(1){if(a){a=a-1&a;b=b+1|0;continue}break}return b} +function Za(a){var b=0;if(a){b=H[a+12>>2];if(b){Ca(b);H[a+12>>2]=0}Ca(a)}}function Kc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;D()}function yd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Ib(H[a>>2],b,c,d)|0}function zd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;D()}function Nb(a){return H[a+12>>2]==H[a+4>>2]|H[a+8>>2]==H[a>>2]}function xd(a,b,c){a=a|0;b=b|0;c=c|0;return Ec(H[a>>2],b,c)|0}function ob(a){var b=0;if(a){b=H[a+8>>2];if(b){Ca(b)}Ca(a)}}function $c(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;qa=0;return 0}function Ya(a,b,c,d,e,f,g,h){return Zb(a,b,c,d,e,f,g,h,0)}function Gc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;D()}function hc(a,b,c,d){return ra[H[a+44>>2]](a,b,c,d)|0}function Wa(a,b,c){H[((b<<2)+a|0)+28>>2]=(c<<5)+20832}function Eb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return 1}function qb(a,b,c){return ra[H[a+40>>2]](a,b,0,c)|0}function de(a){if(a){return 31-Q(a-1^a)|0}return 32}function Kd(a,b,c){a=a|0;b=b|0;c=c|0;qa=-1;return-1}function Ha(a,b,c,d,e,f,g,h){Zb(a,b,c,d,e,f,g,h,1)}function rc(a){if(!a){return 0}H[6605]=a;return-1}function ge(a,b,c){ce(a,0,b,c);qa=pa;return oa}function yc(a,b,c){a=a|0;b=b|0;c=c|0;return-1}function Ud(a,b,c){a=a|0;b=b|0;c=c|0;return 0}function Kb(a,b,c){a=a|0;b=b|0;c=c|0;return 1}function fe(a,b,c,d){a=ce(a,b,c,d);return a}function Fa(a){if(!a){return 0}return ub(a)}function Ed(a,b){a=a|0;b=b|0;Lb(H[a>>2],b)}function fc(a){return H[a+8>>2]-H[a>>2]|0}function ad(a){a=a|0;ia();ha(a+128|0);D()}function Jc(a,b,c){a=a|0;b=b|0;c=c|0;D()}function ub(a){a=a|0;return fb(8,a)|0}function zb(a){return H[a+28>>2]!=2}function fd(a,b){a=a|0;b=b|0;_(a|0)}function ed(a,b){a=a|0;b=b|0;Y(a|0)}function vc(a,b,c){if(c){y(a,b,c)}}function $a(a,b,c){if(c){B(a,b,c)}}function Oc(a,b){a=a|0;b=b|0;D()}function nb(a){return ic(a,32)}function Ia(a){return ic(a,16)}function bd(a){a=a|0;ja();D()}function Db(){return Ea(1,36)}function Pb(a,b){a=a|0;b=b|0}function cb(a){if(a){Ca(a)}}function Oa(a){H[a>>2]=0}function Ic(a){a=a|0;D()} +// EMSCRIPTEN_END_FUNCS +a=I;m(n);var ra=[null,Pb,Ud,Kd,yc,yc,ab,qd,id,hd,Wc,Vc,Uc,Tc,Sc,Rc,Qc,Pc,Kb,Nc,Mc,Lc,Hc,Bc,$d,_d,Zd,Yd,Xd,Wd,Vd,Td,Sd,Rd,Qd,Pd,Od,Nd,Md,Eb,Ld,Jd,Eb,Eb,Id,Hd,Cd,wd,vd,ud,td,sd,rd,pd,od,nd,md,ld,kd,jd,Ic,Ic,Jc,Kb,Fb,Lb,Mb,vb,Jb,Oc,Ib,Ec,Gc,Kc,hb,bb,Ic,Ic,Jc,Dd,Gd,Oc,yd,xd,Gc,Kc,Ed,Fd,xc,zd,Ad,Bd,Pb,fd,ed,_c,Zc,Yc,ad,bd,Ic,cd,dd,Ic,$c];function sa(){return E.byteLength>>16}function xa(ya){ya=ya|0;var ta=sa()|0;var ua=ta+ya|0;if(ta{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=instantiateSync(wasmBinaryFile,info);return receiveInstance(result[0])}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var HEAP32;function _copy_pixels_1(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);imageData.set(compG)}function _copy_pixels_3(compR_ptr,compG_ptr,compB_ptr,nb_pixels){compR_ptr>>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*3);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);const compA=HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var ENV={};var getExecutableName=()=>thisProgram;var getEnvStrings=()=>{if(!getEnvStrings.strings){var lang=(globalThis.navigator?.language??"C").replace("-","_")+".UTF-8";var env={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:lang,_:getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(`${x}=${env[x]}`)}getEnvStrings.strings=strings}return getEnvStrings.strings};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var HEAPU32;var _environ_get=(__environ,environ_buf)=>{var bufSize=0;var envp=0;for(var string of getEnvStrings()){var ptr=environ_buf+bufSize;HEAPU32[__environ+envp>>2]=ptr;bufSize+=stringToUTF8(string,ptr,Infinity)+1;envp+=4}return 0};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var _environ_sizes_get=(penviron_count,penviron_buf_size)=>{var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;for(var string of strings){bufSize+=lengthBytesUTF8(string)+1}HEAPU32[penviron_buf_size>>2]=bufSize;return 0};var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);return 70}var printCharBuffers=[null,[],[]];var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(heapOrArray.buffer?heapOrArray.subarray(idx,endPtr):new Uint8Array(heapOrArray.slice(idx,endPtr)))};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};function _gray_to_rgba(compG_ptr,nb_pixels){compG_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);for(let i=0;i>=2;compA_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compA=HEAP32.subarray(compA_ptr,compA_ptr+nb_pixels);for(let i=0;i>=2;compG_ptr>>=2;compB_ptr>>=2;const imageData=Module.imageData=new Uint8ClampedArray(nb_pixels*4);const compR=HEAP32.subarray(compR_ptr,compR_ptr+nb_pixels);const compG=HEAP32.subarray(compG_ptr,compG_ptr+nb_pixels);const compB=HEAP32.subarray(compB_ptr,compB_ptr+nb_pixels);for(let i=0;i{HEAP8.set(array,buffer)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["writeArrayToMemory"]=writeArrayToMemory;var _malloc,_free,_jp2_decode,__emscripten_timeout,dynCall_iji,dynCall_jji,dynCall_iiji,dynCall_jiji,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_malloc=Module["_malloc"]=wasmExports["t"];_free=Module["_free"]=wasmExports["u"];_jp2_decode=Module["_jp2_decode"]=wasmExports["v"];__emscripten_timeout=wasmExports["w"];dynCall_iji=wasmExports["dynCall_iji"];dynCall_jji=wasmExports["dynCall_jji"];dynCall_iiji=wasmExports["dynCall_iiji"];dynCall_jiji=wasmExports["dynCall_jiji"];memory=wasmMemory=wasmExports["r"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={n:__abort_js,m:__emscripten_runtime_keepalive_clear,j:__setitimer_js,f:_copy_pixels_1,e:_copy_pixels_3,d:_copy_pixels_4,k:_emscripten_resize_heap,o:_environ_get,p:_environ_sizes_get,i:_fd_seek,b:_fd_write,q:_gray_to_rgba,h:_graya_to_rgba,c:_jsPrintWarning,l:_proc_exit,g:_rgb_to_rgba,a:_storeErrorMessage};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=createWasm();await run(); +;return Module}export default OpenJPEG; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.mjs b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.mjs index e81d97ac1..3c02dd0cf 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.mjs +++ b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.mjs @@ -22673,7 +22673,7 @@ class PDFWorker { const worker = await import( /*webpackIgnore: true*/ /*@vite-ignore*/ - this.workerSrc); + "./pdf.worker.mjs"); return worker.WorkerMessageHandler; }; return util_shadow(this, "_setupFakeWorkerGlobal", loader()); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.worker.mjs b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.worker.mjs index cb6c79d09..f116c93f6 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.worker.mjs +++ b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/pdf.worker.mjs @@ -15219,10 +15219,12 @@ class WasmImage { async #getJsModule(fallbackCallback) { let instance = null; try { - const mod = await import( + const mod = await ( /*webpackIgnore: true*/ /*@vite-ignore*/ - `${WasmImage.#wasmUrl}${this._noWasmFilename}`); + this._noWasmFilename === "jbig2_nowasm_fallback.js" ? import("./jbig2_nowasm_fallback.js") + : this._noWasmFilename === "openjpeg_nowasm_fallback.js" ? import("./openjpeg_nowasm_fallback.js") + : Promise.reject(new Error("Unknown PDF image decoder"))); instance = mod.default(); } catch (ex) { warn(`#getJsModule: ${ex}`); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/pet.js b/safari/Glaze/Glaze Extension/Resources/lib/pet.js index ff1bf56bf..1ae55ad1d 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/pet.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/pet.js @@ -1,6 +1,5 @@ // Local pet game (W24). Sources are existing device records only; the worker reads and reconciles them in its queue. var GZ_PET = globalThis.GZ_PET || (() => { - const lib = (name, file) => globalThis[name] || require(file); const KEY = /^pet:https?:\/\/[a-z\d.-]+(?::\d+)?\/(?:\d+|_\d+_\d+)$/i; const WORK = /^(?:assignment|quiz|discussion_topic|sub_assignment):(?:\d{1,20}|_\d{1,20}_\d{1,20})$/; const ITEM = /^(?:(?:assignment|quiz|discussion_topic|sub_assignment):(?:\d{1,20}|_\d{1,20}_\d{1,20})|task:[\w-]{1,40})$/; @@ -38,7 +37,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { // Keep the first account zone and a forward-only day so tabs, reloads and clock rollback cannot refill care. Forward-only holds // to a day of skew (a zone, a few hours back). A day further ahead was a clock that was wrong, and counts as unused; // a nearer correction keeps the daily spend and never leaves a cooldown locked for hours. - const T = lib('GZ_TODO', './todo.js'), saved = cleanCare(raw), zone = T.zone(saved?.zone || tz) || new Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; + const T = (globalThis.GZ_TODO || require('./todo.js')), saved = cleanCare(raw), zone = T.zone(saved?.zone || tz) || new Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; const today = T.dayOf(now, zone), ahead = saved && saved.day > today + 1, day = ahead ? today : Math.max(today, saved?.day || 0); const used = saved ? ahead || today > saved.day ? 0 : saved.used : CARE_LIMIT; // A future timestamp must not become `now` on every check: that slides the cooldown forward forever. Preserve the @@ -49,7 +48,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const taskSource = (todo, k) => [k.startsWith('task:') ? todo.tasks.find((x) => `task:${x.id}` === k)?.done ?? null : null, todo.items[k]?.done ?? null, todo.items[k]?.on ?? null]; const sourceValid = (v) => Array.isArray(v) && v.length === 3 && (v[0] === null || Number.isFinite(v[0]) && v[0] >= 0 && v[0] < 1e14) && (v[1] === null || typeof v[1] === 'boolean') && (v[2] === null || typeof v[2] === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v[2]) && Number.isFinite(Date.parse(v[2])) && new Date(v[2]).toISOString().slice(0, 10) === v[2]); const restoredTodo = (state, raw) => { - const todo = lib('GZ_TODO', './todo.js').clean(raw); + const todo = (globalThis.GZ_TODO || require('./todo.js')).clean(raw); for (const [k, saved] of Object.entries(state.restored)) { const actual = taskSource(todo, k); actual.forEach((value, i) => { if (value !== saved.to[i]) saved.from[i] = saved.to[i] = value; }); @@ -64,7 +63,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { return todo; }; const timerSignal = (raw, now = Date.now()) => { - const timer = lib('GZ_WIDGETS', './widgets.js').cleanTimer(raw); + const timer = (globalThis.GZ_WIDGETS || require('./widgets.js')).cleanTimer(raw); return `${timer.phase}:${timer.end}:${timer.left}:${timer.phase === 'work' && timer.end > now ? 'focus' : 'break'}`; }; const backup = (raw = {}) => { @@ -84,7 +83,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const training = (brain) => brain.counts.reduce((n, v) => n + v, 0) + brain.observations * .2; const brain = current && training(a.brain) >= training(b.brain) ? a.brain : b.brain; if (tasks) { - const before = restoredTodo(a, tasks.before), after = lib('GZ_TODO', './todo.js').clean(tasks.after); + const before = restoredTodo(a, tasks.before), after = (globalThis.GZ_TODO || require('./todo.js')).clean(tasks.after); // Imported completion/plan fields are a baseline, not a new user untick or re-tick. for (const k of new Set(Object.keys(a.earned).map((id) => id.slice(5)))) { const from = taskSource(before, k), to = taskSource(after, k); @@ -121,8 +120,8 @@ var GZ_PET = globalThis.GZ_PET || (() => { positions: Object.fromEntries(Object.entries(obj(raw?.positions)).filter(([k, v]) => Object.hasOwn(DECOR, k) && position(v)).map(([k, v]) => [k, position(v)])), habitat: Object.hasOwn(HABITATS, raw?.habitat) ? raw.habitat : 'room', wander: raw?.wander === true, physics: raw?.physics !== false, - learning: raw?.learning !== false, brain: lib('GZ_PET_BRAIN', './petbrain.js').clean(raw?.brain), - needs: lib('GZ_PET_BRAIN', './petbrain.js').needs(raw?.needs, Number.isFinite(raw?.needs?.at) && raw.needs.at > 0 ? Math.min(now, raw.needs.at) : now), + learning: raw?.learning !== false, brain: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).clean(raw?.brain), + needs: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).needs(raw?.needs, Number.isFinite(raw?.needs?.at) && raw.needs.at > 0 ? Math.min(now, raw.needs.at) : now), }; }; const view = (raw) => { @@ -135,11 +134,11 @@ var GZ_PET = globalThis.GZ_PET || (() => { while (xp >= floor(level + 1)) level++; if (WEAR[s.wear].level > level) s.wear = 'plain'; s.decor = Object.fromEntries(Object.entries(s.decor).filter(([id]) => DECOR[id].level <= level)); - return { ...s, needs: lib('GZ_PET_BRAIN', './petbrain.js').needs(s.needs), xp, work, plans, streakXp: streak, level, floor: floor(level), next: floor(level + 1) }; + return { ...s, needs: (globalThis.GZ_PET_BRAIN || require('./petbrain.js')).needs(s.needs), xp, work, plans, streakXp: streak, level, floor: floor(level), next: floor(level + 1) }; }; // Absent historical data never removes earned progress; an explicit undo or a newer correction does. const reconcile = (raw, { todo, insights, planner, cache, streak, streakOn = true, todoO = { done: 'auto', sync: false }, now, tz }) => { - const T = lib('GZ_TODO', './todo.js'), I = lib('GZ_INSIGHTS', './insights.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')), I = (globalThis.GZ_INSIGHTS || require('./insights.js')); const s = clean(raw, now); if (!s) return null; const d = restoredTodo(s, todo), known = new Map(), rows = new Map(), undone = new Set(); @@ -192,7 +191,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { } } // W11's calculated record is authoritative, including restores. A stale start cannot verify later days. - const saved = lib('GZ_REMIND', './remind.js').clean({ streak }, now).streak; + const saved = (globalThis.GZ_REMIND || require('./remind.js')).clean({ streak }, now).streak; const today = T.dayOf(now, tz); const end = saved?.asOf === null ? null : Math.min(today, saved?.asOf); if (streakOn && saved && end !== null && saved.start < end) { @@ -209,7 +208,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { const got = await local.get([m.key, tk, ik, rk, 'todoCache', 'petCharacter', 'widgetTimer']); const cur = clean(got[m.key]); if (!cur) return { ok: false, error: 'history' }; - const T = lib('GZ_TODO', './todo.js'); + const T = (globalThis.GZ_TODO || require('./todo.js')); const now = Date.now(); const prefs = sync?.get ? await sync.get(['todo', 'enhance', 'card']) : {}; const sanitize = typeof GZ_SANITIZE !== 'undefined' ? GZ_SANITIZE : require('../defaults.js').GZ_SANITIZE; @@ -221,7 +220,7 @@ var GZ_PET = globalThis.GZ_PET || (() => { if (typeof got.petCharacter === 'string') s.sprite = sprites.ID.test(got.petCharacter) ? got.petCharacter : ''; if (s.sprite && !(await sprites.change({ op: 'get', id: s.sprite }, { local })).pet) s.sprite = ''; const v = view(s); - const B = lib('GZ_PET_BRAIN', './petbrain.js'); + const B = (globalThis.GZ_PET_BRAIN || require('./petbrain.js')); const allowance = care(s.care, m.tz, now); let interaction; if ((m.action !== undefined || m.context !== undefined) && (!B.ACTIONS.includes(m.action) || !B.context(m.context))) return { ok: false, error: 'bad' }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/scene.js b/safari/Glaze/Glaze Extension/Resources/lib/scene.js index 75685e949..70156f48c 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/scene.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/scene.js @@ -11,7 +11,7 @@ const { WebGLRenderer, Scene, OrthographicCamera, PerspectiveCamera, Mesh, Points, PlaneGeometry, BufferGeometry, Float32BufferAttribute, ShaderMaterial, RawShaderMaterial, Color, ColorManagement, CustomBlending, AddEquation, OneFactor, DstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, -} = await import(`./three.js${new URL(import.meta.url).search}`); +} = await import(chrome.runtime.getURL(`lib/three.js${new URL(import.meta.url).search}`)); // Colors pass through as written, the way CSS mixes them, so a scene matches the gradient under it. ColorManagement.enabled = false; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/sheet-xlsx.js b/safari/Glaze/Glaze Extension/Resources/lib/sheet-xlsx.js index 0badc172f..d46f08b9e 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/sheet-xlsx.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/sheet-xlsx.js @@ -20,12 +20,13 @@ // lib/zip.js are looked up when a function runs, so they may load later. module.exports for node. var GZ_SHEET_XLSX = globalThis.GZ_SHEET_XLSX || (() => { const fail = (code, message) => { throw Object.assign(new Error(message || code), { code }); }; - const dep = (name, file) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? require(file) : fail('deps', `${file} is not loaded`)); + // Firefox's linter refuses require() of a variable, so each caller passes a require of its own file. + const dep = (name, file, load) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? load() : fail('deps', `${file} is not loaded`)); let M, F, NF, D, MAX_ROWS, MAX_COLS, CAP, INDEXED; const deps = () => { if (CAP) return; - M = dep('GZ_SHEET_MODEL', './sheet-model.js'); F = dep('GZ_SHEET_FORMULA', './sheet-formula.js'); - NF = dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js'); D = dep('GZ_DOCTEXT', './doctext.js'); + M = dep('GZ_SHEET_MODEL', './sheet-model.js', () => require('./sheet-model.js')); F = dep('GZ_SHEET_FORMULA', './sheet-formula.js', () => require('./sheet-formula.js')); + NF = dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js', () => require('./sheet-numfmt.js')); D = dep('GZ_DOCTEXT', './doctext.js', () => require('./doctext.js')); ({ MAX_ROWS, MAX_COLS, CAP } = M); // Indexed colours 0-7 repeat the first eight of the palette; 64 and 65 are the system's text and window colours. INDEXED = [...NF.PALETTE.slice(0, 8), ...NF.PALETTE]; @@ -1411,7 +1412,7 @@ var GZ_SHEET_XLSX = globalThis.GZ_SHEET_XLSX || (() => { const date = opts.date ?? new Date(); const order = ['[Content_Types].xml', '_rels/.rels', ...[...files.keys()].filter((p) => p !== '_rels/.rels')]; const list = [{ name: '[Content_Types].xml', data: new TextEncoder().encode(ct), date }, ...order.slice(1).map((name) => ({ name, data: files.get(name), date }))]; - const Zp = dep('GZ_ZIP', './zip.js'); + const Zp = dep('GZ_ZIP', './zip.js', () => require('./zip.js')); const chunks = await Zp.zipAsync(list, { deflate: opts.deflate !== false }); const size = chunks.reduce((n, p) => n + p.length, 0); const bytes = new Uint8Array(size); diff --git a/safari/Glaze/Glaze Extension/Resources/options.html b/safari/Glaze/Glaze Extension/Resources/options.html index 408e6c6f1..f66ba5e29 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.html +++ b/safari/Glaze/Glaze Extension/Resources/options.html @@ -1888,6 +1888,7 @@

      + diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index d8cd19727..0e8d22289 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1098,11 +1098,11 @@ GZ_UI.load().then((s) => { const gcalSignIn = async () => { try { if (!await chrome.permissions.request({ permissions: ['identity'] })) return t("Glaze can't connect without Chrome's sign-in permission."); - const r = await chrome.identity.getAuthToken({ interactive: true }); + const r = await GZ_GCAL_AUTH.token({ interactive: true }); const scope = chrome.runtime.getManifest().oauth2.scopes[0]; if (Array.isArray(r?.grantedScopes) && !r.grantedScopes.includes(scope)) { // Chrome would hand the same token back: forgotten, so the next Connect asks Google again. - await chrome.identity.removeCachedAuthToken({ token: r.token }).catch(() => {}); + await GZ_GCAL_AUTH.drop(r.token).catch(() => {}); return t("Glaze needs the calendar access on Google's screen. Connect again and leave it ticked."); } return ''; diff --git a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js index dc487d8c9..f12508914 100644 --- a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js @@ -65,8 +65,9 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { // ---- pdf.js, imported the first time a PDF is read let lib = null, viewerLib = null; - const pdfjs = () => (lib ||= import(url('lib/pdfjs/pdf.mjs')).then((P) => { P.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); return P; })); - const viewerjs = () => (viewerLib ||= pdfjs().then(() => import(url('lib/pdfjs/pdf_viewer.mjs')))); + // Firefox's linter accepts a dynamic import only of a literal or straight through chrome.runtime.getURL. + const pdfjs = () => (lib ||= import(chrome.runtime.getURL('lib/pdfjs/pdf.mjs')).then((P) => { P.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); return P; })); + const viewerjs = () => (viewerLib ||= pdfjs().then(() => import(chrome.runtime.getURL('lib/pdfjs/pdf_viewer.mjs')))); // lib/pdf-doc.js over this pdf.js (papers.html loads it before this file), or null where it isn't loaded: then pdf.js // directly, the same checks. const GET = { wasmUrl: url('lib/pdfjs/'), isEvalSupported: false, enableXfa: false, verbosity: 0 }; diff --git a/safari/Glaze/Glaze Extension/Resources/reader.js b/safari/Glaze/Glaze Extension/Resources/reader.js index 2d6cc9b6b..475a4a661 100644 --- a/safari/Glaze/Glaze Extension/Resources/reader.js +++ b/safari/Glaze/Glaze Extension/Resources/reader.js @@ -59,7 +59,7 @@ const pdf = async (file, langs, say) => { if (file.size > R.pdf) fail('size'); - const pdfjs = await import(url('lib/pdfjs/pdf.mjs')); + const pdfjs = await import('./lib/pdfjs/pdf.mjs'); pdfjs.GlobalWorkerOptions.workerSrc = url('lib/pdfjs/pdf.worker.mjs'); // maxImageSize: a picture inside the PDF over the same pixel limit as a photo is left undecoded (its page then reads // as blank, which the review says), whatever the size the page is drawn at. From 3ac7c2edd840b8afa611467194f5f100fe84f834 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:33:59 -0400 Subject: [PATCH 063/907] Cover permission refusal provenance and retryable errors --- test/loader.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/loader.test.js b/test/loader.test.js index 97d94ddb2..2ad8c195b 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -117,6 +117,22 @@ test('success means the file ran to its end: an injection that fails, throws or assert.deepEqual(await send(() => Promise.resolve([{ frameId: 3 }]), 'safari-web-extension:'), { ok: true }); }); +test('only the browser host-permission refusal is final, not a script exception or a similar message', async () => { + const missing = 'Missing host permission for the tab or frames'; + const similar = 'Missing host permission data; try again'; + const message = { type: 'glaze:load', files: ['lib/drafts.js'], doc: 'A' }; + for (const sender of [tab(), { ...tab(), documentId: 'A' }]) { + const refused = worker(() => Promise.reject(new Error(missing))); + assert.deepEqual(await refused.send(message, sender), { ok: false, error: 'sender' }, 'the document probe or injection is refused'); + const retryable = worker(() => Promise.reject(new Error(similar))); + assert.deepEqual(await retryable.send(message, sender), { ok: false, error: similar }, 'a shared prefix does not prove a revoked grant'); + } + const script = worker(() => Promise.resolve([{ frameId: 3, error: new Error(missing) }])); + assert.deepEqual(await script.send(message, { ...tab(), documentId: 'A' }), { ok: false, error: missing }, 'a script exception is still retryable'); + const afterProbe = worker((o) => o.func ? Promise.resolve([{ result: true }]) : Promise.reject(new Error(missing))); + assert.deepEqual(await afterProbe.send(message, tab()), { ok: false, error: 'sender' }, 'a successful probe does not hide an injection refusal'); +}); + // What a feature file reads from content.js when it loads (features/*.js). const CTX = { placeAsked: () => 0, t: (k) => k, locale: () => 'en', capi() {}, canvasCourses() {}, canvasCourseId() {}, need() {}, settings: () => ({}), site: 'canvas', wordsIn: new Set() }; // What a feature file touches as it loads: the listeners it adds (features/keys.js, coursemenu.js, previews.js). From 6cedf5d0e85cd7e40538023618f44af8abde28f5 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:34:32 -0400 Subject: [PATCH 064/907] Keep script errors retryable when classifying host refusals --- lib/background.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/background.js b/lib/background.js index 418f89401..013d80988 100644 --- a/lib/background.js +++ b/lib/background.js @@ -2319,6 +2319,10 @@ const loadInto = (m, sender, files, reply) => { same.then((yes) => { if (!yes) throw new Error('the frame holds another document now'); return chrome.scripting.executeScript({ target, files: injected, injectImmediately: true }); + }).catch((e) => { + // Firefox's browser refusal is final; exceptions returned by the script below are still retryable. + if (String(e?.message || e) === 'Missing host permission for the tab or frames') throw new Error('sender'); + throw e; }).then((results) => { // Every loadable file ends on a value (its module, its catalogue); a file that threw leaves none. Safari's results // for injected files aren't measured here, so there content.js's own check (is the module there?) decides. @@ -2326,11 +2330,7 @@ const loadInto = (m, sender, files, reply) => { if (r?.error) throw new Error(r.error.message || String(r.error)); if (!SAFARI && r?.result == null) throw new Error('did not finish'); reply({ ok: true }); - }).catch((e) => { - // Firefox, once the site's grant is taken back: a refusal that won't change, as content.js's need() reads 'sender'. - const error = String(e?.message || e); - reply({ ok: false, error: /^Missing host permission/.test(error) ? 'sender' : error }); - }); + }).catch((e) => reply({ ok: false, error: String(e?.message || e) })); }; // AI (lib/ai/broker.js, docs/ai-harness.md): off by default, and nothing of it loads until a feature opens a 'glaze:ai' port. From 25b13bf7a3714ae9b7dde6b2ba5eaae27670c0af Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:34:46 -0400 Subject: [PATCH 065/907] Cover unobserved announcement dependency failures --- test/load-recovery.test.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/test/load-recovery.test.js b/test/load-recovery.test.js index 1a3e16b05..cccd6df1d 100644 --- a/test/load-recovery.test.js +++ b/test/load-recovery.test.js @@ -108,20 +108,34 @@ test('a retry gives up after a few minutes, and refusals and a removed extension assert.equal(r.asks.length, 1, 'the extension gone: not asked again'); }); -test("the pop-up's own loads, refused with nobody waiting yet, leave no unhandled rejection", async () => { +test('a sender refusal stops automatic retries but a later sync can load after permission returns', async () => { + let allowed = false; + const p = await boot(() => allowed ? { ok: true } : { ok: false, error: 'sender' }); + await p.wait(60000); + assert.equal(p.asks.length, 1, 'no automatic retry after a host refusal'); + allowed = true; + p.change(); + await p.wait(500); + assert.equal(p.asks.length, 2, 'a later sync may ask again after the failed entry expires'); + assert.ok(p.syncs.length >= 1, 'the restored module syncs'); +}); + +for (const [label, file, name] of [['pop-up', 'popup', 'GZ_POPUP'], ['announcement feature', 'announce', 'GZ_ANNOUNCE']]) { +test(`the ${label}'s own loads, refused with nobody waiting yet, leave no unhandled rejection`, async () => { const stray = []; const seen = (e) => stray.push(e); process.on('unhandledRejection', seen); try { - const c = vm.createContext({ console, document: { documentElement: new El('html') } }); + const c = vm.createContext({ console, document: { documentElement: new El('html') }, addEventListener() {} }); c.globalThis = c; c.GZ_CTX = { t: (k) => k, need: () => Promise.reject(new Error('Missing host permission for the tab or frames')) }; - vm.runInContext(read('features/popup.js'), c, { filename: 'features/popup.js' }); - await new Promise((r) => setTimeout(r, 10)); - await assert.rejects(c.GZ_POPUP.ready, /Missing host permission/, 'its waiters still see the failed load'); + vm.runInContext(read(`features/${file}.js`), c, { filename: `features/${file}.js` }); + await settle(); + await assert.rejects(c[name].ready, /Missing host permission/, 'its waiters still see the failed load'); } finally { process.off('unhandledRejection', seen); } assert.deepEqual(stray.map((e) => e?.message), []); }); +} test('a retry in flight is the one a sync gets: no file is loaded twice', async () => { let release; From 528629ffb01c0fb4467d6ad1f044622202251872 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:35:05 -0400 Subject: [PATCH 066/907] Cover empty and offset chart anchors --- test/sheet-chart-xml.test.js | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/sheet-chart-xml.test.js diff --git a/test/sheet-chart-xml.test.js b/test/sheet-chart-xml.test.js new file mode 100644 index 000000000..5212fc84a --- /dev/null +++ b/test/sheet-chart-xml.test.js @@ -0,0 +1,37 @@ +// Card summaries use the same DrawingML parts as the spreadsheet chart reader. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const X = require('../lib/sheet-xlsx.js'); +const CX = require('../lib/sheet-chart-xml.js'); +const FX = require('./fixtures/sheets/charts.js'); +const marker = (tag, col, row, colOff = 0, rowOff = 0) => `${col}${colOff * CX.EMU}${row}${rowOff * CX.EMU}`; +const frame = ''; +const anchor = (kind, pos) => `${pos}${frame}`; +const drawing = (body) => `${body}`; +const book = (body) => X.read(FX.workbook({ kinds: ['column'], over: { 'xl/drawings/drawing1.xml': drawing(body) } })); +const ext = (w, h) => ``; + +test('glance uses exclusive end boundaries and does not give empty or reversed anchors a cell', async () => { + const wb = await book([ + anchor('twoCellAnchor', marker('from', 1, 1) + marker('to', 5, 4)), + anchor('twoCellAnchor', marker('from', 5, 3) + marker('to', 8, 6, 1, 1)), + anchor('twoCellAnchor', marker('from', 1, 1) + marker('to', 1, 1)), + anchor('twoCellAnchor', marker('from', 2, 2, 12, 5) + marker('to', 2, 2, 1, 1)), + anchor('oneCellAnchor', marker('from', 1, 1) + ext(0, 40)), + anchor('absoluteAnchor', `` + ext(64, 0)), + ].join('')); + const list = CX.glance(wb, wb.sheets[0]); + assert.deepEqual(list.slice(0, 2).map((x) => x.anchor), [[1, 1, 3, 4], [3, 5, 6, 8]]); + for (const x of list.slice(2)) assert.ok(x.anchor[2] < x.anchor[0] || x.anchor[3] < x.anchor[1], `no area: ${JSON.stringify(x.anchor)}`); +}); + +test('glance locates one-cell and absolute extents using actual row and column sizes', async () => { + const wb = await book([ + anchor('oneCellAnchor', marker('from', 0, 0, 100, 30) + ext(100, 30)), + anchor('absoluteAnchor', `` + ext(100, 30)), + anchor('twoCellAnchor', marker('from', 0, 0, 200, 60) + marker('to', 2, 2)), + ].join('')); + const sh = wb.sheets[0]; + sh.colW.set(0, 200 / 7); sh.rowH.set(0, 45); // A is 200px, row 1 is 60px. + assert.deepEqual(CX.glance(wb, sh).map((x) => x.anchor), [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1]]); +}); From ba84e2e5b2487a47cb41c9ea41a20b0a465ecb9a Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:35:17 -0400 Subject: [PATCH 067/907] Handle eager announcement load rejections --- features/announce.js | 1 + 1 file changed, 1 insertion(+) diff --git a/features/announce.js b/features/announce.js index b282b0a83..311c3946b 100644 --- a/features/announce.js +++ b/features/announce.js @@ -33,6 +33,7 @@ var GZ_ANNOUNCE = globalThis.GZ_ANNOUNCE || (() => { const prefs = () => ctx.settings().announce || {}; const libs = ctx.need('lib/popup.js', () => globalThis.GZ_POPUP_LIB).then(() => ctx.need('lib/announce.js', () => globalThis.GZ_ANNOUNCE_LIB)) .then(() => { A = globalThis.GZ_ANNOUNCE_LIB; }); + libs.catch(() => {}); // The pop-up's own files, asked for when first wanted (GZ_ANNOUNCE.ready, or a hover, focus or click on a site's list). let all = null, loaded = false; const load = () => (all ||= Promise.all([libs, ctx.need('features/popup.js', () => globalThis.GZ_POPUP).then(() => GZ_POPUP.ready), ctx.need('lib/lmsq.js', () => globalThis.GZ_LMSQ)]) From 5d9e1597915eb4bba56ceff6cb04251a9e0dfc82 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:35:40 -0400 Subject: [PATCH 068/907] Export and submit a workbook imported from .xlsm as .xlsm with Excel's macro-enabled type --- i18n/de.js | 3 ++- i18n/es.js | 3 ++- i18n/fr.js | 3 ++- i18n/nl.js | 3 ++- i18n/pt-BR.js | 3 ++- lib/doc-kinds.js | 2 +- lib/writer-papers.js | 2 +- lib/writer-submit.js | 4 +++- papers.js | 2 +- scripts/sheets-check.js | 26 ++++++++++++++++++++++ sheet-grid.js | 2 +- sheet-kind.js | 23 +++++++++++-------- test/doc-kinds.test.js | 7 +++--- test/fixtures/sheet-xlsm.js | 19 ++++++++++++++++ test/sheet-kind.test.js | 44 +++++++++++++++++++++++++++++++++++++ test/writer-submit.test.js | 10 +++++++++ writer-kinds.js | 4 ++-- writer-submit.js | 4 +++- 18 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/sheet-xlsm.js diff --git a/i18n/de.js b/i18n/de.js index de78f17ff..dca4a403d 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "nach Angaben der App", "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", - "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast." + "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", + "Export .xlsm": ".xlsm exportieren" }; diff --git a/i18n/es.js b/i18n/es.js index eabe7a199..7568ab4f3 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "según la propia app", "Don't allow": "No permitir", "Unnamed app": "App sin nombre", - "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras." + "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", + "Export .xlsm": "Exportar .xlsm" }; diff --git a/i18n/fr.js b/i18n/fr.js index c2b2a093b..0ab81213c 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "selon l’app elle-même", "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", - "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse." + "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", + "Export .xlsm": "Exporter .xlsm" }; diff --git a/i18n/nl.js b/i18n/nl.js index 5bb584391..9c1082c80 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "zoals de app zichzelf noemt", "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", - "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde." + "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", + "Export .xlsm": ".xlsm exporteren" }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index f76333eaf..aaa4cfd6f 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3750,5 +3750,6 @@ "as the app reports itself": "segundo o próprio app", "Don't allow": "Não permitir", "Unnamed app": "App sem nome", - "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta." + "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", + "Export .xlsm": "Exportar .xlsm" }; diff --git a/lib/doc-kinds.js b/lib/doc-kinds.js index d71f6ba90..122072a12 100644 --- a/lib/doc-kinds.js +++ b/lib/doc-kinds.js @@ -18,7 +18,7 @@ var GZ_DOC_KINDS = globalThis.GZ_DOC_KINDS || (() => { // larger one; param: its name in a page's address. const KINDS = freeze({ paper: { label: 'Document', icon: 'paper', accept: ['.docx'], mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', max: 15e6, param: 'paper' }, - sheet: { label: 'Spreadsheet', icon: 'sheet', accept: ['.xlsx', '.csv', '.tsv'], mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', max: 15e6, param: 'sheet' }, + sheet: { label: 'Spreadsheet', icon: 'sheet', accept: ['.xlsx', '.xlsm', '.csv', '.tsv'], mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', max: 15e6, param: 'sheet' }, pdf: { label: 'PDF', icon: 'pdf', accept: ['.pdf'], mime: 'application/pdf', max: FILE, big: 'Choose a PDF smaller than 50 MB.', param: 'pdf' }, slides: { label: 'Slides', icon: 'slides', accept: ['.pptx', '.ppsx'], mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', max: FILE, big: 'Choose a PowerPoint file smaller than 50 MB.', param: 'slides' }, }); diff --git a/lib/writer-papers.js b/lib/writer-papers.js index b03bb7e9d..fdc075a9b 100644 --- a/lib/writer-papers.js +++ b/lib/writer-papers.js @@ -163,7 +163,7 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { const index = async (paper) => { const list = (await get(INDEX)) || []; const row = { key: paper.key, kind: paper.kind || 'paper', named: paper.named === true, title: paper.title || '', course: paper.courseName || '', url: paper.url || '', words: paper.words || 0, format: paper.format, updated: paper.updated, submitted: paper.submitted || null, - ...(trashed(paper) && { trashed: paper.trashed }), ...(glance(paper.grid) && { grid: paper.grid }) }; + ...(trashed(paper) && { trashed: paper.trashed }), ...(glance(paper.grid) && { grid: paper.grid }), ...(paper.kind === 'sheet' && paper.ext === '.xlsm' && { ext: '.xlsm' }) }; return [row, ...list.filter((r) => r.key !== paper.key)].slice(0, 2000); }; diff --git a/lib/writer-submit.js b/lib/writer-submit.js index da22eb53f..74dacc205 100644 --- a/lib/writer-submit.js +++ b/lib/writer-submit.js @@ -13,7 +13,9 @@ var GZ_WRITER_SUBMIT_LIB = globalThis.GZ_WRITER_SUBMIT_LIB || (() => { '.docx': { kind: 'paper', mime: MIME }, '.pdf': { kind: 'pdf', mime: 'application/pdf' }, '.pptx': { kind: 'slides', mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, '.ppsx': { kind: 'slides', mime: 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' }, - '.xlsx': { kind: 'sheet', mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, '.csv': { kind: 'sheet', mime: 'text/csv' }, + '.xlsx': { kind: 'sheet', mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, + // A workbook that keeps its macros (lib/sheet-xlsx.js extension) goes as Excel's macro-enabled file. + '.xlsm': { kind: 'sheet', mime: 'application/vnd.ms-excel.sheet.macroEnabled.12' }, '.csv': { kind: 'sheet', mime: 'text/csv' }, '.tsv': { kind: 'sheet', mime: 'text/tab-separated-values' }, }); const extOf = (name) => /(\.[^./\\]+)$/.exec(typeof name === 'string' ? name : '')?.[1].toLowerCase() || ''; diff --git a/papers.js b/papers.js index f49093db1..f0f7dd876 100644 --- a/papers.js +++ b/papers.js @@ -488,7 +488,7 @@ if (!c) return; if (menu?.k === k) { closeMenu(true); return; } closeMenu(); - const items = bin ? [['restore', 'Restore'], ['forever', 'Delete forever']] : [PA.local(k) ? ['rename', 'Rename'] : null, ['copy', 'Make a copy'], ['export', DK.get(DK.kindOf(c.row))?.exportLabel || (DK.kindOf(c.row) === 'pdf' ? 'Export .pdf' : 'Export .docx')], ['delete', 'Delete']].filter(Boolean); + const items = bin ? [['restore', 'Restore'], ['forever', 'Delete forever']] : [PA.local(k) ? ['rename', 'Rename'] : null, ['copy', 'Make a copy'], ['export', DK.get(DK.kindOf(c.row))?.exportLabelOf?.(c.row) || DK.get(DK.kindOf(c.row))?.exportLabel || (DK.kindOf(c.row) === 'pdf' ? 'Export .pdf' : 'Export .docx')], ['delete', 'Delete']].filter(Boolean); const added = bin ? [] : [...slots.card.values()]; const el = h('div', { class: 'pop gp-menu gd-menu', role: 'menu', 'aria-labelledby': c.link.id }, items.map(([act, label]) => btn({ role: 'menuitem', tabindex: '-1', 'data-act': act, onclick: () => { closeMenu(act !== 'rename'); run(act, k); } }, t(label))), diff --git a/scripts/sheets-check.js b/scripts/sheets-check.js index 0a82606ed..d4921ed86 100644 --- a/scripts/sheets-check.js +++ b/scripts/sheets-check.js @@ -478,6 +478,32 @@ const seeded = (key, wb = M.workbook()) => { const arec = await until(() => local(`writerPaper:${again}`), 'its record'); check(arec.kind === 'sheet' && arec.cells === count(original) + 1, `the export imports again with every cell (${arec.cells})`); + // ---- an .xlsm (test/fixtures/sheet-xlsm.js): it imports, its card and grid say Export .xlsm, and the export is + // Budget.xlsm with its macro project byte for byte + const XM = require(path.join(root, 'test/fixtures/sheet-xlsm.js')), DTX = require('../lib/doctext.js'); + const xlsmFile = path.join(tmp, 'Budget.xlsm'); + fs.writeFileSync(xlsmFile, XM.xlsm()); + await openHome(); + check(await page.evaluate(() => /\.xlsm/.test(document.querySelector('#gd-file').accept)), 'the chooser takes .xlsm'); + const cardsM = await page.evaluate(() => [...document.querySelectorAll('.gd-card')].map((c) => c.dataset.key)); + await page.setInputFiles('#gd-file', xlsmFile); + const mkey = await until(() => page.evaluate((was) => [...document.querySelectorAll('.gd-card')].map((c) => c.dataset.key).find((k) => !was.includes(k)), cardsM), 'the .xlsm card'); + const mrec = await until(() => local(`writerPaper:${mkey}`), 'the .xlsm record'); + await page.click(`.gd-card[data-key="${mkey}"] .gd-more`); + const cardMenu = await until(() => page.evaluate(() => [...document.querySelectorAll('.gd-menu [role="menuitem"]')].map((b) => b.textContent)).then((x) => x.length && x), 'the card menu'); + await page.keyboard.press('Escape'); + check(mrec.kind === 'sheet' && mrec.ext === '.xlsm' && cardMenu.includes('Export .xlsm') && !cardMenu.includes('Export .xlsx'), `the .xlsm imports and its card says Export .xlsm (${cardMenu.join(', ')})`); + await page.click(`.gd-card[data-key="${mkey}"] .gd-title`); + await grid(); + await page.click('#gx-more'); + check(await page.evaluate(() => document.querySelector('.gx-menu [data-act="export"]')?.textContent) === 'Export .xlsm', 'the grid\'s menu says Export .xlsm'); + const [mdl] = await Promise.all([page.waitForEvent('download'), page.click('.gx-menu [data-act="export"]')]); + const mout = path.join(tmp, `out-${mdl.suggestedFilename()}`); + await mdl.saveAs(mout); + const mz = DTX.unzip(new Uint8Array(fs.readFileSync(mout))), vba = await mz.bytes('xl/vbaProject.bin'), types = await mz.text('[Content_Types].xml'); + check(mdl.suggestedFilename() === 'Budget.xlsm' && vba && Buffer.from(vba).equals(Buffer.from(XM.VBA)) && /macroEnabled\.main\+xml/.test(types), + `the .xlsm exports as ${mdl.suggestedFilename()}, its macro project verbatim and the macro-enabled workbook type`); + // A script can finish loading without publishing its codec. Opening must show the read error and a usable home. const failed = await context.newPage(); try { diff --git a/sheet-grid.js b/sheet-grid.js index 4441c57d7..ae5697849 100644 --- a/sheet-grid.js +++ b/sheet-grid.js @@ -1258,7 +1258,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { }; if (chartB) toggle(chartB, () => KINDS.map(([kind, label]) => ({ act: `chart-${kind}`, label: t(label), run: () => insertChart(kind) })), t('Chart')); const fileNow = async () => o.fileOf(wb, rec?.title || host?.title?.() || ''); - toggle(moreB, () => [{ act: 'export', label: t(o.exportLabel()), run: () => { flush(); fileNow().then(o.download, (e) => { console.error(e); say(t('Not saved. Try again in a moment.')); }); } }], t('More')); + toggle(moreB, () => [{ act: 'export', label: t(o.exportLabel(wb)), run: () => { flush(); fileNow().then(o.download, (e) => { console.error(e); say(t('Not saved. Try again in a moment.')); }); } }], t('More')); const when = new Intl.DateTimeFormat(o.locale || undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); versB.addEventListener('click', async () => { if (menu?.opener === versB) { closeMenu(true); return; } diff --git a/sheet-kind.js b/sheet-kind.js index c12f538b5..61593b8bf 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -1,5 +1,5 @@ // Spreadsheets on the Documents page (papers.html): the sheet kind's page code (lib/doc-kinds.js). Settings → Writer → -// Spreadsheets (writer.sheets, defaults.js) adds the New band's Spreadsheet tile (create) and takes .xlsx, .csv and .tsv files +// Spreadsheets (writer.sheets, defaults.js) adds the New band's Spreadsheet tile (create) and takes .xlsx, .xlsm, .csv and .tsv files // (importFile); existing spreadsheets always open, copy and export. A card shows the record's 6×4 text (sheet-data.js // glance). Opening one mounts the grid (sheet-grid.js) on the calc engine (lib/sheet-*.js). .xlsx files are read and written // by GZ_SHEET_XLSX (lib/sheet-xlsx.js), loaded on demand before a file or the grid opens. Storage is sheet-data.js's alone. @@ -16,6 +16,11 @@ const ENGINE = ['lib/sheet-model.js', 'lib/sheet-numfmt.js', 'lib/sheet-formula.js', 'lib/sheet-calc.js', ...FNS.map((f) => `lib/sheet-fn-${f}.js`), 'sheet-data.js']; const SAYS = { size: 'Choose a spreadsheet smaller than 15 MB.', read: 'That file could not be read.' }; const xlsx = () => globalThis.GZ_SHEET_XLSX ?? null; + // A workbook that keeps a macro project (lib/sheet-xlsx.js extension) is written, named and labelled .xlsm, its record + // and index row saying ext '.xlsm' (a card's ⋯ menu reads the row); every other one .xlsx. + const XLSM = 'application/vnd.ms-excel.sheet.macroEnabled.12'; + const extOf = (wb) => (typeof xlsx()?.extension === 'function' && xlsx().extension(wb) === 'xlsm' ? '.xlsm' : '.xlsx'); + const labelOf = (ext) => (ext === '.xlsm' ? 'Export .xlsm' : 'Export .xlsx'); // The .xlsx reader and writer and what they stand on, loaded the first time a spreadsheet is listed, opened, imported or // written (never with the page). One shared load; a failure rejects the operation and allows a later retry. const CODECS = ['lib/doctext.js', 'lib/zip.js', 'lib/sheet-xlsx.js']; @@ -92,7 +97,7 @@ const got = rec?.key ? await GZ_SHEET_DATA.read(get, rec.key) : null; if (!got) return {}; const { book, parts } = GZ_SHEET_DATA.split(got.wb); - return { book, grid: got.rec.grid, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; + return { book, grid: got.rec.grid, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, ...(got.rec.ext === '.xlsm' && { ext: '.xlsm' }), parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; }; // ---- files: .xlsx, .csv and .tsv through GZ_SHEET_XLSX @@ -118,16 +123,16 @@ const wb = await workbookOf(file, progress); progress?.({ stage: 'save' }); const key = GZ_WRITER_PAPERS.newKey(), title = stem(file.name); - await saveAll(key, wb, { named: !!title, title }, 'start'); + await saveAll(key, wb, { named: !!title, title, ...(extOf(wb) === '.xlsm' && { ext: '.xlsm' }) }, 'start'); const notice = noticeOf(wb); return notice ? { key, notice } : key; }; const safe = (s) => String(s || '').replace(/[\\/:*?"<>|\u0000-\u001f]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120); - // A spreadsheet as .xlsx, waiting for the writer. → { name, mime, bytes }. + // A spreadsheet as .xlsx (.xlsm with its macros), waiting for the writer. → { name, mime, bytes }. const fileOf = async (wb, title) => { const name = safe(title) || t('Spreadsheet'); - const X = await codecs(), bytes = await X.write(wb); - return { name: `${name}.xlsx`, mime: DK.KINDS.sheet.mime, bytes }; + const X = await codecs(), bytes = await X.write(wb), ext = extOf(wb); + return { name: `${name}${ext}`, mime: ext === '.xlsm' ? XLSM : DK.KINDS.sheet.mime, bytes }; }; const file = async (key) => { await need([...ENGINE, 'sheet-grid.js']); @@ -164,7 +169,7 @@ if (prior) had = prior.rec; const sigs = new Map(Object.keys(had?.parts || {}).map((n) => [n, 'stale'])); const checkpoint = prior?.wb || wb, gz = await SD.pack(checkpoint); - const paper = (await SD.write(papers(), key, wb, sigs, { base: had?.revision || 0, patch: { doc: null, words: 0 }, reason: 'import', show: shower(wb), version: { reason: prior ? 'import' : 'start', gz, cells: SD.measure(checkpoint).cells } })).saved.paper; + const paper = (await SD.write(papers(), key, wb, sigs, { base: had?.revision || 0, patch: { doc: null, words: 0, ext: extOf(wb) === '.xlsm' ? '.xlsm' : null }, reason: 'import', show: shower(wb), version: { reason: prior ? 'import' : 'start', gz, cells: SD.measure(checkpoint).cells } })).saved.paper; const notice = noticeOf(wb); return notice ? { ...paper, notice } : paper; }; @@ -181,7 +186,7 @@ await need(CHARTS).catch((e) => console.error(e)); if (!host) document.documentElement.dataset.mode = 'sheet'; const v = GZ_SHEET_GRID.mount({ key, wb, rec: got?.rec || null, sigs: got?.sigs || new Map(), fresh: !got, t: host?.t || t, locale, papers: papers(), show: shower, - local: GZ_WRITER_PAPERS.local(key), fileOf, download, exportLabel: () => 'Export .xlsx', host }); + local: GZ_WRITER_PAPERS.local(key), fileOf, download, exportLabel: (book) => labelOf(book ? extOf(book) : null), host }); globalThis.GZ_PAPERS_MOTION?.shown('paper'); return v; }; @@ -193,7 +198,7 @@ const DEFAULTS = ['lib/color.js', 'lib/surfaces.js', 'lib/fonts.js', 'defaults.js']; const on = Promise.resolve().then(() => (typeof GZ_SANITIZE === 'undefined' ? need(DEFAULTS) : null)).then(() => chrome.storage.sync.get('writer')) .then((r) => GZ_SANITIZE({ writer: r?.writer }).writer.sheets === true, () => false); - DK.wait(on.then((yes) => DK.register('sheet', { edit, view, into, empty, thumb, file, exportFile, version, text, measure, copy, says: SAYS, exportLabel: 'Export .xlsx', + DK.wait(on.then((yes) => DK.register('sheet', { edit, view, into, empty, thumb, file, exportFile, version, text, measure, copy, says: SAYS, exportLabel: 'Export .xlsx', exportLabelOf: (row) => labelOf(row?.ext), ...(yes && { importFile, create }) }))); globalThis.GZ_SHEET_KIND = Object.freeze({ shower, fileOf }); })(); diff --git a/test/doc-kinds.test.js b/test/doc-kinds.test.js index 722fa125d..8e407724d 100644 --- a/test/doc-kinds.test.js +++ b/test/doc-kinds.test.js @@ -7,7 +7,7 @@ const DK = require('../lib/doc-kinds.js'); test('paper, sheet, pdf and slides are the kinds, frozen', () => { assert.deepEqual(Object.keys(DK.KINDS), ['paper', 'sheet', 'pdf', 'slides']); assert.deepEqual(DK.KINDS.paper, { label: 'Document', icon: 'paper', accept: ['.docx'], mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', max: 15e6, param: 'paper' }); - assert.deepEqual(DK.KINDS.sheet, { label: 'Spreadsheet', icon: 'sheet', accept: ['.xlsx', '.csv', '.tsv'], mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', max: 15e6, param: 'sheet' }); + assert.deepEqual(DK.KINDS.sheet, { label: 'Spreadsheet', icon: 'sheet', accept: ['.xlsx', '.xlsm', '.csv', '.tsv'], mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', max: 15e6, param: 'sheet' }); assert.deepEqual(DK.KINDS.pdf, { label: 'PDF', icon: 'pdf', accept: ['.pdf'], mime: 'application/pdf', max: 50e6, big: 'Choose a PDF smaller than 50 MB.', param: 'pdf' }); assert.deepEqual(DK.KINDS.slides, { label: 'Slides', icon: 'slides', accept: ['.pptx', '.ppsx'], mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', max: 50e6, big: 'Choose a PowerPoint file smaller than 50 MB.', param: 'slides' }); for (const k of Object.values(DK.KINDS)) assert.ok(Object.isFrozen(k) && Object.isFrozen(k.accept)); @@ -28,12 +28,13 @@ test('a file\'s kind is its extension\'s, any case', () => { assert.equal(DK.forFile('ESSAY.DOCX'), 'paper'); assert.equal(DK.forFile('my.essay.Docx'), 'paper'); assert.equal(DK.forFile('Grades.xlsx'), 'sheet'); + assert.equal(DK.forFile('Budget.XLSM'), 'sheet'); assert.equal(DK.forFile('export.CSV'), 'sheet'); assert.equal(DK.forFile('data.TSV'), 'sheet'); assert.equal(DK.forFile('Worksheet.PDF'), 'pdf'); assert.equal(DK.forFile('Week 3.PPTX'), 'slides'); assert.equal(DK.forFile('show.ppsx'), 'slides'); - for (const name of ['essay.doc', 'docx', 'essay', '', null, undefined, 'essay.docx.zip', 'essay.pdf.zip', 'deck.ppt', 'deck.pptx.zip', 'deck.odp', 'book.xls', 'book.xlsm', 'book.ods', 'book.xlsx.zip']) assert.equal(DK.forFile(name), null, String(name)); + for (const name of ['essay.doc', 'docx', 'essay', '', null, undefined, 'essay.docx.zip', 'essay.pdf.zip', 'deck.ppt', 'deck.pptx.zip', 'deck.odp', 'book.xls', 'book.ods', 'book.xlsx.zip']) assert.equal(DK.forFile(name), null, String(name)); }); test('a kind\'s page code registers onto its facts', () => { @@ -122,7 +123,7 @@ test('the sheet kind opens files only once it registers importFile', () => { assert.equal(K.opens('Grades.xlsx'), 'sheet'); assert.equal(K.opens('grades.csv'), 'sheet'); assert.equal(K.opens('grades.tsv'), 'sheet'); - assert.equal(K.accept(), '.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.xlsx,.csv,.tsv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + assert.equal(K.accept(), '.docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.xlsx,.xlsm,.csv,.tsv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); freshSheet(); }); diff --git a/test/fixtures/sheet-xlsm.js b/test/fixtures/sheet-xlsm.js new file mode 100644 index 000000000..de293fe21 --- /dev/null +++ b/test/fixtures/sheet-xlsm.js @@ -0,0 +1,19 @@ +// A small macro-enabled workbook (.xlsm) as Excel writes one: the macro-enabled main part, a sheet with A1 "Macro" and +// B1 42, and its VBA project (xl/vbaProject.bin, opaque bytes kept verbatim). For test/sheet-kind.test.js and +// scripts/sheets-check.js. +const Z = require('../../lib/zip.js'); +const RT = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'; +const NS = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'; +const VBA = Uint8Array.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1, 0x56, 0x42, 0x41]); +const rels = (list) => `\n${list.map(([id, type, target]) => ``).join('')}`; +const PARTS = { + '[Content_Types].xml': '', + '_rels/.rels': rels([['rId1', 'officeDocument', 'xl/workbook.xml']]), + 'xl/workbook.xml': ``, + 'xl/_rels/workbook.xml.rels': rels([['rId1', 'worksheet', 'worksheets/sheet1.xml'], ['rId2', 'vbaProject', 'vbaProject.bin']]), + 'xl/worksheets/sheet1.xml': `Macro42`, + 'xl/vbaProject.bin': VBA, +}; +const enc = new TextEncoder(); +const xlsm = () => new Uint8Array(Buffer.concat(Z.zip(Object.entries(PARTS).map(([name, s]) => ({ name, data: typeof s === 'string' ? enc.encode(s) : s, date: new Date(2026, 0, 1) }))))); +module.exports = { xlsm, VBA, MIME: 'application/vnd.ms-excel.sheet.macroEnabled.12' }; diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index d81482923..1c5e1d055 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -582,6 +582,50 @@ test('the codecs load on demand: an .xlsx read into the record, the file written assert.ok(PA.local(key.key)); }); +// A workbook brought as .xlsm keeps its macros (lib/sheet-xlsx.js) and goes out as .xlsm with Excel's macro-enabled type, +// on Documents and in an assignment's frame, its record and index row saying so for the card's Export label. +test('an imported .xlsm round-trips as .xlsm: its name, type, macro project and Export label', async () => { + const X = require('../lib/sheet-xlsx.js'), XM = require('./fixtures/sheet-xlsm.js'), Z = require('../lib/doctext.js'); + const s = store(), P = PA.open(s, () => 1000, { versions: async () => 40 }), K = 'bb.school.test|_900_1|_101_1|_7_1'; + await P.save(K, { kind: 'paper', doc: { type: 'doc', content: [] }, title: 'Lab 3' }); + const G = require('../sheet-grid.js'); + for (const f of ['date', 'math', 'info']) require(`../lib/sheet-fn-${f}.js`); + const ctx0 = { local: s, GZ_WRITER_PAPERS: { ...PA, open: () => P }, GZ_SHEET_MODEL: M, GZ_SHEET_DATA: SD, GZ_SHEET_GRID: G, GZ_SHEET_CALC: require('../lib/sheet-calc.js'), + GZ_SHEET_NUMFMT: require('../lib/sheet-numfmt.js'), CompressionStream, DecompressionStream, Response, Blob }; + const { DK, ctx } = load({ sheets: true }, ctx0); + ctx.GZ_WRITER_PAPER = { need: async (files) => { if (files.includes('lib/sheet-xlsx.js')) ctx.GZ_SHEET_XLSX = X; } }; + await DK.settled(); + const k = DK.get('sheet'), bytes = XM.xlsm(); + assert.equal(DK.opens('Budget.xlsm'), 'sheet', 'Documents takes an .xlsm'); + const file = (name, b) => ({ name, size: b.length, arrayBuffer: async () => b.buffer.slice(b.byteOffset, b.byteOffset + b.length) }); + const vba = async (out) => { const z = Z.unzip(out.bytes); return z.names.includes('xl/vbaProject.bin') && Buffer.from(await z.bytes('xl/vbaProject.bin')).equals(Buffer.from(XM.VBA)); }; + const key = await k.importFile(file('Budget.xlsm', bytes)); + const rec = await P.get(key), row = (await P.list()).find((r) => r.key === key); + assert.equal(rec.ext, '.xlsm'); + assert.equal(row.ext, '.xlsm', 'the index row says it for the card'); + assert.equal(k.exportLabelOf(row), 'Export .xlsm'); + const out = await k.file(key); + assert.equal(out.name, 'Budget.xlsm'); + assert.equal(out.mime, XM.MIME); + assert.ok(await vba(out), 'the macro project goes out verbatim'); + assert.equal((await X.read(out.bytes)).sheets[0].rows.get(0).get(1).v, 42); + // In an assignment's frame: the attempt's record becomes the workbook, written as .xlsm too. + const into = await k.into(K, file('Budget.xlsm', bytes)); + assert.equal(into.ext, '.xlsm'); + const there = await k.file(K); + assert.ok(there.name.endsWith('.xlsm') && there.mime === XM.MIME && await vba(there)); + // Everything else stays .xlsx. + const src = M.workbook(); + put(src.sheets[0], 0, 0, { t: 's', v: 'Plain' }); + const plain = await k.importFile(file('Plain.xlsx', await X.write(src))); + const prow = (await P.list()).find((r) => r.key === plain); + assert.equal(prow.ext, undefined); + assert.equal(k.exportLabelOf(prow), 'Export .xlsx'); + assert.deepEqual([(await k.file(plain)).name, (await k.file(plain)).mime], ['Plain.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']); + assert.equal((await k.into(K, file('Plain.xlsx', await X.write(src)))).ext, null, 'a workbook without macros brought over one is .xlsx again'); + assert.equal(k.exportLabel, 'Export .xlsx'); +}); + test('a cell\'s dynamic-array and data-table fields (cm, vm, dt) survive a save', () => { const wb = M.workbook(); put(wb.sheets[0], 0, 0, { t: 'n', v: 1, f: 'SEQUENCE(3)', cm: 1 }); diff --git a/test/writer-submit.test.js b/test/writer-submit.test.js index f6fcde7e5..761d69b19 100644 --- a/test/writer-submit.test.js +++ b/test/writer-submit.test.js @@ -29,6 +29,16 @@ test('the file is Lastname_Title.docx, the title before its subtitle, letters an assert.equal(S.kb(20), 1); }); +test('a workbook kept with its macros goes as .xlsm, Excel\'s macro-enabled type; every other one as .xlsx', () => { + assert.equal(S.fileName({ surname: 'Rivera', title: 'Budget', ext: '.xlsm' }), 'Rivera_Budget.xlsm'); + assert.equal(S.fileName({ surname: 'Rivera', title: 'Budget', ext: '.xlsx' }), 'Rivera_Budget.xlsx'); + assert.equal(S.mimeOf('Rivera_Budget.xlsm'), 'application/vnd.ms-excel.sheet.macroEnabled.12'); + assert.equal(S.mimeOf('Rivera_Budget.xlsx'), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + assert.equal(S.kindOf('Budget.XLSM'), 'sheet'); + assert.ok(S.safeName('Rivera_Budget.xlsm', 'sheet')); + assert.equal(S.route('Budget.xlsm', { kinds: ['sheet'] }), 'choose', 'brought to an assignment, it opens in Glaze or submits as is'); +}); + test('the probed attempt: the file area\'s own input, the drop zone, and the footer\'s Submit and Save and Close', () => { const body = page(), ed = editorOf(body), area = S.areaOf(ed); assert.match(area.className, /makeStylessubmission-/); diff --git a/writer-kinds.js b/writer-kinds.js index 96745403b..c1049ae11 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -56,8 +56,8 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { submitB.disabled = typeof P().submit !== 'function'; const owns = async () => GZ_DOC_KINDS.kindOf(await store().get(key)) === id; const surface = { - // A spreadsheet's file always waits for the .xlsx writer (sheet-kind.js fileOf). - kind: () => id, key: () => key, ext: () => (id === 'sheet' ? '.xlsx' : ext), context: () => paper.context(), paper: () => paper.paper(), compact: () => false, blank: () => false, importing: () => busy, + // A spreadsheet's file always waits for the .xlsx writer (sheet-kind.js fileOf): .xlsm when its record says so. + kind: () => id, key: () => key, ext: () => (id === 'sheet' ? (ext === '.xlsm' ? '.xlsm' : '.xlsx') : ext), context: () => paper.context(), paper: () => paper.paper(), compact: () => false, blank: () => false, importing: () => busy, pages: () => v?.pages?.() || null, notify: (m) => v?.say?.(t(m)), focused: () => {}, resume: () => {}, look: () => null, version: async (reason = 'submit') => { if (!await surface.save()) return null; diff --git a/writer-submit.js b/writer-submit.js index 922431bf1..1459ef916 100644 --- a/writer-submit.js +++ b/writer-submit.js @@ -254,7 +254,9 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { try { out = !other && built && built.doc === snapshot.doc && built.format === snapshot.format && built.key === snapshot.key ? built.out : await build(); } catch (e) { console.error(e); throw other ? 'made' : 'build'; } s = L().advance(s, { step: 'build' }); if (mine.cancelled || !same() || !L().may(s, 'attach') || sheet !== mine) throw 'page'; - const r = await send(run, { key: snapshot.key, name, bytes: out.bytes.slice().buffer, note: note.value, text, ...ultra, kind: kindId, mime: out.mime || L().mimeOf(name), version: sending.version?.id ?? null }); + // A kind's file names its own extension (a spreadsheet with macros: .xlsm): the one sent says it too. + const ext = /(\.[^./\\]+)$/.exec(String(out.name || ''))?.[1].toLowerCase(), sent = other && ext && L().kindOf(`x${ext}`) === kindId && L().kindOf(name) === kindId ? name.replace(/\.[^.]+$/, ext) : name; + const r = await send(run, { key: snapshot.key, name: sent, bytes: out.bytes.slice().buffer, note: note.value, text, ...ultra, kind: kindId, mime: out.mime || L().mimeOf(name), version: sending.version?.id ?? null }); if (mine.cancelled || sheet !== mine) return; if (!r.ok) throw r.error || 'page'; mine.back = null; From 1a372aa4c811491df3650bf6cbb63a46bc5fa420 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:35:55 -0400 Subject: [PATCH 069/907] Map chart coverage through actual grid sizes --- lib/sheet-chart-xml.js | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index 15b6f7dc7..719f208c3 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -448,23 +448,47 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { return { kind: 'plot', title: title || null, legend: c.legend, groups, axes: c.axes, blanks: c.blanks, view3D: c.view3D ?? null, walls: c.walls ?? null }; }; - // The card's view of a sheet's charts (sheet-data.js keeps it in the record): each chart's kind, the cells it covers - // [r0, c0, r1, c1] (a size in pixels counted in the grid's default 64×20 cells) and up to five of its colours, from the - // parsed model alone. kind: column, bar, line, area, scatter, pie, doughnut, pie3D, column3D, bar3D or chart (a frame). + // Pixel coordinates and cells use the grid's sizes, including hidden rows/columns. Prefix deltas keep a distant + // anchor cheap: no walk through a million default rows, and these tables are shared by every chart in the summary. + const glanceAxis = (values, hidden, base, limit, px) => { + const sizes = new Map([...values].filter(([i]) => Number.isInteger(i) && i >= 0 && i < limit).map(([i, v]) => [i, px(v)])); + for (const i of hidden) if (Number.isInteger(i) && i >= 0 && i < limit) sizes.set(i, 0); + let delta = 0; + const changes = [...sizes].sort((a, b) => a[0] - b[0]).map(([i, size]) => ({ i, delta: (delta += size - base) })); + const position = (i, off = 0) => { + let lo = 0, hi = changes.length; + while (lo < hi) { const mid = (lo + hi) >>> 1; if (changes[mid].i < i) lo = mid + 1; else hi = mid; } + return i * base + (lo ? changes[lo - 1].delta : 0) + off; + }; + const cell = (p, end = false) => { + if (p < 0) return end ? Math.ceil(p / base) - 1 : Math.floor(p / base); + let lo = 0, hi = limit; + while (lo < hi) { const mid = (lo + hi) >>> 1; if (end ? position(mid) < p : position(mid) <= p) lo = mid + 1; else hi = mid; } + return end ? lo - 1 : Math.max(0, lo - 1); + }; + return { position, cell }; + }; + // The card's view: kind, inclusive cells [r0, c0, r1, c1], and up to five hex colours. Empty or reversed boxes have + // an empty range, so the card counts them without painting a phantom cell. const glance = (wb, sh) => { const theme = Array.isArray(wb?.keep?.theme) && wb.keep.theme.length >= 10 ? wb.keep.theme : DEFAULT_THEME; + const C = glanceAxis(sh.colW, sh.hidden.cols, 64, MAXC, (w) => Number.isFinite(w) ? Math.max(0, Math.trunc(((256 * w + 18) / 256) * 7)) : 64); + const R = glanceAxis(sh.rowH, sh.hidden.rows, 20, MAXR, (h) => Number.isFinite(h) ? Math.max(0, Math.round(h * 4 / 3)) : 20); return charts(wb, sh).slice(0, 32).map(({ anchor: a, chart: c }) => { - const at = a.from ?? (a.pos && { col: Math.floor(a.pos.x / 64), colOff: a.pos.x % 64, row: Math.floor(a.pos.y / 20), rowOff: a.pos.y % 20 }) ?? { col: 0, colOff: 0, row: 0, rowOff: 0 }; - const end = a.to ? [a.to.row - (a.to.rowOff <= 0 && a.to.row > at.row ? 1 : 0), a.to.col - (a.to.colOff <= 0 && a.to.col > at.col ? 1 : 0)] - : [at.row + Math.max(0, Math.ceil((at.rowOff + (a.ext?.h ?? 0)) / 20) - 1), at.col + Math.max(0, Math.ceil((at.colOff + (a.ext?.w ?? 0)) / 64) - 1)]; + const x0 = a.from ? C.position(a.from.col, a.from.colOff) : a.pos?.x ?? 0; + const y0 = a.from ? R.position(a.from.row, a.from.rowOff) : a.pos?.y ?? 0; + const x1 = a.to ? C.position(a.to.col, a.to.colOff) : x0 + (a.ext?.w ?? 0); + const y1 = a.to ? R.position(a.to.row, a.to.rowOff) : y0 + (a.ext?.h ?? 0); + const covered = [x0, y0, x1, y1].every(Number.isFinite) && x1 > x0 && y1 > y0 + ? [R.cell(y0), C.cell(x0), R.cell(y1, true), C.cell(x1, true)] : [0, 0, -1, -1]; const g = c?.kind === 'plot' ? c.groups?.[0] : null; - if (!g) return { kind: 'chart', anchor: [at.row, at.col, ...end], colours: [] }; + if (!g) return { kind: 'chart', anchor: covered, colours: [] }; const kind = g.type === 'bar' ? (g.dir === 'bar' ? 'bar' : 'column') : g.type === 'bar3D' ? (g.dir === 'bar' ? 'bar3D' : 'column3D') : g.type; const s0 = g.series[0]; const round = /^(pie|doughnut|pie3D)$/.test(g.type) || (g.vary && g.series.length === 1); const colours = round && s0 ? Array.from({ length: Math.min(5, Math.max(1, s0.val?.cache?.pts?.length ?? 0)) }, (_, i) => s0.points?.get(i) ?? autoColor(i, theme)) : g.series.slice(0, 5).map((s) => { const x = g.type === 'line' || g.type === 'scatter' ? s.line : s.fill; return x && x !== 'none' ? x : s.color; }); - return { kind, anchor: [at.row, at.col, ...end], colours: colours.filter((x) => /^#[0-9a-f]{6}$/i.test(x || '')) }; + return { kind, anchor: covered, colours: colours.filter((x) => /^#[0-9a-f]{6}$/i.test(x || '')) }; }); }; From c2a2b95747ee8bcd9bab4f97e9bb4741db87c502 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:35:55 -0400 Subject: [PATCH 070/907] Cover chart metadata when starting an empty sheet --- test/sheet-kind.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index dd08aa7bd..77f4e5a58 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -730,3 +730,14 @@ test('the card draws a chart over the cells it covers and counts the charts outs const none = DK.get('sheet').thumb({ kind: 'sheet', book: JSON.parse(JSON.stringify(SD.split(plain).book)), grid: SD.glance(plain), charts: [] }); assert.ok(!none.children.some((e) => /gx-th-(chart|more)/.test(e.className))); }); + +test('starting an empty spreadsheet clears charts left by a prior spreadsheet kind', async () => { + const s = store(), P = PA.open(s), wb = M.workbook(); + await SD.write(P, KEY, wb, new Map()); + await P.save(KEY, { charts: [{ kind: 'column', anchor: [0, 0, 2, 2], colours: ['#156082'] }] }); + await P.save(KEY, { kind: 'paper', doc: { type: 'doc', content: [] } }, { reason: 'import' }); + const { DK } = load({ sheets: true }); + await DK.settled(); + await P.save(KEY, { kind: 'sheet', doc: null, ...DK.get('sheet').empty() }, { reason: 'import' }); + assert.deepEqual((await P.get(KEY)).charts, []); +}); From 75f7d5c263154458a2b98e653db0780309c844b0 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:36:44 -0400 Subject: [PATCH 071/907] Clear chart summaries when starting an empty sheet --- sheet-kind.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sheet-kind.js b/sheet-kind.js index e5ff34d35..21ea347d8 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -219,7 +219,7 @@ return notice ? { ...paper, notice } : paper; }; // An empty attempt started as a spreadsheet (writer-kinds.js): what its record takes; the grid opens a blank workbook. - const empty = () => ({ grid: Array.from({ length: 4 }, () => Array(6).fill('')), text: '', cells: 0, sheets: 1 }); + const empty = () => ({ charts: [], grid: Array.from({ length: 4 }, () => Array(6).fill('')), text: '', cells: 0, sheets: 1 }); const create = () => `papers.html?sheet=${encodeURIComponent(GZ_WRITER_PAPERS.newKey())}&new`; // host: an assignment's frame (view below): { mount, t, title, tools }. → the grid's surface (sheet-grid.js mount). const edit = async (key, { fresh = false, host = null } = {}) => { From a08faf0d28431c6d5b57ed6f17d495db9a90cb7a Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:36:45 -0400 Subject: [PATCH 072/907] Cover legacy drawing counts and repeated card renders --- test/sheet-kind.test.js | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index 77f4e5a58..aa3c8782e 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -741,3 +741,58 @@ test('starting an empty spreadsheet clears charts left by a prior spreadsheet ki await P.save(KEY, { kind: 'sheet', doc: null, ...DK.get('sheet').empty() }, { reason: 'import' }); assert.deepEqual((await P.get(KEY)).charts, []); }); + +const cardKind = async (overrides = {}) => { + const { El } = require('./stand-in-dom.js'); + const document = { createElement: (tag) => new El(tag, document), createElementNS: (ns, tag) => new El(tag, document) }; + const { DK } = load({ sheets: true }, { document, GZ_SHEET_XLSX: {}, ...overrides }); + await DK.settled(); + return DK.get('sheet'); +}; +const badgeCount = (box) => Number(box.children.find((e) => e.className === 'gx-th-more')?.children.at(-1).textContent || 0); + +test('legacy chart counting accepts XML quotes and ignores commented graphic frames', async (t) => { + const X = require('../lib/sheet-xlsx.js'), FX = require('./fixtures/sheets/charts.js'); + const wb = await X.read(FX.workbook({ kinds: ['column'] })), k = await cardKind(); + const rec = { book: JSON.parse(JSON.stringify(SD.split(wb).book)) }, part = rec.book.keep.parts['xl/drawings/drawing1.xml']; + const xml = new TextDecoder().decode(SD.dec(part.data)); + part.data = xml.replaceAll('"', "'"); + await t.test('single quotes are valid XML', () => assert.equal(badgeCount(k.thumb(rec)), 1)); + part.data = xml.replace('', ``); + await t.test('a comment does not add another chart', () => assert.equal(badgeCount(k.thumb(rec)), 1)); + part.data = xml.replaceAll('a:', 'draw-ml:').replace('xmlns:a=', 'xmlns:draw-ml='); + await t.test('an XML prefix may contain a hyphen', () => assert.equal(badgeCount(k.thumb(rec)), 1)); +}); + +test('legacy counting follows drawing ownership and counts one frame per anchor', async (t) => { + const X = require('../lib/sheet-xlsx.js'), CX = require('../lib/sheet-chart-xml.js'), FX = require('./fixtures/sheets/charts.js'); + const wb = await X.read(FX.workbook({ kinds: ['column'] })), k = await cardKind(); + const rec = () => ({ book: JSON.parse(JSON.stringify(SD.split(wb).book)) }); + const part = wb.keep.parts['xl/drawings/drawing1.xml'], xml = new TextDecoder().decode(part.data); + const frame = //.exec(xml)[0]; + part.data = xml.replace(frame, `${frame}${frame}`); + assert.equal(CX.charts(wb, wb.sheets[0]).length, 1); + await t.test('Choice and Fallback are the same chart', () => assert.equal(badgeCount(k.thumb(rec())), 1)); + for (const uri of ['http://schemas.openxmlformats.org/drawingml/2006/table', 'http://schemas.openxmlformats.org/drawingml/2006/diagram', 'http://schemas.openxmlformats.org/drawingml/2006/picture']) { + part.data = xml.replace('uri="http://schemas.openxmlformats.org/drawingml/2006/chart"', `uri="${uri}"`).replace(/]*\/>/, ''); + assert.equal(badgeCount(k.thumb(rec())), 0, uri); + } + part.data = xml; + M.apply(wb, { op: 'addSheet', sheet: { ...structuredClone(wb.sheets[0]), id: 2, name: 'Data (2)' }, at: 1, active: 1 }); + wb.active = 1; + assert.equal(CX.charts(wb, wb.sheets[1]).length, 0, 'the duplicate does not own the shared drawing'); + assert.equal(badgeCount(k.thumb(rec())), 0, 'fallback agrees with the loaded reader'); +}); + +test('legacy counting decodes an unchanged part only once and invalidates a replaced part', async () => { + const X = require('../lib/sheet-xlsx.js'), FX = require('./fixtures/sheets/charts.js'); + const wb = await X.read(FX.workbook({ kinds: ['column'] })); + let decoded = 0; + const k = await cardKind({ atob: (s) => { decoded++; return atob(s); } }); + const rec = { book: JSON.parse(JSON.stringify(SD.split(wb).book)) }; + for (let i = 0; i < 5; i++) assert.equal(badgeCount(k.thumb(rec)), 1); + assert.equal(decoded, 1); + rec.book.keep.parts['xl/drawings/drawing1.xml'].data = { $b64: btoa('') }; + assert.equal(badgeCount(k.thumb(rec)), 0); + assert.equal(decoded, 2); +}); From f6fd9e33df1f29e381ab9e5070fd19ffbe351b9c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:38:16 -0400 Subject: [PATCH 073/907] Test that Disconnect keeps a grant Calendar shares, a failed Disconnect says so, and picked file ids must be strings --- scripts/google-check.js | 8 ++++++++ test/google.test.js | 17 +++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/google-check.js b/scripts/google-check.js index 611f9a8da..d7ad347a4 100644 --- a/scripts/google-check.js +++ b/scripts/google-check.js @@ -189,6 +189,14 @@ const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.go ok(log.auth.length === 2, 'the held token served both picks: no new sign-in'); await noToken('after picking'); + // A Disconnect the worker didn't carry out is no Disconnect. + await sw.evaluate(() => { globalThis.__gzDisconnect = google.disconnect; google.disconnect = async () => ({ ok: false, why: 'network' }); }); + await page.locator('#gdrive-disconnect').click(); + await until(async () => (await page.locator('#hint').textContent()).includes("Couldn't disconnect"), 'failed disconnect'); + ok(await page.locator('#hint').evaluate((h) => h.classList.contains('err')), 'a failed Disconnect says so, as an error'); + ok(await page.locator('#gdrive-disconnect').isVisible() && (await page.locator('#gdrive-who').textContent()) === 'student@school.edu', 'and the account and Disconnect stay'); + await sw.evaluate(() => { google.disconnect = __gzDisconnect; }); + // Disconnect. await page.locator('#gdrive-disconnect').click(); await until(async () => await page.locator('#gdrive-connect').isVisible(), 'disconnected'); diff --git a/test/google.test.js b/test/google.test.js index 8eca3bdab..929d15783 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -242,12 +242,16 @@ test('disconnect revokes the grant, clears the cached tokens, gives identity bac await assert.rejects(w.g.token(), (e) => e.why === 'connect', 'the token is gone from memory'); assert.deepEqual(w.log.permRemoved, [{ permissions: ['identity'] }]); - // Calendar sync connected: its permission and Chrome's cache stay. - const k = harness({ keep: true }); - await k.g.connect(); - await k.g.disconnect(); - assert.deepEqual([k.log.cleared, k.log.permRemoved], [0, []]); - assert.deepEqual(k.local, {}); + // Calendar sync connected: the grant they share isn't revoked, and its permission and Chrome's cache stay. + for (const flow of ['chrome', 'web']) { + const k = harness({ flow, keep: true }); + await k.g.connect(); + assert.deepEqual(await k.g.disconnect(), { ok: true, revoked: false, had: false }); + assert.ok(!k.log.fetch.some((f) => f.url === G.REVOKE), `${flow}: nothing revoked while Calendar uses the grant`); + assert.deepEqual([k.log.cleared, k.log.permRemoved], [0, []]); + assert.deepEqual(k.local, {}); + await assert.rejects(k.g.token(), (e) => e.why === 'connect', `${flow}: Drive's own state is gone`); + } }); test('the picker\'s answer: our state, files with an id, a name and a type, or a cancel', () => { @@ -265,6 +269,7 @@ test('the picker\'s answer: our state, files with an id, a name and a type, or a assert.equal(why(at({ state: s, action: 'picked', files: '[' })), 'picker'); assert.equal(why(at({ state: s, action: 'picked', files: '[]' })), 'picker'); assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, id: '../x' }]) })), 'picker'); + for (const id of [undefined, null, 123, true, ['abc'], { id: 'abc' }]) assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, id }]) })), 'picker', `id ${JSON.stringify(id)}`); assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, mimeType: 'pdf' }]) })), 'picker'); assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify([{ ...file, name: '' }]) })), 'picker'); assert.equal(why(at({ state: s, action: 'picked', files: JSON.stringify(Array(101).fill(file)) })), 'picker'); From eaa0dcf47404c3ee3effb2de753bf8bf0a5cc069 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:38:42 -0400 Subject: [PATCH 074/907] Count owned chart anchors in legacy thumbnails --- sheet-kind.js | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/sheet-kind.js b/sheet-kind.js index 21ea347d8..f06b1621e 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -90,13 +90,48 @@ return [svgEl('path', { d: 'M3 15h18M6 15v-5M11 15V3M16 15V8', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.6 })]; }; const svgOf = (kind, colours) => { const g = svgEl('svg', { viewBox: '0 0 24 16', preserveAspectRatio: 'xMidYMid meet' }); g.append(...glyph(kind, colours)); return g; }; - // A record without the summary: its active sheet's drawing, its chart frames counted from the kept XML (no parse). + // Legacy records: scan only the active sheet's owned drawing, with chart children + // of the first graphic frame in each anchor, just as the chart reader does (Choice/Fallback is one object). + const countXml = (xml) => { + const stack = []; + let anchor = null, n = 0; + const local = (name) => name.slice(name.lastIndexOf(':') + 1); + const leave = (name) => { + const i = stack.lastIndexOf(name); + if (i < 0) return; + stack.length = i; + if (anchor && stack.length < anchor.depth) anchor = null; + if (anchor?.frame && stack.length < anchor.frame) anchor.frame = 0; + if (anchor?.data && stack.length < anchor.data) anchor.data = 0; + }; + // Quoted attributes may contain >; comments and CDATA never contribute tags. No DOM or chart models per card. + for (const m of xml.matchAll(/<(?:!--[\s\S]*?--|!\[CDATA\[[\s\S]*?\]\]|[!?][^>]*|\/?[\w:.-]+(?:[^"'<>]|"[^"]*"|'[^']*')*)>/g)) { + const tag = /^<(\/?)([\w:.-]+)/.exec(m[0]); + if (!tag) continue; + const [, close, name] = tag, k = local(name); + if (close) { leave(name); continue; } + if (stack.length === 1 && local(stack[0]) === 'wsDr' && /^(twoCellAnchor|oneCellAnchor|absoluteAnchor)$/.test(k)) anchor = { depth: stack.length + 1, frame: 0, data: 0, frameSeen: false, dataSeen: false, found: false }; + if (anchor) { + if (k === 'graphicFrame' && !anchor.frameSeen) { anchor.frame = stack.length + 1; anchor.frameSeen = true; } + else if (k === 'graphicData' && anchor.frame && !anchor.dataSeen) { anchor.data = stack.length + 1; anchor.dataSeen = true; } + else if (k === 'chart' && anchor.data && stack.length === anchor.data && !anchor.found) { anchor.found = true; n++; } + } + stack.push(name); + if (/\/\s*>$/.test(m[0])) leave(name); + } + return n; + }; const counted = (book) => { const sh = book?.sheets?.[book.active] || book?.sheets?.[0], path = sh?.keep?.drawing?.path, parts = book?.keep?.parts; - const part = path && parts && (parts[path] ?? parts[Object.keys(parts).find((k) => k.toLowerCase() === String(path).toLowerCase())]); - let xml = part?.data; - if (xml && typeof xml === 'object' && typeof xml.$b64 === 'string') { try { xml = atob(xml.$b64); } catch { xml = ''; } } - return typeof xml === 'string' ? (xml.match(/<(?:\w+:)?graphicData\b[^>]*uri="[^"]*chart[^"]*"/g) || []).length : 0; + if (!path || !parts) return 0; + const owners = book.sheets.filter((s) => s.keep?.drawing?.path === path); + if ((owners.find((s) => s.keep.sourceId === s.id) || owners[0]) !== sh) return 0; + const part = parts[path] ?? parts[Object.keys(parts).find((k) => k.toLowerCase() === String(path).toLowerCase())]; + if (!part || typeof part !== 'object') return 0; + const data = part.data?.$b64 ?? part.data; + let xml = data; + if (typeof part.data?.$b64 === 'string') { try { xml = atob(data); } catch { xml = ''; } } + return typeof xml === 'string' ? countXml(xml) : 0; }; const chartsOn = (box, rec) => { const list = Array.isArray(rec?.charts) ? rec.charts.filter((x) => x && Array.isArray(x.anchor) && x.anchor.length === 4 && x.anchor.every(Number.isSafeInteger)) : null; From 5617b058c42ae660ff79bf756429a3ad9be9fa6e Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:38:52 -0400 Subject: [PATCH 075/907] Reuse legacy drawing counts across card renders --- sheet-kind.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/sheet-kind.js b/sheet-kind.js index f06b1621e..8881674a4 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -90,8 +90,9 @@ return [svgEl('path', { d: 'M3 15h18M6 15v-5M11 15V3M16 15V8', fill: 'none', stroke: 'currentColor', 'stroke-width': 1.6 })]; }; const svgOf = (kind, colours) => { const g = svgEl('svg', { viewBox: '0 0 24 16', preserveAspectRatio: 'xMidYMid meet' }); g.append(...glyph(kind, colours)); return g; }; - // Legacy records: scan only the active sheet's owned drawing, with chart children + // Legacy records: scan only the active sheet's owned drawing, once per unchanged part. Recognize chart children // of the first graphic frame in each anchor, just as the chart reader does (Choice/Fallback is one object). + const counts = new WeakMap(); const countXml = (xml) => { const stack = []; let anchor = null, n = 0; @@ -128,10 +129,13 @@ if ((owners.find((s) => s.keep.sourceId === s.id) || owners[0]) !== sh) return 0; const part = parts[path] ?? parts[Object.keys(parts).find((k) => k.toLowerCase() === String(path).toLowerCase())]; if (!part || typeof part !== 'object') return 0; - const data = part.data?.$b64 ?? part.data; + const data = part.data?.$b64 ?? part.data, cached = counts.get(part); + if (cached?.data === data) return cached.n; let xml = data; if (typeof part.data?.$b64 === 'string') { try { xml = atob(data); } catch { xml = ''; } } - return typeof xml === 'string' ? countXml(xml) : 0; + const n = typeof xml === 'string' ? countXml(xml) : 0; + counts.set(part, { data, n }); + return n; }; const chartsOn = (box, rec) => { const list = Array.isArray(rec?.charts) ? rec.charts.filter((x) => x && Array.isArray(x.anchor) && x.anchor.length === 4 && x.anchor.every(Number.isSafeInteger)) : null; From 71e2663b28fc68365592aa140596092f6629ec67 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:38:52 -0400 Subject: [PATCH 076/907] Cover chart counts beyond the thumbnail marker limit --- test/sheet-kind.test.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index aa3c8782e..ed503e0c5 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -796,3 +796,38 @@ test('legacy counting decodes an unchanged part only once and invalidates a repl assert.equal(badgeCount(k.thumb(rec)), 0); assert.equal(decoded, 2); }); + +test('more than 32 charts keep an exact count and prioritize markers that reach the card', async () => { + const X = require('../lib/sheet-xlsx.js'), CX = require('../lib/sheet-chart-xml.js'), FX = require('./fixtures/sheets/charts.js'); + const wb = await X.read(FX.workbook({ kinds: ['column'] })); + const part = wb.keep.parts['xl/drawings/drawing1.xml'], xml = new TextDecoder().decode(part.data); + const a = //.exec(xml)[0]; + const inside = a.replace('6', '1').replace('1', '0'); + part.data = xml.replace(a, a.repeat(39) + inside); + globalThis.GZ_SHEET_CHART_XML = CX; + try { + assert.equal(SD.charts(wb).length, 40, 'no charts disappear at the descriptor limit'); + const s = store(), P = PA.open(s), out = await SD.write(P, KEY, wb, new Map()); + const rec = out.saved.paper, k = await cardKind(); + assert.equal(rec.chartCount, 40); + assert.ok(rec.charts.length <= 32 && JSON.stringify(rec.charts).length < 8000, 'bounded stored descriptors'); + const box = k.thumb(rec); + assert.equal(box.children.filter((e) => e.className === 'gx-th-chart').length, 1, 'chart 40 reaches the card'); + assert.equal(badgeCount(box), 39); + const { DK } = load({ sheets: true }, { local: s, GZ_SHEET_DATA: SD }); + await DK.settled(); + const copy = await DK.get('sheet').copy(rec); + assert.equal(copy.chartCount, 40); + assert.deepEqual(copy.charts, rec.charts); + const B = require('../lib/backup.js'); + const entries = B.pack({ settings: {}, tasks: {}, local: s.data }); + const restored = store(), R = PA.open(restored); + const papers = Object.fromEntries(entries.filter((e) => /^papers\/\d+\.json$/.test(e.name)).map((e) => { + const value = JSON.parse(new TextDecoder().decode(e.data)); + return [value.key ? `writerPaper:${value.key}` : `writerVersions:${KEY}`, value]; + })); + await R.merge(papers); + assert.equal((await R.get(KEY)).chartCount, 40); + assert.deepEqual((await R.get(KEY)).charts, rec.charts); + } finally { delete globalThis.GZ_SHEET_CHART_XML; } +}); From 1bd0f76f942d0e4e7203706d9279e3073b43080b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:39:11 -0400 Subject: [PATCH 077/907] Keep a grant Calendar shares on Disconnect, say when Disconnect fails, and take only string ids from the picker --- i18n/de.js | 3 ++- i18n/es.js | 3 ++- i18n/fr.js | 3 ++- i18n/nl.js | 3 ++- i18n/pt-BR.js | 3 ++- lib/google.js | 17 +++++++++++------ options.js | 1 + 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/i18n/de.js b/i18n/de.js index 538396c8c..777ed081e 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze braucht die Anmeldeberechtigung des Browsers, um sich zu verbinden.", "Connect again and allow Google Drive access.": "Verbinde dich erneut und erlaube den Zugriff auf Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal." + "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal.", + "Couldn't disconnect. Try again in a moment.": "Konnte nicht trennen. Versuch es gleich noch einmal." }; diff --git a/i18n/es.js b/i18n/es.js index 4c9e88b00..1bf89ec29 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze necesita el permiso de inicio de sesión del navegador para conectarse.", "Connect again and allow Google Drive access.": "Vuelve a conectar y permite el acceso a Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento." + "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento.", + "Couldn't disconnect. Try again in a moment.": "No se pudo desconectar. Inténtalo de nuevo en un momento." }; diff --git a/i18n/fr.js b/i18n/fr.js index 55fa9ebab..7c0af3238 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze a besoin de l’autorisation de connexion du navigateur pour se connecter.", "Connect again and allow Google Drive access.": "Reconnecte-toi et autorise l’accès à Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant." + "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant.", + "Couldn't disconnect. Try again in a moment.": "Impossible de se déconnecter. Réessaie dans un instant." }; diff --git a/i18n/nl.js b/i18n/nl.js index 409a7389a..623a2f91a 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze heeft de aanmeldtoestemming van de browser nodig om te verbinden.", "Connect again and allow Google Drive access.": "Verbind opnieuw en sta toegang tot Google Drive toe.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw." + "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw.", + "Couldn't disconnect. Try again in a moment.": "Kon niet loskoppelen. Probeer het zo opnieuw." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 1595d7d42..0e3b504fc 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "O Glaze precisa da permissão de login do navegador para se conectar.", "Connect again and allow Google Drive access.": "Conecte de novo e permita o acesso ao Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco." + "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco.", + "Couldn't disconnect. Try again in a moment.": "Não foi possível desconectar. Tente de novo daqui a pouco." }; diff --git a/lib/google.js b/lib/google.js index 16e3d8599..37037ad01 100644 --- a/lib/google.js +++ b/lib/google.js @@ -65,7 +65,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { try { list = r.action === 'picked' ? JSON.parse(r.files) : null; } catch { /* not JSON */ } if (!Array.isArray(list) || !list.length || list.length > 100) throw new Fail('picker'); return list.map((f) => { - if (!ID.test(f?.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); + if (typeof f?.id !== 'string' || !ID.test(f.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); return { id: f.id, name: f.name, mimeType: f.mimeType }; }); }; @@ -190,9 +190,16 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { }; const account = async () => (await saved())?.email || null; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and - // the identity permission dropped, the account forgotten. → { ok, revoked: whether Google confirmed, had: a token to revoke } + // the identity permission dropped, the account forgotten. While Calendar sync holds the grant too (keep), revoking would + // end its access as well (a revocation covers every scope the project was granted): only Drive's own state goes. + // → { ok, revoked: whether Google confirmed, had: a token to revoke } const disconnect = () => turn(async () => { const g = await saved(); + if (await Promise.resolve().then(() => env.keep?.()).catch(() => true)) { + held = null; + await env.storage.remove('google'); + return { ok: true, revoked: false, had: false }; + } const t = g && usable(g.flow) ? await tokenFor(g.flow, false, g.email).catch(() => null) : null; const r = t && await send(REVOKE, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), @@ -200,10 +207,8 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { held = null; const i = id(); if (t && g.flow === 'chrome') await Promise.resolve().then(() => i?.removeCachedAuthToken({ token: t })).catch(() => {}); - if (!await Promise.resolve().then(() => env.keep?.()).catch(() => false)) { - if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); - await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); - } + if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); await env.storage.remove('google'); return { ok: true, revoked: !!r?.ok, had: !!t }; }); diff --git a/options.js b/options.js index c7052a3b8..c20604fcc 100644 --- a/options.js +++ b/options.js @@ -1188,6 +1188,7 @@ GZ_UI.load().then((s) => { gdBusy = true; try { const r = await gdOp('disconnect'); + if (!r.ok) return hint(t("Couldn't disconnect. Try again in a moment."), true); await gdLoad(); $('#gdrive-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), !!r.had && !r.revoked); From aee44f7c5c8422ca7bf3c32f9ec8253e0cc13488 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:39:15 -0400 Subject: [PATCH 078/907] Keep Firefox lint clean: the chart modules require their files by name --- lib/sheet-chart-xml.js | 11 ++++++----- lib/sheet-chart.js | 7 ++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index 34d66be11..37e0f58ee 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -9,10 +9,11 @@ // to the cache. Colours are '#RRGGBB' from the theme (wb.keep.theme), the series' own, or Excel's automatic order. // Classic script; doctext, the model and the formula module are looked up when a function runs. module.exports for node. var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { - const dep = (name, file) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? require(file) : null); - const D = () => dep('GZ_DOCTEXT', './doctext.js'); - const M = () => dep('GZ_SHEET_MODEL', './sheet-model.js'); - const F = () => dep('GZ_SHEET_FORMULA', './sheet-formula.js'); + // Firefox's linter refuses require() of a variable, so each caller passes a require of its own file. + const dep = (name, load) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? load() : null); + const D = () => dep('GZ_DOCTEXT', () => require('./doctext.js')); + const M = () => dep('GZ_SHEET_MODEL', () => require('./sheet-model.js')); + const F = () => dep('GZ_SHEET_FORMULA', () => require('./sheet-formula.js')); const EMU = 9525; // per CSS pixel const MAX_POINTS = 10000; // one chart's resolved data and SVG must stay bounded @@ -568,7 +569,7 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const plan = (wb, sh, g, kind, show) => { if ((g.r1 - g.r0 + 1) * (g.c1 - g.c0 + 1) > MAX_POINTS) return null; const cell = (r, c) => sh.rows.get(r)?.get(c) ?? null; - const NFm = dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js'); + const NFm = dep('GZ_SHEET_NUMFMT', () => require('./sheet-numfmt.js')); const fmtOf = (x) => { const f = wb.styles?.[x?.s ?? 0]?.fmt; return f === undefined || f === null ? 'General' : NFm.code(f); }; const isNum = (x) => x?.t === 'n', isText = (x) => x?.t === 's' && x.v !== '', blank = (x) => !x || x.t === 'z' || (x.t === 's' && x.v === ''); const isDate = (x) => isNum(x) && NFm.isDate(fmtOf(x)); diff --git a/lib/sheet-chart.js b/lib/sheet-chart.js index 5dc3ec8c8..2517426de 100644 --- a/lib/sheet-chart.js +++ b/lib/sheet-chart.js @@ -6,9 +6,10 @@ // .gc-*). A chart Glaze doesn't draw is a frame with its title and kind. Nothing here writes the file's chart parts. // Classic script, loaded with the grid (sheet-kind.js); module.exports for node. var GZ_SHEET_CHART = globalThis.GZ_SHEET_CHART || (() => { - const dep = (name, file) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? require(file) : null); - const CX = () => dep('GZ_SHEET_CHART_XML', './sheet-chart-xml.js'); - const NF = () => dep('GZ_SHEET_NUMFMT', './sheet-numfmt.js'); + // Firefox's linter refuses require() of a variable, so each caller passes a require of its own file. + const dep = (name, load) => (typeof globalThis[name] !== 'undefined' ? globalThis[name] : typeof module !== 'undefined' ? load() : null); + const CX = () => dep('GZ_SHEET_CHART_XML', () => require('./sheet-chart-xml.js')); + const NF = () => dep('GZ_SHEET_NUMFMT', () => require('./sheet-numfmt.js')); const SVG = 'http://www.w3.org/2000/svg'; const P = 8, TITLE = 14, TICK = 10, LEG = 11, ATITLE = 11; From bbdb6d65cbbc05030f8e6ae752e3df26f1c31f90 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:39:25 -0400 Subject: [PATCH 079/907] Refresh the Safari resource mirror for the Drive review fixes --- .../Glaze/Glaze Extension/Resources/i18n/de.js | 3 ++- .../Glaze/Glaze Extension/Resources/i18n/es.js | 3 ++- .../Glaze/Glaze Extension/Resources/i18n/fr.js | 3 ++- .../Glaze/Glaze Extension/Resources/i18n/nl.js | 3 ++- .../Glaze Extension/Resources/i18n/pt-BR.js | 3 ++- .../Glaze Extension/Resources/lib/google.js | 17 +++++++++++------ .../Glaze/Glaze Extension/Resources/options.js | 1 + 7 files changed, 22 insertions(+), 11 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/de.js b/safari/Glaze/Glaze Extension/Resources/i18n/de.js index 538396c8c..777ed081e 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/de.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/de.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze braucht die Anmeldeberechtigung des Browsers, um sich zu verbinden.", "Connect again and allow Google Drive access.": "Verbinde dich erneut und erlaube den Zugriff auf Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal." + "Google Drive didn't connect. Try again in a moment.": "Google Drive hat sich nicht verbunden. Versuch es gleich noch einmal.", + "Couldn't disconnect. Try again in a moment.": "Konnte nicht trennen. Versuch es gleich noch einmal." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/es.js b/safari/Glaze/Glaze Extension/Resources/i18n/es.js index 4c9e88b00..1bf89ec29 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/es.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/es.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze necesita el permiso de inicio de sesión del navegador para conectarse.", "Connect again and allow Google Drive access.": "Vuelve a conectar y permite el acceso a Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento." + "Google Drive didn't connect. Try again in a moment.": "Google Drive no se conectó. Inténtalo de nuevo en un momento.", + "Couldn't disconnect. Try again in a moment.": "No se pudo desconectar. Inténtalo de nuevo en un momento." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js index 55fa9ebab..7c0af3238 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/fr.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/fr.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze a besoin de l’autorisation de connexion du navigateur pour se connecter.", "Connect again and allow Google Drive access.": "Reconnecte-toi et autorise l’accès à Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant." + "Google Drive didn't connect. Try again in a moment.": "Google Drive ne s’est pas connecté. Réessaie dans un instant.", + "Couldn't disconnect. Try again in a moment.": "Impossible de se déconnecter. Réessaie dans un instant." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js index 409a7389a..623a2f91a 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/nl.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/nl.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "Glaze heeft de aanmeldtoestemming van de browser nodig om te verbinden.", "Connect again and allow Google Drive access.": "Verbind opnieuw en sta toegang tot Google Drive toe.", - "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw." + "Google Drive didn't connect. Try again in a moment.": "Google Drive kon niet verbinden. Probeer het zo opnieuw.", + "Couldn't disconnect. Try again in a moment.": "Kon niet loskoppelen. Probeer het zo opnieuw." }; diff --git a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js index 1595d7d42..0e3b504fc 100644 --- a/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js +++ b/safari/Glaze/Glaze Extension/Resources/i18n/pt-BR.js @@ -3754,5 +3754,6 @@ "Google Drive": "Google Drive", "Glaze needs the browser's sign-in permission to connect.": "O Glaze precisa da permissão de login do navegador para se conectar.", "Connect again and allow Google Drive access.": "Conecte de novo e permita o acesso ao Google Drive.", - "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco." + "Google Drive didn't connect. Try again in a moment.": "O Google Drive não se conectou. Tente de novo daqui a pouco.", + "Couldn't disconnect. Try again in a moment.": "Não foi possível desconectar. Tente de novo daqui a pouco." }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js index 16e3d8599..37037ad01 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -65,7 +65,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { try { list = r.action === 'picked' ? JSON.parse(r.files) : null; } catch { /* not JSON */ } if (!Array.isArray(list) || !list.length || list.length > 100) throw new Fail('picker'); return list.map((f) => { - if (!ID.test(f?.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); + if (typeof f?.id !== 'string' || !ID.test(f.id) || typeof f.name !== 'string' || !f.name || f.name.length > 1000 || !MIME.test(f.mimeType)) throw new Fail('picker'); return { id: f.id, name: f.name, mimeType: f.mimeType }; }); }; @@ -190,9 +190,16 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { }; const account = async () => (await saved())?.email || null; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and - // the identity permission dropped, the account forgotten. → { ok, revoked: whether Google confirmed, had: a token to revoke } + // the identity permission dropped, the account forgotten. While Calendar sync holds the grant too (keep), revoking would + // end its access as well (a revocation covers every scope the project was granted): only Drive's own state goes. + // → { ok, revoked: whether Google confirmed, had: a token to revoke } const disconnect = () => turn(async () => { const g = await saved(); + if (await Promise.resolve().then(() => env.keep?.()).catch(() => true)) { + held = null; + await env.storage.remove('google'); + return { ok: true, revoked: false, had: false }; + } const t = g && usable(g.flow) ? await tokenFor(g.flow, false, g.email).catch(() => null) : null; const r = t && await send(REVOKE, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), @@ -200,10 +207,8 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { held = null; const i = id(); if (t && g.flow === 'chrome') await Promise.resolve().then(() => i?.removeCachedAuthToken({ token: t })).catch(() => {}); - if (!await Promise.resolve().then(() => env.keep?.()).catch(() => false)) { - if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); - await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); - } + if (g?.flow === 'chrome') await Promise.resolve().then(() => i?.clearAllCachedAuthTokens()).catch(() => {}); + await Promise.resolve().then(() => env.permissions.remove({ permissions: ['identity'] })).catch(() => {}); await env.storage.remove('google'); return { ok: true, revoked: !!r?.ok, had: !!t }; }); diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index c7052a3b8..c20604fcc 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1188,6 +1188,7 @@ GZ_UI.load().then((s) => { gdBusy = true; try { const r = await gdOp('disconnect'); + if (!r.ok) return hint(t("Couldn't disconnect. Try again in a moment."), true); await gdLoad(); $('#gdrive-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), !!r.had && !r.revoked); From 865f47b865e3a4cdaa7dbd6971bba491f45ba25c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:39:54 -0400 Subject: [PATCH 080/907] Cover totals from older capped chart summaries --- test/sheet-kind.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index ed503e0c5..91733b665 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -814,6 +814,7 @@ test('more than 32 charts keep an exact count and prioritize markers that reach const box = k.thumb(rec); assert.equal(box.children.filter((e) => e.className === 'gx-th-chart').length, 1, 'chart 40 reaches the card'); assert.equal(badgeCount(box), 39); + assert.equal(badgeCount(k.thumb({ ...rec, chartCount: undefined, charts: SD.charts(wb).slice(0, 32) })), 40, 'old capped summaries recover their total from the drawing'); const { DK } = load({ sheets: true }, { local: s, GZ_SHEET_DATA: SD }); await DK.settled(); const copy = await DK.get('sheet').copy(rec); From 05450455c5c0c7d201cc6764ac541c9032099839 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:40:07 -0400 Subject: [PATCH 081/907] Keep exact chart totals with bounded card markers --- lib/sheet-chart-xml.js | 2 +- sheet-data.js | 7 +++++-- sheet-kind.js | 14 ++++++++------ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/sheet-chart-xml.js b/lib/sheet-chart-xml.js index 719f208c3..ab6c1d552 100644 --- a/lib/sheet-chart-xml.js +++ b/lib/sheet-chart-xml.js @@ -474,7 +474,7 @@ var GZ_SHEET_CHART_XML = globalThis.GZ_SHEET_CHART_XML || (() => { const theme = Array.isArray(wb?.keep?.theme) && wb.keep.theme.length >= 10 ? wb.keep.theme : DEFAULT_THEME; const C = glanceAxis(sh.colW, sh.hidden.cols, 64, MAXC, (w) => Number.isFinite(w) ? Math.max(0, Math.trunc(((256 * w + 18) / 256) * 7)) : 64); const R = glanceAxis(sh.rowH, sh.hidden.rows, 20, MAXR, (h) => Number.isFinite(h) ? Math.max(0, Math.round(h * 4 / 3)) : 20); - return charts(wb, sh).slice(0, 32).map(({ anchor: a, chart: c }) => { + return charts(wb, sh).map(({ anchor: a, chart: c }) => { const x0 = a.from ? C.position(a.from.col, a.from.colOff) : a.pos?.x ?? 0; const y0 = a.from ? R.position(a.from.row, a.from.rowOff) : a.pos?.y ?? 0; const x1 = a.to ? C.position(a.to.col, a.to.colOff) : x0 + (a.ext?.w ?? 0); diff --git a/sheet-data.js b/sheet-data.js index 5a451a3f4..81fbfe7c9 100644 --- a/sheet-data.js +++ b/sheet-data.js @@ -171,8 +171,11 @@ var GZ_SHEET_DATA = globalThis.GZ_SHEET_DATA || (() => { const write = async (papers, key, wb, sigs, { patch = {}, dirty = null, version = null, show = plain, reason, base = sigs?.revision ?? 0 } = {}) => { const p = plan(wb, sigs, dirty); if (!p.changed && !version && !Object.keys(patch).length) return { sigs, saved: null }; - const m = measure(wb); - const saved = await papers.save(key, { kind: 'sheet', ...(p.book && { book: p.book }), grid: glance(wb, show), charts: charts(wb), text: text(wb, show), cells: m.cells, sheets: m.sheets, ...patch }, + const m = measure(wb), allCharts = charts(wb); + // Keep the exact count, but only the top 32 markers that can paint on the card. Off-card descriptors add no value + // to storage or rendering; a later visible chart must not be lost behind earlier off-card ones. + const preview = allCharts?.filter(({ anchor: [r0, c0, r1, c1] }) => r0 <= 3 && c0 <= 5 && r1 >= 0 && c1 >= 0 && r1 >= r0 && c1 >= c0).slice(-32) ?? null; + const saved = await papers.save(key, { kind: 'sheet', ...(p.book && { book: p.book }), grid: glance(wb, show), charts: preview, chartCount: allCharts?.length ?? null, text: text(wb, show), cells: m.cells, sheets: m.sheets, ...patch }, { base, parts: p.parts, drop: p.drop, changed: p.changed, ...(version && { version }), ...(reason && { reason }) }); p.sigs.revision = saved.paper.revision; return { sigs: p.sigs, saved }; diff --git a/sheet-kind.js b/sheet-kind.js index 8881674a4..f646363b7 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -66,7 +66,7 @@ return ws.some((w) => w !== ws[0]) && largest > 0 ? ws.map((w) => w / largest) : null; }; // ---- the card's charts: the record's summary (sheet-data.js charts) as a small picture of each kind over the cells it - // covers in A1:F4, the rest counted in the corner; a record kept before the summary counts its drawing's charts. + // covers in A1:F4, the undrawn charts counted in the corner; a record kept before the summary counts its drawing's charts. const NS = 'http://www.w3.org/2000/svg'; const svgEl = (tag, attrs) => { const e = document.createElementNS(NS, tag); for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, String(v)); return e; }; const HEX = /^#[0-9a-f]{6}$/i; @@ -138,12 +138,14 @@ return n; }; const chartsOn = (box, rec) => { - const list = Array.isArray(rec?.charts) ? rec.charts.filter((x) => x && Array.isArray(x.anchor) && x.anchor.length === 4 && x.anchor.every(Number.isSafeInteger)) : null; - let away = list ? 0 : counted(rec?.book); + const list = Array.isArray(rec?.charts) ? rec.charts.slice(-32).filter((x) => x && Array.isArray(x.anchor) && x.anchor.length === 4 && x.anchor.every(Number.isSafeInteger)) : null; + const kept = Array.isArray(rec?.charts) ? rec.charts.length : 0; + const total = Number.isSafeInteger(rec?.chartCount) && rec.chartCount >= 0 ? rec.chartCount : kept === 32 ? Math.max(kept, counted(rec?.book)) : kept; + let away = list ? Math.max(0, total - list.length) : counted(rec?.book); for (const x of list || []) { const [r0, c0, r1, c1] = x.anchor; if (r0 > 3 || c0 > 5 || r1 < 0 || c1 < 0 || r1 < r0 || c1 < c0) { away++; continue; } - const kind = typeof x.kind === 'string' ? x.kind : 'chart', colours = Array.isArray(x.colours) ? x.colours.filter((c) => HEX.test(c)).slice(0, 5) : []; + const kind = typeof x.kind === 'string' ? x.kind : 'chart', colours = Array.isArray(x.colours) ? x.colours.slice(0, 5).filter((c) => typeof c === 'string' && HEX.test(c)) : []; const m = h('i', { class: 'gx-th-chart', 'data-kind': kind }); m.style.gridRow = `${Math.max(0, r0) + 2} / ${Math.min(3, r1) + 3}`; m.style.gridColumn = `${Math.max(0, c0) + 2} / ${Math.min(5, c1) + 3}`; @@ -180,7 +182,7 @@ const got = rec?.key ? await GZ_SHEET_DATA.read(get, rec.key) : null; if (!got) return {}; const { book, parts } = GZ_SHEET_DATA.split(got.wb); - return { book, grid: got.rec.grid, charts: got.rec.charts, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; + return { book, grid: got.rec.grid, charts: got.rec.charts, chartCount: got.rec.chartCount, text: got.rec.text, cells: got.rec.cells, sheets: got.rec.sheets, parts: Object.fromEntries([...parts].map(([n, rows]) => [n, { rows }])) }; }; // ---- files: .xlsx, .csv and .tsv through GZ_SHEET_XLSX @@ -258,7 +260,7 @@ return notice ? { ...paper, notice } : paper; }; // An empty attempt started as a spreadsheet (writer-kinds.js): what its record takes; the grid opens a blank workbook. - const empty = () => ({ charts: [], grid: Array.from({ length: 4 }, () => Array(6).fill('')), text: '', cells: 0, sheets: 1 }); + const empty = () => ({ charts: [], chartCount: 0, grid: Array.from({ length: 4 }, () => Array(6).fill('')), text: '', cells: 0, sheets: 1 }); const create = () => `papers.html?sheet=${encodeURIComponent(GZ_WRITER_PAPERS.newKey())}&new`; // host: an assignment's frame (view below): { mount, t, title, tools }. → the grid's surface (sheet-grid.js mount). const edit = async (key, { fresh = false, host = null } = {}) => { From ca832fb332688e6ce1ea4137e7844717a115c5aa Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:41:18 -0400 Subject: [PATCH 082/907] Verify chart lifecycle and clipped card rendering --- scripts/documents-check.js | 17 +++++++++ test/sheet-kind.test.js | 74 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/scripts/documents-check.js b/scripts/documents-check.js index 8ee0e17ad..e7d9e1110 100644 --- a/scripts/documents-check.js +++ b/scripts/documents-check.js @@ -747,6 +747,23 @@ const PROBE = ({ lang }) => { }, SHEET_CHARTS); console.log(`50 spreadsheet cards with charts back to back: ${sheetMs.toFixed(3)} ms each`); check(sheetMs > 0 && sheetMs <= 6, `a spreadsheet card with charts costs ${sheetMs.toFixed(3)} ms (50 timed together)`); + const clipped = await sheets.evaluate(() => { + const k = GZ_DOC_KINDS.get('sheet'), results = []; + for (const anchor of [[-2, -2, 1, 2], [3, 5, 8, 10], [0, 0, 3, 5]]) { + const box = k.thumb({ charts: [{ kind: 'column', anchor, colours: ['url(https://example.test/chart)', '#123456'] }] }); + document.body.append(box); + try { + const cells = [...box.querySelectorAll(':scope > .gx-th-c')], a = cells[0].getBoundingClientRect(), z = cells.at(-1).getBoundingClientRect(); + const marker = box.querySelector('.gx-th-chart').getBoundingClientRect(); + results.push(marker.left >= a.left && marker.top >= a.top && marker.right <= z.right && marker.bottom <= z.bottom && marker.width > 0 && marker.height > 0 + && [...box.querySelectorAll('rect')].every((e) => /^#[0-9a-f]{6}$/i.test(e.getAttribute('fill')))); + } finally { box.remove(); } + } + const box = k.thumb({ charts: [{ kind: 'column', anchor: [0, 0, 1, 1], colours: [] }], chartCount: 40 }); + return { bounds: results, count: box.querySelector('.gx-th-more')?.textContent }; + }); + check(clipped.bounds.every(Boolean), `partial chart markers stay within A1:F4 and use hex colours (${JSON.stringify(clipped.bounds)})`); + check(clipped.count === '39', `a bounded chart summary retains its exact total (${clipped.count})`); await sheets.close(); await worker.evaluate(async (keys) => { const gone = new Set(keys); await chrome.storage.local.remove(keys.map((k) => `writerPaper:${k}`)); await chrome.storage.local.set({ writerPapers: (await chrome.storage.local.get('writerPapers')).writerPapers.filter((r) => !gone.has(r.key)) }); }, [SHEET_CHARTS, SHEET_PLAIN]); diff --git a/test/sheet-kind.test.js b/test/sheet-kind.test.js index 91733b665..0a7fdf88f 100644 --- a/test/sheet-kind.test.js +++ b/test/sheet-kind.test.js @@ -832,3 +832,77 @@ test('more than 32 charts keep an exact count and prioritize markers that reach assert.deepEqual((await R.get(KEY)).charts, rec.charts); } finally { delete globalThis.GZ_SHEET_CHART_XML; } }); + +test('chart metadata follows sheet switch, rename, deletion, import and packed restoration', async () => { + const X = require('../lib/sheet-xlsx.js'), CX = require('../lib/sheet-chart-xml.js'), FX = require('./fixtures/sheets/charts.js'); + const wb = await X.read(FX.workbook({ kinds: ['column'] })), s = store(), P = PA.open(s); + globalThis.GZ_SHEET_CHART_XML = CX; + try { + let sigs = new Map(); + const save = async (w) => { const out = await SD.write(P, KEY, w, sigs); sigs = out.sigs; return out.saved?.paper || P.get(KEY); }; + const first = await save(wb), packed = await SD.pack(wb); + assert.equal(first.chartCount, 1); + M.apply(wb, { op: 'rename', sheet: 1, name: 'Sales' }); + assert.equal((await save(wb)).chartCount, 1); + M.apply(wb, { op: 'addSheet', at: 1, active: 1 }); + const plain = await save(wb); + assert.deepEqual(plain.charts, []); assert.equal(plain.chartCount, 0); + wb.active = 0; + assert.equal((await save(wb)).chartCount, 1); + M.apply(wb, { op: 'removeSheet', sheet: 1 }); + assert.equal((await save(wb)).chartCount, 0); + const restored = await save(await SD.unpack(packed)); + assert.equal(restored.chartCount, 1); + const { DK } = load({ sheets: true }, { local: s, GZ_SHEET_DATA: SD, GZ_SHEET_XLSX: X, GZ_SHEET_MODEL: M, + GZ_SHEET_CALC: { engine: () => ({ recalc() {} }) }, GZ_WRITER_PAPERS: { ...PA, open: () => P } }); + await DK.settled(); + // workbookOf takes the codec's real delimited workbook, as an import into this record. + const file = { name: 'plain.csv', size: 10, arrayBuffer: async () => new TextEncoder().encode('No charts\n').buffer }; + assert.deepEqual((await DK.get('sheet').into(KEY, file)).charts, []); + const version = (await P.versions(KEY)).find((v) => v.reason === 'import'); + const back = await SD.unpack((await P.version(KEY, version.id)).gz); + assert.equal(SD.charts(back).length, 1, 'import checkpoint keeps the chart workbook'); + sigs = (await SD.read((ks) => s.get(ks), KEY)).sigs; + assert.equal((await save(back)).chartCount, 1); + } finally { delete globalThis.GZ_SHEET_CHART_XML; } +}); + +test('card clipping, colours and cell text remain safe for untrusted file values', async () => { + const k = await cardKind(); + const box = k.thumb({ grid: [['']], charts: [ + { kind: 'column', anchor: [-2, -2, 1, 2], colours: ['url(https://example.test/a)', '#123456', '\" onload=\"alert(1)', '#abcdef', ''] }, + { kind: 'line', anchor: [3, 5, 8, 10], colours: ['url(javascript:alert(1))'] }, + { kind: 'column', anchor: [2, 2, 1, 1], colours: [] }, + ] }); + const markers = box.children.filter((e) => e.className === 'gx-th-chart'); + assert.deepEqual(markers.map((e) => [e.style.gridRow, e.style.gridColumn]), [['2 / 4', '2 / 5'], ['5 / 6', '7 / 8']]); + assert.equal(badgeCount(box), 1); + assert.equal(box.children.find((e) => e.className === 'gx-th-c').textContent, ''); + for (const m of markers) for (const shape of m.children[0].children) for (const attr of ['fill', 'stroke']) { + const value = shape.attrs[attr]; + assert.ok(value === undefined || value === 'none' || /^#[0-9a-f]{6}$/i.test(value), `${attr}: ${value}`); + } +}); + +test('legacy cards ignore chartsheets and other worksheets drawing parts', async () => { + const X = require('../lib/sheet-xlsx.js'), FX = require('./fixtures/sheets/charts.js'); + const original = await X.read(FX.workbook({ kinds: ['column'] })); + const drawing = new TextDecoder().decode(original.keep.parts['xl/drawings/drawing1.xml'].data); + const frame = //.exec(drawing)[0]; + const ns = 'xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'; + const wb = await X.read(FX.workbook({ kinds: ['column'], over: { + 'xl/workbook.xml': ``, + 'xl/_rels/workbook.xml.rels': FX.rels([['rId1', 'worksheet', 'worksheets/sheet1.xml'], ['rId2', 'chartsheet', 'chartsheets/sheet2.xml'], ['rId3', 'worksheet', 'worksheets/sheet3.xml'], ['rId4', 'sharedStrings', 'sharedStrings.xml']]), + 'xl/chartsheets/sheet2.xml': ``, + 'xl/chartsheets/_rels/sheet2.xml.rels': FX.rels([['rId1', 'drawing', '../drawings/drawing2.xml']]), + 'xl/worksheets/sheet3.xml': ``, + 'xl/worksheets/_rels/sheet3.xml.rels': FX.rels([['rId1', 'drawing', '../drawings/drawing3.xml']]), + 'xl/drawings/drawing2.xml': drawing.replace(frame, frame.repeat(3)), + 'xl/drawings/drawing3.xml': drawing.replace(frame, frame.repeat(2)), + } })); + const k = await cardKind(), rec = () => ({ book: JSON.parse(JSON.stringify(SD.split(wb).book)) }); + assert.equal(wb.sheets.length, 2, 'the model contains worksheets only'); + assert.equal(badgeCount(k.thumb(rec())), 1, 'the active chartsheet does not leak its drawing into Data'); + wb.active = 1; + assert.equal(badgeCount(k.thumb(rec())), 2, 'Other counts only its own drawing'); +}); From aa091ae5536f0fc128bbfe46929dec19189c33ab Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:41:50 -0400 Subject: [PATCH 083/907] The pre-store backup reader is a fixture, so a shallow checkout runs the backup test --- test/backup-files.test.js | 6 +- test/fixtures/backup-before-store.js | 654 +++++++++++++++++++++++++++ 2 files changed, 657 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/backup-before-store.js diff --git a/test/backup-files.test.js b/test/backup-files.test.js index 7e024d577..72cf098eb 100644 --- a/test/backup-files.test.js +++ b/test/backup-files.test.js @@ -13,7 +13,7 @@ const D = require('../defaults.js'); const { fakeIDB } = require('./helpers/fake-idb.cjs'); const fs = require('node:fs'); const vm = require('node:vm'); -const { execFileSync } = require('node:child_process'); +const path = require('node:path'); globalThis.GZ_WRITER_STYLES = require('../lib/writer-styles.js'); const NOW = Date.now() - 60000; @@ -166,8 +166,8 @@ test('a reader from before the store sees only base64 records in papers, and no test('a reader from before the store refuses a backup whose documents it would silently omit', async () => { const a = device(); await seeded(a); - // The immutable main revision merged into this lane: the 4.0.0 reader before the file store. - const oldSource = execFileSync('git', ['show', '8434813b:lib/backup.js'], { cwd: __dirname, encoding: 'utf8' }); + // The 4.0.0 reader before the file store, lib/backup.js as of 8434813b, kept as a file: CI's shallow checkout lacks it. + const oldSource = fs.readFileSync(path.join(__dirname, 'fixtures', 'backup-before-store.js'), 'utf8'); const old = { exports: {} }; vm.runInNewContext(oldSource, { module: old, require: (p) => require(`../lib/${p}`), Blob, TextEncoder, TextDecoder, Uint8Array, DataView, URL }); const { file } = await backUp(a); diff --git a/test/fixtures/backup-before-store.js b/test/fixtures/backup-before-store.js new file mode 100644 index 000000000..85b8189bc --- /dev/null +++ b/test/fixtures/backup-before-store.js @@ -0,0 +1,654 @@ +// One backup file (Settings → Privacy and data → Backup and reset, options.js): a zip of glaze-backup.json (the categories +// below), share-keys.json and each Notes account's own export (lib/notes.js exportZip) under notes//. Restore also takes +// a settings file (glaze-settings.json) and a Notes zip from before. All of a file is read and checked before anything is +// written; each category then joins what is here by its own store's rules. What Glaze read from a school (caches, course +// names, card pictures) stays out. Classic script for the editor and the worker (lib/background.js 'glaze:backup', apply); +// module.exports for node. +var GZ_BACKUP = globalThis.GZ_BACKUP || (() => { + // Each library as the page or the worker loaded it (a script's const is no property of globalThis), or node's. + const LIBS = { + GZ_TODO: () => (typeof GZ_TODO !== 'undefined' ? GZ_TODO : require('./todo.js')), GZ_NOTES: () => (typeof GZ_NOTES !== 'undefined' ? GZ_NOTES : require('./notes.js')), + GZ_DOCTEXT: () => (typeof GZ_DOCTEXT !== 'undefined' ? GZ_DOCTEXT : require('./doctext.js')), GZ_WIDGETS: () => (typeof GZ_WIDGETS !== 'undefined' ? GZ_WIDGETS : require('./widgets.js')), + GZ_CLASSES: () => (typeof GZ_CLASSES !== 'undefined' ? GZ_CLASSES : require('./classes.js')), GZ_GPA: () => (typeof GZ_GPA !== 'undefined' ? GZ_GPA : require('./gpa.js')), + GZ_SHARING: () => (typeof GZ_SHARING !== 'undefined' ? GZ_SHARING : require('./sharing.js')), GZ_RELAY: () => (typeof GZ_RELAY !== 'undefined' ? GZ_RELAY : require('./relay.js')), + GZ_HISTORY: () => (typeof GZ_HISTORY !== 'undefined' ? GZ_HISTORY : require('./history.js')), GZ_PET: () => (typeof GZ_PET !== 'undefined' ? GZ_PET : require('./pet.js')), + GZ_XPARTS: () => (typeof GZ_XPARTS !== 'undefined' ? GZ_XPARTS : require('./lookparts.js')), + }; + const lib = (name) => LIBS[name](); + const FORMAT = 'glaze-backup', VERSION = 1, MANIFEST = 'glaze-backup.json', KEYS = 'share-keys.json'; + // Refused by size before being read: a settings file, and a backup (two Notes accounts at their cap, and the rest). + const LIMIT = { json: 16 << 20, member: 64 << 20, command: 32 << 20, zip: 512 << 20, entries: 40000, records: 1000 }; + // In the order the restore list shows them: two columns, row by row. + const CATS = ['settings', 'notes', 'tasks', 'dashNotes', 'classes', 'gpa', 'widgets', 'shareKeys', 'papers', 'writerWords', 'sources']; + // The stores' own keys (lib/widgets.js NOTES_KEY, lib/classes.js STORE_KEY, lib/gpa.js KEY, lib/notes.js KEY; test/backup.test.js + // keeps them equal): the editor doesn't load those libraries. + const STORES = { + dashNotes: /^notes:https?:\/\/[a-z\d.-]+(?::\d+)?\/(\d+|_\d+_\d+)$/i, + classes: /^classes:https?:\/\/[a-z\d.-]+(?::\d+)?\/(?:\d+|_\d+_\d+)$/i, + gpa: /^gpaTerms:https?:\/\/[a-z\d.-]+(?::\d+)?:\w{1,40}$/i, + }; + const ACCOUNT = /^notesIdx:https?:\/\/[a-z\d.-]+(?::\d+)?\/(\d{1,20}|_\d{1,20}_\d{1,20})$/i; + const obj = (v) => !!v && typeof v === 'object' && !Array.isArray(v); + const fail = (why) => Object.assign(new Error(why), { why }); + // JSON.parse, refusing a key that could reach an object's prototype anywhere in it. + const BAD = new Set(['__proto__', 'constructor', 'prototype']); + const parse = (text) => JSON.parse(text, (k, v) => { if (BAD.has(k)) throw fail('bad'); return v; }); + const keyList = (raw) => { + if (!Array.isArray(raw) || raw.length > 20 || raw.some((x) => { + if (!obj(x) || (x.type !== null && x.type !== 'collection')) return true; + const k = lib('GZ_SHARING').importKeys({ ...x, type: null }, true); + return !k || lib('GZ_RELAY').normalize(k.server) !== k.server; + })) throw fail('bad'); + return raw; + }; + // Read only the directory and one member at a time. Reuse the ZIP reader's CRC, header and bounded inflate checks; + // rebuilding a one-member directory lets it release each inflated member instead of caching the whole archive. + const archive = async (file) => { + const Z = lib('GZ_DOCTEXT'); + if (!file.slice) file = new Blob([await file.arrayBuffer()]); // node fixtures; browser Files always have slice + const bytes = async (a, b) => new Uint8Array(await file.slice(a, b).arrayBuffer()); + const tailAt = Math.max(0, file.size - 65557), tail = await bytes(tailAt, file.size), tv = new DataView(tail.buffer); + let end = -1; + for (let i = tail.length - 22; i >= 0; i--) if (tv.getUint32(i, true) === 0x06054b50 && i + 22 + tv.getUint16(i + 20, true) === tail.length) { end = i; break; } + if (end < 0) throw fail('bad'); + const count = tv.getUint16(end + 10, true), size = tv.getUint32(end + 12, true), at = tv.getUint32(end + 16, true); + if (tv.getUint16(end + 4, true) || tv.getUint16(end + 6, true) || tv.getUint16(end + 8, true) !== count + || count > LIMIT.entries || size > LIMIT.json || at + size !== tailAt + end) throw fail('bad'); + const cd = await bytes(at, at + size), v = new DataView(cd.buffer), entries = new Map(); + let p = 0, total = 0; + for (let i = 0; i < count; i++) { + if (p + 46 > size || v.getUint32(p, true) !== 0x02014b50) throw fail('bad'); + const n = v.getUint16(p + 28, true), next = p + 46 + n + v.getUint16(p + 30, true) + v.getUint16(p + 32, true); + if (next > size || v.getUint16(p + 34, true)) throw fail('bad'); + const name = new TextDecoder().decode(cd.subarray(p + 46, p + 46 + n)); + const json = name === MANIFEST || name === KEYS || /^(?:notes\/\d+\/)?notes\.json$/.test(name); + const length = v.getUint32(p + 24, true), compressed = v.getUint32(p + 20, true), start = v.getUint32(p + 42, true); + if (length > (json ? LIMIT.json : LIMIT.member) || compressed > LIMIT.member || (total += length) > LIMIT.zip) throw fail('big'); + if (entries.has(name) || start + 30 + compressed > at || name.split('/').includes('..')) throw fail('bad'); + entries.set(name, { header: cd.slice(p, next), start, compressed }); + p = next; + } + if (p !== size) throw fail('bad'); + const raw = async (name) => { + const e = entries.get(name); + if (!e) return null; + const h = await bytes(e.start, e.start + 30), hv = new DataView(h.buffer); + const length = 30 + hv.getUint16(26, true) + hv.getUint16(28, true) + e.compressed; + const descriptor = (hv.getUint16(6, true) & 8) ? 16 : 0; + if (e.start + length > at) throw fail('bad'); + const member = await bytes(e.start, Math.min(at, e.start + length + descriptor)); + const out = new Uint8Array(member.length + e.header.length + 22); + out.set(member); out.set(e.header, member.length); + const ov = new DataView(out.buffer), stop = member.length + e.header.length; + ov.setUint32(member.length + 42, 0, true); + ov.setUint32(stop, 0x06054b50, true); ov.setUint16(stop + 8, 1, true); ov.setUint16(stop + 10, 1, true); + ov.setUint32(stop + 12, e.header.length, true); ov.setUint32(stop + 16, member.length, true); + return Z.unzip(out, { limits: { input: out.length, entry: LIMIT.member, total: LIMIT.member, entries: 1 } }).bytes(name); + }; + return { names: [...entries.keys()], bytes: raw, text: async (name) => { const b = await raw(name); return b && new TextDecoder().decode(b); } }; + }; + // A store's records by key: every key the store's, every value an object, no more than LIMIT.records. + const records = (raw, re) => { + if (!obj(raw)) throw fail('bad'); + const out = Object.entries(raw); + if (out.length > LIMIT.records || out.some(([k, v]) => !re.test(k) || !obj(v))) throw fail('bad'); + return Object.fromEntries(out); + }; + const whole = (category, records) => { + for (const raw of Object.values(records)) { + if (category === 'classes') { + const clean = lib('GZ_CLASSES').clean(raw), courses = raw.courses ?? []; + if (!Array.isArray(courses) || courses.length !== clean.courses.length || courses.some((c, i) => !Array.isArray(c.periods) || c.periods.length !== clean.courses[i].periods.length)) throw fail('bad'); + } else if (category === 'gpa') { + const clean = lib('GZ_GPA').clean(raw), terms = raw.t ?? []; + if (!Array.isArray(terms) || terms.length !== clean.t.length || terms.some((t, i) => t.c !== undefined && (!Array.isArray(t.c) || t.c.length !== clean.t[i].c.length))) throw fail('bad'); + } + } + return records; + }; + const paperKey = /^(writerPaper:|writerVersions:)(?:[a-z\d.-]+(?::\d{1,5})?\|_\d+_\d+\|_\d+_\d+\|_\d+_\d+|recovery:[a-z\d-]+|doc:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})$|^writerPresets:[a-z\d.-]+(?::\d{1,5})?\|_\d+_\d+$/i; + // The kinds of document this build keeps (lib/doc-kinds.js KINDS; test/backup.test.js holds them equal). A spreadsheet's + // record goes into its backup file with its bands (writerSheet:::, lib/writer-papers.js PART) as $parts, + // so one restore step writes them together; a PDF's or deck's record holds its file (base64, at most 3 MB of it). + const KINDS = ['paper', 'sheet', 'pdf', 'slides'], PART = 'writerSheet:', PART_NAME = /^\d{1,9}:\d{1,5}$/; + const kindOf = (v) => (obj(v) && typeof v.kind === 'string' ? v.kind : 'paper'); + const paperVersion = (v) => !obj(v) || v.kind === undefined || KINDS.includes(v.kind); + const B64 = /^[A-Za-z0-9+/]*={0,2}$/, FILE_MAX = 4e6, EXT = { pdf: ['.pdf'], slides: ['.pptx', '.ppsx'] }; + const count = (n) => n == null || (Number.isSafeInteger(n) && n >= 0); + const text = (s, max) => s == null || (typeof s === 'string' && s.length <= max); + const base64 = (s, max) => typeof s === 'string' && s.length > 0 && s.length <= max && s.length % 4 === 0 && B64.test(s); + const decodedSize = (s) => s.length / 4 * 3 - (s.endsWith('==') ? 2 : s.endsWith('=') ? 1 : 0); + const thumbnail = (s) => { + if (s == null) return true; + const m = typeof s === 'string' && /^data:image\/(?:png|jpeg|webp);base64,(.*)$/.exec(s); + return !!m && s.length <= 8e6 && base64(m[1], 8e6); + }; + // A PDF's or deck's fields (pdf-kind.js, slides-kind.js STORE): the file, its size, its first page's picture, what was read. + const fileFields = (kind, v) => base64(v.file, FILE_MAX) && (v.size == null || (Number.isSafeInteger(v.size) && v.size > 0 && v.size <= 3e6 && v.size === decodedSize(v.file))) + && (v.ext == null || EXT[kind].includes(v.ext)) && text(v.text, 2e5) && count(v.pages) && count(v.slides) && count(v.words) + && thumbnail(v.thumb); + // A spreadsheet's record (sheet-data.js): its book, card text and counts, and each band the record names, at the revision + // that wrote it. + const glance = (g) => g == null || (Array.isArray(g) && g.length === 4 && g.every((row) => Array.isArray(row) && row.length === 6 && row.every((x) => typeof x === 'string' && x.length <= 40))); + // sheet-data.js join builds Maps from these tuples. Reject shapes that would throw, overwrite cells, or leave a + // band's cells behind because the workbook has no matching sheet. + const bandRows = (name, rows, book) => { + const [sheet, band] = name.split(':'), lo = Number(band) * 1000; + return Array.isArray(book?.sheets) && book.sheets.some((s) => String(s?.id) === sheet) && Array.isArray(rows) + && rows.every((row, i) => Array.isArray(row) && row.length === 2 && Number.isSafeInteger(row[0]) && row[0] >= lo && row[0] < Math.min(lo + 1000, 1048576) + && (!i || row[0] > rows[i - 1][0]) && Array.isArray(row[1]) + && row[1].every((cell, j) => Array.isArray(cell) && cell.length >= 2 && cell.length <= 10 && Number.isSafeInteger(cell[0]) + && cell[0] >= 0 && cell[0] < 16384 && (!j || cell[0] > row[1][j - 1][0]))); + }; + const sheetFields = (v) => { + const names = Object.keys(v.parts ?? {}), bands = v.$parts ?? {}; + return (v.book == null || obj(v.book)) && glance(v.grid) && text(v.text, 2e4) && count(v.cells) && count(v.sheets) + && (v.parts == null || obj(v.parts)) && obj(bands) && names.every((n) => PART_NAME.test(n) && Number.isSafeInteger(v.parts[n])) + && Object.keys(bands).length === names.length && names.every((n) => obj(bands[n]) && bands[n].rev === v.parts[n] && bandRows(n, bands[n].rows, v.book) && Object.keys(bands[n]).every((k) => k === 'rows' || k === 'rev')); + }; + const paperLibraries = async () => { + if (typeof module !== 'undefined') return { P: require('./writer-papers.js'), S: require('./writer-schema.js'), ST: require('./writer-styles.js'), SR: require('./writer-sources.js') }; + if (!globalThis.GZ_WRITER_SCHEMA || !globalThis.GZ_WRITER_PAPERS || !globalThis.GZ_WRITER_STYLES || !globalThis.GZ_WRITER_SOURCES) { + const files = ['lib/pm.js', 'lib/writer-schema.js', 'lib/writer-styles.js', 'lib/writer-papers.js', 'lib/writer-sources.js']; + if (typeof importScripts === 'function') importScripts(...files.map((f) => chrome.runtime.getURL(f))); + else for (const file of files) await new Promise((ok, no) => { const script = document.createElement('script'); script.src = chrome.runtime.getURL(file); script.onload = ok; script.onerror = no; document.head.append(script); }); + } + return { P: GZ_WRITER_PAPERS, S: GZ_WRITER_SCHEMA, ST: GZ_WRITER_STYLES, SR: GZ_WRITER_SOURCES }; + }; + const paperRecords = async (raw) => { + if (!obj(raw) || Object.keys(raw).length > 6000) throw fail('bad'); + const { P, S, ST } = await paperLibraries(); + const doc = (json) => { + const node = S.paper.nodeFromJSON(json); node.check(); + const { setup, paste } = node.attrs; + if (setup != null && JSON.stringify(ST.setup(setup)) !== JSON.stringify(setup) || ![null, 'keep', 'match'].includes(paste)) throw fail('bad'); + const parsed = S.parse(S.paper, S.serialize(S.paper, node), { document: true }); + // HTML carries block/run attributes; page setup and the paste choice live only on the document. + if (!parsed.type.create(node.attrs, parsed.content).eq(node)) throw fail('bad'); + }; + // A kind this build doesn't know (a later one's) is left out with its versions, so the rest still restores. + const later = new Set(Object.entries(raw).filter(([key, value]) => key.startsWith('writerPaper:') && obj(value) && !KINDS.includes(kindOf(value))).map(([key]) => key.slice(12))); + const out = {}; + const size = (v) => new TextEncoder().encode(JSON.stringify(v)).length; + for (const [key, original] of Object.entries(raw)) { + const value = key.startsWith('writerVersions:') && Array.isArray(original) ? original.filter(paperVersion) : original; + if (!paperKey.test(key)) throw fail('bad'); + if (later.has(key.replace(/^writer(?:Paper|Versions):/, ''))) continue; + out[key] = value; + if (key.startsWith('writerPaper:')) { + if (!obj(value) || value.key !== key.slice(12) || !Number.isFinite(value.updated) || (value.title !== undefined && (typeof value.title !== 'string' || value.title.length > 1000))) throw fail('bad'); + const kind = kindOf(value); + if (kind !== 'sheet' && value.$parts !== undefined) throw fail('bad'); + // Another kind's record holds no document; one left there by an older save is checked as a paper's is. + if (kind === 'paper' || value.doc != null) doc(value.doc); + if (kind !== 'paper' && !(kind === 'sheet' ? sheetFields(value) : fileFields(kind, value))) throw fail('bad'); + } else if (key.startsWith('writerVersions:')) { + if (!Array.isArray(value) || value.length > 100) throw fail('bad'); + for (const v of value) { + if (!obj(v) || typeof v.id !== 'string' || !Number.isFinite(v.at)) throw fail('bad'); + const kind = kindOf(v); + // A spreadsheet's version is its workbook packed (gz); a PDF's or deck's, its file; a paper's, its document. + if (kind !== 'paper' && v.doc != null) doc(v.doc); + if (kind === 'sheet') { if (!base64(v.gz, 16e6) || !count(v.cells)) throw fail('bad'); v.bytes = v.gz.length; } + else if (kind === 'paper') { doc(v.doc); v.bytes = size(v.doc); } + else { if (!fileFields(kind, v)) throw fail('bad'); v.bytes = size(v.doc ?? null) + size(Object.fromEntries(['file', 'size', 'pages', 'thumb', 'ext', 'slides', 'text'].filter((f) => v[f] !== undefined).map((f) => [f, v[f]]))); } + } + } else if (!Array.isArray(value) || value.length > P.CAP.presets || value.some((v) => !ST.custom(v))) throw fail('bad'); + } + return out; + }; + // The writer's personal dictionary (writer-proof.js 'writerWords': words, the newest last) and each box's saved sources + // (lib/writer-sources.js `writer:sources:|||` → { sources, at }). + const WORDS = { max: 5000, length: 60 }; + const sourceKey = /^writer:sources:(?:[\w.!~*'()%-]{1,2700}\|){3}[\w.!~*'()%-]{1,2700}$/; + const wordsOf = (raw) => { + if (!Array.isArray(raw) || raw.length > WORDS.max || raw.some((w) => typeof w !== 'string' || !w.trim() || w !== w.trim() || w.length > WORDS.length)) throw fail('bad'); + return raw; + }; + const sourcesOf = async (raw) => { + if (!obj(raw) || Object.keys(raw).length > 6000) throw fail('bad'); + const { SR } = await paperLibraries(); + for (const [key, value] of Object.entries(raw)) { + if (!sourceKey.test(key) || !obj(value) || !Number.isFinite(value.at) || !Array.isArray(value.sources) || value.sources.length > SR.MAX.sources + || value.sources.some((x) => SR.normalize(x)?.id !== x?.id)) throw fail('bad'); + } + return raw; + }; + const tasksOf = (raw) => { + if (!obj(raw) || (raw.tasks !== undefined && !obj(raw.tasks)) || (raw.pets !== undefined && !obj(raw.pets))) throw fail('bad'); + if (raw.pets !== undefined && !lib('GZ_PET').backup(raw.pets)) throw fail('bad'); + const T = lib('GZ_TODO'); + return { tasks: Object.fromEntries(Object.entries(raw.tasks || {}).filter(([k]) => T.STORE_KEY.test(k))), ...(raw.pets !== undefined && { pets: raw.pets }) }; + }; + const filled = (v) => obj(v) && Object.keys(v).length > 0; + const looksLikeSettings = (raw) => obj(raw) && ['colors', 'look', 'bg', 'motion', 'presets'].some((k) => k in raw); + const settingsOf = (raw) => { + if (!looksLikeSettings(raw)) throw fail('bad'); + if (raw.parts != null) { + const P = lib('GZ_XPARTS'), parts = P.bundle(raw.parts); + if (!parts || !P.same(parts, raw.parts)) throw fail('bad'); + } + return raw; + }; + + // ---- reading a file: → { kind: 'backup' | 'settings' | 'notes', cats, zip? } or throws, with why: 'big', 'json' (not + // JSON), 'none' (no Glaze data), 'newer' (a later format), 'bad' (damaged or not Glaze's). + // A settings file from Export settings: its settings, its tasks and pets, its share keys. + const settingsFile = (raw) => { + if (!looksLikeSettings(raw)) throw fail('none'); + const { tasks, pets, shareKeys, ...settings } = raw; + const cats = { settings: settingsOf(settings) }; + const taskData = tasksOf({ tasks: tasks ?? {}, ...(pets !== undefined && { pets }) }); + if (filled(taskData.tasks) || filled(taskData.pets)) cats.tasks = taskData; + if (shareKeys !== undefined) keyList(shareKeys); + if (shareKeys?.length) cats.shareKeys = shareKeys; + return { kind: 'settings', cats }; + }; + // A Notes export in the zip (dir: '' or notes//): its notes.json checked as an import checks it, and every file it + // declares there, whole. + const checked = async (zip, dir, account = '') => { + const N = lib('GZ_NOTES'); + const json = await zip.text(`${dir}notes.json`); + if (typeof json !== 'string') throw fail('bad'); + const raw = parse(json); + let data; + try { data = N.parseExport(raw, { site: raw.site }); } catch { throw fail('bad'); } + if (account && raw.site !== new URL(account.slice(9)).origin) throw fail('bad'); + for (const k of ['notes', 'folders', 'files']) if (!Array.isArray(raw[k]) || raw[k].length !== data[k].length) throw fail('bad'); + const files = new Set(data.files.map((f) => f.id)); + if (data.notes.some((n) => n.big || n.html.length > N.LIMIT.body) || raw.notes.some((n) => [...N.refs(n.html)].some((id) => !files.has(id)))) throw fail('bad'); + for (const f of data.files) { + const bytes = await zip.bytes(dir + f.path); + if (bytes?.length !== f.size || (f.kind === 'image' && !N.imageType(bytes))) throw fail('bad'); + } + return { site: typeof raw.site === 'string' ? raw.site : '', count: data.notes.length }; + }; + const read = async (file) => { + const zipped = /\.zip$/i.test(file.name || '') || file.type === 'application/zip'; + if (file.size > (zipped ? LIMIT.zip : LIMIT.json)) throw fail('big'); + if (!zipped) { + let raw; + try { raw = parse(await file.text()); } catch (e) { throw fail(e.why || 'json'); } + return settingsFile(raw); + } + let zip; + try { zip = await archive(file); } catch (e) { throw e.why ? e : fail('bad'); } + try { + const keysText = await zip.text(KEYS); + const shareKeys = typeof keysText === 'string' ? parse(keysText) : undefined; + if (shareKeys !== undefined) keyList(shareKeys); + const keys = shareKeys?.length ? { shareKeys } : {}; + const manifest = await zip.text(MANIFEST); + // A Notes zip from before: one account's notes, for an account on this device the student picks. + if (typeof manifest !== 'string') { + const n = await checked(zip, ''); + return { kind: 'notes', cats: { notes: [{ account: '', dir: '', site: n.site, count: n.count }], ...keys }, zip }; + } + const m = parse(manifest); + if (!obj(m) || m.format !== FORMAT || !Number.isInteger(m.version)) throw fail('bad'); + if (m.version > VERSION) throw fail('newer'); + if (m.version !== VERSION) throw fail('bad'); + const cats = {}; + if (m.settings !== undefined) cats.settings = settingsOf(m.settings); + if (m.tasks !== undefined) { const r = tasksOf(m.tasks); if (filled(r.tasks) || filled(r.pets)) cats.tasks = r; } + for (const c of ['dashNotes', 'classes', 'gpa']) if (m[c] !== undefined) { const r = whole(c, records(m[c], STORES[c])); if (Object.keys(r).length) cats[c] = r; } + if (m.papers !== undefined) { + if (!Array.isArray(m.papers) || m.papers.length > 6000) throw fail('bad'); + const raw = {}; + for (const row of m.papers) { if (!obj(row) || !paperKey.test(row.key) || !/^papers\/\d+\.json$/.test(row.file) || Object.hasOwn(raw, row.key)) throw fail('bad'); raw[row.key] = parse(await zip.text(row.file)); } + const r = await paperRecords(raw); + if (Object.keys(r).length) cats.papers = r; + } + if (m.writerWords !== undefined) { const r = wordsOf(m.writerWords); if (r.length) cats.writerWords = r; } + if (m.sources !== undefined) { const r = await sourcesOf(m.sources); if (Object.keys(r).length) cats.sources = r; } + if (m.widgets !== undefined) { if (!obj(m.widgets)) throw fail('bad'); cats.widgets = m.widgets; } + if (m.notes !== undefined) { + if (!Array.isArray(m.notes) || m.notes.length > 100) throw fail('bad'); + const seen = new Set(), notes = []; + for (const a of m.notes) { + if (!obj(a) || typeof a.account !== 'string' || !ACCOUNT.test(a.account) || typeof a.dir !== 'string' || !/^notes\/\d{1,3}\/$/.test(a.dir) + || seen.has(a.account) || seen.has(a.dir)) throw fail('bad'); + seen.add(a.account).add(a.dir); + notes.push({ account: a.account, dir: a.dir, count: (await checked(zip, a.dir, a.account)).count }); + } + if (notes.length) cats.notes = notes; + } + Object.assign(cats, keys); + if (!Object.keys(cats).length) throw fail('none'); + return { kind: 'backup', cats, zip }; + } catch (e) { throw e.why ? e : fail('bad'); } + }; + + // ---- making one ------------------------------------------------------------------------------------------------ + // One Notes account as its export's entries (lib/notes.js exportZip), read through the worker one note at a time (Chrome's + // message limit is smaller than a Notes account), each at the revision listed: a mixed-revision archive is not a backup. + // send(op, rest) → the worker's answer, throwing when it isn't ok. + const notesOut = async (account, send, step = () => {}) => { + const N = lib('GZ_NOTES'); + const data = (await send('backupNotes', { key: account })).data, seen = new Set(); + for (const n of data.notes) { + step(); + const body = await send('notesRead', { key: account, id: n.id }); + if (body.rev !== n.rev) throw fail('changed'); + n.html = body.html; + for (const id of N.refs(n.html)) if (!seen.has(id)) { + seen.add(id); + const f = await send('notesGetFile', { key: account, id }); + data.files.push({ id, ...f, bytes: N.unb64(f.data) }); + } + } + return N.exportZip(data); + }; + // parts: { settings, tasks: { tasks, pets }, local (storage.local, all of it), notes: [{ account, entries }], keys (share + // keys), version (Glaze's) } → the zip's entries for GZ_ZIP.zip. + const pack = ({ settings, tasks, local, notes = [], keys = [], version = '' }, date = new Date()) => { + const pick = (re) => Object.fromEntries(Object.entries(local).filter(([k, v]) => re.test(k) && obj(v))); + const enc = new TextEncoder(); + const dirs = notes.map((n, i) => ({ account: n.account, dir: `notes/${i + 1}/` })); + // Every kind's record and versions; a spreadsheet's record with the bands it names ($parts), never a stale one. + const withBands = (key, value) => { + if (!key.startsWith('writerPaper:') || kindOf(value) !== 'sheet') return value; + const { partDrops, ...rec } = value, k = key.slice(12); + const bands = Object.fromEntries(Object.keys(obj(rec.parts) ? rec.parts : {}).map((n) => [n, local[`${PART}${k}:${n}`]])); + // A successful backup must not silently leave a document behind when one of its named bands is missing. + if (Object.values(bands).some((p) => !obj(p))) throw fail('bad'); + return { ...rec, $parts: bands }; + }; + const papers = Object.entries(local).filter(([key]) => paperKey.test(key)) + .map(([key, value], i) => ({ key, value: key.startsWith('writerVersions:') && Array.isArray(value) ? value.filter(paperVersion) : withBands(key, value), file: `papers/${i + 1}.json` })); + // Restore stages each record through the worker's 32 MiB write fence (lib/background.js BROKER.backup.allow). + // Count the whole command, including its UUID token and wrapper, as that fence does: JSON characters, not bytes. + if (papers.some(({ key, value }) => JSON.stringify({ op: 'stage', token: '0'.repeat(36), action: 'own', value: { papers: { [key]: value } } }).length > LIMIT.command)) throw fail('big'); + const words = Array.isArray(local.writerWords) ? local.writerWords.filter((w) => typeof w === 'string' && w.trim() === w && w && w.length <= WORDS.length).slice(-WORDS.max) : []; + const sources = pick(sourceKey); + const m = { format: FORMAT, version: VERSION, made: date.toISOString(), glaze: version, settings, tasks, + dashNotes: pick(STORES.dashNotes), classes: pick(STORES.classes), gpa: pick(STORES.gpa), + ...(obj(local.widgetLayout) && { widgets: local.widgetLayout }), notes: dirs, ...(papers.length && { papers: papers.map(({ key, file }) => ({ key, file })) }), + ...(words.length && { writerWords: words }), ...(Object.keys(sources).length && { sources }) }; + const entries = [ + { name: MANIFEST, data: enc.encode(JSON.stringify(m, null, 1)), date }, + ...(keys.length ? [{ name: KEYS, data: enc.encode(JSON.stringify(keys, null, 2)), date }] : []), + ...papers.map(({ file, value }) => ({ name: file, data: enc.encode(JSON.stringify(value)), date })), + ...notes.flatMap((n, i) => n.entries.map((e) => ({ ...e, name: dirs[i].dir + e.name }))), + ]; + if (notes.length > 100 || entries.length > LIMIT.entries || entries.reduce((n, e) => n + e.data.length + 76 + enc.encode(e.name).length * 2, 22) > LIMIT.zip + || entries.some((e) => e.data.length > LIMIT.member || (/(?:^|\/)(?:glaze-backup|notes|share-keys)\.json$/.test(e.name) && e.data.length > LIMIT.json))) throw fail('big'); + return entries; + }; + // glaze-backup-2026-10-25.zip, by this device's date. + const fileName = (d = new Date()) => `glaze-backup-${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}.zip`; + + // ---- restoring ------------------------------------------------------------------------------------------------- + // The worker's part of a restore (lib/background.js 'glaze:backup'), in one turn of its write queue and one write: r { + // dashNotes, classes, gpa, widgets, favorites, tasks (when the settings, which bring their own, aren't restored), notes + // (accounts a Notes import then fills), papers, writerWords, sources }, each optional. Everything is checked first; a part that can't be taken whole + // writes nothing. → { ok } or { ok: false, error }. + const apply = async (r, { local }) => { + const W = lib('GZ_WIDGETS'), C = lib('GZ_CLASSES'), G = lib('GZ_GPA'); + const H = lib('GZ_HISTORY'), N = lib('GZ_NOTES'); + let parts, papers; + try { + parts = { + dashNotes: r?.dashNotes === undefined ? {} : records(r.dashNotes, STORES.dashNotes), + classes: r?.classes === undefined ? {} : whole('classes', records(r.classes, STORES.classes)), + gpa: r?.gpa === undefined ? {} : whole('gpa', records(r.gpa, STORES.gpa)), + tasks: r?.tasks === undefined ? null : tasksOf(r.tasks), + }; + if (!obj(r) || Object.keys(r).some((k) => !['dashNotes', 'classes', 'gpa', 'widgets', 'favorites', 'tasks', 'notes', 'papers', 'writerWords', 'sources'].includes(k)) + || (r.widgets !== undefined && !obj(r.widgets)) || (r.favorites !== undefined && !Array.isArray(r.favorites)) + || (r.notes !== undefined && (!Array.isArray(r.notes) || r.notes.length > 100 || r.notes.some((k) => typeof k !== 'string' || !ACCOUNT.test(k))))) throw fail('bad'); + } catch { return { ok: false, error: 'bad' }; } + try { + if (r.papers !== undefined) papers = await paperRecords(r.papers); + if (r.writerWords !== undefined) wordsOf(r.writerWords); + if (r.sources !== undefined) await sourcesOf(r.sources); + } catch { return { ok: false, error: 'bad' }; } + const tasks = parts.tasks ? Object.fromEntries(Object.entries(parts.tasks.tasks).map(([k, v]) => [k, lib('GZ_TODO').clean(v)])) : {}; + const pets = parts.tasks?.pets === undefined ? {} : lib('GZ_PET').backup(parts.tasks.pets); + if (!pets) return { ok: false, error: 'bad' }; + const petKeys = [...new Set([...Object.keys(pets), ...Object.keys(tasks).map((k) => `pet:${k.slice(5)}`)])]; + const keys = [...Object.keys(parts.dashNotes), ...Object.keys(parts.classes), ...Object.keys(parts.gpa), ...Object.keys(tasks), ...petKeys, ...(r.notes || []), + ...(r.favorites ? ['lookFavorites'] : []), ...(r.writerWords ? ['writerWords'] : []), ...Object.keys(r.sources || {})]; + const here = keys.length ? await local.get(keys) : {}; + const patch = {}; + // Dashboard notes: the newer typing stays (lib/widgets.js saveNote). + for (const [k, v] of Object.entries(parts.dashNotes)) { + const note = W.cleanNote(v); + if (note.at > W.cleanNote(here[k]).at) patch[k] = note; + } + // Class schedules: a course comes back unless this device saved it later, deleted it later, or pressed Delete all later + // (lib/classes.js change); the backup's own deletions count the same way here. A period keeps the zone it was saved in. + for (const [k, v] of Object.entries(parts.classes)) { + const d = C.clean(here[k]), b = C.clean(v); + const since = (t, at) => !!t && t >= at; // a time here at or after `at` + for (const c of b.courses) { + const i = d.courses.findIndex((x) => x.id === c.id); + if (since(d.cleared, c.at) || since(d.gone[c.id], c.at) || (i >= 0 && d.courses[i].at >= c.at)) continue; + const course = { ...c, periods: c.periods.map((p) => ({ ...p, ...(!p.tz && (b.tz || d.tz) && { tz: b.tz || d.tz }) })) }; + delete d.gone[c.id]; + if (i < 0) d.courses.push(course); else d.courses[i] = course; + } + for (const [id, at] of Object.entries(b.gone)) if (at > (d.gone[id] || 0) && !(d.courses.find((x) => x.id === id)?.at > at)) { d.gone[id] = at; d.courses = d.courses.filter((x) => x.id !== id); } + if (b.cleared > d.cleared) { d.cleared = b.cleared; d.courses = d.courses.filter((x) => x.at > b.cleared); } + d.tz ||= b.tz; + if (d.courses.length > C.MAX.courses) return { ok: false, error: 'classes' }; + const next = C.clean(d); + if (JSON.stringify(next) !== JSON.stringify(C.clean(here[k]))) patch[k] = next; + } + // GPA past terms: each term by when it last changed, against removals and Clear (lib/gpa.js change); a goal set here stays. + for (const [k, v] of Object.entries(parts.gpa)) { + const d = G.clean(here[k]), b = G.clean(v), cl = Math.max(d.cl || 0, b.cl || 0), gone = { ...d.d }; + for (const [id, at] of Object.entries(b.d || {})) gone[id] = Math.max(gone[id] || 0, at); + const at = (t) => t?.at || 0, keep = (t) => (!cl || at(t) > cl) && !(gone[t.id] >= at(t)); + const terms = d.t.map((t) => { const o = b.t.find((x) => x.id === t.id); return o && at(o) > at(t) ? o : t; }).filter(keep); + for (const t of b.t) if (!d.t.some((x) => x.id === t.id) && keep(t)) terms.push(t); + const rec = { t: terms, ...(Object.keys(gone).length && { d: gone }), ...((d.g || b.g) && { g: d.g || b.g }), ...(cl && { cl }) }; + if (terms.length > 40) return { ok: false, error: 'gpa' }; + const next = G.clean(rec); + if (JSON.stringify(next) !== JSON.stringify(G.clean(here[k]))) patch[k] = next; + } + // Widgets: the backup's layout and music link, as a settings restore takes its settings whole. + if (r.widgets) patch.widgetLayout = W.layout(r.widgets, W.mods(r.widgets)); + // Look favorites: one per code, this device's kept where both have it, newest first. + if (r.favorites) { + const mine = H.favorites(here.lookFavorites), theirs = H.favorites(r.favorites).filter((f) => f.code.length <= (1 << 20) && !mine.some((x) => x.code === f.code)); + if (theirs.length) patch.lookFavorites = [...mine, ...theirs].sort((a, b) => (b.at || 0) - (a.at || 0)).slice(0, H.MAX_FAVORITES); + } + // Tasks without the settings: as a settings restore takes them (lib/todo.js restore), each account's list whole, and its + // pet's history joined with what is here (lib/pet.js merge). + if (parts.tasks) { + const P = lib('GZ_PET'); + for (const key of petKeys) { + if (!here[key] && !pets[key]) continue; + const tk = `todo:${key.slice(4)}`; + pets[key] = P.merge(here[key], pets[key] ?? here[key], Object.hasOwn(tasks, tk) ? { before: here[tk], after: tasks[tk] } : null); + if (!pets[key]) return { ok: false, error: 'history' }; + } + Object.assign(patch, tasks, pets); + } + // A Notes account the backup has and this device doesn't: an empty one, for the Notes import to fill. + for (const k of r.notes || []) if (!Object.hasOwn(here, k)) patch[k] = N.clean(); + // The dictionary: the backup's words this device lacks, ahead of its own, so a full list drops the backup's first. + if (r.writerWords) { + const mine = Array.isArray(here.writerWords) ? here.writerWords.filter((w) => typeof w === 'string') : [], low = new Set(mine.map((w) => w.toLowerCase())); + const theirs = r.writerWords.filter((w) => !low.has(w.toLowerCase()) && low.add(w.toLowerCase())); + if (theirs.length) patch.writerWords = [...theirs, ...mine].slice(-WORDS.max); + } + // Saved sources: each box's list joined with the backup's, one entry per work (lib/writer-sources.js dedupe). + if (r.sources) { + const { SR } = await paperLibraries(); + for (const [k, v] of Object.entries(r.sources)) { + const mine = Array.isArray(here[k]?.sources) ? here[k].sources : [], sources = SR.dedupe([...mine, ...v.sources]); + if (JSON.stringify(sources) !== JSON.stringify(SR.dedupe(mine))) patch[k] = { sources, at: Math.max(Number.isFinite(here[k]?.at) ? here[k].at : 0, v.at) }; + } + } + if (r.papers !== undefined) { + const { P } = await paperLibraries(); + await P.open({ ...local, set: async (v) => { if (await local.set(v) === false) throw fail('write'); } }).merge(papers); + } + return !Object.keys(patch).length || await local.set(patch) ? { ok: true } : { ok: false, error: 'write' }; + }; + // One Notes account from the zip into account `key`, through the worker's Notes import (lib/sessions.js): its files, then + // its notes five at a time, each kept only where it is newer than what is here. → { done, skipped }. + const notesIn = async (zip, dir, key, send, step = () => {}) => { + const N = lib('GZ_NOTES'); + const json = await zip.text(`${dir}notes.json`), raw = parse(json); + const data = N.parseExport(raw, { site: new URL(key.slice(9)).origin }); + for (const f of data.files) { + step(); + const owner = data.notes.find((n) => N.refs(n.html).has(f.id)); + if (owner) await send('notesFile', { key, file: { id: f.id, note: owner.id, kind: f.kind, name: f.name, data: N.b64(await zip.bytes(dir + f.path)) } }); + } + let done = 0, skipped = 0; + for (let i = 0; i < data.notes.length || i === 0; i += 5) { + step(); + const r = await send('notesImport', { key, json: JSON.stringify({ format: raw.format, version: raw.version, site: raw.site, folders: data.folders, files: data.files, notes: data.notes.slice(i, i + 5) }) }); + done += r.done || 0; skipped += r.skipped || 0; + } + return { done, skipped }; + }; + + // Scratch values live in IndexedDB, not a second in-memory copy of every attachment. Nothing in this database is + // installed data. A worker restart or an abandoned upload is discarded when the next restore begins. + const scratch = async () => { + const db = await new Promise((resolve, reject) => { + const r = indexedDB.open('glaze-backup-staging', 1); + r.onupgradeneeded = () => r.result.createObjectStore('values'); + r.onsuccess = () => resolve(r.result); r.onerror = () => reject(r.error); + }); + const tx = (mode, fn) => new Promise((resolve, reject) => { + const t = db.transaction('values', mode), r = fn(t.objectStore('values')); + let value; + if (r) r.onsuccess = () => { value = r.result; }; + t.oncomplete = () => resolve(value); t.onerror = t.onabort = () => reject(t.error); + }); + return { get: (k) => tx('readonly', (s) => s.get(k)), put: (k, v) => tx('readwrite', (s) => s.put(v, k)), + clear: () => tx('readwrite', (s) => s.clear()), close: () => db.close() }; + }; + // Replay every store's existing restore on overlays first, in the worker's write queue. Only a complete successful + // replay is committed. The journal holds the old values until all writes succeed, including Notes bodies and keys. + const atomic = async (steps, env, disk) => { + const memory = new Map(); + disk ||= { get: async (k) => memory.get(k), put: async (k, v) => { memory.set(k, structuredClone(v)); } }; + const changes = [], areas = {}, databases = {}; + const check = (r) => { if (!r?.ok) throw fail(r?.error || 'write'); return r; }; + for (const name of ['local', 'sync']) { + const source = env[name], patch = new Map(); + const view = async (keys) => { + const out = { ...await source.get(keys) }; + for (const [k, v] of patch) if (keys === null || [].concat(keys).includes(k)) { if (v === undefined) delete out[k]; else out[k] = structuredClone(v); } + return out; + }; + areas[name] = { get: view, set: async (o) => { for (const [k, v] of Object.entries(o)) patch.set(k, structuredClone(v)); return true; }, + remove: async (ks) => { for (const k of [].concat(ks)) patch.set(k, undefined); return true; } }; + changes.push({ prepare: async () => { + if (!patch.size) return null; + const keys = [...patch.keys()], before = await source.get(keys), gone = keys.filter((k) => !Object.hasOwn(before, k)); + const set = Object.fromEntries([...patch].filter(([, v]) => v !== undefined)), remove = keys.filter((k) => patch.get(k) === undefined); + const write = async (put, del) => { + if (Object.keys(put).length && await source.set(put) === false) throw fail('write'); + if (del.length && await source.remove(del) === false) throw fail('write'); + }; + return { put: () => write(set, remove), undo: () => write(before, gone) }; + } }); + } + for (const name of ['notes', 'shares']) { + const source = env[name], patch = new Map(); + if (!source) continue; + const label = (s, k) => JSON.stringify([name, s, k]); + const get = async (s, k) => patch.has(label(s, k)) ? (await disk.get(label(s, k)))?.value : source.get(s, k); + const keys = async (s, prefix = '') => { + const set = new Set(await source.keys(s, prefix)); + for (const [id, x] of patch) if (x.s === s && x.k.startsWith(prefix)) { if (x.gone) set.delete(x.k); else set.add(x.k); } + return [...set]; + }; + const put = async (s, k, v, gone = false) => { + const id = label(s, k); + await disk.put(id, { value: v }); patch.set(id, { s, k, gone }); + }; + databases[name] = { get, put, del: async (s, ks) => { for (const k of [].concat(ks)) await put(s, k, undefined, true); }, keys, + scan: async (s, p) => Promise.all((await keys(s, p)).map(async (k) => [k, await get(s, k)])), + all: async (s) => Promise.all((await keys(s)).map((k) => get(s, k))), estimate: () => source.estimate?.() }; + // Database records first; the local indexes and sync preferences are published only once their bytes are in place. + changes.unshift({ prepare: async () => { + const entries = [...patch], attempted = []; + for (const [id, { s, k }] of entries) await disk.put(`before:${id}`, { value: await source.get(s, k) }); + return { + put: async () => { for (const [id, { s, k, gone }] of entries) { attempted.push([id, s, k]); if (gone) await source.del(s, [k]); else await source.put(s, k, (await disk.get(id)).value); } }, + undo: async () => { for (const [id, s, k] of attempted.reverse()) { const v = (await disk.get(`before:${id}`)).value; if (v === undefined) await source.del(s, [k]); else await source.put(s, k, v); } }, + }; + } }); + } + let session, skipped = 0; + try { + for await (const { action, value } of steps) { + if (action === 'own') check(await apply(value, areas)); + else if (action === 'settings') check(await env.settings(value, areas)); + else if (action === 'send') { + if (!['notesFile', 'notesImport', 'importKeys'].includes(value.op)) throw fail('bad'); + session ||= env.sessions({ ...areas, notes: databases.notes, db: databases.shares }); + const r = check(await session.handle(value)); skipped += r.skipped || 0; + } else throw fail('bad'); + } + const commits = []; + for (const c of changes) { const ready = await c.prepare(); if (ready) commits.push(ready); } + const attempted = []; + try { + for (const c of commits) { attempted.push(c); await c.put(); } + } catch (e) { + let undone = true; + for (const c of attempted.reverse()) try { await c.undo(); } catch { undone = false; } + return { ok: false, error: e.why || 'write', undone }; + } + return { ok: true, skipped }; + } catch (e) { return { ok: false, error: e.why || e.message || 'bad' }; } + finally { session?.stop(); } + }; + + // Upload only the selected categories through begin/own/settings/send. finish replays them on the worker's overlays + // and commits them together; abort always clears the scratch upload. Old Notes ZIPs require an explicit account. + const restore = async (plan, ticked, io, account = '') => { + const c = plan.cats, on = (k) => ticked.has(k) && c[k] !== undefined; + const out = { failed: [], changed: false, skipped: 0 }; + const notes = on('notes') ? c.notes.map((n) => ({ ...n, account: n.account || account })).filter((n) => ACCOUNT.test(n.account)) : []; + if (on('notes') && notes.length !== c.notes.length) return { ...out, failed: CATS.filter(on) }; + const favorites = on('settings') && Array.isArray(c.settings.favorites) && c.settings.favorites.length ? c.settings.favorites : null; + const own = { ...Object.fromEntries(['dashNotes', 'classes', 'gpa', 'widgets', 'writerWords', 'sources'].filter(on).map((k) => [k, c[k]])), + ...(favorites && { favorites }), ...(on('tasks') && !on('settings') && { tasks: c.tasks }), ...(notes.length && plan.kind === 'backup' && { notes: notes.map((n) => n.account) }) }; + try { + await io.begin(); + if (Object.keys(own).length && !(await Promise.resolve().then(() => io.own(own)).catch(() => null))?.ok) return { ...out, failed: CATS.filter(on) }; + out.changed = Object.keys(own).length > 0; + // A record's older file may already be in its backup history. Stage that history first, so merge can reuse its + // version instead of taking a redundant checkpoint and evicting a distinct version at the cap. + if (on('papers')) for (const [key, value] of Object.entries(c.papers).sort(([a], [b]) => Number(b.startsWith('writerVersions:')) - Number(a.startsWith('writerVersions:')))) { + if (!(await io.own({ papers: { [key]: value } }))?.ok) return { ...out, failed: CATS.filter(on) }; + out.changed = true; + } + if (on('settings')) { + const { favorites: _, ...settings } = c.settings; + const r = await Promise.resolve().then(() => io.settings(on('tasks') ? { ...settings, ...c.tasks } : settings)).catch(() => ({ ok: false })); + if (r.ok) out.changed = true; else out.failed.push('settings', ...(on('tasks') ? ['tasks'] : [])); + out.undone = r.undone; + out.media = r.media; + } + if (on('shareKeys')) { + try { await io.send('importKeys', { keys: c.shareKeys }); out.changed = true; } catch { out.failed.push('shareKeys'); } + } + // An old Notes zip goes into an account picked from those here; a backup's accounts are made where missing (apply). + for (const n of notes) { + try { out.skipped += (await notesIn(plan.zip, n.dir, n.account, io.send, io.step)).skipped; out.changed = true; } catch { if (!out.failed.includes('notes')) out.failed.push('notes'); } + } + if (on('notes') && !notes.length) out.failed.push('notes'); + if (out.failed.length) return { ...out, changed: false }; + const committed = await io.finish(); + if (!committed?.ok) return { ...out, changed: committed?.undone === false, undone: committed?.undone, failed: CATS.filter(on) }; + return { ...out, skipped: committed.skipped || 0 }; + } catch { return { ...out, changed: false, failed: CATS.filter(on) }; } + finally { await io.abort?.().catch(() => {}); } + }; + + return { atomic, scratch, FORMAT, VERSION, MANIFEST, KEYS, LIMIT, CATS, STORES, ACCOUNT, KINDS, parse, read, notesOut, pack, fileName, apply, notesIn, restore }; +})(); + +if (typeof module !== 'undefined') module.exports = GZ_BACKUP; From 3a76343f54e976086e646f09c0ad620de269614c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:41:50 -0400 Subject: [PATCH 084/907] MCP setup tests expect no node when the running one sits in a folder others can write, as on GitHub's runners --- test/mcp-install.test.js | 5 ++++- test/mcp-mcpb.test.js | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/test/mcp-install.test.js b/test/mcp-install.test.js index 37aa3b954..695e76b47 100644 --- a/test/mcp-install.test.js +++ b/test/mcp-install.test.js @@ -15,6 +15,9 @@ const B = require('../mcp/lib/browsers.js'); const IDS = require('../mcp/lib/ids.js'); const L = require('../mcp/lib/launcher.js'); const I = require('../mcp/lib/install.js'); +// Whether setup would name the node running these tests. It won't on GitHub's hosted runners, where node sits under +// /opt/hostedtoolcache, a folder anyone can write; nodes.js refuses that, as it should. +const trusted = (platform) => !require('../mcp/lib/nodes.js').trust(platform).path(process.execPath); const D = require('../mcp/lib/doctor.js'); const CLI = require('../mcp/bin/glaze-mcp.js'); const { nodeAt } = require('./helpers/mcp-pipe.cjs'); @@ -305,7 +308,7 @@ test('launchers name node by the PATH entry that leads to it, so a Homebrew upgr assert.equal(I.stableNode(cellar, 'darwin', { HOME: d }), cellar); // The default node is this one, named that way. fs.symlinkSync(process.execPath, path.join(mk(d, 'link'), 'node')); - assert.equal(I.context({ platform: 'darwin', env: { HOME: d, PATH: path.join(d, 'link') } }).node, path.join(d, 'link', 'node')); + assert.equal(I.context({ platform: 'darwin', env: { HOME: d, PATH: path.join(d, 'link') } }).node, trusted('darwin') ? path.join(d, 'link', 'node') : null); assert.equal(I.context({ platform: 'darwin', env: { HOME: d, PATH: path.join(d, 'link') }, node: '/given/node' }).node, '/given/node'); }); diff --git a/test/mcp-mcpb.test.js b/test/mcp-mcpb.test.js index 8ba9ac8f2..72598fc74 100644 --- a/test/mcp-mcpb.test.js +++ b/test/mcp-mcpb.test.js @@ -29,6 +29,9 @@ const posixOnly = (why) => (POSIX ? false : why); const windowsOnly = (why) => (POSIX ? why : false); // Loaded per test, so a missing module fails that test alone. const N = () => require('../mcp/lib/nodes.js'); +// Whether setup would name the node running these tests. It won't on GitHub's hosted runners, where node sits under +// /opt/hostedtoolcache, a folder anyone can write; nodes.js refuses that, as it should. +const trusted = (platform) => !N().trust(platform).path(process.execPath); const M = () => require('../mcp/lib/mcpb.js'); const P = () => require('../mcp/scripts/pack-mcpb.js'); @@ -277,7 +280,7 @@ test('a stable PATH alias must be trusted even when it resolves to the running n assert.equal(got, node); fs.unlinkSync(alias); fs.symlinkSync(process.execPath, alias); - assert.equal(I.context({ platform: 'darwin', env }).node, process.execPath, 'setup also refuses the unsafe alias'); + assert.equal(I.context({ platform: 'darwin', env }).node, trusted('darwin') ? process.execPath : null, 'setup also refuses the unsafe alias'); }); test('choose checks the running node file, ancestors and symlink targets before using it', { skip: posixOnly('POSIX modes and symlinks') }, async (t) => { @@ -307,7 +310,7 @@ test('the new helper files reach nothing outside the computer and start processe test('install refuses to default to Claude\'s runtime when no node is given', () => { // context() names this node unless told otherwise; under Electron it names none, so setup can't start Claude's app. const ctx = I.context({ platform: 'darwin', env: { HOME: '/h' } }); - assert.equal(typeof ctx.node, 'string'); + assert.equal(ctx.node, trusted('darwin') ? process.execPath : null); assert.equal(I.context({ platform: 'darwin', env: { HOME: '/h' }, node: null }).node, null, 'null means no node, not the default'); }); @@ -522,7 +525,8 @@ test('the command: --from=mcpb sets up, then runs agent mode; any other --from i assert.equal(out.status, 0, out.stderr); const { root, hosts } = B.folder(process.platform, { HOME: home, XDG_DATA_HOME: env.XDG_DATA_HOME }); const l = L.parse(fs.readFileSync(path.join(hosts, B0.id), 'utf8')); - assert.equal(fs.realpathSync(l.node), fs.realpathSync(process.execPath)); + if (trusted(process.platform)) assert.equal(fs.realpathSync(l.node), fs.realpathSync(process.execPath)); + else assert.equal(l.node, null, 'a node others can change goes in no launcher'); assert.equal(l.script, path.join(root, VERSION, 'bin', 'glaze-mcp.js')); assert.doesNotMatch(out.stderr, /unknown option|GLAZE_MCP_BROWSER/); assert.equal(CLI.mode(['--from=mcpb']).mode, 'agent'); From e42cb241cae0d56ae225cb2b21986bd3c811c36c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:43:33 -0400 Subject: [PATCH 085/907] PDF tools: Highlight, Pen, Text, Image and Shapes over pdf.js's editor, with colour, size, Undo and Redo, autosaved as the PDF's own annotations --- i18n/de.js | 7 +- i18n/es.js | 7 +- i18n/fr.js | 7 +- i18n/nl.js | 7 +- i18n/pt-BR.js | 7 +- lib/pdf-tools.js | 250 +++++++++++++++++++++++++++++++++++++++++ pdf-kind.css | 16 +++ pdf-kind.js | 37 +++--- scripts/pdf-check.js | 135 ++++++++++++++++++++++ test/i18n.test.js | 4 +- test/pdf-tools.test.js | 55 +++++++++ 11 files changed, 513 insertions(+), 19 deletions(-) create mode 100644 lib/pdf-tools.js create mode 100644 test/pdf-tools.test.js diff --git a/i18n/de.js b/i18n/de.js index de78f17ff..9d761af36 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3750,5 +3750,10 @@ "as the app reports itself": "nach Angaben der App", "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", - "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast." + "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", + "Signature": "Unterschrift", + "Rectangle": "Rechteck", + "Ellipse": "Ellipse", + "Arrow": "Pfeil", + "The owner of this PDF doesn't allow adding to it.": "Der Eigentümer dieses PDFs erlaubt keine Ergänzungen." }; diff --git a/i18n/es.js b/i18n/es.js index eabe7a199..3c2c92fe0 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3750,5 +3750,10 @@ "as the app reports itself": "según la propia app", "Don't allow": "No permitir", "Unnamed app": "App sin nombre", - "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras." + "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", + "Signature": "Firma", + "Rectangle": "Rectángulo", + "Ellipse": "Elipse", + "Arrow": "Flecha", + "The owner of this PDF doesn't allow adding to it.": "El propietario de este PDF no permite añadirle nada." }; diff --git a/i18n/fr.js b/i18n/fr.js index c2b2a093b..f3933e6aa 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3750,5 +3750,10 @@ "as the app reports itself": "selon l’app elle-même", "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", - "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse." + "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", + "Signature": "Signature", + "Rectangle": "Rectangle", + "Ellipse": "Ellipse", + "Arrow": "Flèche", + "The owner of this PDF doesn't allow adding to it.": "Le propriétaire de ce PDF n’autorise pas les ajouts." }; diff --git a/i18n/nl.js b/i18n/nl.js index 5bb584391..2456bf32e 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3750,5 +3750,10 @@ "as the app reports itself": "zoals de app zichzelf noemt", "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", - "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde." + "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", + "Signature": "Handtekening", + "Rectangle": "Rechthoek", + "Ellipse": "Ellips", + "Arrow": "Pijl", + "The owner of this PDF doesn't allow adding to it.": "De eigenaar van deze pdf staat geen toevoegingen toe." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index f76333eaf..c02b75fc0 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3750,5 +3750,10 @@ "as the app reports itself": "segundo o próprio app", "Don't allow": "Não permitir", "Unnamed app": "App sem nome", - "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta." + "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", + "Signature": "Assinatura", + "Rectangle": "Retângulo", + "Ellipse": "Elipse", + "Arrow": "Seta", + "The owner of this PDF doesn't allow adding to it.": "O proprietário deste PDF não permite adicionar nada a ele." }; diff --git a/lib/pdf-tools.js b/lib/pdf-tools.js new file mode 100644 index 000000000..e0347bff3 --- /dev/null +++ b/lib/pdf-tools.js @@ -0,0 +1,250 @@ +// The PDF viewer's tools (pdf-kind.js, loaded with the viewer of a kept PDF): Highlight, Pen, Text, Image, Shapes and +// Signature over pdf.js's own annotation editor (lib/pdfjs: AnnotationEditorUIManager, reached through the viewer's +// EventBus), with colour, size, Undo and Redo, as a group in Glaze's toolbar. What they add are the PDF's own annotations +// (Highlight, Ink, FreeText, Stamp), saved by the viewer's autosave as an incremental update and exported as they are, +// still editable. A shape is an Ink annotation whose strokes Glaze lays out (shape below), put on the page through +// AnnotationEditorLayer.deserialize, so it moves, resizes, recolours and undoes as pdf.js's own ink does. A file whose +// owner forbids adding annotations (getPermissions) shows the tools disabled, with the reason. +// Classic script; module.exports for node (shape is pure). +var GZ_PDF_TOOLS = globalThis.GZ_PDF_TOOLS || (() => { + const SHAPES = [['rect', 'Rectangle'], ['ellipse', 'Ellipse'], ['line', 'Line'], ['arrow', 'Arrow']]; + // Ink's colours and sizes, Text's, Highlight's. pdf.js's own highlight colours (its viewer's defaults). + const INKS = [['#000000', 'Black'], ['#1a56db', 'Blue'], ['#d93025', 'Red'], ['#188038', 'Green'], ['#8e24aa', 'Purple']]; + const MARKS = [['#FFFF98', 'Yellow'], ['#53FFBC', 'Green'], ['#80EBFF', 'Blue'], ['#FFCBE6', 'Pink'], ['#FF4F5F', 'Red']]; + const SIZES = { ink: [1, 2, 4, 8, 12], text: [10, 12, 14, 18, 24, 36], highlight: [8, 12, 16, 24] }; + const HIGHLIGHTS = MARKS.map(([c, n]) => `${n.toLowerCase()}=${c}`).join(','); + const NO_TOOLS = "The owner of this PDF doesn't allow adding to it."; + + // ---- a shape as pdf.js's Ink JSON. box: [x0, y0, x1, y1] in the page's PDF space (y up): a rectangle's or an ellipse's + // corners, a line's or an arrow's ends. Each corner is given twice: pdf.js smooths an ink path through the midpoints of + // its points (Outline.createBezierPoints), and a point given twice keeps its corner sharp, before and after a reload + // (an Ink annotation keeps only its points). → { annotationType, color, thickness, opacity, paths, rect, rotation, + // pageIndex }. + const twice = (pts) => pts.flatMap(([x, y]) => [x, y, x, y]); + const shape = (kind, [x0, y0, x1, y1], { color = [0, 0, 0], thickness = 2, pageIndex = 0, rotation = 0, type = 15 } = {}) => { + let strokes; + if (kind === 'rect') strokes = [twice([[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]])]; + else if (kind === 'ellipse') { + const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2, rx = Math.abs(x1 - x0) / 2, ry = Math.abs(y1 - y0) / 2, n = 72, pts = []; + for (let i = 0; i <= n + 1; i++) { const a = (i / n) * 2 * Math.PI; pts.push(cx + rx * Math.cos(a), cy + ry * Math.sin(a)); } + strokes = [pts]; + } else if (kind === 'line') strokes = [twice([[x0, y0], [x1, y1]])]; + else if (kind === 'arrow') { + const len = Math.hypot(x1 - x0, y1 - y0) || 1, head = Math.min(len * 0.4, 10 + 2.5 * thickness), a = Math.atan2(y1 - y0, x1 - x0), w = Math.PI / 7; + const side = (s) => [x1 - head * Math.cos(a + s * w), y1 - head * Math.sin(a + s * w)]; + strokes = [twice([[x0, y0], [x1, y1]]), twice([side(1), [x1, y1], side(-1)])]; + } else throw new TypeError(`no shape ${kind}`); + const xs = strokes.flatMap((s) => s.filter((_, i) => i % 2 === 0)), ys = strokes.flatMap((s) => s.filter((_, i) => i % 2 === 1)), pad = thickness / 2 + 1; + return { annotationType: type, color: [...color], thickness, opacity: 1, paths: { points: strokes.map((s) => Float32Array.from(s)) }, + rect: [Math.min(...xs) - pad, Math.min(...ys) - pad, Math.max(...xs) + pad, Math.max(...ys) + pad], rotation, pageIndex }; + }; + const rgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16)); + + // ---- the group. o: { P (pdf.mjs), viewer, bus, t, h, btn, openMenu(opener, items), modified(), scroll, signatures + // (lib/pdf-sign.js's manager, or null), say(text) }. Mounted before the viewer has its document: pdf.js hands over its + // editor once page 1 is read (annotationeditoruimanager). → { el, ready (resolves true when the tools are on, false when + // the file forbids them), dispose }. + const SVG = 'http://www.w3.org/2000/svg'; + const ICONS = { + select: 'M5 3l14 8-6 1.5L10 19z', highlight: 'M4 20h7M14.5 4.5l5 5L10 19H5v-5z', pen: 'M4 20c3-1 4-3 6-5s5-7 7-9a2 2 0 0 1 3 3c-2 2-7 5-9 7s-4 3-5 6', text: 'M5 6V4h14v2M12 4v16M9 20h6', + image: 'M4 5h16v14H4zM4 16l5-5 4 4 2-2 5 5M15 9h.01', shape: 'M3 10h8v10H3zM17 3a4 4 0 1 1 0 8 4 4 0 0 1 0-8M14 21l7-7', signature: 'M3 17c3-6 5-9 6-7s-2 8 1 7 3-5 5-4 1 3 3 3 2-1 3-2M3 21h18', + undo: 'M9 14 4 9l5-5M4 9h10a6 6 0 0 1 0 12h-3', redo: 'm15 14 5-5-5-5M20 9H10a6 6 0 0 0 0 12h3', caret: 'm6 9 6 6 6-6', + }; + const icon = (d) => { const s = document.createElementNS(SVG, 'svg'); s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('class', 'gp-ico'); s.setAttribute('aria-hidden', 'true'); const p = document.createElementNS(SVG, 'path'); p.setAttribute('d', d); s.append(p); return s; }; + + const mount = (o) => { + const { P, viewer, bus, t, h, btn, openMenu } = o, T = P.AnnotationEditorType, K = P.AnnotationEditorParamsType; + const life = new AbortController(), signal = life.signal; + const MODES = { highlight: T.HIGHLIGHT, pen: T.INK, text: T.FREETEXT, image: T.STAMP, shape: T.INK, signature: T.SIGNATURE }; + const TOOLS = [['highlight', 'Highlight'], ['pen', 'Pen'], ['text', 'Text'], ['image', 'Image'], ['shape', 'Shapes'], ['signature', 'Signature']].filter(([id]) => id !== 'signature' || o.signatures); + const family = (id) => (id === 'pen' || id === 'shape' ? 'ink' : id === 'text' ? 'text' : id === 'highlight' ? 'highlight' : null); + const params = { ink: { color: INKS[0][0], size: 2 }, text: { color: INKS[0][0], size: 12 }, highlight: { color: MARKS[0][0], size: 12 } }; + let tool = null, shapeKind = 'rect', ui = null, on = false, armed = false, drag = null; + + const buttons = Object.fromEntries(TOOLS.map(([id, label]) => [id, btn({ class: 'gq-ib gq-tool', 'data-tool': id, 'aria-pressed': 'false', title: t(label), 'aria-label': t(label), disabled: true, ...(id === 'shape' && { 'aria-haspopup': 'menu', 'aria-expanded': 'false' }) }, icon(ICONS[id]))])); + const swatch = h('span', { class: 'gq-swatch' }); + const colorB = btn({ class: 'gq-ib', 'data-act': 'color', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Color'), 'aria-label': t('Color'), disabled: true }, swatch); + const sizeL = h('span', { class: 'gq-size' }); + const sizeB = btn({ class: 'gp-box gq-sizeb', 'data-act': 'size', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Size'), 'aria-label': t('Size'), disabled: true }, sizeL, icon(ICONS.caret)); + const undoB = btn({ class: 'gq-ib', 'data-act': 'undo', title: t('Undo'), 'aria-label': t('Undo'), disabled: true }, icon(ICONS.undo)); + const redoB = btn({ class: 'gq-ib', 'data-act': 'redo', title: t('Redo'), 'aria-label': t('Redo'), disabled: true }, icon(ICONS.redo)); + const el = h('span', { class: 'gq-tools', role: 'group', 'aria-label': t('Tools') }, ...Object.values(buttons), colorB, sizeB, h('span', { class: 'gp-sepv' }), undoB, redoB); + + // What the colour and size controls show: the current tool's (Pen and Shapes share ink's). + const showParams = () => { + const f = family(tool), p = f && params[f]; + colorB.hidden = sizeB.hidden = !p; + if (!p) return; + swatch.style.background = p.color; + sizeL.textContent = String(p.size); + }; + const push = () => { + const f = family(tool), p = f && params[f]; + if (!p || !ui) return; + const send = (type, value) => bus.dispatch('switchannotationeditorparams', { source: el, type, value }); + if (f === 'ink') { send(K.INK_COLOR, p.color); send(K.INK_THICKNESS, p.size); } + else if (f === 'text') { send(K.FREETEXT_COLOR, p.color); send(K.FREETEXT_SIZE, p.size); } + else { send(K.HIGHLIGHT_COLOR, p.color); send(K.HIGHLIGHT_THICKNESS, p.size); } + }; + const pressed = () => { + for (const [id, b] of Object.entries(buttons)) b.setAttribute('aria-pressed', String(id === tool)); + el.dataset.active = tool || ''; + showParams(); + }; + // pdf.js switches its mode itself too (a double click on a note it drew, a pasted editor): the group follows. + const toolOf = (mode) => (mode === T.INK ? (tool === 'shape' ? 'shape' : 'pen') : Object.keys(MODES).find((id) => MODES[id] === mode && id !== 'shape') || null); + let after = null; + bus.on('annotationeditormodechanged', ({ mode }) => { + tool = toolOf(mode); + pressed(); push(); + const next = after; after = null; + if (next && mode === next.mode) next.run(); + }, { signal }); + bus.on('switchannotationeditormode', (e) => { if (on && e.source !== el) viewer.annotationEditorMode = { mode: e.mode, editId: e.editId ?? null, isFromKeyboard: !!e.isFromKeyboard, mustEnterInEditMode: !!e.mustEnterInEditMode, editComment: !!e.editComment }; }, { signal }); + const setMode = (mode, { keyboard = false, then = null } = {}) => { + after = then && { mode, run: then }; + if (viewer.annotationEditorMode === mode) { const next = after; after = null; next?.run(); tool = toolOf(mode) || tool; pressed(); return; } + viewer.annotationEditorMode = { mode, isFromKeyboard: keyboard && mode === T.FREETEXT }; + }; + // A new editor in the middle of the page in view: an image's picker, a signature's dialog (pdf.js's CREATE). + const create = () => bus.dispatch('switchannotationeditorparams', { source: el, type: K.CREATE, value: null }); + const choose = (id, e) => { + if (!on) return; + const keyboard = e?.detail === 0; + if (id === 'image' || id === 'signature') { tool = id; setMode(MODES[id], { then: create }); return; } + if (tool === id && id !== 'shape') { off(); return; } + tool = id; + setMode(MODES[id], { keyboard }); + pressed(); + }; + const off = () => { tool = null; armed = false; if (on) viewer.annotationEditorMode = { mode: T.NONE }; pressed(); }; + for (const [id, b] of Object.entries(buttons)) { + if (id === 'shape') b.addEventListener('click', () => openMenu(b, SHAPES.map(([k, label]) => [t(label), (e) => { shapeKind = k; choose('shape'); armed = true; if (e?.detail === 0) placeShape(k); }, { 'data-shape': k, 'aria-checked': String(k === shapeKind), role: 'menuitemradio' }]))); + else b.addEventListener('click', (e) => choose(id, e)); + } + colorB.addEventListener('click', () => { + const f = family(tool); + if (!f) return; + openMenu(colorB, (f === 'highlight' ? MARKS : INKS).map(([c, label]) => [h('span', { class: 'gq-menu-sw' }, ((sw) => { sw.style.background = c; return sw; })(h('span', { class: 'gq-swatch' })), t(label)), () => { params[f].color = c; showParams(); push(); }, { 'data-color': c, role: 'menuitemradio', 'aria-checked': String(params[f].color === c) }])); + }); + sizeB.addEventListener('click', () => { + const f = family(tool); + if (!f) return; + openMenu(sizeB, SIZES[f].map((n) => [String(n), () => { params[f].size = n; showParams(); push(); }, { 'data-size': String(n), role: 'menuitemradio', 'aria-checked': String(params[f].size === n) }])); + }); + undoB.addEventListener('click', () => bus.dispatch('editingaction', { source: el, name: 'undo' })); + redoB.addEventListener('click', () => bus.dispatch('editingaction', { source: el, name: 'redo' })); + bus.on('editingstateschanged', ({ details }) => { + if (!on) return; + undoB.disabled = !details.hasSomethingToUndo; + redoB.disabled = !details.hasSomethingToRedo; + }, { signal }); + + // ---- pdf.js's editor: every command it records (an editor added, moved, resized, recoloured, typed into, deleted) + // and every undo or redo is a change the viewer saves. + let ready; + const settled = new Promise((r) => { ready = r; }); + let session = null, pressedDown = false; + bus.on('annotationeditoruimanager', ({ uiManager }) => { + ui = uiManager; + // The layer a pen is drawing on: pdf.js keeps its strokes as one drawing until the session ends (commit). + const setSession = ui.setCurrentDrawingSession.bind(ui); + ui.setCurrentDrawingSession = (layer) => { session = layer; return setSession(layer); }; + for (const name of ['addCommands', 'undo', 'redo']) { + const own = ui[name].bind(ui); + ui[name] = (...args) => { const out = own(...args); o.modified(); return out; }; + } + }, { signal }); + // Once page 1 is read: the file's permissions decide. pdf.js makes no editor where the owner forbids changing the + // file's contents; Glaze also honours the bit for adding annotations (ISO 32000-1, table 22). + const decide = async (doc) => { + const perms = await doc.getPermissions().catch(() => null); + on = !!ui && (!perms || perms.includes(P.PermissionFlag.MODIFY_ANNOTATIONS)); + for (const b of [...Object.values(buttons), colorB, sizeB]) { b.disabled = !on; if (!on) b.title = t(NO_TOOLS); } + if (!on) { el.title = t(NO_TOOLS); el.dataset.off = ''; } + showParams(); + ready(on); + return on; + }; + + // ---- shapes: drawn by dragging on a page (a box, or a line's two ends) while Shapes is the tool; chosen with the + // keyboard, one goes in the middle of the page in view, selected, to move with the arrow keys. + const layerOf = (pageIndex) => viewer.getPageView(pageIndex)?.annotationEditorLayer?.annotationEditorLayer || null; + const put = async (pageIndex, box) => { + const pv = viewer.getPageView(pageIndex), layer = layerOf(pageIndex); + if (!pv || !layer || !ui) return null; + const data = shape(shapeKind, box, { color: rgb(params.ink.color), thickness: params.ink.size, pageIndex, rotation: pv.viewport.rotation, type: T.INK }); + const editor = await layer.deserialize(data); + if (!editor) return null; + ui.addCommands({ cmd: () => layer.addOrRebuild(editor), undo: () => editor.remove(), mustExec: true }); + return editor; + }; + const placeShape = async (kind) => { + shapeKind = kind; + const pv = viewer.getPageView((viewer.currentPageNumber || 1) - 1); + if (!pv) return; + const [x0, y0, x1, y1] = pv.pdfPage.view, cx = (x0 + x1) / 2, cy = (y0 + y1) / 2, w = (x1 - x0) / 6; + const box = kind === 'line' || kind === 'arrow' ? [cx - w, cy, cx + w, cy] : [cx - w, cy - w * 0.66, cx + w, cy + w * 0.66]; + const editor = await put(pv.id - 1, box); + if (editor) { ui.setSelected(editor); editor.div?.focus(); } + }; + o.scroll.addEventListener('pointerdown', (e) => { + if (!on || tool !== 'shape' || !armed || e.button !== 0) return; + const pageDiv = e.target.closest?.('.page[data-page-number]'); + if (!pageDiv || e.target.closest('.annotationEditorLayer > div')) return; + e.preventDefault(); e.stopPropagation(); + const pv = viewer.getPageView(Number(pageDiv.dataset.pageNumber) - 1), r = pageDiv.getBoundingClientRect(); + const at = (ev) => [Math.max(0, Math.min(r.width, ev.clientX - r.left)), Math.max(0, Math.min(r.height, ev.clientY - r.top))]; + const preview = document.createElementNS(SVG, 'svg'); + preview.setAttribute('class', 'gq-shape-draft'); + const mark = document.createElementNS(SVG, shapeKind === 'ellipse' ? 'ellipse' : shapeKind === 'rect' ? 'rect' : 'line'); + mark.setAttribute('stroke', params.ink.color); mark.setAttribute('stroke-width', String(params.ink.size * pv.scale)); mark.setAttribute('fill', 'none'); + preview.append(mark); pageDiv.append(preview); + drag = { pv, from: at(e), to: at(e), preview, mark }; + const draw = () => { + const [[ax, ay], [bx, by]] = [drag.from, drag.to]; + if (shapeKind === 'rect') Object.entries({ x: Math.min(ax, bx), y: Math.min(ay, by), width: Math.abs(bx - ax), height: Math.abs(by - ay) }).forEach(([k, v]) => mark.setAttribute(k, v)); + else if (shapeKind === 'ellipse') Object.entries({ cx: (ax + bx) / 2, cy: (ay + by) / 2, rx: Math.abs(bx - ax) / 2, ry: Math.abs(by - ay) / 2 }).forEach(([k, v]) => mark.setAttribute(k, v)); + else Object.entries({ x1: ax, y1: ay, x2: bx, y2: by }).forEach(([k, v]) => mark.setAttribute(k, v)); + }; + pageDiv.setPointerCapture?.(e.pointerId); + const move = (ev) => { drag.to = at(ev); draw(); }; + const up = async (ev) => { + pageDiv.removeEventListener('pointermove', move); pageDiv.removeEventListener('pointerup', up); pageDiv.removeEventListener('pointercancel', up); + const d = drag; drag = null; d.preview.remove(); + if (ev.type === 'pointercancel') return; + d.to = at(ev); + if (Math.hypot(d.to[0] - d.from[0], d.to[1] - d.from[1]) < 4) return; + const [ax, ay] = d.pv.viewport.convertToPdfPoint(...d.from), [bx, by] = d.pv.viewport.convertToPdfPoint(...d.to); + await put(d.pv.id - 1, shapeKind === 'line' || shapeKind === 'arrow' ? [ax, ay, bx, by] : [Math.min(ax, bx), Math.min(ay, by), Math.max(ax, bx), Math.max(ay, by)]); + }; + pageDiv.addEventListener('pointermove', move, { signal }); + pageDiv.addEventListener('pointerup', up, { signal }); + pageDiv.addEventListener('pointercancel', up, { signal }); + draw(); + }, { capture: true, signal }); + // Esc with nothing of pdf.js's to close puts the tool down. + document.addEventListener('keydown', (e) => { if (!e.defaultPrevented && e.key === 'Escape' && tool && !document.querySelector('.gq-menu, dialog[open]')) { e.preventDefault(); off(); } }, { signal }); + + o.scroll.addEventListener('pointerdown', () => { pressedDown = true; }, { capture: true, signal }); + addEventListener('pointerup', () => { pressedDown = false; }, { capture: true, signal }); + addEventListener('pointercancel', () => { pressedDown = false; }, { capture: true, signal }); + // Before a save: the pen's drawing becomes its annotation (unless a stroke is under way: its end saves again). Leaving + // (all): the note being typed is kept too. + const commit = (all = false) => { + if (!ui) return; + try { + if (session && !pressedDown) session.commitOrRemove(); + if (all) ui.commitOrRemove(); + } catch (e) { console.error(e); } + }; + + pressed(); + const dispose = () => { life.abort(); drag?.preview.remove(); }; + return { el, decide, commit, ready: settled, dispose, get tool() { return tool; } }; + }; + + return { SHAPES, INKS, MARKS, SIZES, HIGHLIGHTS, NO_TOOLS, shape, mount }; +})(); + +if (typeof module !== 'undefined') module.exports = GZ_PDF_TOOLS; else GZ_PDF_TOOLS; diff --git a/pdf-kind.css b/pdf-kind.css index d30889e0d..6586bea6d 100644 --- a/pdf-kind.css +++ b/pdf-kind.css @@ -44,3 +44,19 @@ .gq .gp-tool .gp-sepv, #gq-zoom-out, #gq-zoom-in { display: none; } .gq .gp-tool .gp-box { min-width: 0; } } +/* The tools (lib/pdf-tools.js): a group in the toolbar, the chosen one pressed; colour and size for the chosen tool. */ +.gq-tools { display: inline-flex; align-items: center; gap: 2px; flex: none; } +.gq-tools[hidden] { display: none; } +.gq-ib[aria-pressed="true"] { background: color-mix(in srgb, var(--gz-accent) 22%, transparent); color: var(--gz-ink); } +.gq-ib:disabled, .gq-tools .gp-box:disabled { opacity: .45; cursor: default; } +.gq-ib:disabled:hover { background: none; } +.gq-tools [hidden] { display: none; } +.gq-swatch { display: inline-block; width: 16px; height: 16px; border-radius: 50%; box-shadow: 0 0 0 1px var(--gz-line-strong); flex: none; } +.gq .gp-tool .gq-sizeb { min-width: 0; gap: 4px; padding: 5px 8px; font-variant-numeric: tabular-nums; } +.gq-menu-sw { display: inline-flex; align-items: center; gap: 8px; } +.gq-menu [aria-checked="true"] { font-weight: 700; } +/* A shape as it is dragged out, before pdf.js takes it as ink. */ +.gq-shape-draft { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 30; overflow: visible; } +/* pdf.js's bar on a selected mark (Delete, its colour): Glaze's buttons inherit their font (writer.css), and the mark's + layer carries the page's text scale, so its buttons take back a control's own size. */ +.gq-scroll .annotationEditorLayer .editToolbar button { font: 13px/1 system-ui, sans-serif; } diff --git a/pdf-kind.js b/pdf-kind.js index dc487d8c9..a5eabce82 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -3,7 +3,8 @@ // card, exports as itself, and opens in pdf.js's own viewer (lib/pdfjs: pdf.mjs and pdf_viewer.mjs, imported only when a // PDF is first read; pdf.js parses in its module worker, pdf.worker.mjs): the visible pages drawn, zoom, the text layer and // pdf.js's find, AcroForm fields filled and kept (saved by lib/pdf-doc.js, or as pdf.js writes them where that isn't -// loaded), the file's permissions honoured, and a form only XFA can show refused. The page itself stays white, a +// loaded), marks added with the tools (lib/pdf-tools.js: highlights, ink, notes, pictures, shapes) and kept as the +// PDF's own annotations, the file's permissions honoured, and a form only XFA can show refused. The page itself stays white, a // print document; its chrome is the paper's (writer-paper.css: the top bar, the toolbar, ⋯) in the student's Look. // Off, Documents takes Word files only, as before, and a PDF already kept still lists, opens and fills. pdf.js's words are // Glaze's (lib/pdf-l10n.js). @@ -195,7 +196,8 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const root = document.documentElement; const life = new AbortController(), signal = life.signal; if (host) words = host.t || null; else root.dataset.mode = 'pdf'; - await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/pdf-l10n.js']); + // A kept PDF's tools (lib/pdf-tools.js) come with its viewer; a preview has none. + await GZ_WRITER_PAPER.need(['lib/writer-papers.js', 'lib/pdf-l10n.js', ...(src ? [] : ['lib/pdf-tools.js'])]); const sheet = (href) => new Promise((ok) => { document.head.append(h('link', { rel: 'stylesheet', href, onload: ok, onerror: ok })); }); const st = papers(), PA = GZ_WRITER_PAPERS; let rec = src ? null : await st.get(key); @@ -231,16 +233,23 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { if (titleIn) titleIn.value = rec?.title || ''; // pdf.js's viewer over the pages: only those in view drawn, forms as fields, links in a new tab, the file's own - // permissions (copying, filling) honoured. Its editors stay off until the tools step turns them on. + // permissions (copying, filling, adding) honoured. const bus = new V.EventBus(), links = new V.PDFLinkService({ eventBus: bus, externalLinkTarget: V.LinkTarget.BLANK, externalLinkRel: 'noopener noreferrer nofollow' }); const finder = new V.PDFFindController({ eventBus: bus, linkService: links }); const l10n = GZ_PDF_L10N.make({ t, lang: locale() }); + // A kept PDF's editor (its tools, lib/pdf-tools.js) waits at NONE until a tool is chosen; a preview has none. const viewer = new V.PDFViewer({ container: scroll, viewer: pagesEl, eventBus: bus, linkService: links, findController: finder, l10n, annotationMode: src ? P.AnnotationMode.ENABLE : P.AnnotationMode.ENABLE_FORMS, - annotationEditorMode: P.AnnotationEditorType.DISABLE, enablePermissions: true, imageResourcesPath: url('lib/pdfjs/images/'), removePageBorders: false }); + annotationEditorMode: src ? P.AnnotationEditorType.DISABLE : P.AnnotationEditorType.NONE, annotationEditorHighlightColors: src ? null : GZ_PDF_TOOLS.HIGHLIGHTS, + enablePermissions: true, imageResourcesPath: url('lib/pdfjs/images/'), removePageBorders: false }); links.setViewer(viewer); + // Every change the tools make saves as a filled field does (modified, below). + let modified = () => {}; + const tools = src ? null : GZ_PDF_TOOLS.mount({ P, viewer, bus, t, h, btn, openMenu: (...a) => openMenu(...a), modified: () => modified(), scroll, signatures: null, say: (x) => say(x) }); + if (tools) findB.after(h('span', { class: 'gp-sepv' }), tools.el); l10n.translate(scroll); const refuse = (e) => { said.textContent = t(e instanceof FileError ? e.message : UNREAD); said.hidden = false; for (const b of [zoomOut, zoomB, zoomIn, findB, pageIn, saveB]) if (b) b.disabled = true; + if (tools) tools.el.hidden = true; // A file the store no longer has: Delete (to Trash, as Documents' own) or Import… it again, where this is a document of its own. if (e?.why === 'missing' && own) said.append(' ', btn({ class: 'gp-box', 'data-act': 'delete', onclick: () => { st.trash(key).then(() => location[window.top === window ? 'assign' : 'replace']('papers.html'), () => say(t('Not saved. Try again in a moment.'))); } }, t('Delete')), ' ', btn({ class: 'gp-box', 'data-act': 'reimport', onclick: () => { pick.click(); } }, t('Import…'))); @@ -265,7 +274,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { else if (e.pageNumber === 1 && !e.cssTransform && !e.isDetailView) ok(true); }, { signal })); if (doc) { - bus.on('pagesinit', () => { viewer.currentScaleValue = 'page-width'; }); + bus.on('pagesinit', () => { viewer.currentScaleValue = 'page-width'; tools?.decide(doc); }); viewer.setDocument(doc); links.setDocument(doc, null); } @@ -320,13 +329,13 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const openMenu = (opener, items) => { if (menu?.opener === opener) { closeMenu(true); return; } closeMenu(); - const el = h('div', { class: 'pop gp-menu gq-menu', role: 'menu' }, items.map(([label, run, props = {}]) => btn({ role: 'menuitem', tabindex: '-1', ...props, onclick: () => { closeMenu(true); run(); } }, label))); + const el = h('div', { class: 'pop gp-menu gq-menu', role: 'menu' }, items.map(([label, run, props = {}]) => btn({ role: 'menuitem', tabindex: '-1', ...props, onclick: (e) => { closeMenu(true); run(e); } }, label))); document.body.append(el); const r = opener.getBoundingClientRect(), w = el.offsetWidth; el.style.left = `${Math.max(4, Math.min(r.left, innerWidth - w - 4))}px`; el.style.top = `${r.bottom + 6}px`; el.addEventListener('keydown', (e) => { - const all = [...el.querySelectorAll('[role="menuitem"]')], i = all.indexOf(document.activeElement); + const all = [...el.querySelectorAll('[role^="menuitem"]:not([disabled])')], i = all.indexOf(document.activeElement); if (e.key === 'Escape' || e.key === 'Tab') { e.preventDefault(); closeMenu(true); } else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) { e.preventDefault(); all[e.key === 'Home' ? 0 : e.key === 'End' ? all.length - 1 : (i + (e.key === 'ArrowUp' ? all.length - 1 : 1)) % all.length].focus(); } }); @@ -334,7 +343,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { el.setAttribute('data-open', ''); opener.setAttribute('aria-expanded', 'true'); menu = { el, opener }; - el.querySelector('[role="menuitem"]')?.focus(); + el.querySelector('[role^="menuitem"]:not([disabled])')?.focus(); }; document.addEventListener('pointerdown', (e) => { if (menu && !menu.el.contains(e.target) && !menu.opener.contains(e.target)) closeMenu(); }, { capture: true, signal }); zoomB.addEventListener('click', () => openMenu(zoomB, [...PRESETS.map(([v, label]) => [t(label), () => { zoomTo(v); }, { 'data-zoom': v }]), @@ -404,6 +413,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const say = (text) => { if (state.textContent !== text) state.textContent = text; }; let dirty = false, saving = null, timer = 0; const save = async () => { + tools?.commit(); if (!doc || !dirty) return true; dirty = false; clearTimeout(timer); say(t('Saving…')); @@ -424,7 +434,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { return saving; }; if (doc && !src) { - const modified = () => { dirty = true; say(''); clearTimeout(timer); timer = setTimeout(flush, 1000); }; + modified = () => { dirty = true; say(''); clearTimeout(timer); timer = setTimeout(flush, 1000); }; doc.annotationStorage.onSetModified = modified; // pdf.js only calls onSetModified once until its current save finishes. Track each input as well, so a field // changed during that save cannot be mistaken for the value its worker already captured. @@ -432,17 +442,18 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { ui.addEventListener('change', (e) => { if (e.target.closest?.('.annotationLayer')) { modified(); flush(); } }, { signal }); } back?.addEventListener('click', (e) => { + tools?.commit(true); if (!dirty && !saving) return; e.preventDefault(); flush().then((ok) => { if (ok) location[window.top === window ? 'assign' : 'replace'](back.href); }); }); // The embedded frame's Close, Escape and history routes await the same storage acknowledgement as Documents. - addEventListener('glaze:papers:beforeleave', (e) => { if (dirty || saving) e.detail.wait(flush()); }, { signal }); - document.addEventListener('visibilitychange', () => { if (document.hidden && dirty) flush(); }, { signal }); + addEventListener('glaze:papers:beforeleave', (e) => { tools?.commit(true); if (dirty || saving) e.detail.wait(flush()); }, { signal }); + document.addEventListener('visibilitychange', () => { if (document.hidden) tools?.commit(true); if (document.hidden && dirty) flush(); }, { signal }); // Native tab navigation cannot await a promise. Protect unsaved work until the write acknowledges it. - addEventListener('beforeunload', (e) => { if (dirty || saving) { flush(); e.preventDefault(); e.returnValue = ''; } }, { signal }); + addEventListener('beforeunload', (e) => { tools?.commit(true); if (dirty || saving) { flush(); e.preventDefault(); e.returnValue = ''; } }, { signal }); const dispose = async () => { - life.abort(); clearTimeout(timer); clearTimeout(drawTimer); sizing.disconnect(); l10n.destroy(); closeMenu(); + life.abort(); clearTimeout(timer); clearTimeout(drawTimer); sizing.disconnect(); l10n.destroy(); closeMenu(); tools?.dispose(); viewer.setDocument(null); links.setDocument(null); finder.setDocument(null); if (doc) doc.annotationStorage.onSetModified = null; await task?.destroy().catch(() => {}); diff --git a/scripts/pdf-check.js b/scripts/pdf-check.js index eb0d6ce16..b89e92190 100644 --- a/scripts/pdf-check.js +++ b/scripts/pdf-check.js @@ -475,6 +475,141 @@ const photo = () => { check(true, 'after a reload the moved record opens from the store'); await openHome(); + // ---- the tools (lib/pdf-tools.js): Pen, Highlight, Text, Image and the four shapes on a fresh copy of the form, each + // drawn as a student does, autosaved, still there after a reload, and exported as the PDF's own annotations. + const marked = path.join(tmp, 'Marked up.pdf'); + fs.writeFileSync(marked, fixture()); + await openHome(); + await page.setInputFiles('#gd-file', marked); + const tKey = await until(() => page.evaluate(() => [...document.querySelectorAll('.gd-card')].find((c) => c.querySelector('.gd-title')?.textContent === 'Marked up')?.dataset.key), 'the Marked up card'); + await page.goto(`${HOME}?pdf=${encodeURIComponent(tKey)}`); + await until(() => page.evaluate(() => !!document.querySelector('.pdfViewer .page[data-page-number="1"] canvas') && document.querySelector('[data-tool="pen"]')?.disabled === false), 'the tools on'); + const group = await page.evaluate(() => ({ tools: [...document.querySelectorAll('.gq-tools [data-tool]')].map((b) => b.dataset.tool), label: document.querySelector('.gq-tools').getAttribute('aria-label'), + undo: document.querySelector('.gq-tools [data-act="undo"]').disabled, redo: document.querySelector('.gq-tools [data-act="redo"]').disabled, inBar: !!document.querySelector('.gp-tool .gq-tools'), pdfjsBar: !!document.querySelector('#toolbarContainer, #editorModeButtons') })); + check(['highlight', 'pen', 'text', 'image', 'shape'].every((x) => group.tools.includes(x)) && group.inBar && !group.pdfjsBar && group.undo && group.redo, `the tools are a group in Glaze's toolbar, Undo and Redo off until there is something (${JSON.stringify(group)})`); + const count = (sel) => page.evaluate((s) => document.querySelectorAll(`.page[data-page-number="1"] .annotationEditorLayer ${s}`).length, sel); + const stateNow = () => page.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent); + const fileHash = async () => (await local(`writerPaper:${tKey}`)).file.hash; + const autosaved = async (prev, label) => until(async () => { const h = await fileHash(); return (await stateNow()) === 'Saved' && h !== prev ? h : null; }, label); + const pageBox = () => page.evaluate(() => document.querySelector('.pdfViewer .page[data-page-number="1"]').getBoundingClientRect().toJSON()); + await page.evaluate(() => document.querySelector('.gq-scroll').scrollTo(0, 0)); + let b = await pageBox(), h0 = await fileHash(); + const drag = async ([x0, y0], [x1, y1]) => { await page.mouse.move(b.x + x0, b.y + y0); await page.mouse.down(); for (let i = 1; i <= 8; i++) await page.mouse.move(b.x + x0 + ((x1 - x0) * i) / 8, b.y + y0 + ((y1 - y0) * i) / 8); await page.mouse.up(); }; + // Pen, red and 4 thick. + await page.click('[data-tool="pen"]'); + await page.click('[data-act="color"]'); await page.click('.gq-menu [data-color="#d93025"]'); + await page.click('[data-act="size"]'); await page.click('.gq-menu [data-size="4"]'); + const penShown = await page.evaluate(() => ({ pressed: document.querySelector('[data-tool="pen"]').getAttribute('aria-pressed'), size: document.querySelector('[data-act="size"]').textContent })); + await drag([120, 260], [300, 300]); + await until(async () => (await count('.inkEditor')) === 1, 'the pen stroke'); + const h1 = await autosaved(h0, 'the pen stroke saved'); + check(penShown.pressed === 'true' && penShown.size === '4' && !await page.evaluate(() => document.querySelector('[data-act="undo"]').disabled), `Pen: a stroke on page 1, autosaved (${JSON.stringify(penShown)})`); + // Highlight: the words of page 1, selected with the pointer. + await page.click('[data-tool="highlight"]'); + const words = await page.evaluate(() => [...document.querySelectorAll('.page[data-page-number="1"] .textLayer span')].find((s) => /page 1/.test(s.textContent))?.getBoundingClientRect().toJSON()); + await page.mouse.move(words.x + 2, words.y + words.height / 2); await page.mouse.down(); + for (let i = 1; i <= 6; i++) await page.mouse.move(words.x + 2 + ((words.width - 4) * i) / 6, words.y + words.height / 2); + await page.mouse.up(); + await until(async () => (await count('.highlightEditor')) === 1, 'the highlight'); + const h2 = await autosaved(h1, 'the highlight saved'); + check(true, 'Highlight: the selected words are highlighted, autosaved'); + // Text: a note typed where the page is clicked; choosing Text again puts the tool down and keeps it. + await page.click('[data-tool="text"]'); + await page.mouse.click(b.x + 120, b.y + 380); + await until(() => page.evaluate(() => document.activeElement?.closest?.('.freeTextEditor')), 'the text box'); + await page.keyboard.type('Glaze note'); + await page.click('[data-tool="text"]'); + await until(() => page.evaluate(() => [...document.querySelectorAll('.page[data-page-number="1"] .freeTextEditor')].some((e) => /Glaze note/.test(e.textContent))), 'the note kept'); + const h3 = await autosaved(h2, 'the note saved'); + check(await page.evaluate(() => document.querySelector('[data-tool="text"]').getAttribute('aria-pressed')) === 'false', 'Text: a typed note on the page, autosaved; choosing Text again puts it down'); + // Image: pdf.js's own file chooser, the picture in the middle of the page. + const png = path.join(tmp, 'stamp.png'); + // A 48 px blue square with a white cross, as a PNG. + const chunk = (type, data) => { const z = require('node:zlib'), b = Buffer.alloc(4); b.writeUInt32BE(data.length); const td = Buffer.concat([Buffer.from(type), data]), c = Buffer.alloc(4); c.writeUInt32BE(z.crc32(td) >>> 0); return Buffer.concat([b, td, c]); }; + const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(48, 0); ihdr.writeUInt32BE(48, 4); ihdr.set([8, 2, 0, 0, 0], 8); + const rows = Buffer.concat(Array.from({ length: 48 }, (_, y) => Buffer.concat([Buffer.from([0]), ...Array.from({ length: 48 }, (_, x) => Buffer.from(Math.abs(x - y) < 3 || Math.abs(x + y - 47) < 3 ? [255, 255, 255] : [26, 86, 219]))]))); + fs.writeFileSync(png, Buffer.concat([Buffer.from('89504e470d0a1a0a', 'hex'), chunk('IHDR', ihdr), chunk('IDAT', require('node:zlib').deflateSync(rows)), chunk('IEND', Buffer.alloc(0))])); + const chooser = page.waitForEvent('filechooser'); + await page.click('[data-tool="image"]'); + await (await chooser).setFiles(png); + await until(async () => (await count('.stampEditor')) === 1 && page.evaluate(() => !!document.querySelector('.stampEditor canvas')), 'the picture'); + const h4 = await autosaved(h3, 'the picture saved'); + check(true, 'Image: a picture from a file on the page, autosaved'); + // Shapes: a rectangle and an ellipse dragged out, a line chosen with the keyboard (in the middle, selected), an arrow dragged. + await page.keyboard.press('Escape'); + const shapeTo = async (kind) => { await page.click('[data-tool="shape"]'); await page.click(`.gq-menu [data-shape="${kind}"]`); }; + await shapeTo('rect'); await drag([100, 450], [260, 540]); + await until(async () => (await count('.inkEditor')) === 2, 'the rectangle'); + await shapeTo('ellipse'); await drag([300, 450], [460, 540]); + await until(async () => (await count('.inkEditor')) === 3, 'the ellipse'); + await page.locator('[data-tool="shape"]').focus(); + await page.keyboard.press('Enter'); + await until(() => page.evaluate(() => document.activeElement?.closest?.('.gq-menu')), 'the shapes menu by keyboard'); + for (let i = 0; i < 6 && !(await page.evaluate(() => document.activeElement?.dataset.shape === 'line')); i++) await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await until(async () => (await count('.inkEditor')) === 4, 'the line by keyboard'); + const lineSel = await page.evaluate(() => !!document.querySelector('.page[data-page-number="1"] .inkEditor.selectedEditor')); + await shapeTo('arrow'); await drag([120, 600], [320, 640]); + await until(async () => (await count('.inkEditor')) === 5, 'the arrow'); + const h5 = await autosaved(h4, 'the shapes saved'); + check(lineSel, 'Shapes: a rectangle, an ellipse and an arrow dragged out, a line put in by keyboard and selected, autosaved'); + // pdf.js's own bar on a selected mark (its colour and Delete): its buttons keep their size under the page's text scale. + const glyph = await page.evaluate(() => { const d = document.querySelector('.selectedEditor .editToolbar .deleteButton'), c = getComputedStyle(d); return { font: parseFloat(c.fontSize), line: c.lineHeight, h: d.getBoundingClientRect().height }; }); + check(glyph.font <= 16 && glyph.h <= 40, `pdf.js's bar on a selected mark keeps its buttons small (${JSON.stringify(glyph)})`); + await shot('tools-1280.png'); + await page.setViewportSize({ width: 640, height: 900 }); + await shot('tools-640.png'); + await page.setViewportSize({ width: 1280, height: 900 }); + // Undo takes the arrow back; Redo puts it again. + await page.click('[data-act="undo"]'); + await until(async () => (await count('.inkEditor:not(.hidden)')) === 4, 'undo'); + const redoOn = await page.evaluate(() => !document.querySelector('[data-act="redo"]').disabled); + await page.click('[data-act="redo"]'); + await until(async () => (await count('.inkEditor')) === 5, 'redo'); + check(redoOn, 'Undo takes the last shape back and Redo puts it again (pdf.js\'s own undo)'); + await until(async () => (await stateNow()) === 'Saved' && !await page.evaluate(() => document.querySelector('.gp-state').textContent === ''), 'saved after redo'); + await page.keyboard.press('Escape'); + // A reload: the PDF's own annotations, drawn by pdf.js's annotation layer. + await page.reload(); + const kinds = await until(() => page.evaluate(() => { + const l = document.querySelector('.page[data-page-number="1"] .annotationLayer'); + const n = (c) => l?.querySelectorAll(`.${c}`).length || 0; + const k = { ink: n('inkAnnotation'), highlight: n('highlightAnnotation'), text: n('freeTextAnnotation'), stamp: n('stampAnnotation') }; + return k.ink === 5 && k.highlight === 1 && k.text === 1 && k.stamp === 1 ? k : null; + }), 'the annotations after a reload'); + check(!!kinds, `after a reload every mark is still on page 1 (${JSON.stringify(kinds)})`); + // Editable again: Pen's mode makes pdf.js's ink annotations editors. + await until(() => page.evaluate(() => document.querySelector('[data-tool="pen"]')?.disabled === false), 'the tools on after a reload'); + await page.click('[data-tool="pen"]'); + await until(async () => (await count('.inkEditor')) === 5, 'the kept ink as editors'); + await page.click('[data-tool="pen"]'); + check(true, 'after a reload the kept ink is editable again'); + // Export: the file as saved, its marks the PDF's own annotations (not drawn into the page), the pen's red kept. + const tDown = page.waitForEvent('download'); + await page.click('.gq-more'); + await page.click('.gq-menu [data-act="export"]'); + const tFile = path.join(out, 'annotated.pdf'); + await (await tDown).saveAs(tFile); + const tBytes = fs.readFileSync(tFile), tText = tBytes.toString('latin1'); + const subtypes = Object.fromEntries(['Ink', 'Highlight', 'FreeText', 'Stamp'].map((s) => [s, (tText.match(new RegExp(`/Subtype\\s*/${s}\\b`, 'g')) || []).length])); + check(sha(tBytes) === (await local(`writerPaper:${tKey}`)).file.hash && subtypes.Ink === 5 && subtypes.Highlight === 1 && subtypes.FreeText === 1 && subtypes.Stamp === 1 && /\/C\s*\[\s*0\.85\d*\s+0\.18\d*\s+0\.14\d*\s*\]/.test(tText), + `Export .pdf: the saved file, its marks as editable annotations (${JSON.stringify(subtypes)}, ${tBytes.length} bytes)`); + + // ---- a file whose owner forbids adding to it: the tools are there, disabled, and say why. + const noMarks = path.join(tmp, 'No marks.pdf'), FX = require('./pdf-fixtures.js'); + fs.writeFileSync(noMarks, FX.write(FX.pagesOf(FX.LABELS.map((label) => ({ label }))).objects, { id: 'no-marks', encrypt: { owner: FX.PASSWORDS.owner, permissions: (0xfffffffc & ~0x20 & ~0x08) | 0 } })); + await openHome(); + await page.setInputFiles('#gd-file', noMarks); + const nKey = await until(() => page.evaluate(() => [...document.querySelectorAll('.gd-card')].find((c) => c.querySelector('.gd-title')?.textContent === 'No marks')?.dataset.key), 'the No marks card'); + await page.goto(`${HOME}?pdf=${encodeURIComponent(nKey)}`); + const refused = await until(() => page.evaluate(() => { + const g = document.querySelector('.gq-tools'); + return g && 'off' in g.dataset ? { all: [...g.querySelectorAll('[data-tool]')].every((x) => x.disabled), why: g.title, each: g.querySelector('[data-tool="pen"]').title } : null; + }), 'the tools refused'); + check(refused.all && refused.why === "The owner of this PDF doesn't allow adding to it." && refused.each === refused.why, `a file that forbids adding annotations shows the tools disabled, with the reason (${JSON.stringify(refused)})`); + await openHome(); + + // A PDF whose first page can't be read (its page tree points at nothing) is refused at import; one already kept says // so in the viewer instead of an empty viewer waiting for pages that never come. const broken = Buffer.from(fixture().toString('latin1').replace(/\/Kids \[(\d+) 0 R/, '/Kids [99 0 R'), 'latin1'); diff --git a/test/i18n.test.js b/test/i18n.test.js index 4b0dc4d8c..75ee4cf00 100644 --- a/test/i18n.test.js +++ b/test/i18n.test.js @@ -64,7 +64,7 @@ const fromJs = (js, calls = /\b(?:t|tr)\s*\(/g) => { const JS = ['sharing/quick.js', 'lib/mcp/approve-page.js', 'features/welcome.js', 'lib/sessions-ui.js', 'content.js', 'content-ultra.js', 'content-canvas.js', 'options.js', 'popup.js', 'ui.js', 'lib/page.js', 'lib/petlibrary.js', 'lib/petsprites.js', 'lib/todo.js', 'lib/ultra.js', 'lib/editor-x.js', 'lib/editor-xbg.js', 'lib/editor-xmotion.js', 'lib/editor-pointer.js', 'lib/editor-xparts.js', 'lib/editor-mods.js', 'lib/editor-widget.js', 'lib/editor-navbar.js', 'lib/editor-ai.js', 'lib/editor-writer.js', 'lib/parts.js', 'lib/lookparts.js', ...fs.readdirSync(path.join(root, 'features')).filter((f) => f.endsWith('.js')).map((f) => `features/${f}`)]; // Glaze Writer's frame (writer.html, papers.html) and the libraries whose words it shows. lib/writer-docx.js's t() reads // a run's marks, so only its warnings and refusals are words (below). -const WRITER = ['writer.js', 'papers.js', 'sheet-kind.js', 'sheet-grid.js', 'lib/sheet-chart.js', 'pdf-kind.js', 'slides-kind.js', ...fs.readdirSync(root).filter((f) => /^writer-.+\.js$/.test(f)), ...fs.readdirSync(path.join(root, 'lib')).filter((f) => /^writer-.+\.js$/.test(f) && f !== 'writer-docx.js').map((f) => `lib/${f}`)]; +const WRITER = ['writer.js', 'papers.js', 'sheet-kind.js', 'sheet-grid.js', 'lib/sheet-chart.js', 'pdf-kind.js', 'lib/pdf-tools.js', 'slides-kind.js', ...fs.readdirSync(root).filter((f) => /^writer-.+\.js$/.test(f)), ...fs.readdirSync(path.join(root, 'lib')).filter((f) => /^writer-.+\.js$/.test(f) && f !== 'writer-docx.js').map((f) => `lib/${f}`)]; JS.push(...WRITER); // The capitalised strings of a `const NAME = …;` table (its labels and messages, not its ids, CSS or numbers). const tableOf = (f, ...names) => { @@ -87,6 +87,8 @@ const tableOf = (f, ...names) => { const writerWords = [ ...tableOf('writer.js', 'LABELS'), ...tableOf('writer-cite.js', 'TYPES', 'FIELDS'), ...tableOf('writer-ai.js', 'STATUS'), ...tableOf('writer-submit.js', 'SAYS'), ...tableOf('writer-proof.js', 'LABEL'), + // The PDF tools' names, colours and the reason a file refuses them (lib/pdf-tools.js). + ...tableOf('lib/pdf-tools.js', 'SHAPES', 'INKS', 'MARKS', 'TOOLS', 'NO_TOOLS'), ...tableOf('writer-paper.js', 'SPACING', 'KIND', 'STYLES', 'REASONS'), ...tableOf('lib/writer-format.js', 'ROWS', 'names'), ...tableOf('lib/ai/tasks/writer-feedback.js', 'GRADE_REASONS'), ...tableOf('lib/ai/failure.js', 'REASONS', 'ERRORS'), // Labels handed to the paper chrome's helpers, which translate them. diff --git a/test/pdf-tools.test.js b/test/pdf-tools.test.js new file mode 100644 index 000000000..bdf2f6d6f --- /dev/null +++ b/test/pdf-tools.test.js @@ -0,0 +1,55 @@ +// The PDF tools' shapes (lib/pdf-tools.js shape): a rectangle, an ellipse, a line and an arrow as pdf.js's Ink JSON, the +// strokes in the page's PDF space with each corner given twice, so pdf.js's smoothing keeps them sharp. The tools on a +// page are scripts/pdf-check.js. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const T = require('../lib/pdf-tools.js'); + +const pairs = (a) => Array.from({ length: a.length / 2 }, (_, i) => [a[2 * i], a[2 * i + 1]]); +// pdf.js's smoothing (Outline.createBezierPoints): a stroke runs through the midpoints of its points, from the first to +// the midpoint of the last two. Each corner's own point must be on that path. +const through = (pts) => { const out = [pts[0]]; for (let i = 1; i < pts.length - 1; i++) out.push([(pts[i][0] + pts[i + 1][0]) / 2, (pts[i][1] + pts[i + 1][1]) / 2]); return out; }; +const has = (path, [x, y]) => path.some(([a, b]) => Math.abs(a - x) < 1e-3 && Math.abs(b - y) < 1e-3); + +test('a rectangle: one closed stroke through its four corners, sharp', () => { + const s = T.shape('rect', [100, 200, 300, 260], { color: [255, 0, 0], thickness: 4, pageIndex: 2, rotation: 90 }); + assert.equal(s.annotationType, 15); + assert.deepEqual(s.color, [255, 0, 0]); + assert.equal(s.thickness, 4); + assert.equal(s.pageIndex, 2); + assert.equal(s.rotation, 90); + assert.equal(s.paths.points.length, 1); + assert.ok(s.paths.points[0] instanceof Float32Array); + const path = through(pairs(s.paths.points[0])); + for (const c of [[100, 200], [300, 200], [300, 260], [100, 260]]) assert.ok(has(path, c), `corner ${c}`); + assert.deepEqual(path.at(-1), [100, 200], 'closed'); + assert.deepEqual(s.rect, [97, 197, 303, 263], 'the box with half the stroke and a point around it'); +}); + +test('a line and an arrow: from the first point to the second; the arrow\'s head a second stroke at its tip', () => { + const line = T.shape('line', [10, 10, 110, 10]); + assert.equal(line.paths.points.length, 1); + const lp = through(pairs(line.paths.points[0])); + assert.ok(has(lp, [10, 10]) && has(lp, [110, 10])); + const arrow = T.shape('arrow', [10, 10, 110, 10], { thickness: 2 }); + assert.equal(arrow.paths.points.length, 2); + const head = through(pairs(arrow.paths.points[1])); + assert.ok(has(head, [110, 10]), 'the head meets the tip'); + const [one, tip, two] = pairs(arrow.paths.points[1]).filter((_, i) => i % 2 === 0); + assert.deepEqual(tip, [110, 10]); + assert.ok(one[0] < 110 && two[0] < 110 && (one[1] - 10) * (two[1] - 10) < 0, 'the head\'s sides go back from the tip, either side'); + assert.ok(arrow.rect[1] < 10 && arrow.rect[3] > 10, 'the box takes the head in'); +}); + +test('an ellipse: a closed stroke within its box, touching each side', () => { + const s = T.shape('ellipse', [0, 0, 200, 100]); + const pts = pairs(s.paths.points[0]); + assert.ok(pts.every(([x, y]) => x >= -1e-3 && x <= 200 + 1e-3 && y >= -1e-3 && y <= 100 + 1e-3)); + for (const [x, y] of [[200, 50], [100, 100], [0, 50], [100, 0]]) assert.ok(pts.some(([a, b]) => Math.abs(a - x) < 0.2 && Math.abs(b - y) < 0.2), `touches ${x},${y}`); +}); + +test('no other shapes; the highlight colours are pdf.js\'s option string', () => { + assert.throws(() => T.shape('star', [0, 0, 1, 1]), /no shape star/); + assert.deepEqual(T.SHAPES.map(([k]) => k), ['rect', 'ellipse', 'line', 'arrow']); + assert.match(T.HIGHLIGHTS, /^yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F$/); +}); From 43107274bb53ba881a87a6a8491baba4c61c72cd Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:43:50 -0400 Subject: [PATCH 086/907] Syllabus one-pass tests compare the whole input with its half, so a slower machine doesn't fail them --- test/syllabus.test.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/test/syllabus.test.js b/test/syllabus.test.js index be47123cb..67533e394 100644 --- a/test/syllabus.test.js +++ b/test/syllabus.test.js @@ -19,23 +19,31 @@ test('section-like digits and contents leaders stay bounded at the input limit', assert.equal(probe.status, 0, probe.stderr); }); -test('unmatched parentheses at the input limit are read in one pass', () => { +// "In one pass": the whole input costs at most 3× its first half (linear work doubles, quadratic quadruples). Measured +// against itself so a slower machine doesn't fail it (GitHub's runners took 1.5 s where ours take 0.8 s); 5 s caps it. +// make(k) builds the input at k of its full size. +const onePass = (make, o) => { const cpu = () => { const { user, system } = process.cpuUsage(); return (user + system) / 1000; }; - for (const text of ['('.repeat(S.MAX), '(word '.repeat(S.MAX / 6), `Homework (${'1 '.repeat(S.MAX / 2 - 10)}%`, `${'(Quiz 10% '.repeat(S.MAX / 10)}`]) { - const t0 = cpu(); - S.extract(text, { start: Date.UTC(2026, 7, 31) }); - assert.ok(cpu() - t0 < 1500, `${Math.round(cpu() - t0)} ms for ${JSON.stringify(text.slice(0, 12))}…`); + const cost = (text) => { const t0 = cpu(); S.extract(text, o); return cpu() - t0; }; + cost(make(0.05)); + const half = cost(make(0.5)), whole = cost(make(1)); + assert.ok(whole < 5000 && whole < 3 * Math.max(half, 50), `${Math.round(whole)} ms for ${JSON.stringify(make(1).slice(0, 20))}…, ${Math.round(half)} ms for half`); +}; + +test('unmatched parentheses at the input limit are read in one pass', () => { + const n = (x, k) => Math.round(x * k); + for (const make of [(k) => '('.repeat(n(S.MAX, k)), (k) => '(word '.repeat(n(S.MAX / 6, k)), (k) => `Homework (${'1 '.repeat(n(S.MAX / 2 - 10, k))}%`, + (k) => '(Quiz 10% '.repeat(n(S.MAX / 10, k))]) { + onePass(make, { start: Date.UTC(2026, 7, 31) }); } }); test('dense schedules at the input limit are read in one pass', () => { - const cpu = () => { const { user, system } = process.cpuUsage(); return (user + system) / 1000; }; - const lines = (l) => `${l.repeat(Math.ceil(2000 / l.length)).slice(0, 1999)}\n`.repeat(100); - for (const text of [lines('Exam Oct 5-7 '), lines('Quiz 10/5 '), lines('opens Oct 5 and by Oct 7 Exam '), 'Mon\nSep 4\nTopic\tProblem Set 1\ndue\n'.repeat(5000), - 'Quizzes are due every Monday. Final exam: Dec 11\n'.repeat(4000), 'Exam 1 (Thu\n'.repeat(16000)]) { - const t0 = cpu(); - S.extract(text, { start: Date.UTC(2026, 7, 31), week1: Date.UTC(2026, 7, 31), md: true }); - assert.ok(cpu() - t0 < 1500, `${Math.round(cpu() - t0)} ms for ${JSON.stringify(text.slice(0, 20))}…`); + const lines = (l) => (k) => `${l.repeat(Math.ceil(2000 / l.length)).slice(0, 1999)}\n`.repeat(Math.round(100 * k)); + const rows = (l, count) => (k) => l.repeat(Math.round(count * k)); + for (const make of [lines('Exam Oct 5-7 '), lines('Quiz 10/5 '), lines('opens Oct 5 and by Oct 7 Exam '), rows('Mon\nSep 4\nTopic\tProblem Set 1\ndue\n', 5000), + rows('Quizzes are due every Monday. Final exam: Dec 11\n', 4000), rows('Exam 1 (Thu\n', 16000)]) { + onePass(make, { start: Date.UTC(2026, 7, 31), week1: Date.UTC(2026, 7, 31), md: true }); } }); From c9b1da2a4cef748dd7d50a2b10510ca6f71d933f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:43:50 -0400 Subject: [PATCH 087/907] The --from=mcpb test accepts another trusted node or none when the running one can't be trusted --- test/mcp-mcpb.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/mcp-mcpb.test.js b/test/mcp-mcpb.test.js index 72598fc74..579b7d2d3 100644 --- a/test/mcp-mcpb.test.js +++ b/test/mcp-mcpb.test.js @@ -526,7 +526,8 @@ test('the command: --from=mcpb sets up, then runs agent mode; any other --from i const { root, hosts } = B.folder(process.platform, { HOME: home, XDG_DATA_HOME: env.XDG_DATA_HOME }); const l = L.parse(fs.readFileSync(path.join(hosts, B0.id), 'utf8')); if (trusted(process.platform)) assert.equal(fs.realpathSync(l.node), fs.realpathSync(process.execPath)); - else assert.equal(l.node, null, 'a node others can change goes in no launcher'); + // Setup then names another node it finds and trusts, or none. + else assert.ok(l.node === null || (!N().trust(process.platform).path(l.node) && fs.realpathSync(l.node) !== fs.realpathSync(process.execPath)), `a node others can change goes in no launcher: ${l.node}`); assert.equal(l.script, path.join(root, VERSION, 'bin', 'glaze-mcp.js')); assert.doesNotMatch(out.stderr, /unknown option|GLAZE_MCP_BROWSER/); assert.equal(CLI.mode(['--from=mcpb']).mode, 'agent'); From d674f6c9c613b7a0c6e138a1eac05ebd69f0590a Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:46:17 -0400 Subject: [PATCH 088/907] Open Canvas files in Glaze's viewers: Open in Glaze on a PDF or deck's preview and beside links to one, read on the click through the worker's byte port; slides open from memory with Save to Documents --- canvas.css | 7 + content.js | 5 +- features/canvasopen.js | 219 +++++++++++++++++++++++++++++++ features/docs.js | 16 ++- features/download.js | 36 +++++- i18n/de.js | 4 +- i18n/es.js | 4 +- i18n/fr.js | 4 +- i18n/nl.js | 4 +- i18n/pt-BR.js | 4 +- lib/background.js | 16 ++- lib/download.js | 6 +- options.html | 2 +- scripts/canvas-suite-check.js | 237 ++++++++++++++++++++++++++++++++++ slides-kind.js | 54 ++++++-- test/download.test.js | 10 +- test/perf-budget.test.js | 7 +- writer-kinds.js | 12 +- 18 files changed, 608 insertions(+), 39 deletions(-) create mode 100644 features/canvasopen.js create mode 100644 scripts/canvas-suite-check.js diff --git a/canvas.css b/canvas.css index 8f5e02ceb..15ebf5e81 100644 --- a/canvas.css +++ b/canvas.css @@ -44,6 +44,13 @@ html[data-gz-hide-banner] .ic-DashboardCard__header_image { background-image: no #gz-doc-tools button[aria-pressed="true"] { color: var(--gz-accent-ink); background: var(--gz-accent); } #gz-doc-tools [hidden] { display: none; } #gz-doc-tools svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } +/* Open in Glaze (features/canvasopen.js): beside a file link; Canvas's preview out of view while Glaze's viewer shows the file. */ +[data-gz-co-hidden] { display: none !important; } +.gz-co { margin-inline-start: 0.5rem; padding: 0.125rem 0.625rem; border: 1px solid var(--gz-border-strong, var(--gz-line-strong)); border-radius: 999px; background: var(--gz-surface-fill, var(--gz-surface)); color: var(--gz-ink); font: inherit; font-size: 0.8125rem; line-height: 1.5; cursor: pointer; } +.gz-co:hover { color: var(--gz-accent); border-color: var(--gz-accent); } +.gz-co[aria-expanded="true"] { color: var(--gz-accent-ink); background: var(--gz-accent); border-color: var(--gz-accent); } +.gz-co[aria-busy="true"] { cursor: progress; opacity: 0.7; } +.gz-co:focus-visible { outline: 2px solid var(--gz-accent); outline-offset: 2px; } .gz-doc-time { font-size: 11px; color: var(--gz-ink-muted); } /* Documents in a dark palette (enhance.docPages): a preview frame dimmed or inverted whole. Canvas's own viewer (its frame opens /api/v1/canvadoc_session) does this to its pages alone, from inside (features/docviewer.js). */ diff --git a/content.js b/content.js index 23d2ccbc5..b5b4e8b62 100644 --- a/content.js +++ b/content.js @@ -914,7 +914,7 @@ // ---- document buttons (Extras) --------------------------------------------------------------- // Round buttons in the bottom-right gutter, top frame only: dark documents while a file preview is on screen (dark // palettes only), focus mode on a course document. features/docs.js, loaded where they can appear: a course document - // with the switch on, or a file preview (on Ultra, in any frame: a same-origin preview's images are turned back). + // with the switch on, or a file preview (on Ultra, in any frame: a same-origin preview's images are turned back), or a Canvas file. const PREVIEWS = ultra ? '#site-wrap :is(.js-file-viewer-inline-preview, [class*="fileViewerContainer"]) iframe:not(.gz-pv)' : '#doc_preview iframe, #file-preview-iframe, iframe.ef-file-preview-frame:not(.annotated-document-submission), .file_preview iframe'; const DOC = ultra ? '#site-wrap .document-content-root' : '#wiki_page_show .show-content, #assignment_show .description'; const ICONS = { dark: 'M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z', focus: 'M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5', paint: 'M12 3.5c3.4 4 6 7 6 10.2a6 6 0 0 1-12 0c0-3.2 2.6-6.2 6-10.2z' }; @@ -948,7 +948,8 @@ else globalThis.GZ_DOCREVIEW?.sync(false); globalThis.GZ_KEYS?.sync(); if (globalThis.GZ_DOCS) return GZ_DOCS.sync(); - if (observer && ((ultra && document.querySelector(PREVIEWS)) || (solo && settings.enhance.toggles && document.querySelector(`${DOC}, ${PREVIEWS}`)))) { + if (observer && ((ultra && document.querySelector(PREVIEWS)) || (solo && settings.enhance.toggles && document.querySelector(`${DOC}, ${PREVIEWS}`)) + || (solo && site === 'canvas' && settings.enhance.bbPreviews && document.querySelector(`${PREVIEWS},.attachment,.instructure_file_link`)))) { need('features/docs.js', () => globalThis.GZ_DOCS).then(() => GZ_DOCS.sync(), () => {}); } }; diff --git a/features/canvasopen.js b/features/canvasopen.js new file mode 100644 index 000000000..30e873b59 --- /dev/null +++ b/features/canvasopen.js @@ -0,0 +1,219 @@ +// Canvas's files in Glaze's viewers (enhance.bbPreviews, Settings → Documents → Previews in Glaze; each kind only while its +// Settings → Writer switch is on: writer.pdf, writer.slides), loaded on demand: features/docs.js loads this on a Canvas page +// with a file preview or a file link and hands it each pass. A .pdf, .pptx or .ppsx file's preview (the file page's +// #doc_preview, Files' preview at ?preview=) gets Open in Glaze among the document buttons; a link to one (a module's File +// row, a file link in a page) gets it beside the link. Only the click reads the file: Canvas's API names it and signs its +// address, and the worker's byte port reads it (features/download.js canvasFile, Download all's path; a file server Glaze +// doesn't hold is asked for first, in Glaze's Allow frame). Glaze's viewer (writer.html in view mode, the kind's show) loads +// out of sight beside Canvas's preview, or below the link, on the one-use pass the worker gives a Canvas page +// ('glaze:canvas-view'), and once drawn takes the preview's place, or opens. Anything else keeps Canvas's preview and the +// button says so. The button again, the switch off, or the preview gone puts Canvas's back; Esc in the viewer returns to the +// button. Classic script, through GZ_CTX; a second injection keeps the first. +var GZ_CANVAS_OPEN = globalThis.GZ_CANVAS_OPEN || (() => { + const X = globalThis.GZ_CTX; + if (!X || X.site !== 'canvas') return null; + const html = document.documentElement; + const HEIGHT = 'max(640px, calc(100vh - 120px))'; // an assignment's paper, as Ultra's previews (features/bbpreview.js) + const SHOW = 30000; // the viewer drawn within this, or Canvas's preview stays + // The kinds a file opens as, by extension, with the Writer switch each needs and the largest file each reads + // (lib/doc-kinds.js KINDS max). + const KINDS = { pdf: { kind: 'pdf', on: 'pdf', max: 50e6 }, pptx: { kind: 'slides', on: 'slides', max: 50e6 }, ppsx: { kind: 'slides', on: 'slides', max: 50e6 } }; + const MIMES = { 'application/pdf': 'pdf', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': 'ppsx' }; + const typeOf = (name, mime = '') => KINDS[/\.([a-z0-9]{1,5})\s*$/i.exec(String(name || ''))?.[1].toLowerCase()] || KINDS[MIMES[mime]] || null; + const on = (k) => { const s = X.settings(); return X.on() && s.enabled !== false && s.enhance?.bbPreviews !== false && (!k || s.writer?.[k.on] === true); }; + const fileId = (v) => (/^\d{1,20}$/.test(String(v ?? '')) ? String(v) : ''); + const clean = (s) => String(s || '').replace(/\s+/g, ' ').trim().slice(0, 200); + + // ---- what can open: the preview on screen, and the links to files + // The file page (#doc_preview names its file) or Files' preview (?preview= and its frame); → { key, frame: what Glaze's + // takes the place of (the file page's whole box: Glaze's frame goes after it, never inside, where content.js PREVIEWS + // would take it for Canvas's), id, name, type }. + const PREVIEW_FRAMES = '#doc_preview iframe, iframe.ef-file-preview-frame, #file-preview-iframe'; + const previewTarget = () => { + const dp = document.querySelector('#doc_preview[data-attachment_id]'); + const frame = document.querySelector(PREVIEW_FRAMES); + if (!frame) return null; + let id = '', name = '', mime = ''; + if (dp?.contains(frame)) { id = fileId(dp.dataset.attachment_id); name = dp.dataset.attachment_name; mime = dp.dataset.mimetype || ''; } + else { + // Its name: the preview's header, else the frame's label, else the list's link that opened it. + id = fileId(new URLSearchParams(location.search).get('preview')); + name = [document.querySelector('.ef-file-preview-header-filename')?.textContent, frame.getAttribute('aria-label')?.replace(/^File preview for /, ''), frame.title, + id && document.querySelector(`a[href*="preview=${id}"]`)?.textContent].find((x) => typeOf(x)) || ''; + } + const type = id && typeOf(name, mime); + return type ? { key: `p:${id}`, frame: dp?.contains(frame) ? dp : frame, id, name: clean(name), type } : null; + }; + // A module's File row (its class names the file: Attachment_) and a page's file links on this site. + const linkTargets = () => { + const out = []; + for (const li of document.querySelectorAll('.context_module_item.attachment')) { + const id = /\bAttachment_(\d{1,20})\b/.exec(li.className)?.[1], a = li.querySelector('a.item_link'); + const name = clean(a?.getAttribute('title') || a?.textContent), type = id && a && typeOf(name); + if (type) out.push({ key: `l:${id}:${li.id}`, link: a, after: a, below: li.querySelector('.ig-row') || li, id, name, type }); + } + for (const a of document.querySelectorAll('#content a.instructure_file_link[href], #content a[data-api-returntype="File"][href]')) { + let u = null; + try { u = new URL(a.href, location.href); } catch { continue; } + const id = u.origin === location.origin && /^\/courses\/\d+\/files\/(\d{1,20})(?:\/|$)/.exec(u.pathname)?.[1]; + const name = clean(a.getAttribute('title') || a.textContent), type = id && typeOf(name); + if (type) out.push({ key: `a:${id}:${out.length}`, link: a, after: a, below: a.closest('p, li') || a, id, name, type }); + } + return out; + }; + + // ---- the Look as the frame wears it (features/bbpreview.js look) + const TOKENS = [...GZ_COLORS.map(([k]) => k), '--gz-radius', '--gz-font', '--gz-ground', '--gz-surface-paint', '--gz-surface-paint-2', '--gz-surface-opacity', '--gz-glass-mix', '--gz-border-line', '--gz-border-strong']; + const look = () => { + const cs = getComputedStyle(html), bg = html.getAttribute('data-gz-bg'), glass = !!bg && bg !== 'solid'; + const value = (k) => (k === '--gz-glass-mix' && !glass ? '100%' : cs.getPropertyValue(k).trim() || (k === '--gz-surface-opacity' ? '100%' : '')); + return { tokens: Object.fromEntries(TOKENS.map((k) => [k, value(k)]).filter(([, v]) => v)), dark: html.hasAttribute('data-gz-dark-theme'), glass, motion: html.getAttribute('data-gz-motion') || 'off' }; + }; + + // ---- the buttons + const ICON = 'M14 3h7v7M10 14 21 3M19 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5'; + let n = 0; + const button = (round) => { + const b = document.createElement('button'); + b.type = 'button'; + b.id = `gz-co-${++n}`; // gz- ids are skipped by the sweep + b.className = round ? 'gz-co-round' : 'gz-co'; + if (round) { + b.dataset.gz = 'open'; + const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), p = document.createElementNS(svg.namespaceURI, 'path'); + svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true'); p.setAttribute('d', ICON); + svg.append(p); b.append(svg); + } + return b; + }; + // A button's words: Open in Glaze, or what went wrong (the round one says it as its name). + const label = (v, text) => { + if (v.round) { if (v.button.title !== text) v.button.title = text; } + else if (v.button.textContent !== text) v.button.textContent = text; + }; + const SAID = { locked: "This file isn't available to you right now.", big: v => (v.type.kind === 'pdf' ? 'Choose a PDF smaller than 50 MB.' : 'Choose a PowerPoint file smaller than 50 MB.') }; + const views = new Map(); // key → the view + + // ---- one file's view: the bytes on the click, Glaze's frame beside Canvas's preview or below the link + const mark = (v, state) => { if (state) v.button.setAttribute('data-gz-co', state); else v.button.removeAttribute('data-gz-co'); }; + const close = (v, why = '') => { + clearTimeout(v.wait); + v.ctl?.abort(); v.ctl = null; + v.unhello?.(); v.unhello = null; + v.port?.close(); v.port = null; + v.gz?.remove(); v.gz = null; + v.frame?.removeAttribute('data-gz-co-hidden'); + v.shown = false; + v.button.setAttribute(v.round ? 'aria-pressed' : 'aria-expanded', 'false'); + v.button.removeAttribute('aria-busy'); + mark(v, why ? `failed:${why}` : ''); + label(v, why ? X.t(typeof SAID[why] === 'function' ? SAID[why](v) : SAID[why] || "Couldn't open this file in Glaze.") : X.t('Open in Glaze')); + }; + const swap = (v) => { + clearTimeout(v.wait); + v.unhello?.(); v.unhello = null; + v.shown = true; + mark(v, 'shown'); + v.button.removeAttribute('aria-busy'); + v.button.setAttribute(v.round ? 'aria-pressed' : 'aria-expanded', 'true'); + v.frame?.setAttribute('data-gz-co-hidden', ''); + v.gz.style.cssText = `display:block;border:0;width:100%;height:${HEIGHT};background:transparent;color-scheme:${html.hasAttribute('data-gz-dark-theme') ? 'dark' : 'light'}`; + v.gz.removeAttribute('tabindex'); + }; + const hear = (v, m, bytes) => { + if (m.type === 'ready') { + const href = new URL(`/courses/${location.pathname.split('/')[2]}/files/${v.id}`, location.origin).href; + v.port.postMessage({ type: 'init', mode: 'view', kind: v.type.kind, view: { bytes: bytes.buffer, name: v.name, href }, look: look(), locale: X.locale() }, [bytes.buffer]); + } else if (m.type === 'view:shown') swap(v); + else if (m.type === 'view:failed') close(v, `viewer:${String(m.reason || '').replace(/[^\w-]/g, '').slice(0, 20)}`); + else if (m.type === 'view:esc') v.button.focus(); + }; + // writer.html in view mode, out of sight where it will show, on the one-use pass; the bytes go to it once it is ready. + const load = (v, bytes) => { + const src = chrome.runtime.getURL('writer.html'), origin = src.slice(0, src.indexOf('/', src.indexOf('://') + 3)).toLowerCase(); + const f = Object.assign(document.createElement('iframe'), { className: 'gz-co-frame', title: v.name || X.t('Open in Glaze') }); + f.tabIndex = -1; + // Fixed in the window's corner while it loads: a frame out of view isn't drawn (Chromium throttles it), and the link or + // preview it opens for can be below the fold. + const w = Math.round((v.frame || v.below).getBoundingClientRect().width) || 800; + f.style.cssText = `display:block;border:0;width:${w}px;height:${HEIGHT};background:transparent;position:fixed;left:0;top:0;opacity:0;pointer-events:none`; + const ch = new MessageChannel(); + v.gz = f; + v.port = ch.port1; + ch.port1.onmessage = ({ data }) => { if (v.gz === f && data && typeof data.type === 'string') hear(v, data, bytes); }; + v.wait = setTimeout(() => { if (v.gz === f && !v.shown) close(v, 'timeout'); }, SHOW); + let offered = false, transferred = false; + const hello = async (e) => { + if (v.gz !== f || offered || e.source !== f.contentWindow || e.origin !== origin || e.data?.type !== 'glaze:writer:hello') return; + offered = true; + const pass = crypto.randomUUID(); + const ok = await Promise.resolve().then(() => chrome.runtime.sendMessage({ type: 'glaze:canvas-view', pass })).then((r) => r?.ok === true, () => false); + if (v.gz !== f) return; + if (!ok || !f.isConnected) { close(v, 'pass'); return; } + try { f.contentWindow.postMessage({ type: 'glaze:writer', pass }, origin, [ch.port2]); transferred = true; } catch { close(v, 'pass'); } + }; + v.unhello = () => { removeEventListener('message', hello); if (!transferred) ch.port2.close(); }; + addEventListener('message', hello); + f.addEventListener('load', () => { if (v.gz === f && offered) close(v, 'navigated'); }); + f.src = `${src}?host=${encodeURIComponent(location.origin)}`; + (v.frame || v.below).after(f); + }; + const open = async (v) => { + if (v.ctl || v.gz) { close(v); return; } // the button again: Canvas's own back + const ctl = new AbortController(); + v.ctl = ctl; + mark(v, 'reading'); + v.button.setAttribute('aria-busy', 'true'); + label(v, X.t('Open in Glaze')); + try { + await X.need('features/download.js', () => globalThis.GZ_DOWNLOAD); + const f = await GZ_DOWNLOAD.canvasFile(v.id, { signal: ctl.signal, max: v.type.max, beside: v.button }); + if (v.ctl !== ctl || !live(v)) return; + v.ctl = null; + mark(v, 'loading'); + if (!v.name) v.name = clean(f.name); + load(v, f.bytes); + } catch (e) { + if (v.ctl !== ctl) return; + v.ctl = null; + close(v, ctl.signal.aborted ? '' : ['locked', 'big', 'refused'].includes(e?.message) ? e.message : 'read'); + } + }; + const live = (v) => views.get(v.key) === v && v.button.isConnected && on(v.type); + const make = (t, round) => { + const v = { ...t, round, button: button(round), shown: false, ctl: null, gz: null, port: null, wait: 0 }; + label(v, X.t('Open in Glaze')); + v.button.setAttribute(round ? 'aria-pressed' : 'aria-expanded', 'false'); + // Inside Canvas's clickable rows: the click is the button's alone. + v.button.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (e.isTrusted) open(v); }); + views.set(v.key, v); + return v; + }; + const drop = (v) => { close(v); v.button.remove(); views.delete(v.key); }; + + // Each pass: the preview's view (docs.js places its button with the document buttons) and a button by each link. + let pv = null; + const sync = () => { + const pt = on() ? previewTarget() : null, ok = !!pt && on(pt.type); + if (pv && (!ok || pv.key !== pt.key || pv.frame !== pt.frame || !pv.frame.isConnected)) { drop(pv); pv = null; } + if (ok && !pv) pv = make(pt, true); + const seen = new Set(); + if (on()) for (const t of linkTargets()) { + if (!on(t.type)) continue; + seen.add(t.key); + const v = views.get(t.key); + if (v && v.link === t.link && v.button.isConnected) continue; + if (v) drop(v); + t.after.after(make(t, false).button); + } + for (const v of [...views.values()]) if (v !== pv && (!seen.has(v.key) || !v.link.isConnected)) drop(v); + for (const v of views.values()) if (v.gz && !v.gz.isConnected) close(v); + }; + // The round button for the preview on screen, for features/docs.js's group, or null. + const previewButton = () => (pv && pv.frame.isConnected ? pv.button : null); + // What a check reads: each view's state. + const state = () => [...views.values()].map((v) => ({ key: v.key, name: v.name, kind: v.type.kind, round: v.round, shown: v.shown, state: v.button.getAttribute('data-gz-co') || '' })); + return { sync, previewButton, state }; +})(); + +GZ_CANVAS_OPEN; diff --git a/features/docs.js b/features/docs.js index ead4955e4..11b106215 100644 --- a/features/docs.js +++ b/features/docs.js @@ -1,7 +1,8 @@ // Document buttons (enhance.toggles), loaded on demand: content.js loads this where they can appear (a course document // with the switch on, or a file preview's frame), and calls sync() from its passes and esc() on every keydown. // Round buttons in the bottom-right gutter, top frame only: dark documents while a file preview is on screen (dark -// palettes only), focus mode on a course document, with its reading time. Dark documents invert the preview's frame +// palettes only), focus mode on a course document, with its reading time, and on Canvas Open in Glaze for a preview of a +// file Glaze's viewers open (features/canvasopen.js, which this loads). Dark documents invert the preview's frame // (extras.css), which works whatever its origin; where a frame is same-origin, its images and videos are turned back. // Classic script, through GZ_CTX; a second injection keeps the first. var GZ_DOCS = globalThis.GZ_DOCS || (() => { @@ -10,6 +11,8 @@ var GZ_DOCS = globalThis.GZ_DOCS || (() => { const { t, typing, docButton, setVar, setDark, PREVIEWS, DOC } = ctx; const html = document.documentElement; const UNFLIP = 'img, video, picture, canvas { filter: invert(1) hue-rotate(180deg); }'; + // Where a Canvas file can open in Glaze (features/canvasopen.js): a preview, a module's File row, a file link. + const CANVAS_FILES = '#doc_preview, iframe.ef-file-preview-frame, #file-preview-iframe, .context_module_item.attachment, #content a.instructure_file_link, #content a[data-api-returntype="File"]'; const framesShown = new Set(); const framesSeen = new WeakSet(); let docTools = null; @@ -95,6 +98,10 @@ var GZ_DOCS = globalThis.GZ_DOCS || (() => { // This part already loads for Ultra's preview frames, even with reading tools off. Keep their viewer hook lazy too. if (globalThis.GZ_BB_PREVIEW_UI) GZ_BB_PREVIEW_UI.sync(); else if (ultra && ctx.on() && ctx.settings().enhance.bbPreviews && ctx.settings().writer.pdf && document.querySelector(PREVIEWS)) ctx.need('features/bbpreview.js', () => globalThis.GZ_BB_PREVIEW_UI).then(() => GZ_BB_PREVIEW_UI.sync(), () => {}); + // Canvas's files in Glaze's viewers, as lazy: loaded where a preview or a file link is (content.js loads this part there). + if (globalThis.GZ_CANVAS_OPEN) GZ_CANVAS_OPEN.sync(); + else if (!ultra && ctx.solo && ctx.on() && ctx.settings().enhance.bbPreviews && document.querySelector(CANVAS_FILES)) ctx.need('features/canvasopen.js', () => globalThis.GZ_CANVAS_OPEN).then(sync, () => {}); + const openB = !ultra && ctx.solo ? globalThis.GZ_CANVAS_OPEN?.previewButton() || null : null; const on = ctx.solo && ctx.on() && ctx.settings().enhance.toggles; const doc = on && !(!ultra && document.querySelector('.ef-file-preview-overlay, #file-preview-modal-alert')) && document.querySelector(DOC); if (focusAt && (focusAt !== doc || focusPath !== location.pathname)) setFocus(false); @@ -105,7 +112,7 @@ var GZ_DOCS = globalThis.GZ_DOCS || (() => { for (const f of framesShown) if (!f.isConnected) { framesShown.delete(f); frameIO.unobserve(f); } const visible = [...framesShown].find((f) => f.isConnected && f.getClientRects().length); const dark = on && ctx.darkTheme() && !!visible; - if (!doc && !dark) { + if (!doc && !dark && !openB) { docTools?.remove(); docTools = null; html.removeAttribute('data-gz-doc-tools'); @@ -123,9 +130,12 @@ var GZ_DOCS = globalThis.GZ_DOCS || (() => { } if (!ultra) { // In the reading flow (including a modal's flow), so neither content nor Canvas controls sit under it. - const before = doc || visible?.closest('#doc_preview, .ef-file-preview-stretch, .file_preview') || visible; + // Canvas's preview Glaze's viewer stands in for is out of view, and the buttons stay above where it was. + const at = visible || (openB ? document.querySelector(PREVIEWS) : null); + const before = doc || at?.closest('#doc_preview, .ef-file-preview-stretch, .file_preview') || at; if (before && docTools.nextElementSibling !== before) before.before(docTools); } + if (openB && openB.parentElement !== docTools) docTools.append(openB); const [time, darkB, focusB] = docTools.children; const mins = focusAt && GZ_PAGE.readMinutes(ultra ? focusAt.querySelector('.bbml-editor')?.textContent : focusAt.textContent); time.hidden = !mins; diff --git a/features/download.js b/features/download.js index ca139c4ad..5a41f3aa8 100644 --- a/features/download.js +++ b/features/download.js @@ -76,12 +76,11 @@ var GZ_DOWNLOAD = globalThis.GZ_DOWNLOAD || (() => { b.toggleAttribute('data-job', !!j); b.style.setProperty('--gz-pct', `${j?.done ? 0 : j?.pct || 0}%`); }; - // Glaze's Allow frame (features/allow.html) beside a folder's button, for a host the worker can't reach yet: its one button - // asks the browser for that host. → whether it was allowed; no when the download stops first or the button is gone. - // Only the frame's own answer counts (a page can post anything); the worker checks the grant again either way. - const allowFrame = (fid, host, signal, ticket) => new Promise((resolve) => { - const b = document.getElementById(`gz-dl-${fid}`); - if (!b || signal.aborted) return resolve(false); + // Glaze's Allow frame (features/allow.html) beside a button (a folder's; Open in Glaze's), for a host the worker can't reach + // yet: its one button asks the browser for that host. → whether it was allowed; no when the download stops first or the + // button is gone. Only the frame's own answer counts (a page can post anything); the worker checks the grant again either way. + const allowFrame = (b, host, signal, ticket) => new Promise((resolve) => { + if (!b?.isConnected || signal.aborted) return resolve(false); const f = document.createElement('iframe'); f.className = 'gz-dl-allow'; f.title = t('Allow {host}', { host }); @@ -106,7 +105,7 @@ var GZ_DOWNLOAD = globalThis.GZ_DOWNLOAD || (() => { const cid = ultra ? courseId() : canvasCourseId(); const ctl = new AbortController(); const job = { ctl, label: t('Finding files · Cancel'), pct: 0 }; - const bridge = ultra ? null : canvasBytes(ctl.signal, (host, ticket) => allowFrame(fid, host, ctl.signal, ticket)); + const bridge = ultra ? null : canvasBytes(ctl.signal, (host, ticket) => allowFrame(document.getElementById(`gz-dl-${fid}`), host, ctl.signal, ticket)); jobs.set(fid, job); const say = (label, pct = 0) => { Object.assign(job, { label, pct }); paintDl(fid); }; say(job.label); @@ -263,8 +262,31 @@ var GZ_DOWNLOAD = globalThis.GZ_DOWNLOAD || (() => { } }; + // One Canvas file by its id, read on a click as Download all reads each (features/canvasopen.js): Canvas's API names it and + // signs its address, the worker's byte port reads it, an unheld file server asked for beside `beside`. → { name, bytes }; + // throws an Error whose message is why: locked (not yours to open now), big (over max bytes), url, read, refused. + const canvasFile = async (id, { signal, max, beside }) => { + await need('lib/zip.js', () => globalThis.GZ_ZIP); + const f = await capi(`/files/${id}`).catch(() => null); + signal.throwIfAborted(); + if (!f || f.locked_for_user || f.hidden_for_user) throw new Error('locked'); + if (!(f.size <= max)) throw new Error('big'); + const signed = await capi(`/files/${id}/public_url`).catch(() => null); + signal.throwIfAborted(); + const url = GZ_ZIP.canvasURL({ ...f, url: signed?.public_url }, location.origin); + if (!url) throw new Error('url'); + const bridge = canvasBytes(signal, (host, ticket) => allowFrame(beside, host, signal, ticket)); + try { + const bytes = await bridge.read(url).catch((e) => { throw new Error(e?.refused ? 'refused' : e?.limit ? 'big' : 'read'); }); + if (!bytes?.length) throw new Error('read'); + if (bytes.length > max) throw new Error('big'); + return { name: f.display_name || f.filename || '', bytes }; + } finally { bridge.close(); } + }; + return { sync, + canvasFile, // Glaze switched off: what is still being fetched stops. stop: () => { for (const j of jobs.values()) j.ctl.abort(); canvasAsked = new Map(); }, }; diff --git a/i18n/de.js b/i18n/de.js index c5eb46d75..acdcda522 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3751,5 +3751,7 @@ "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", - "Print with notes…": "Mit Notizen drucken…" + "Print with notes…": "Mit Notizen drucken…", + "This file isn't available to you right now.": "Diese Datei ist für dich gerade nicht verfügbar.", + "Couldn't open this file in Glaze.": "Diese Datei konnte nicht in Glaze geöffnet werden." }; diff --git a/i18n/es.js b/i18n/es.js index 9635b6f0e..09a069026 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3751,5 +3751,7 @@ "Don't allow": "No permitir", "Unnamed app": "App sin nombre", "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", - "Print with notes…": "Imprimir con notas…" + "Print with notes…": "Imprimir con notas…", + "This file isn't available to you right now.": "Este archivo no está disponible para ti en este momento.", + "Couldn't open this file in Glaze.": "No se pudo abrir este archivo en Glaze." }; diff --git a/i18n/fr.js b/i18n/fr.js index 1eeac37f9..b7950ee81 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3751,5 +3751,7 @@ "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", - "Print with notes…": "Imprimer avec les notes…" + "Print with notes…": "Imprimer avec les notes…", + "This file isn't available to you right now.": "Ce fichier ne t'est pas accessible pour le moment.", + "Couldn't open this file in Glaze.": "Impossible d'ouvrir ce fichier dans Glaze." }; diff --git a/i18n/nl.js b/i18n/nl.js index 3fae266f9..8485ad07d 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3751,5 +3751,7 @@ "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", - "Print with notes…": "Afdrukken met notities…" + "Print with notes…": "Afdrukken met notities…", + "This file isn't available to you right now.": "Dit bestand is nu niet voor je beschikbaar.", + "Couldn't open this file in Glaze.": "Dit bestand kon niet in Glaze worden geopend." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 890233169..731bfeb94 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3751,5 +3751,7 @@ "Don't allow": "Não permitir", "Unnamed app": "App sem nome", "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", - "Print with notes…": "Imprimir com anotações…" + "Print with notes…": "Imprimir com anotações…", + "This file isn't available to you right now.": "Este arquivo não está disponível para você agora.", + "Couldn't open this file in Glaze.": "Não foi possível abrir este arquivo no Glaze." }; diff --git a/lib/background.js b/lib/background.js index 755b32f2a..b81bfecb1 100644 --- a/lib/background.js +++ b/lib/background.js @@ -2079,6 +2079,20 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { return true; }); +// Canvas files in Glaze's viewers (features/canvasopen.js): a Canvas page Glaze runs on asks, on the student's click, for the +// one-use pass of the writer.html frame it opens to show a file it read. The frame claims it as Writer's own ('glaze:writer' +// above); nothing goes into the page's world, and with no asking frame kept (from: null) the claim seats no AI host. +chrome.runtime.onMessage.addListener((m, s, reply) => { + if (m?.type !== 'glaze:canvas-view') return; + const pass = typeof m.pass === 'string' && /^[\w-]{32,64}$/.test(m.pass) ? m.pass : ''; + fromCanvas(s).then((o) => { + const ok = !!o && !!pass && !readerPasses.has(pass) && readerPasses.size < 50; + if (ok) readerPasses.set(pass, { type: 'glaze:writer', tab: s.tab.id, at: Date.now(), from: null }); + reply({ ok }); + }, () => reply({ ok: false })); + return true; +}); + // Code mods (lib/modbroker.js): a mod's API calls ('glaze:mod') and the page's own asks ('glaze:modpage'), // only from Glaze's content script in the top frame of an LMS page Glaze runs on and is on for, never a quiz or a tool's // page, or from the Documents page. Parts also go to same-site Ultra course frames. The broker checks everything else. @@ -2286,7 +2300,7 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { task.then(reply, () => reply({ ok: false })); return true; }); -const LOADABLE = new Set(['lib/motion.js', 'lib/scene-host.js', 'lib/nav.js', 'features/light.js', 'lib/nav-items.js', 'lib/navlayout.js', 'content-ultra.js', 'content-canvas.js', 'features/nav.js', 'lib/navbar.js', 'features/navbar-canvas.js', 'features/navbar-moodle.js', 'features/navbar.js', 'features/navbar-widgets.js', 'features/settingspage.js', 'lib/stream.js', 'features/stream.js', 'lib/folders.js', 'features/folders.js', 'lib/zip.js', 'lib/drafts.js', 'lib/gradecalc.js', 'features/grades.js', 'features/schedule.js', 'lib/todo.js', 'features/todo.js', 'i18n/de.js', 'i18n/es.js', 'i18n/fr.js', 'i18n/nl.js', 'i18n/pt-BR.js', 'lib/cards.js', 'features/cards.js', 'features/cardtasks.js', 'lib/moodle.js', 'features/moodle.js', 'lib/seek.js', 'features/search.js', 'features/discussion-nav.js', 'lib/remind.js', 'features/reminders.js', 'features/palette.js', 'features/keys.js', 'features/coursemenu.js', 'features/previews.js', 'features/download.js', 'lib/widgets.js', 'lib/sidebar.js', 'features/sidebar.js', 'features/widgets.js', 'features/coursecards.js', 'features/dashboard.js', 'lib/lookx.js', 'lib/lookset.js', 'lib/looks.js', 'features/lookx.js', 'lib/lookparts.js', 'features/lookparts.js', 'lib/motionx.js', 'features/drafts.js', 'features/docs.js', 'features/bbpreview.js', 'features/docreview.js', 'features/place.js', 'lib/gradescope.js', 'lib/ics.js', 'lib/calexport.js', 'features/calexport.js', 'lib/gpa.js', 'lib/doctext.js', 'features/import.js', 'features/gpaterms.js', 'lib/syllabus.js', 'lib/syllabus-ai.js', 'features/syllabus.js', 'lib/classes.js', 'features/classes.js', 'lib/insights.js', 'features/insights.js', 'lib/notes.js', 'features/notes.js', 'lib/ultra.js', 'lib/lmsq.js', 'lib/pet.js', 'lib/petbrain.js', 'lib/petphysics.js', 'features/petworld.js', 'lib/petsprites.js', 'lib/petplayer.js', 'features/pet.js', 'lib/coursetabs.js', 'features/coursetabs.js', 'features/sharing.js', 'lib/parts.js', 'features/rules.js', 'features/motionx.js', 'lib/glsl.js', 'features/mods.js', 'features/modui.js', 'features/modwidgets.js', 'features/routes.js', 'lib/popup.js', 'features/popup.js', 'lib/announce.js', 'features/announce.js', 'features/announce-read.js', 'lib/rubric.js', 'features/rubric.js', 'features/editor-menu.js', 'features/sticky-fold.js', 'lib/writer-ultra.js', 'features/writer.js', 'lib/writer-target.js', 'lib/writer-context-discussion.js', 'features/writer-quote.js', 'lib/writer-context-assignment.js', 'lib/writer-submit.js', 'features/writer-submit.js']); +const LOADABLE = new Set(['lib/motion.js', 'lib/scene-host.js', 'lib/nav.js', 'features/light.js', 'lib/nav-items.js', 'lib/navlayout.js', 'content-ultra.js', 'content-canvas.js', 'features/nav.js', 'lib/navbar.js', 'features/navbar-canvas.js', 'features/navbar-moodle.js', 'features/navbar.js', 'features/navbar-widgets.js', 'features/settingspage.js', 'lib/stream.js', 'features/stream.js', 'lib/folders.js', 'features/folders.js', 'lib/zip.js', 'lib/drafts.js', 'lib/gradecalc.js', 'features/grades.js', 'features/schedule.js', 'lib/todo.js', 'features/todo.js', 'i18n/de.js', 'i18n/es.js', 'i18n/fr.js', 'i18n/nl.js', 'i18n/pt-BR.js', 'lib/cards.js', 'features/cards.js', 'features/cardtasks.js', 'lib/moodle.js', 'features/moodle.js', 'lib/seek.js', 'features/search.js', 'features/discussion-nav.js', 'lib/remind.js', 'features/reminders.js', 'features/palette.js', 'features/keys.js', 'features/coursemenu.js', 'features/previews.js', 'features/download.js', 'lib/widgets.js', 'lib/sidebar.js', 'features/sidebar.js', 'features/widgets.js', 'features/coursecards.js', 'features/dashboard.js', 'lib/lookx.js', 'lib/lookset.js', 'lib/looks.js', 'features/lookx.js', 'lib/lookparts.js', 'features/lookparts.js', 'lib/motionx.js', 'features/drafts.js', 'features/docs.js', 'features/bbpreview.js', 'features/canvasopen.js', 'features/docreview.js', 'features/place.js', 'lib/gradescope.js', 'lib/ics.js', 'lib/calexport.js', 'features/calexport.js', 'lib/gpa.js', 'lib/doctext.js', 'features/import.js', 'features/gpaterms.js', 'lib/syllabus.js', 'lib/syllabus-ai.js', 'features/syllabus.js', 'lib/classes.js', 'features/classes.js', 'lib/insights.js', 'features/insights.js', 'lib/notes.js', 'features/notes.js', 'lib/ultra.js', 'lib/lmsq.js', 'lib/pet.js', 'lib/petbrain.js', 'lib/petphysics.js', 'features/petworld.js', 'lib/petsprites.js', 'lib/petplayer.js', 'features/pet.js', 'lib/coursetabs.js', 'features/coursetabs.js', 'features/sharing.js', 'lib/parts.js', 'features/rules.js', 'features/motionx.js', 'lib/glsl.js', 'features/mods.js', 'features/modui.js', 'features/modwidgets.js', 'features/routes.js', 'lib/popup.js', 'features/popup.js', 'lib/announce.js', 'features/announce.js', 'features/announce-read.js', 'lib/rubric.js', 'features/rubric.js', 'features/editor-menu.js', 'features/sticky-fold.js', 'lib/writer-ultra.js', 'features/writer.js', 'lib/writer-target.js', 'lib/writer-context-discussion.js', 'features/writer-quote.js', 'lib/writer-context-assignment.js', 'lib/writer-submit.js', 'features/writer-submit.js']); chrome.runtime.onMessage.addListener((m, sender, reply) => { if (m?.type !== 'glaze:load') return; const files = Array.isArray(m.files) ? m.files : []; diff --git a/lib/download.js b/lib/download.js index 2b2495609..dec2c50c7 100644 --- a/lib/download.js +++ b/lib/download.js @@ -8,11 +8,13 @@ const GZ_DOWNLOAD = (() => { } catch { return null; } }; // A course page's top frame, or a frame of it on the page's own site: Glaze's course tabs (features/coursetabs.js) run a - // course's pages in frames, and Download all is drawn there. Either way the frame's own address passes the path check. + // course's pages in frames, and Download all is drawn there. Either way the frame's own address passes the path check: + // the course's home and Modules (Download all), or a page that shows or links a file (Open in Glaze, features/canvasopen.js). + const PAGES = /^\/courses\/\d+(?:\/(?:modules(?:\/items\/\d+)?|files(?:\/\d+|\/folder\/[^/]+(?:\/[^/]+)*)?|pages\/[^/]+|wiki|assignments\/(?:\d+|syllabus)|discussion_topics\/\d+|announcements))?\/?$/; const sender = (s, id) => { try { const u = new URL(s.url); - if (s.id !== id || !Number.isInteger(s.tab?.id) || !/^\/courses\/\d+(?:\/modules)?\/?$/.test(u.pathname)) return false; + if (s.id !== id || !Number.isInteger(s.tab?.id) || !PAGES.test(u.pathname)) return false; return s.frameId === 0 || (Number.isInteger(s.frameId) && s.frameId > 0 && u.origin !== 'null' && (s.origin === undefined || s.origin === u.origin) && u.origin === new URL(s.tab.url).origin); } catch { return false; } diff --git a/options.html b/options.html index 408e6c6f1..5ffb714c5 100644 --- a/options.html +++ b/options.html @@ -1420,7 +1420,7 @@

      Export calendar (.ics)

      A quiz you are taking, and tools such as New Quizzes, show without Glaze.
      -
      +
      More options

      Document review

      diff --git a/scripts/canvas-suite-check.js b/scripts/canvas-suite-check.js new file mode 100644 index 000000000..f3b27265b --- /dev/null +++ b/scripts/canvas-suite-check.js @@ -0,0 +1,237 @@ +// Canvas's files in Glaze's viewers (features/canvasopen.js), headless: the real extension (a staged copy that also holds the +// check's file server) on saved Canvas pages under an invented Canvas cloud host, its API and file server answered by the +// check; all else aborts and DNS resolves nowhere. A file page's PDF preview gets Open in Glaze with the document buttons: +// the click (and only it) reads the file through the worker's byte port, Glaze's PDF viewer takes the preview's place, and the +// button again puts Canvas's back. A module's .pptx row and a page's file link open the slides viewer below them, Esc there +// returns to the button, and Save to Documents keeps the deck. Settings → Writer → Slides off: no button by the deck (the PDF +// keeps its own); Previews in Glaze off: none at all. A file the server refuses, or one locked, keeps Canvas's preview and the +// button says so. Screenshots at 1280 px. +// NODE_PATH= nice node scripts/canvas-suite-check.js [evidence directory] +const { chromium } = require('playwright'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'), os = require('node:os'), path = require('node:path'), crypto = require('node:crypto'); +const env = require('./check-env.js'), root = path.resolve(process.env.EXT || path.join(__dirname, '..')); +const G = require(path.join(root, 'defaults.js')), Z = require(path.join(root, 'lib/zip.js')); +const O = 'https://school.instructure.com', HOST = 'files.cdn-a.test'; +const out = path.resolve(process.argv[2] || path.join(os.tmpdir(), 'gz-canvas-suite-check')); +const checks = [], errors = [], fetched = []; +const pause = (ms) => new Promise((r) => setTimeout(r, ms)); +const until = async (fn, label, ms = 30000) => { + const start = Date.now(); let last; + while (Date.now() - start < ms) { try { const v = await fn(); if (v) return v; } catch (e) { last = e; } await pause(80); } + throw Error(`${typeof label === 'function' ? await label() : label} timed out${last ? `: ${last.message}` : ''}`); +}; +const check = (v, label) => { assert.ok(v, label); checks.push(label); console.log(`ok ${label}`); }; +const fixture = (f) => fs.readFileSync(path.join(root, 'test/fixtures', f), 'utf8'); + +// A two-page Letter PDF whose pages say "This is page n". +const pdf = () => { + const objs = []; + const add = (body) => { objs.push(body); return objs.length; }; + const catalog = add(null), pages = add(null), font = add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>'), kids = []; + for (let n = 1; n <= 2; n++) { + const s = `BT /F1 24 Tf 72 700 Td (This is page ${n}) Tj ET`, content = add(`<< /Length ${s.length} >>\nstream\n${s}\nendstream`); + kids.push(add(`<< /Type /Page /Parent ${pages} 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 ${font} 0 R >> >> /Contents ${content} 0 R >>`)); + } + objs[catalog - 1] = `<< /Type /Catalog /Pages ${pages} 0 R >>`; + objs[pages - 1] = `<< /Type /Pages /Kids [${kids.map((k) => `${k} 0 R`).join(' ')}] /Count 2 >>`; + let s = '%PDF-1.7\n'; + const at = []; + objs.forEach((body, i) => { at.push(s.length); s += `${i + 1} 0 obj\n${body}\nendobj\n`; }); + const x = s.length; + s += `xref\n0 ${objs.length + 1}\n0000000000 65535 f \n${at.map((o) => `${String(o).padStart(10, '0')} 00000 n \n`).join('')}trailer\n<< /Size ${objs.length + 1} /Root ${catalog} 0 R >>\nstartxref\n${x}\n%%EOF\n`; + return Buffer.from(s, 'latin1'); +}; +// The slides fixture deck (test/fixtures/pptx/deck), zipped as slides-check does. +const deck = () => { + const dir = path.join(root, 'test/fixtures/pptx/deck'); + const walk = (d, base = '') => fs.readdirSync(d, { withFileTypes: true }).filter((e) => e.name !== '.DS_Store') + .flatMap((e) => (e.isDirectory() ? walk(path.join(d, e.name), `${base}${e.name}/`) : [[`${base}${e.name}`, fs.readFileSync(path.join(d, e.name))]])); + return Buffer.concat(Z.zip(walk(dir).sort(([a], [b]) => (a < b ? -1 : 1)).map(([name, data]) => ({ name, data: new Uint8Array(data), date: new Date(2026, 9, 29, 12) })))); +}; +// The course's files: 21 a PDF, 22 a deck, 23 a PDF the server refuses, 24 locked, 25 a Word file (no kind of Glaze's here). +const FILES = { + 21: { name: 'Syllabus.pdf', body: pdf(), type: 'application/pdf' }, + 22: { name: 'Week 3.pptx', body: deck(), type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, + 23: { name: 'Broken.pdf', body: null, type: 'application/pdf' }, + 24: { name: 'Answers.pdf', body: pdf(), type: 'application/pdf', locked: true }, + 25: { name: 'Notes.docx', body: Buffer.from('docx'), type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }, +}; +const api = (u) => { + const f = /^\/api\/v1\/files\/(\d+)(\/public_url)?$/.exec(u.pathname), x = f && FILES[f[1]]; + if (x) return f[2] ? { public_url: `https://${HOST}/${f[1]}/${encodeURIComponent(x.name)}?sig=s` } : { id: f[1], display_name: x.name, filename: x.name, size: x.body?.length || 1000, 'content-type': x.type, locked_for_user: !!x.locked }; + if (u.pathname === '/api/v1/users/self' || u.pathname === '/api/v1/users/self/profile') return { id: '3', name: 'Sam' }; + return []; +}; +const strip = (s) => s.replace(/]*>[\s\S]*?<\/script>/gi, '').replace(/]*>[\s\S]*?<\/iframe>/gi, ''); +// The file page of file `id`: Canvas's #doc_preview with its own preview frame. +const FILE_PAGE = (id) => strip(fixture('canvas-file-preview.html')) + .replace(/]*) id="doc_preview"[^>]*>/, (m, a) => ``); +// Files with its preview open (?preview=): the overlay as Canvas's Files draws it (ef-file-preview-*, the class names +// content.js PREVIEWS reads; its markup not yet saved from a live Canvas), the deck in it. +const FILES_PREVIEW = (id) => strip(fixture('canvas-file-preview.html')).replace(/
      ]*>/, `
      `); +// Modules: the File row copied once per file, each its own id and name. +const MODULES = (() => { + const page = strip(fixture('canvas-modules.html')); + const row = /
    • \s*(?=
    • row.replaceAll('context_module_item_272', `context_module_item_${80 + i}`).replaceAll('Attachment_21', `Attachment_${id}`).replaceAll('Syllabus.pdf', FILES[id].name) + .replaceAll('/modules/items/272', `/modules/items/${80 + i}`).replaceAll('module-item-272', `module-item-${80 + i}`)).join('\n'); + return page.replace(row, rows); +})(); +// A page with a file link to the deck, as the rich content editor writes one. +const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      ]*>)/, `$1

      Read Week 3.pptx first.

      `); + +(async () => { + let ctx, held = false, tmp, passed = false; + const lock = env.lockPath(); + try { + for (let i = 0; ; i++) { + try { fs.mkdirSync(lock); held = true; fs.writeFileSync(path.join(lock, 'owner'), `${process.pid}\ncanvas-suite-check\n`); break; } + catch (e) { if (e.code !== 'EEXIST' || i >= 240) throw e; if (i % 12 === 0) console.error('[canvas-suite-check] waiting for heavy lock'); await pause(5000); } + } + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-canvas-suite-')); + fs.mkdirSync(out, { recursive: true }); + check(/show-content/.test(WIKI) && WIKI.includes('instructure_file_link') && (MODULES.match(/class="context_module_item [^"]*attachment/g) || []).length >= 5 && FILE_PAGE(21).includes('canvas-own'), 'the saved pages carry the preview, the five File rows and the file link'); + const ext = env.staged(root, [`https://${HOST}/*`]); + const id = [...crypto.createHash('sha256').update(ext).digest('hex').slice(0, 32)].map((c) => String.fromCharCode(97 + parseInt(c, 16))).join(''); + await require('./wait-for-browser.js')(); + ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...env.chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, deviceScaleFactor: 1, locale: 'en-US', reducedMotion: 'reduce', + args: [`--disable-extensions-except=${ext}`, `--load-extension=${ext}`, '--host-resolver-rules=MAP * ~NOTFOUND', '--disable-background-networking'] }); + env.noFirstRun(ctx); + await ctx.route('**/*', (r) => { + const u = new URL(r.request().url()); + if (['chrome-extension:', 'blob:', 'data:'].includes(u.protocol)) return r.continue(); + if (u.origin === O && u.pathname.startsWith('/api/v1/')) return r.fulfill({ contentType: 'application/json', body: JSON.stringify(api(u)) }); + if (u.origin === O && u.pathname.startsWith('/preview-stub/')) return r.fulfill({ contentType: 'text/html', body: 'Canvas preview

      Canvas\'s own preview

      ' }); + const page = /^\/courses\/25\/files\/(\d+)$/.exec(u.pathname); + if (u.origin === O && page && FILES[page[1]]) return r.fulfill({ contentType: 'text/html', body: FILE_PAGE(page[1]) }); + if (u.origin === O && u.pathname === '/courses/25/files' && FILES[u.searchParams.get('preview')]) return r.fulfill({ contentType: 'text/html', body: FILES_PREVIEW(u.searchParams.get('preview')) }); + if (u.origin === O && u.pathname === '/courses/25/modules') return r.fulfill({ contentType: 'text/html', body: MODULES }); + if (u.origin === O && u.pathname === '/courses/25/pages/week-3') return r.fulfill({ contentType: 'text/html', body: WIKI }); + if (u.hostname === HOST) { + fetched.push(u.pathname); + const f = FILES[u.pathname.split('/')[1]]; + return f?.body ? r.fulfill({ contentType: f.type, body: f.body }) : r.fulfill({ status: 500, body: '' }); + } + return r.abort(); + }); + const sw = ctx.serviceWorkers().find((w) => w.url().startsWith(`chrome-extension://${id}/`)) || await ctx.waitForEvent('serviceworker', { predicate: (w) => w.url().startsWith(`chrome-extension://${id}/`) }); + const set = ({ enhance = {}, writer = {} } = {}) => sw.evaluate((s) => chrome.storage.sync.set(s), G.GZ_SANITIZE({ preset: 'Frost', navbar: { on: false }, footer: false, + enhance: { ...Object.fromEntries(Object.keys(G.GZ_DEFAULTS.enhance).map((k) => [k, false])), bbPreviews: true, docPages: 'plain', ...enhance }, writer: { ...G.GZ_DEFAULTS.writer, pdf: true, slides: true, ...writer } })); + await set(); + const page = await ctx.newPage(); + page.on('pageerror', (e) => errors.push(`page: ${e.message}`)); + page.on('console', (m) => { const x = m.text(); if (m.type() === 'error' && !/Failed to load resource|ERR_FAILED|net::/.test(x)) errors.push(`console: ${x}`); }); + // What the page shows of Glaze, for a wait that times out. + const seen = () => page.evaluate(() => JSON.stringify({ glaze: [...document.documentElement.attributes].map((a) => a.name).filter((n) => n.startsWith('data-gz')).length, tools: document.getElementById('gz-doc-tools')?.outerHTML.slice(0, 300) || null, + buttons: document.querySelectorAll('.gz-co, [data-gz="open"]').length, preview: !!document.querySelector('#doc_preview iframe') })).then((x) => `${x}; errors ${JSON.stringify(errors)}`); + const shot = async (name) => { await page.screenshot({ path: path.join(out, name) }); }; + const frameOf = async (sel = 'iframe.gz-co-frame') => { const el = await page.$(sel); return el && el.contentFrame(); }; + const gz = (sel = 'iframe.gz-co-frame') => page.evaluate((sel) => { const f = document.querySelector(sel); if (!f) return null; const cs = getComputedStyle(f); return { shown: cs.display === 'block' && cs.opacity === '1' && f.getBoundingClientRect().height > 300, n: document.querySelectorAll('iframe.gz-co-frame').length }; }, sel); + + // ---- a file page's PDF preview: Open in Glaze with the document buttons, read only on the click + await page.goto(`${O}/courses/25/files/21`); + const roundB = '#gz-doc-tools button[data-gz="open"]'; + await until(() => page.locator(roundB).count(), async () => `Open in Glaze with the document buttons (${await seen()})`); + const before = await page.evaluate((b) => ({ title: document.querySelector(b).title, pressed: document.querySelector(b).getAttribute('aria-pressed'), canvas: !!document.getElementById('canvas-own')?.getBoundingClientRect().height, above: !!(document.getElementById('gz-doc-tools').compareDocumentPosition(document.getElementById('doc_preview')) & Node.DOCUMENT_POSITION_FOLLOWING) }), roundB); + check(before.title === 'Open in Glaze' && before.pressed === 'false' && before.canvas && before.above, `the file page: Open in Glaze above Canvas's preview, not pressed (${JSON.stringify(before)})`); + check(fetched.length === 0, 'nothing was read from the file server before the click'); + await page.click(roundB); + await until(async () => (await gz())?.shown, async () => `Glaze's viewer in the preview's place (${JSON.stringify(await gz())}; fetched ${JSON.stringify(fetched)}; state ${await page.getAttribute(roundB, 'data-gz-co')}; errors ${JSON.stringify(errors)})`); + let fr = await until(() => frameOf(), 'the viewer\'s document'); + const pdfView = await until(() => fr.evaluate(() => { const t = document.querySelector('.pdfViewer .page[data-page-number="1"] .textLayer')?.textContent || ''; return /This is page 1/.test(t) && { doc: document.documentElement.dataset.doc, acts: [...document.querySelectorAll('[data-act]')].map((b) => b.dataset.act).join() }; }), 'page 1 drawn in Glaze\'s PDF viewer'); + const swapped = await page.evaluate((b) => ({ hidden: document.getElementById('doc_preview').hasAttribute('data-gz-co-hidden'), pressed: document.querySelector(b).getAttribute('aria-pressed'), after: document.getElementById('doc_preview').nextElementSibling?.classList.contains('gz-co-frame') }), roundB); + check(pdfView.doc === 'pdf' && /original/.test(pdfView.acts) && /save/.test(pdfView.acts) && swapped.hidden && swapped.pressed === 'true' && swapped.after, + `the PDF opens in Glaze's viewer in the preview's place, with Open original and Save to Documents (${JSON.stringify({ ...pdfView, ...swapped })})`); + check(fetched.length === 1 && fetched[0] === '/21/Syllabus.pdf', `the click read the file once, through the worker's byte port (${JSON.stringify(fetched)})`); + await shot('canvas-file-pdf-1280.png'); + await page.click(roundB); + await until(() => page.evaluate(() => !document.querySelector('iframe.gz-co-frame') && !document.getElementById('doc_preview').hasAttribute('data-gz-co-hidden')), 'the button again: Canvas\'s preview back'); + check(await page.getAttribute(roundB, 'aria-pressed') === 'false', 'the button again puts Canvas\'s preview back'); + + // ---- failure: the file server refuses; Canvas's preview stays and the button says so + await page.goto(`${O}/courses/25/files/23`); + await until(() => page.locator(roundB).count(), 'Open in Glaze on the broken file'); + await page.click(roundB); + const broke = await until(() => page.evaluate((b) => { const x = document.querySelector(b); return /^failed:/.test(x.getAttribute('data-gz-co') || '') && { state: x.dataset.gzCo, title: x.title, frames: document.querySelectorAll('iframe.gz-co-frame').length, canvas: !document.getElementById('doc_preview').hasAttribute('data-gz-co-hidden') && document.getElementById('canvas-own').getBoundingClientRect().height > 0 }; }, roundB), 'the broken file\'s failure'); + check(broke.title === "Couldn't open this file in Glaze." && broke.frames === 0 && broke.canvas, `a file the server refuses: Canvas's preview stays, the button says so (${JSON.stringify(broke)})`); + + // ---- Files' preview (?preview=): the deck opens in the preview frame's place + await page.goto(`${O}/courses/25/files?preview=22`); + await until(() => page.locator(roundB).count(), 'Open in Glaze on Files\' preview'); + await page.click(roundB); + await until(async () => (await gz())?.shown, 'the slides viewer in Files\' preview'); + fr = await until(() => frameOf(), 'the preview\'s viewer document'); + const filesPv = await until(() => fr.evaluate(() => document.getElementById('gs-at')?.textContent === '1 / 10' && document.querySelector('.gp-top h1')?.textContent)); + check(filesPv === 'Week 3.pptx' && await page.evaluate(() => document.querySelector('iframe.ef-file-preview-frame').hasAttribute('data-gz-co-hidden') && document.querySelector('iframe.ef-file-preview-frame').nextElementSibling?.classList.contains('gz-co-frame')), + `Files' preview of a deck opens in Glaze's slides viewer in the preview frame's place (${filesPv})`); + await shot('canvas-files-preview-slides-1280.png'); + + // ---- Modules: the deck's row opens the slides viewer below it; the PDF row has its own button, the Word row none + await page.goto(`${O}/courses/25/modules`); + const rowB = (n) => `#context_module_item_${n} .gz-co`; + await until(() => page.evaluate(() => document.querySelectorAll('.gz-co').length === 4), async () => `buttons by the four rows Glaze opens (${await page.evaluate(() => document.querySelectorAll('.gz-co').length)})`); + const rows = await page.evaluate(() => [80, 81, 82, 83, 84].map((n) => document.querySelector(`#context_module_item_${n} .gz-co`)?.textContent || '')); + check(JSON.stringify(rows) === JSON.stringify(['Open in Glaze', 'Open in Glaze', 'Open in Glaze', 'Open in Glaze', '']), `Open in Glaze by the .pptx and .pdf rows, none by the .docx (${JSON.stringify(rows)})`); + await page.click(rowB(80)); + await until(async () => (await gz())?.shown, 'the slides viewer below the deck\'s row'); + fr = await until(() => frameOf(), 'the slides viewer\'s document'); + const slides = await until(() => fr.evaluate(() => document.querySelector('#gs-canvas .gs-slide') && { at: document.getElementById('gs-at')?.textContent, doc: document.documentElement.dataset.doc, title: document.querySelector('.gp-top h1')?.textContent, acts: [...document.querySelectorAll('[data-act]')].map((b) => b.dataset.act).join() }), 'slide 1 in Glaze\'s slides viewer'); + const placed = await page.evaluate(() => ({ expanded: document.querySelector('#context_module_item_80 .gz-co').getAttribute('aria-expanded'), inline: document.querySelector('#context_module_item_80 a.item_link').nextElementSibling?.classList.contains('gz-co'), below: document.querySelector('#context_module_item_80 .ig-row').nextElementSibling?.classList.contains('gz-co-frame') })); + check(slides.at === '1 / 10' && slides.doc === 'slides' && slides.title === 'Week 3.pptx' && /save/.test(slides.acts) && placed.expanded === 'true' && placed.inline && placed.below, + `the deck opens in Glaze's slides viewer below its row (${JSON.stringify({ ...slides, ...placed })})`); + await shot('canvas-modules-slides-1280.png'); + // Esc in the viewer: back to the row's button. + await fr.focus('#gs-canvas'); + await fr.press('#gs-canvas', 'Escape'); + await until(() => page.evaluate(() => document.activeElement === document.querySelector('#context_module_item_80 .gz-co')), 'Esc in the viewer returns to the button'); + check(true, 'Esc in the viewer returns to the row\'s button'); + // Save to Documents keeps the deck as a document of the slides kind. + await fr.click('[data-act="save"]'); + await until(() => fr.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent === 'Saved to Documents'), async () => `Saved to Documents (says "${await fr.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent)}"; errors ${JSON.stringify(errors)})`); + const kept = await sw.evaluate(async () => { const all = await chrome.storage.local.get(null); return Object.entries(all).filter(([k, v]) => k.startsWith('writerPaper:') && v?.kind === 'slides').map(([, v]) => ({ title: v.title, slides: v.slides })); }); + check(kept.length === 1 && kept[0].title === 'Week 3' && kept[0].slides === 10, `Save to Documents keeps the deck (${JSON.stringify(kept)})`); + await page.click(rowB(80)); + await until(() => page.evaluate(() => !document.querySelector('iframe.gz-co-frame')), 'the button again closes the viewer'); + check(await page.getAttribute(rowB(80), 'aria-expanded') === 'false', 'the row\'s button again closes Glaze\'s viewer'); + // A locked file: said on the button, nothing opened. + await page.click(rowB(83)); + const locked = await until(() => page.evaluate(() => { const b = document.querySelector('#context_module_item_83 .gz-co'); return /^failed:/.test(b.dataset.gzCo || '') && { text: b.textContent, frames: document.querySelectorAll('iframe.gz-co-frame').length }; }), 'the locked file\'s answer'); + check(locked.text === "This file isn't available to you right now." && locked.frames === 0, `a locked file: the button says so, nothing opens (${JSON.stringify(locked)})`); + check(!fetched.includes('/24/Answers.pdf'), 'and nothing of it was read'); + + // ---- a page's file link opens the deck below its paragraph + await page.goto(`${O}/courses/25/pages/week-3`); + await until(() => page.locator('#content .show-content .gz-co').count(), 'Open in Glaze by the file link'); + await page.click('#content .show-content .gz-co'); + await until(async () => (await gz())?.shown, 'the slides viewer below the link'); + fr = await until(() => frameOf(), 'the link\'s viewer document'); + check(await until(() => fr.evaluate(() => document.getElementById('gs-at')?.textContent === '1 / 10')) && await page.evaluate(() => document.querySelector('#content .show-content p .gz-co') && document.querySelector('#content .show-content p').nextElementSibling?.classList.contains('gz-co-frame')), + 'a page\'s file link opens the deck below its paragraph'); + + // ---- Settings → Writer → Slides off: no button by the deck; the PDF keeps its own + await set({ writer: { slides: false } }); + await page.goto(`${O}/courses/25/modules`); + await until(() => page.locator(rowB(81)).count(), 'the PDF row\'s button with Slides off'); + check(await page.locator(rowB(80)).count() === 0, 'Slides off: no Open in Glaze by the deck'); + // ---- Previews in Glaze off: none at all (Download all's buttons show the page's pass ran) + await set({ enhance: { bbPreviews: false, download: true } }); + await page.goto(`${O}/courses/25/modules`); + await until(() => page.locator('.gz-dl').count(), 'Download all on the page with Previews in Glaze off'); + check(await page.locator('.gz-co').count() === 0, 'Previews in Glaze off: no Open in Glaze on Canvas'); + + check(errors.length === 0, `no page errors (${errors.join('; ')})`); + passed = true; + } catch (e) { + console.error(e); + process.exitCode = 1; + } finally { + await ctx?.close().catch(() => {}); + if (tmp) fs.rmSync(tmp, { recursive: true, force: true }); + if (held) fs.rmSync(lock, { recursive: true, force: true }); + fs.mkdirSync(out, { recursive: true }); + fs.writeFileSync(path.join(out, 'canvas-suite-results.json'), `${JSON.stringify({ passed, checks, errors, fetched }, null, 2)}\n`); + console.log(`${passed ? 'PASS' : 'FAIL'} canvas-suite-check: ${checks.length} checks`); + } +})(); diff --git a/slides-kind.js b/slides-kind.js index 8c00f9d61..d2fca8089 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -196,7 +196,8 @@ // A deck as a new standalone document, titled by its file's name. → its key. const importFile = async (file, { progress = null } = {}) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); - await need(LIBS); + // A deck shown from elsewhere saves from writer.html, which has no store of papers loaded until then. + await need([...LIBS, 'lib/writer-papers.js']); const fields = await bring(file, progress), title = stem(file.name); const key = GZ_WRITER_PAPERS.newKey(); await papers().save(key, { kind: 'slides', ...fields, named: !!title, title }, { reason: 'import' }); @@ -263,19 +264,22 @@ // host: an assignment's frame (writer-kinds.js view): { mount, t, title, tools }, the viewer in mount with the // assignment's title and the frame's tools (Submit…) before ⋯, no way back to Documents; → its surface. - // o: papers.js's { fresh } (a kind that opens a new document empty), or view's { host }. - const edit = async (key, { host = null } = {}) => { + // o: papers.js's { fresh } (a kind that opens a new document empty), or view's { host }, or show's { host, src }: a deck's + // bytes from elsewhere (src { bytes, name, href }: a Canvas file the page read, writer-kinds.js) opened from memory, nothing + // kept; Open original (href) and Save to Documents (the same bytes through importFile) beside ⋯. host then also has + // shown() once the slide is drawn, failed(reason), esc(). + const edit = async (key, { host = null, src = null } = {}) => { const root = document.documentElement; const life = new AbortController(), signal = life.signal; if (host) words = host.t || null; else root.dataset.mode = 'slides'; await need(LIBS); - const rec = await papers().get(key).catch(() => null); + const rec = src ? null : await papers().get(key).catch(() => null); // A record from before the store moves into it as it is opened (the worker reads its base64 again). if (typeof rec?.file === 'string') papers().migrate?.(key).catch(() => {}); let failed = null; - const bytes = await STORE.get(rec).catch((e) => { failed = e; return null; }); + const bytes = src ? (src.bytes?.length ? src.bytes : null) : await STORE.get(rec).catch((e) => { failed = e; return null; }); const deck = bytes ? await GZ_PPTX.read(bytes).catch((e) => { console.warn(e); return null; }) : null; - const Rr = R(), labels = LABELS(), title = host?.title?.() || rec?.title || t('Untitled document'), n = deck ? deck.slides.length : 0; + const Rr = R(), labels = LABELS(), title = host?.title?.() || rec?.title || src?.name || t('Untitled document'), n = deck ? deck.slides.length : 0; const media = deck ? await blobs(bytes, deck, new Set(deck.media.map((m) => m.path))) : new Map(); let cur = 0, zoom = 'fit', term = '', hits = [], hit = -1, menu = null, showing = null; let notesOpen = false; @@ -294,6 +298,8 @@ const findNext = btn({ id: 'gs-find-next', class: 'gs-btn', 'aria-label': t('Next'), title: t('Next') }, icon('down')); const notesB = btn({ id: 'gs-notes', class: 'gs-btn gs-label', 'aria-pressed': String(notesOpen) }, t('Notes')); const presentB = btn({ id: 'gs-present', class: 'gs-btn gs-label gs-go' }, icon('present'), h('span', { text: t('Present') })); + const origB = src && /^https?:/.test(src.href || '') ? btn({ class: 'gs-btn gs-label', 'data-act': 'original' }, t('Open original')) : null; + const saveB = src ? btn({ class: 'gs-btn gs-label', 'data-act': 'save', disabled: !deck }, t('Save to Documents')) : null; const moreB = btn({ class: 'gs-btn gs-more', 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-label': t('More'), title: t('More') }, icon('dots')); const canvasEl = h('div', { id: 'gs-canvas', class: 'gs-canvas', role: 'tabpanel', tabindex: '0' }); const notesEl = h('section', { id: 'gs-notes-pane', class: 'gs-notes', 'aria-label': t('Notes'), hidden: !notesOpen }); @@ -310,7 +316,7 @@ const view = h('div', { class: 'gp gp-standalone gs-view' }, h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), state), h('div', { class: 'gs-tool' }, h('div', { class: 'gs-group' }, prevB, atEl, nextB), h('div', { class: 'gs-group' }, outB, zoomB, inB), - h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), moreB), + h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), origB, saveB, moreB), h('div', { class: 'gs-work' }, railEl, h('div', { class: 'gs-main' }, canvasEl, notesEl))); (host?.mount || document.body).append(view); if (!deck) { @@ -318,7 +324,7 @@ canvasEl.append(said); for (const b of [prevB, nextB, outB, zoomB, inB, q, findPrev, findNext, notesB, presentB]) b.disabled = true; // A file the store no longer has: Delete (to Trash, as Documents' own) or Import… it again, where this is a document of its own. - if (failed?.why === 'missing' && !host && GZ_WRITER_PAPERS.local(key)) { + if (failed?.why === 'missing' && !host && !src && GZ_WRITER_PAPERS.local(key)) { const pick = h('input', { type: 'file', accept: DK.KINDS.slides.accept.join(','), hidden: true, onchange: () => { const f = pick.files[0]; pick.value = ''; @@ -557,7 +563,7 @@ menu = h('div', { class: 'pop gp-menu gs-menu', role: 'menu', 'aria-label': t('More') }, btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print', disabled: !deck, onclick: () => { closeMenu(true); print(false).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print…')), notesAny ? btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print-notes', onclick: () => { closeMenu(true); print(true).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print with notes…')) : null, - btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'export', disabled: !bytes, onclick: () => { closeMenu(true); exportFile(key).catch((e) => console.warn(e)); } }, t('Export .pptx').replace('.pptx', rec?.ext === '.ppsx' ? '.ppsx' : '.pptx'))); + src ? null : btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'export', disabled: !bytes, onclick: () => { closeMenu(true); exportFile(key).catch((e) => console.warn(e)); } }, t('Export .pptx').replace('.pptx', rec?.ext === '.ppsx' ? '.ppsx' : '.pptx'))); document.body.append(menu); const r = moreB.getBoundingClientRect(); menu.style.setProperty('left', `${Math.max(4, Math.min(r.right - menu.offsetWidth, innerWidth - menu.offsetWidth - 4))}px`); @@ -593,15 +599,41 @@ show(to); }, { signal }); + // A file from elsewhere: the original in a new tab; the deck into Documents as a new document, as an import makes one. + origB?.addEventListener('click', () => { window.open(src.href, '_blank', 'noopener,noreferrer'); }); + saveB?.addEventListener('click', async () => { + if (!deck || saveB.disabled) return; + saveB.disabled = true; + const say = (x) => { state.textContent = x; }; + say(t('Saving…')); + const progress = DK.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); + try { + // Only while Settings → Writer → Slides takes files, as Documents' own import. + if (typeof DK.get('slides')?.importFile !== 'function') throw new Error('off'); + const ext = /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx', name = /\.pp[ts]x$/i.test(src.name || '') ? src.name : `${src.name || t('Slides')}${ext}`; + await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); + progress.end(); + say(t('Saved to Documents')); + } catch (e) { + const big = e instanceof RangeError && e.message === SAYS.size; + if (!big) console.error(e); + progress.fail(e); + say(t(big ? SAYS.size : 'Not saved. Try again in a moment.')); + saveB.disabled = false; + } + }); + if (src) document.addEventListener('keydown', (e) => { if (!e.defaultPrevented && e.key === 'Escape' && !presenting && !menu) { e.preventDefault(); host?.esc?.(); } }, { signal }); + draw(); globalThis.GZ_PAPERS_MOTION?.shown('paper'); + if (src) { if (deck) host?.shown?.(); else host?.failed?.(bytes ? 'read' : 'init'); } // What an assignment's submit sheet sends (writer-submit.js): the deck as it was brought. return { pages: () => null, say: (text) => { if (state.textContent !== text) state.textContent = text || ''; }, ready: () => !!deck, save: async () => true, dispose: () => { life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); endPrint(); for (const u of media.values()) { URL.revokeObjectURL(u); made.delete(u); } }, - file: async () => { const f = await file(key); if (!f) throw new Error('no file'); return { bytes: f.bytes, mime: f.mime, ext: rec?.ext === '.ppsx' ? '.ppsx' : '.pptx' }; } }; + file: async () => { if (src) return { bytes: src.bytes, mime: MIME[/\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx'], ext: /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx' }; const f = await file(key); if (!f) throw new Error('no file'); return { bytes: f.bytes, mime: f.mime, ext: rec?.ext === '.ppsx' ? '.ppsx' : '.pptx' }; } }; }; // A deck brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. @@ -619,6 +651,6 @@ const DEFAULTS = ['lib/color.js', 'lib/surfaces.js', 'lib/fonts.js', 'defaults.js']; const sanitizer = () => (typeof GZ_SANITIZE === 'function' ? null : GZ_WRITER_PAPER.need(DEFAULTS)); const on = Promise.resolve().then(sanitizer).then(() => chrome.storage.sync.get('writer')).then((r) => GZ_SANITIZE({ writer: r?.writer }).writer.slides === true).catch(() => false); - DK.wait(on.then((yes) => DK.register('slides', { edit, view: (key, host) => edit(key, { host }), into, thumb, file, exportFile, text, ...(yes && { importFile }), measure, copy, says: SAYS, exportLabel: 'Export .pptx' }))); + DK.wait(on.then((yes) => DK.register('slides', { edit, view: (key, host) => edit(key, { host }), show: (src, host) => edit(null, { host, src: { ...src } }), into, thumb, file, exportFile, text, ...(yes && { importFile }), measure, copy, says: SAYS, exportLabel: 'Export .pptx' }))); globalThis.GZ_SLIDES_KIND = Object.freeze({ STORE }); })(); diff --git a/test/download.test.js b/test/download.test.js index e42603841..f99d72540 100644 --- a/test/download.test.js +++ b/test/download.test.js @@ -19,7 +19,15 @@ test('only our content script on a course Modules page, or in a frame of one on assert.equal(D.sender(frame, 'ours'), true); assert.equal(D.sender({ ...frame, url: 'https://canvas.example/courses/25' }, 'ours'), true); for (const patch of [{ tab: { id: 1 } }, { tab: { id: 1, url: 'https://canvas.school.edu/' } }, { tab: { id: 1, url: 'bad' } }, { url: 'https://canvas.school.edu/courses/25/modules' }, - { url: 'https://canvas.example/courses/25/files' }, { frameId: undefined }, { frameId: -1 }, { frameId: 1.5 }, { url: 'about:blank', tab: { id: 1, url: 'about:blank' } }]) assert.equal(D.sender({ ...frame, ...patch }, 'ours'), false, JSON.stringify(patch)); + { url: 'https://canvas.example/courses/25/settings' }, { frameId: undefined }, { frameId: -1 }, { frameId: 1.5 }, { url: 'about:blank', tab: { id: 1, url: 'about:blank' } }]) assert.equal(D.sender({ ...frame, ...patch }, 'ours'), false, JSON.stringify(patch)); +}); +test('Open in Glaze reads one file through the same port from a course page that shows or links one, and nowhere else', () => { + const s = { id: 'ours', tab: { id: 1 }, frameId: 0 }; + for (const p of ['/courses/25/files', '/courses/25/files/21', '/courses/25/files/folder/week%203', '/courses/25/modules/items/272', '/courses/25/pages/week-3', '/courses/25/wiki', + '/courses/25/assignments/7', '/courses/25/assignments/syllabus', '/courses/25/discussion_topics/9', '/courses/25/announcements']) + assert.equal(D.sender({ ...s, url: `https://canvas.example${p}?preview=21` }, 'ours'), true, p); + for (const p of ['/courses/25/settings', '/courses/25/grades', '/courses/25/gradebook', '/courses/25/users', '/files/21', '/courses/25/files/21/download', '/courses/x/files']) + assert.equal(D.sender({ ...s, url: `https://canvas.example${p}` }, 'ours'), false, p); }); test('the byte budget counts actual chunks across files and stops at 250 MiB', () => { assert.equal(D.bytes(0, 0), 0); diff --git a/test/perf-budget.test.js b/test/perf-budget.test.js index ed46a522b..2d4814f2f 100644 --- a/test/perf-budget.test.js +++ b/test/perf-budget.test.js @@ -270,6 +270,10 @@ const COURSE_LOOKS_SYNC = 1342 + 9; // approval): content.js keeps Glaze's frame out of PREVIEWS (12), and defaults.js adds enhance.bbPreviews (28), on every // site. The viewer's loader lives in the already-lazy features/docs.js. Measured against 90d6220d. const BB_PREVIEWS = 12 + 28; +// Canvas's files in Glaze's viewers (the 4.0.1 plan's Canvas entry points; a named raise under the 2026-10-19 approval): +// content.js loads the already-lazy features/docs.js where a Canvas page shows a file preview or links a file (162), on every +// site (one shared file); docs.js loads features/canvasopen.js, which does the rest. Measured against f3e110c2. +const CANVAS_OPEN = 162; // Each site's first script is the manifest's: Ultra's, Canvas's, and MoodleCloud's, which is what lib/background.js registers // for other Moodle hosts: Canvas's without its part. const entry = (has) => m.content_scripts.find(has).js; @@ -278,7 +282,7 @@ const FIRST_SCRIPT = { ultra: entry((c) => c.js.includes('content-ultra.js')), c for (const [site, files] of Object.entries(FIRST_SCRIPT)) { test(`the first content script on ${site} stays within its budget`, () => { const total = files.reduce((n, f) => n + size(f), 0); - const baseline = FIRST_SCRIPT_BASELINE[site] + LOOK_PARTS.reduce((n, p) => n + p[site], 0) + CODE_MODS[site] + NAV_LAYOUT_REFRESH + MEDIA_BG[site] + NAVBAR[site] + CANVAS_TABS[site] + HIDE_MODULES[site] + LOAD_AUDIT[site] + DASH_AUDIT[site] + AUDIT_REVIEW[site] + SIDEBAR[site] + SIDEBAR_ACCOUNT[site] + SIDEBAR_UI[site] + SIDEBAR_ULTRA[site] + SIDEBAR_FRAMES[site] + SIDEBAR_SETTINGS[site] + FRESH_DEFAULTS + CONTRAST_ROLES[site] + ULTRA_OFF[site] + EN_US_YEARS + LOOK_OFFER + LMS_CONSENT[site] + ULTRA_SIGNOUT[site] + LMS_REQUESTS[site] + DOC_PAGES + NAV_KEYS + NAV_FRAME_HIDE[site] + EN_PLURALS + SCENE_RECOVERY + CTAB_ONE_BG + FROSTDEV_LOOK, budget = Math.round(baseline * 1.02) + UI_SWAP + ULTRA_VT_ABORT[site] + VT_GUARD[site] + TOOLBAR_ICON + LOCAL_DEV + STREAM_FIX[site] + NAV_GATE[site] + LOAD_RECOVERY + TABS_AUTO[site] + POINTER_SWITCH + POPUP_CORE + RUBRIC_SETTINGS + SYLLABUS_DOCS + WRITER + WRITER_SETTINGS + CTAB_PALETTE[site] + LOOK_CATALOG + COURSE_LOOKS_SYNC + BB_PREVIEWS; + const baseline = FIRST_SCRIPT_BASELINE[site] + LOOK_PARTS.reduce((n, p) => n + p[site], 0) + CODE_MODS[site] + NAV_LAYOUT_REFRESH + MEDIA_BG[site] + NAVBAR[site] + CANVAS_TABS[site] + HIDE_MODULES[site] + LOAD_AUDIT[site] + DASH_AUDIT[site] + AUDIT_REVIEW[site] + SIDEBAR[site] + SIDEBAR_ACCOUNT[site] + SIDEBAR_UI[site] + SIDEBAR_ULTRA[site] + SIDEBAR_FRAMES[site] + SIDEBAR_SETTINGS[site] + FRESH_DEFAULTS + CONTRAST_ROLES[site] + ULTRA_OFF[site] + EN_US_YEARS + LOOK_OFFER + LMS_CONSENT[site] + ULTRA_SIGNOUT[site] + LMS_REQUESTS[site] + DOC_PAGES + NAV_KEYS + NAV_FRAME_HIDE[site] + EN_PLURALS + SCENE_RECOVERY + CTAB_ONE_BG + FROSTDEV_LOOK, budget = Math.round(baseline * 1.02) + UI_SWAP + ULTRA_VT_ABORT[site] + VT_GUARD[site] + TOOLBAR_ICON + LOCAL_DEV + STREAM_FIX[site] + NAV_GATE[site] + LOAD_RECOVERY + TABS_AUTO[site] + POINTER_SWITCH + POPUP_CORE + RUBRIC_SETTINGS + SYLLABUS_DOCS + WRITER + WRITER_SETTINGS + CTAB_PALETTE[site] + LOOK_CATALOG + COURSE_LOOKS_SYNC + BB_PREVIEWS + CANVAS_OPEN; assert.ok(total <= budget, `${site}: ${total} bytes in ${files.length} files: over the budget of ${budget} (${baseline} + 2%). Load new code on demand instead, or put what only one site does in its part.`); }); } @@ -403,6 +407,7 @@ const STYLE_READS = [ ['features/rubric.js', 'getComputedStyle(a.box)', 1], // One recognized rubric section's padding and font per layout pass; no descendant sweep. ['features/writer.js', 'getComputedStyle(html)', 1], // The root's Look tokens, when a Writer box opens or 's Look attributes change. ['features/bbpreview.js', 'getComputedStyle(html)', 1], // The root's Look tokens, once as Glaze's viewer opens for a Blackboard preview. + ['features/canvasopen.js', 'getComputedStyle(html)', 1], // The root's Look tokens, once as Glaze's viewer opens for a Canvas file. ['lib/writer-paginate.js', 'getComputedStyle(view.dom)', 1], // One editor root per batched layout pass: its fractional width gives the exact zoom without a descendant sweep. ['lib/editor.js', 'getComputedStyle(doc.documentElement)', 1], // One root direction read per category keypress, never on scroll or an LMS page. ['lib/scene-host.js', 'getComputedStyle(c)', 1], // only direct siblings of the course/routed shell, when deciding whether it covers visible content diff --git a/writer-kinds.js b/writer-kinds.js index 96745403b..f814c2ab3 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -201,20 +201,22 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { }); // ---- a file shown from elsewhere (writer.js view mode; features/bbpreview.js: Blackboard's preview of an attachment, - // converted to a PDF, which the page read): init { kind: 'pdf', view: { bytes (an ArrayBuffer, transferred), name, href } } - // → the kind's show, nothing kept unless the student saves it to Documents. The page hears view:shown once page 1 is + // converted to a PDF, which the page read; features/canvasopen.js: a Canvas file the page read on a click): init { kind: + // 'pdf' or 'slides', view: { bytes (an ArrayBuffer, transferred), name, href } } → the kind's show, nothing kept unless the + // student saves it to Documents. The page hears view:shown once page 1 is // drawn, view:failed (with a reason) for anything else, so it keeps Blackboard's frame, and view:esc for an Esc the // viewer didn't take. + const SHOWN = ['pdf', 'slides']; frame.on('view', async (m) => { const v = m.view, failed = (reason) => frame.say({ type: 'view:failed', reason: String(reason || 'error') }); const bytes = v?.bytes instanceof ArrayBuffer && v.bytes.byteLength ? new Uint8Array(v.bytes) : null; - if (m.kind !== 'pdf' || !bytes) { failed('init'); return; } - const k = await kind('pdf').catch((e) => { console.error(e); return null; }); + if (!SHOWN.includes(m.kind) || !bytes) { failed('init'); return; } + const k = await kind(m.kind).catch((e) => { console.error(e); return null; }); if (typeof k?.show !== 'function') { failed('kind'); return; } const root = document.documentElement, el = document.createElement('div'); el.className = 'gk'; document.body.append(el); - root.dataset.doc = 'pdf'; + root.dataset.doc = m.kind; root.dataset.view = 'full'; const name = typeof v.name === 'string' ? v.name.slice(0, 200) : '', href = typeof v.href === 'string' && /^https?:/.test(v.href) ? v.href : ''; try { From 05bccac4281bfea6d875f4fcc38e43ee7083db7f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:47:15 -0400 Subject: [PATCH 089/907] Describe optional Google Drive in the privacy policy --- PRIVACY.md | 56 +++++++++++++++++++++++++++++++++---- docs/privacy.md | 7 +++-- test/privacy-claims.test.js | 16 +++++++++++ 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 211b7969e..0b032d55c 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -1,6 +1,6 @@ # Privacy Policy — Glaze -_Last updated: 2026-10-29_ +_Last updated: 2026-10-30_ ## User-installed mods @@ -385,6 +385,12 @@ not those unknown legacy copies or unrelated stickers. event ends, a hash of what was written and Google's version tag for it; Google's sync cursor; and the IDs of deadline events you deleted in Google Calendar, so Glaze doesn't put them back. No sign-in token: Chrome keeps that itself. **Disconnect** deletes all of it. +- **Google Drive** (off until you connect), unavailable in this build while Google verification is pending. In a future + build that enables it, `chrome.storage.local` holds one record, `google`, on this device only: the email address of + the Google account you connected (shown as the connected account), which sign-in flow Glaze used and when you + connected (`{ email, flow, at }`). No access token: Chrome keeps it in its own sign-in cache; Edge, Brave and Firefox + hold it only in the memory of Glaze's background worker; Safari will keep it in the Keychain. **Disconnect** deletes + the record. - **Gradescope in To Do** (off by default), in `chrome.storage.local`, on this device only: which Gradescope account it's from (your name on Gradescope, and a SHA-256 hash of your Gradescope email address, never the address itself), which Canvas or Blackboard course each of your Gradescope courses goes with (by its Gradescope course id and short name, or "none of @@ -525,7 +531,9 @@ not those unknown legacy copies or unrelated stickers. file's hash, size, page or slide count and first page's picture; where that store isn't available, a file up to 3 MB stays in the record itself. A file goes when no document or version points at it. An assignment attempt you open as one of these is kept the same way under the assignment's key. Glaze reads and draws these files on this device, from the - extension's own files, and sends them nowhere. Delete there moves a paper or document to Trash, which keeps it and its versions on this device; 30 days later + extension's own files, and sends them nowhere. Google Drive (below) is unavailable in this build while Google + verification is pending. In a future build that enables it, a document goes to your own Google Drive only when you + connect Drive and choose Save to Drive or turn on Sync Documents. Delete there moves a paper or document to Trash, which keeps it and its versions on this device; 30 days later it is deleted for good, with its versions and saved sources, the next time the browser starts or Documents opens, unless you restore it first; Delete forever and Settings' Clear delete them at once. A paper's edits not yet saved wait in the writer frame's own @@ -775,6 +783,37 @@ you import) are bundled. It makes requests in these cases: - Uninstalling Glaze deletes what it stored on the device but doesn't revoke Google's access by itself: disconnect first, or remove it at https://myaccount.google.com/connections. - Edge, Brave, Safari and Firefox don't offer it; Export calendar (.ics) works everywhere. +- **Google Drive** (off until you connect). Unavailable in this build while Google verification is pending, and this + build can't make any of these requests. In a future build that enables it, the requests go directly between your + browser and Google: `https://accounts.google.com` to sign in, `https://openidconnect.googleapis.com` for your + account's email address, `https://www.googleapis.com` for Drive and `https://oauth2.googleapis.com` to revoke access. + To pick a file, Glaze also opens a static picker page (below). There is no Glaze server, and nothing goes to Frostdev. + - **Connect** (your click in Glaze's settings): the browser asks for the `identity` permission and Google shows its own + sign-in and consent for three scopes: `https://www.googleapis.com/auth/drive.file`, `openid` and `email`. With + `drive.file` Glaze can reach only the Drive files it creates and the files you pick in Google's Picker; it can't + list or open anything else in your Drive. The email address is used to show which account is connected. Glaze never + stores the access token: Chrome keeps it in its own sign-in cache (`getAuthToken`); Edge, Brave and Firefox hold it + only in the background worker's memory (`launchWebAuthFlow`); Safari will use the system's sign-in sheet and keep it + in the Keychain. + - **The picker page**: to pick a file, Glaze opens `https://frostdev.io/glaze/drive-picker/`, or its copy at + `https://frostdev-ops.github.io/glaze-drive-picker/` when frostdev.io doesn't answer. The page loads Google's + Picker. The access token reaches it in the address's fragment, which browsers don't send to servers, and the page + sends the token only to Google. The ids, names and types of the files you pick come back to Glaze in the redirect's + fragment. frostdev.io and GitHub see an ordinary page request (your IP address and browser headers), never the token + or file contents. + - **Open from Drive**: Glaze downloads the file you picked, or exports a Google Doc as .docx, a Sheet as .xlsx and + Slides as .pptx, and imports it into Documents on this device. + - **Save to Drive**: Glaze uploads the document's exported file, or saves it as a Google Doc if you choose. + - **Sync Documents** (a setting, off by default): Glaze keeps a folder named Glaze in your Drive with each document's + file and a JSON copy of the document and its record. When both copies changed, both versions are kept. + - **Submit from Drive**: Glaze downloads the Drive file you pick and attaches it to your Blackboard Ultra assignment's + hand-in. + - **Disconnect**: Glaze asks Google to revoke its access (the token goes in the request body), has the browser forget + its cached tokens, removes the `identity` permission and deletes the `google` record. While Google Calendar sync + still uses the same access, Glaze doesn't revoke it, and only Drive's record goes. You can also remove Glaze's + access at https://myaccount.google.com/permissions. + - Uninstalling Glaze doesn't revoke Google's access by itself, and files in your Drive stay there: disconnect first, + or remove access at https://myaccount.google.com/permissions. - **Tasks on course cards** (Canvas, off by default). On the Dashboard's card view, Glaze asks your own Canvas site for your profile (user id and time zone; one request a page, shared with Better To Do) and uses Better To Do's copy of your planner items. Only when that copy is missing or more than two minutes old and the To Do list isn't on the @@ -1064,7 +1103,7 @@ A site you add is kept in a list in the extension's local storage (`canvas`, `mo ## Permissions - `storage` — to save the settings, GPA choices, past terms and goal, the Insights summary, course names, places, what-if scores, due list, Better To Do's tasks and plans, your class schedule, reminder and streak state, marks on documents, - typed-text backups, Looks history and favorites, Dashboard notes and widgets, the notes app's index, Google Calendar sync's state, and Safari update-check timestamp described above. + typed-text backups, Looks history and favorites, Dashboard notes and widgets, the notes app's index, Google Calendar sync's state, Google Drive's connected account, and Safari update-check timestamp described above. Notes' text, pictures and attachments are in the extension's own IndexedDB. - No permission for reading PDFs and photos. The extension's own pages may compile WebAssembly (`'wasm-unsafe-eval'` in its content security policy), which the OCR engine needs; the reader runs only code that ships in the package, and @@ -1076,7 +1115,10 @@ A site you add is kept in a list in the extension's local storage (`canvas`, `mo client, from the manifest they ship. In a build that enables the sync it is asked for only when you press Connect in Google Calendar sync, and removed on Disconnect. It lets Chrome sign Glaze in to Google Calendar with the Google account in your browser; Chrome shows no warning for it. Glaze never asks for `identity.email` and never sees your - password. + password. Google Drive uses it too, on Chrome, Edge, Brave and Firefox: unavailable in this build while Google + verification is pending. In a future build that enables it, it is asked for when you press Connect for Google Drive + and removed on Disconnect, unless Google Calendar sync still uses it. Chrome signs in with the Google account in your + browser; Edge, Brave and Firefox open Google's own sign-in page. - `unlimitedStorage` — requested at install, so the browser doesn't clear your notes, pictures, attachments and the files kept for Look backgrounds to free space. Nothing more is stored or sent because of it; Chrome shows no warning for it. @@ -1132,12 +1174,14 @@ press it (see Glaze Writer above). Glaze never changes grades. ## Google user data -Google Calendar sync is unavailable in the current build while Google verification is pending. The following describes its use of Google data in a future build that enables this optional feature; calendar file export does not require it. +Google Calendar sync and Google Drive are unavailable in the current build while Google verification is pending. The following describes their use of Google data in a future build that enables these optional features; calendar file export does not require them. Glaze's use and transfer of information received from Google APIs adheres to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements. Glaze uses Google Calendar access only to keep the Glaze calendar in step with your -Canvas work, as described above. It doesn't sell, share or use this data for advertising or to train AI models, no +Canvas work, as described above. It uses Google Drive access (`drive.file`, `openid` and `email`) only to open the +files you pick, save and sync your documents to your own Drive, attach a Drive file you pick to an assignment you hand +in, and show which account is connected, as described above. It doesn't sell, share or use this data for advertising or to train AI models, no person reads it, and none of it goes to frostdev or anyone else. ## Hosting diff --git a/docs/privacy.md b/docs/privacy.md index 05b19ea84..0a3de7353 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -1,6 +1,6 @@ # Privacy summary -_Last updated: 2026-10-29_ +_Last updated: 2026-10-30_ A plain-language summary. The full policy is [PRIVACY.md](../PRIVACY.md). @@ -22,7 +22,9 @@ Backups (**Back up…**, and older settings files and notes ZIPs) include share [Glaze Writer](../PRIVACY.md#what-glaze-stores) (Blackboard Ultra) keeps your papers and the documents on its Documents page, their versions and your format presets, your personal dictionary and the sources you save in `chrome.storage.local` on this device, with no server; **Back up…** includes them. Documents' Trash keeps a deleted paper or document for 30 -days, then deletes it. The Documents page loads a web font your Look uses from Google Fonts or jsDelivr, as Settings does. Discussion, +days, then deletes it. Google Drive is unavailable in this build while Google verification is pending; in a future +build that enables it, a document goes to your own Drive only when you connect Drive and choose Save to Drive or Sync +Documents. The Documents page loads a web font your Look uses from Google Fonts or jsDelivr, as Settings does. Discussion, message and test boxes keep their drafts through Back up what you type when it is on, as Blackboard's own editor does. Spelling and grammar are checked on your device. Writer reads the prompt, thread and rubric from your own Blackboard site, read-only, and sends nothing to Blackboard itself: your words reach it only when you press Blackboard's own Post, Reply, Send or @@ -91,6 +93,7 @@ the Looks gallery is a website you visit yourself; Glaze asks it only for the on Better To Do's own tasks and plans stay on your device too, with a two-minute copy of your Canvas planner items or Blackboard due items. A calendar file you export (.ics) is made on your device and saved by your browser; it holds your course and assignment names, and Glaze keeps no copy. Google Calendar sync is unavailable in this build while Google verification is pending. In a future build that enables it, optional sync would send the same events to the Glaze calendar in your Google account and read changes there, directly between your browser and Google (`www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect); Chrome would keep the sign-in. Calendar file export remains available without connecting Google. +Google Drive is unavailable in this build while Google verification is pending. In a future build that enables it, Connect in Settings asks Google for `drive.file`, `openid` and `email`: Glaze can reach only the Drive files it creates or the ones you pick, and the email shows which account is connected. Open from Drive, Save to Drive, Sync Documents (off by default; a Glaze folder in your Drive) and Submit from Drive go directly between your browser and Google (`accounts.google.com`, `openidconnect.googleapis.com`, `www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect). To pick a file, Glaze opens a static page, `https://frostdev.io/glaze/drive-picker/` or its copy `https://frostdev-ops.github.io/glaze-drive-picker/`, that loads Google's Picker; the token reaches it in the address's fragment, which browsers don't send to servers, so those hosts see only an ordinary page request. Glaze stores the connected account's email, sign-in flow and time, never the token, and nothing goes to Frostdev. Glaze's use of Google data adheres to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements. Dashboard notes, the widgets' layout, the timer and the music link you type stay on your device. So do the notes app's notes, folders, pictures and attachments, in Glaze's own storage (not the school page's); its Canvas link picker reads your own Canvas site, and only a link and title are kept. A music widget contacts its player (Spotify, YouTube through youtube-nocookie.com, Apple Music) only after you press Load player. diff --git a/test/privacy-claims.test.js b/test/privacy-claims.test.js index 656d15a41..1a05b91f5 100644 --- a/test/privacy-claims.test.js +++ b/test/privacy-claims.test.js @@ -47,6 +47,22 @@ test('kept background backups and older-browser reset limits are disclosed', () assert.match(read('docs/privacy.md'), /background file bytes/); }); +test('optional Google Drive is disclosed in both privacy texts, with its scope, picker hosts and Limited Use', () => { + for (const file of ['PRIVACY.md', 'docs/privacy.md']) { + const text = read(file); + const limited = text.split(/\n\n|\n(?=Google )/).find((part) => part.includes('Limited Use')) || ''; + assert.match(text, /`(?:https:\/\/www\.googleapis\.com\/auth\/)?drive\.file`/, file); + assert.match(text, /frostdev\.io\/glaze\/drive-picker\//, file); + assert.match(text, /frostdev-ops\.github\.io\/glaze-drive-picker\//, file); + assert.match(text, /Google Drive[^.]*unavailable in (?:this|the current) build while Google verification is pending/, file); + assert.match(limited, /Drive/, file); + assert.match(limited, /\[Google API Services User Data Policy\]\(https:\/\/developers\.google\.com\/terms\/api-services-user-data-policy\), including\s+the Limited Use requirements/, file); + } + const google = policy.slice(policy.indexOf('## Google user data'), policy.indexOf('## Hosting')); + assert.match(google, /Google Calendar sync and Google Drive are unavailable/); + assert.match(google, /Google Drive access \(`drive\.file`/); +}); + const languages = { '': /account identifiers/, de: /Kontokennungen/, es: /identificadores de escuela, curso y cuenta/, fr: /identifiants d’établissement, de cours et de compte/, nl: /account-ID/, 'pt-BR': /identificadores de escola, curso e conta/, From eb931022800aa045739b040164f9ec03f390cff8 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 14:52:40 -0400 Subject: [PATCH 090/907] Open Canvas spreadsheets in Glaze: .xlsx and .csv files in the grid from memory, nothing kept unless saved to Documents --- features/canvasopen.js | 12 ++++++---- scripts/canvas-suite-check.js | 44 ++++++++++++++++++++++++++++------- sheet-grid.js | 16 +++++++------ sheet-kind.js | 42 ++++++++++++++++++++++++++++++++- writer-kinds.js | 4 ++-- 5 files changed, 94 insertions(+), 24 deletions(-) diff --git a/features/canvasopen.js b/features/canvasopen.js index 30e873b59..4ab95ad15 100644 --- a/features/canvasopen.js +++ b/features/canvasopen.js @@ -1,6 +1,6 @@ // Canvas's files in Glaze's viewers (enhance.bbPreviews, Settings → Documents → Previews in Glaze; each kind only while its -// Settings → Writer switch is on: writer.pdf, writer.slides), loaded on demand: features/docs.js loads this on a Canvas page -// with a file preview or a file link and hands it each pass. A .pdf, .pptx or .ppsx file's preview (the file page's +// Settings → Writer switch is on: writer.pdf, writer.slides, writer.sheets), loaded on demand: features/docs.js loads this on a +// Canvas page with a file preview or a file link and hands it each pass. A .pdf, .pptx, .ppsx, .xlsx or .csv file's preview (the file page's // #doc_preview, Files' preview at ?preview=) gets Open in Glaze among the document buttons; a link to one (a module's File // row, a file link in a page) gets it beside the link. Only the click reads the file: Canvas's API names it and signs its // address, and the worker's byte port reads it (features/download.js canvasFile, Download all's path; a file server Glaze @@ -17,8 +17,10 @@ var GZ_CANVAS_OPEN = globalThis.GZ_CANVAS_OPEN || (() => { const SHOW = 30000; // the viewer drawn within this, or Canvas's preview stays // The kinds a file opens as, by extension, with the Writer switch each needs and the largest file each reads // (lib/doc-kinds.js KINDS max). - const KINDS = { pdf: { kind: 'pdf', on: 'pdf', max: 50e6 }, pptx: { kind: 'slides', on: 'slides', max: 50e6 }, ppsx: { kind: 'slides', on: 'slides', max: 50e6 } }; - const MIMES = { 'application/pdf': 'pdf', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': 'ppsx' }; + const KINDS = { pdf: { kind: 'pdf', on: 'pdf', max: 50e6 }, pptx: { kind: 'slides', on: 'slides', max: 50e6 }, ppsx: { kind: 'slides', on: 'slides', max: 50e6 }, + xlsx: { kind: 'sheet', on: 'sheets', max: 15e6 }, csv: { kind: 'sheet', on: 'sheets', max: 15e6 } }; + const MIMES = { 'application/pdf': 'pdf', 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow': 'ppsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx', 'text/csv': 'csv' }; const typeOf = (name, mime = '') => KINDS[/\.([a-z0-9]{1,5})\s*$/i.exec(String(name || ''))?.[1].toLowerCase()] || KINDS[MIMES[mime]] || null; const on = (k) => { const s = X.settings(); return X.on() && s.enabled !== false && s.enhance?.bbPreviews !== false && (!k || s.writer?.[k.on] === true); }; const fileId = (v) => (/^\d{1,20}$/.test(String(v ?? '')) ? String(v) : ''); @@ -91,7 +93,7 @@ var GZ_CANVAS_OPEN = globalThis.GZ_CANVAS_OPEN || (() => { if (v.round) { if (v.button.title !== text) v.button.title = text; } else if (v.button.textContent !== text) v.button.textContent = text; }; - const SAID = { locked: "This file isn't available to you right now.", big: v => (v.type.kind === 'pdf' ? 'Choose a PDF smaller than 50 MB.' : 'Choose a PowerPoint file smaller than 50 MB.') }; + const SAID = { locked: "This file isn't available to you right now.", big: (v) => ({ pdf: 'Choose a PDF smaller than 50 MB.', slides: 'Choose a PowerPoint file smaller than 50 MB.', sheet: 'Choose a spreadsheet smaller than 15 MB.' })[v.type.kind] }; const views = new Map(); // key → the view // ---- one file's view: the bytes on the click, Glaze's frame beside Canvas's preview or below the link diff --git a/scripts/canvas-suite-check.js b/scripts/canvas-suite-check.js index f3b27265b..af01f7db5 100644 --- a/scripts/canvas-suite-check.js +++ b/scripts/canvas-suite-check.js @@ -49,13 +49,15 @@ const deck = () => { .flatMap((e) => (e.isDirectory() ? walk(path.join(d, e.name), `${base}${e.name}/`) : [[`${base}${e.name}`, fs.readFileSync(path.join(d, e.name))]])); return Buffer.concat(Z.zip(walk(dir).sort(([a], [b]) => (a < b ? -1 : 1)).map(([name, data]) => ({ name, data: new Uint8Array(data), date: new Date(2026, 9, 29, 12) })))); }; -// The course's files: 21 a PDF, 22 a deck, 23 a PDF the server refuses, 24 locked, 25 a Word file (no kind of Glaze's here). +// The course's files: 21 a PDF, 22 a deck, 23 a PDF the server refuses, 24 locked, 25 a Word file (no kind of Glaze's here), +// 26 a CSV. const FILES = { 21: { name: 'Syllabus.pdf', body: pdf(), type: 'application/pdf' }, 22: { name: 'Week 3.pptx', body: deck(), type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, 23: { name: 'Broken.pdf', body: null, type: 'application/pdf' }, 24: { name: 'Answers.pdf', body: pdf(), type: 'application/pdf', locked: true }, 25: { name: 'Notes.docx', body: Buffer.from('docx'), type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }, + 26: { name: 'Grades.csv', body: Buffer.from('Name,Score\nAda,91\nGrace,88\n'), type: 'text/csv' }, }; const api = (u) => { const f = /^\/api\/v1\/files\/(\d+)(\/public_url)?$/.exec(u.pathname), x = f && FILES[f[1]]; @@ -75,7 +77,7 @@ const FILES_PREVIEW = (id) => strip(fixture('canvas-file-preview.html')).replace const MODULES = (() => { const page = strip(fixture('canvas-modules.html')); const row = /
    • \s*(?=
    • row.replaceAll('context_module_item_272', `context_module_item_${80 + i}`).replaceAll('Attachment_21', `Attachment_${id}`).replaceAll('Syllabus.pdf', FILES[id].name) + const rows = ['22', '21', '23', '24', '25', '26'].map((id, i) => row.replaceAll('context_module_item_272', `context_module_item_${80 + i}`).replaceAll('Attachment_21', `Attachment_${id}`).replaceAll('Syllabus.pdf', FILES[id].name) .replaceAll('/modules/items/272', `/modules/items/${80 + i}`).replaceAll('module-item-272', `module-item-${80 + i}`)).join('\n'); return page.replace(row, rows); })(); @@ -92,7 +94,7 @@ const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      = 5 && FILE_PAGE(21).includes('canvas-own'), 'the saved pages carry the preview, the five File rows and the file link'); + check(/show-content/.test(WIKI) && WIKI.includes('instructure_file_link') && (MODULES.match(/class="context_module_item [^"]*attachment/g) || []).length >= 6 && FILE_PAGE(21).includes('canvas-own'), 'the saved pages carry the preview, the five File rows and the file link'); const ext = env.staged(root, [`https://${HOST}/*`]); const id = [...crypto.createHash('sha256').update(ext).digest('hex').slice(0, 32)].map((c) => String.fromCharCode(97 + parseInt(c, 16))).join(''); await require('./wait-for-browser.js')(); @@ -171,9 +173,9 @@ const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      page.evaluate(() => !document.querySelector('iframe.gz-co-frame')), 'the button again closes the viewer'); check(await page.getAttribute(rowB(80), 'aria-expanded') === 'false', 'the row\'s button again closes Glaze\'s viewer'); + // The CSV: the grid from memory, an edit kept nowhere, Save to Documents a spreadsheet. + const papersNow = () => sw.evaluate(async () => Object.entries(await chrome.storage.local.get(null)).filter(([k]) => k.startsWith('writerPaper:')).map(([, v]) => ({ kind: v.kind || 'paper', title: v.title }))); + const had = (await papersNow()).length; + await page.click(rowB(85)); + await until(async () => (await gz())?.shown, 'the grid below the CSV\'s row'); + fr = await until(() => frameOf(), 'the grid\'s document'); + const cell = (r, c) => fr.evaluate(([r, c]) => document.querySelector(`#gx-grid .gx-c[data-r="${r}"][data-c="${c}"]`)?.textContent ?? null, [r, c]); + await until(async () => (await cell(1, 0)) === 'Ada', 'Ada in A2'); + const sheet = await fr.evaluate(() => ({ doc: document.documentElement.dataset.doc, title: document.querySelector('.gp-top h1')?.textContent, versions: !!document.getElementById('gx-versions'), acts: [...document.querySelectorAll('.gx-tool [data-act]')].map((b) => b.dataset.act).join() })); + check(sheet.doc === 'sheet' && sheet.title === 'Grades.csv' && !sheet.versions && sheet.acts === 'original,save' && await cell(2, 1) === '88', `the CSV opens in Glaze's grid from memory, no Versions, Open original and Save to Documents (${JSON.stringify(sheet)})`); + await fr.fill('#gx-name', 'B2'); + await fr.press('#gx-name', 'Enter'); + await until(() => fr.evaluate(() => document.activeElement?.id === 'gx-grid'), 'B2 chosen in the Name box'); + await page.keyboard.type('95'); + await page.keyboard.press('Enter'); + await until(async () => (await cell(1, 1)) === '95', 'the edit on the screen'); + const afterEdit = await fr.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent || ''); + check(afterEdit === '' && (await papersNow()).length === had, `an edit shows and is kept nowhere, nor said to be saving (state "${afterEdit}")`); + await fr.click('[data-act="save"]'); + await until(() => fr.evaluate(() => document.querySelector('.gx-said')?.textContent === 'Saved to Documents'), async () => `the CSV saved to Documents (says "${await fr.evaluate(() => document.querySelector('.gx-said')?.textContent)}"; errors ${JSON.stringify(errors)})`); + const sheets = (await papersNow()).filter((p) => p.kind === 'sheet'); + check(sheets.length === 1 && sheets[0].title === 'Grades', `Save to Documents keeps the CSV as a spreadsheet (${JSON.stringify(sheets)})`); + await page.click(rowB(85)); + await until(() => page.evaluate(() => !document.querySelector('iframe.gz-co-frame')), 'the grid closed'); // A locked file: said on the button, nothing opened. await page.click(rowB(83)); const locked = await until(() => page.evaluate(() => { const b = document.querySelector('#context_module_item_83 .gz-co'); return /^failed:/.test(b.dataset.gzCo || '') && { text: b.textContent, frames: document.querySelectorAll('iframe.gz-co-frame').length }; }), 'the locked file\'s answer'); @@ -210,11 +236,11 @@ const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      -
      +
      More options

      Document review

      diff --git a/scripts/canvas-suite-check.js b/scripts/canvas-suite-check.js index af01f7db5..a4651bdd0 100644 --- a/scripts/canvas-suite-check.js +++ b/scripts/canvas-suite-check.js @@ -86,7 +86,7 @@ const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      String.fromCharCode(97 + parseInt(c, 16))).join(''); await require('./wait-for-browser.js')(); - ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...env.chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, deviceScaleFactor: 1, locale: 'en-US', reducedMotion: 'reduce', + ctx = await chromium.launchPersistentContext(path.join(tmp, 'profile'), { ...require('./check-env.js').chromeLaunch(), headless: true, viewport: { width: 1280, height: 900 }, deviceScaleFactor: 1, locale: 'en-US', reducedMotion: 'reduce', args: [`--disable-extensions-except=${ext}`, `--load-extension=${ext}`, '--host-resolver-rules=MAP * ~NOTFOUND', '--disable-background-networking'] }); env.noFirstRun(ctx); await ctx.route('**/*', (r) => { diff --git a/test/frame-trust.test.js b/test/frame-trust.test.js index 725f4af8d..716ee0cc1 100644 --- a/test/frame-trust.test.js +++ b/test/frame-trust.test.js @@ -37,7 +37,7 @@ test('Download all: any other frame is refused before it can ask for a file', as ['another site than its tab', course({ url: `${OTHER}/courses/25/modules`, origin: OTHER })], ['a tab whose page is on another site', course({ tab: { id: 7, url: `${OTHER}/` } })], ['no address for its tab', course({ tab: { id: 7 } })], - ['not a course page', course({ url: `${CANVAS}/courses/25/files` })], + ['not a course page that shows or links a file', course({ url: `${CANVAS}/courses/25/settings` })], ['no tab', course({ tab: undefined })], ['another extension', course({ id: 'other' })], ['opaque sandbox origin', course({ origin: 'null' })], diff --git a/test/ondemand.test.js b/test/ondemand.test.js index 2b9b5c8f9..07836b22d 100644 --- a/test/ondemand.test.js +++ b/test/ondemand.test.js @@ -380,12 +380,16 @@ test("Ultra's grades load where they're used: the what-if on a course's Grades, }); test('the document buttons load on a course document or a file preview, with the switch on', () => { - for (const [why, ultra, toggles, has, loads] of [['a document', false, true, ['doc'], 1], ['switched off', false, false, ['doc'], 0], + for (const [why, ultra, toggles, has, loads, previews = false] of [['a document', false, true, ['doc'], 1], ['switched off', false, false, ['doc'], 0], ['a Canvas file preview', false, true, ['preview'], 1], ['nothing there', false, true, [], 0], - ['an Ultra preview, switched off (its images are turned back)', true, false, ['preview'], 1]]) { + ['an Ultra preview, switched off (its images are turned back)', true, false, ['preview'], 1], + // Open in Glaze (features/canvasopen.js, through docs.js): a Canvas file link or preview with Previews in Glaze on, whatever the buttons' switch. + ['a Canvas file link, buttons off, Previews in Glaze on', false, false, ['link'], 1, true], ['a Canvas file link, Previews in Glaze off', false, false, ['link'], 0, false], + ['an Ultra file link is not Canvas\'s', true, false, ['link'], 0, true]]) { const h = hooks(' // ---- document buttons (Extras)', ' // ---- keyboard shortcuts (Extras)', { ultra, addEventListener() {}, api: 'syncDocTools' }); h.ctx.settings.enhance.toggles = toggles; - h.ctx.document.querySelector = (sel) => (has.some((k) => (k === 'doc' ? /document-content-root|show-content/ : /iframe/).test(sel)) ? {} : null); + h.ctx.settings.enhance.bbPreviews = previews; + h.ctx.document.querySelector = (sel) => (has.some((k) => ({ doc: /document-content-root|show-content/, preview: /iframe/, link: /instructure_file_link/ })[k].test(sel)) ? {} : null); h.ctx.api.syncDocTools(); assert.equal(h.loads.length, loads, why); } From 445ff49fe7f7f24a2833afe877c3c53487be3889 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:03:21 -0400 Subject: [PATCH 101/907] Test that a viewer's mount tells the page the full view before it hides the paper --- test/writer-kinds.test.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/writer-kinds.test.js b/test/writer-kinds.test.js index be0ee9264..25ac9c222 100644 --- a/test/writer-kinds.test.js +++ b/test/writer-kinds.test.js @@ -201,3 +201,33 @@ test('the grid uses its kind\'s packed version for submit and before a file repl await surface.importFile({ name: 'new.pdf' }); assert.deepEqual(calls.versions, [[KEY, 'sending'], [KEY, 'import']]); }); + +// The page sizes the frame from what it hears (features/writer.js view, height): a compact frame follows the paper's +// height reports (writer.js), never under 40 px. Hiding the paper for a viewer (:root[data-doc], writer-paper.css) makes +// its next report the 40 px floor, so the page hears the full view before the paper is hidden, not once the viewer is +// built: else the frame folds to 40 px while the grid draws (writer-kinds-check: the grid's rows gone after it showed). +test('the page hears the full view before the paper is hidden for a viewer', async () => { + const said = []; + let built, hiddenSaid = null; + const el = () => ({ dataset: {}, append() {}, remove() {}, addEventListener() {} }); + const root = el(); + root.dataset = new Proxy({ view: 'compact' }, { set: (o, k, v) => { if (k === 'doc' && hiddenSaid === null) hiddenSaid = said.map((m) => `${m.type}:${m.full}`); o[k] = v; return true; } }); + const document = { documentElement: root, body: el(), head: { append: (l) => queueMicrotask(() => l.onload()) }, createElement: el }; + const P = PA.open(store(), () => Date.now()); + await P.save(KEY, { kind: 'sheet', doc: null }); + const DK = require('../lib/doc-kinds.js'); + const ctx = vm.createContext({ console: { error() {} }, document, GZ_WRITER_PAPERS: { open: () => P }, + GZ_DOC_KINDS: { KINDS: DK.KINDS, kindOf: DK.kindOf, settled: async () => {}, get: (id) => ({ ...DK.KINDS[id], view: () => new Promise((ok) => { built = ok; }) }) }, + GZ_WRITER_FRAME: { t: (s) => s, say: (m) => said.push(m), on() {} }, GZ_WRITER_SETTINGS: { now: () => ({ sheets: true }) }, + GZ_WRITER_SUBMIT_LIB: L, GZ_WRITER_PAPER: { need: async () => {}, submit() {} } }); + vm.runInContext(fs.readFileSync(path.join(__dirname, '..', 'writer-kinds.js'), 'utf8'), ctx); + const paper = { key: () => KEY, context: () => ({}), paper: () => ({}) }; + const mounting = ctx.GZ_WRITER_KINDS.mount('sheet', KEY, paper); + for (let i = 0; i < 50 && !built; i++) await new Promise((r) => setImmediate(r)); + assert.equal(typeof built, 'function', 'the viewer is being built'); + assert.equal(root.dataset.doc, 'sheet', 'the paper is hidden while the viewer is built'); + assert.deepEqual(hiddenSaid, ['view:true'], 'the page was told the frame is full before the paper was hidden'); + built({ save: async () => true, file: async () => null, dispose() {} }); + assert.ok(await mounting, 'the viewer mounts'); + assert.deepEqual(said.filter((m) => m.type === 'view').map((m) => m.full), [true], 'one full view, said once'); +}); From 19ac2609704f4bf8e3ef94dd3b7102f3e28bcdef Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:03:30 -0400 Subject: [PATCH 102/907] Test Drive's build switch and dev copy, the shared grant from Calendar's side, and Connect from Settings framed without a permissions API --- scripts/google-check.js | 38 ++++++++++++++++++ test/audit-documentation.test.js | 8 ++-- test/firefox.test.js | 3 +- test/gcal.test.js | 22 ++++++++++- test/google.test.js | 66 ++++++++++++++++++++++++++------ 5 files changed, 119 insertions(+), 18 deletions(-) diff --git a/scripts/google-check.js b/scripts/google-check.js index 675748e6f..14026cadb 100644 --- a/scripts/google-check.js +++ b/scripts/google-check.js @@ -25,6 +25,11 @@ const ext = env.staged(process.env.EXT || path.join(__dirname, '..'), []); m.optional_permissions = m.optional_permissions.filter((p) => p !== 'identity'); m.oauth2 = { client_id: CHROME_CLIENT, scopes: require('../lib/google-config.js').scopes }; fs.writeFileSync(file, JSON.stringify(m, null, 2)); + // Drive's build-time switch on in this copy only, as scripts/dev-drive.sh does (the staged file is a link: replaced, not edited). + const g = path.join(ext, 'lib/google.js'), src = fs.readFileSync(g, 'utf8'); + if (!src.includes('\n const ON = false;\n')) throw new Error('lib/google.js: no ON switch'); + fs.rmSync(g); + fs.writeFileSync(g, src.replace('\n const ON = false;\n', '\n const ON = true;\n')); } const id = [...crypto.createHash('sha256').update(ext).digest('hex').slice(0, 32)].map((c) => String.fromCharCode(97 + parseInt(c, 16))).join(''); const REDIRECT = `https://${id}.chromiumapp.org/`; @@ -80,6 +85,7 @@ const answer = ({ method, url, headers, body }, via) => { } // Chromium's own account check (its browser sign-in), now that accounts.google.com resolves here: the browser's, not Glaze's. if (u.origin === 'https://accounts.google.com' && u.pathname === '/ListAccounts' && u.searchParams.get('source') === 'ChromiumBrowser') return null; + if (u.href === 'https://school.instructure.com/embed') return { status: 200, headers: { 'content-type': 'text/html' }, body: '' }; log.other.push(`${via} ${u.href}`); return null; }; @@ -240,6 +246,38 @@ const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.go await until(async () => await page.locator('#gdrive-connect').isVisible(), 'disconnected, chrome flow'); ok(log.revoke.at(-1)[1] === 'token=ya29.check-chrome-token', 'Disconnect revokes Chrome\'s token'); + // Settings framed in a school's page with no permissions API (Firefox gives such a frame none): Connect asks nothing + // there and opens Settings' own tab at Writer → Papers. + const lms = await ctx.newPage(); + lms.on('pageerror', (e) => errors.push(`lms: ${e}`)); + await lms.addInitScript(() => { + if (location.protocol !== 'chrome-extension:') return; + Object.defineProperty(chrome, 'permissions', { value: undefined, configurable: true }); + const send = chrome.runtime.sendMessage.bind(chrome.runtime); + globalThis.__gzSent = []; + chrome.runtime.sendMessage = (m, ...a) => { __gzSent.push(m); return send(m, ...a); }; + }); + await lms.goto('https://school.instructure.com/embed'); + const tabId = await sw.evaluate(() => chrome.tabs.query({ url: 'https://school.instructure.com/embed' }).then((t) => t[0].id)); + const ticket = await sw.evaluate(async (tabId) => { + const [r] = await chrome.scripting.executeScript({ target: { tabId }, func: () => chrome.runtime.sendMessage({ type: 'glaze:settings-ticket' }) }); + return r.result.ticket; + }, tabId); + await lms.evaluate((url) => { const f = document.createElement('iframe'); f.src = url; f.style.cssText = 'width:1200px;height:850px'; document.body.append(f); }, `chrome-extension://${id}/options.html?t=${ticket}#writer/papers`); + const frame = await until(async () => lms.frames().find((f) => f.url().includes('/options.html')), 'embedded Settings'); + await until(() => frame.evaluate(() => document.documentElement.hasAttribute('data-embed-ok')), 'claimed frame'); + ok(await frame.evaluate(() => typeof chrome.permissions?.request !== 'function'), 'the frame has no permissions API, as Firefox gives it'); + await until(() => frame.locator('#gdrive').isVisible(), 'the row, framed'); + const tabs = ctx.pages().length, asked = log.auth.length; + await frame.locator('#gdrive-connect').click(); + const own = await until(async () => ctx.pages().find((p) => p !== page && p.url().startsWith(`chrome-extension://${id}/options.html`) && p.url().endsWith('#writer/papers')), 'Settings\' own tab'); + ok(ctx.pages().length > tabs && own, 'framed Connect opens Settings in its own tab, at Writer → Papers'); + const sent = await frame.evaluate(() => __gzSent.map((m) => [m.type, m.op || m.hash])).catch(() => null); + ok(!sent || !sent.some(([type, op]) => type === 'glaze:google' && op === 'connect'), 'and asks the worker for no sign-in from the frame'); + ok(log.auth.length === asked, 'nothing reached Google'); + await own.close(); + await lms.close(); + // Who may ask: a page Glaze doesn't trust for this (the pop-up) is refused. const popup = await ctx.newPage(); await popup.goto(`chrome-extension://${id}/popup.html`); diff --git a/test/audit-documentation.test.js b/test/audit-documentation.test.js index ee7ee7ca7..ed3df615c 100644 --- a/test/audit-documentation.test.js +++ b/test/audit-documentation.test.js @@ -98,9 +98,11 @@ test('BLD-5: the permission text matches the manifest and the packaging scripts' const drive = require('../lib/google-config.js'), { oauth } = require('../scripts/oauth-manifest.js'), { ready } = require('../lib/google.js'); assert.match(code('package.sh'), /node scripts\/oauth-manifest\.js chrome manifest\.json > "\$stage\/manifest\.json"/); assert.match(code('scripts/package-firefox.sh'), /oauth-manifest\.js'\)\)\.oauth\(m, \{ target: 'firefox'/); - const offered = { chrome: ready(drive, 'chrome') || ready(drive, 'web'), firefox: ready(drive, 'web') }; + const ON = require('../lib/google.js').ON; + assert.equal(ON, false, 'Google Drive is built off, so released builds omit its oauth2 and identity'); + const offered = { chrome: ON && (ready(drive, 'chrome') || ready(drive, 'web')), firefox: ON && ready(drive, 'web') }; for (const target of ['chrome', 'firefox']) { - const packed = oauth(manifest, { target, calendar: G.ON, config: drive }); + const packed = oauth(manifest, { target, calendar: G.ON, drive: ON, config: drive }); assert.equal((packed.optional_permissions || []).includes('identity'), offered[target], `${target}: identity exactly while Drive is offered`); } if (offered.chrome || offered.firefox) { @@ -108,7 +110,7 @@ test('BLD-5: the permission text matches the manifest and the packaging scripts' assert.doesNotMatch(policy, /Google Drive[^.]*unavailable in (?:this|the current) build/, 'a build that offers Drive must not call it unavailable'); } assert.match(code('scripts/safari-manifest.sh'), /p != 'identity'/); - if (!ready(drive, 'chrome') && !ready(drive, 'web')) assert.match(policy, /`identity` \(Chrome only, optional\) — not requested by released builds/); + if (!offered.chrome && !offered.firefox) assert.match(policy, /`identity` \(Chrome only, optional\) — not requested by released builds/); assert.match(policy, /Chrome sync \(or Firefox Sync\)/); assert.match(policy, /\*\*Music widget\*\* \(Canvas and Blackboard Ultra/); assert.ok(/music/.test(code('lib/widgets.js').match(/const TILES = \[[^\]]*\]/)[0]), 'the music tile is a widget on both sites'); diff --git a/test/firefox.test.js b/test/firefox.test.js index c4a54ba7b..7062bccbc 100644 --- a/test/firefox.test.js +++ b/test/firefox.test.js @@ -27,8 +27,7 @@ test('package-firefox.sh builds a Firefox manifest whose background scripts all // Nothing without consent; agent access sends the student's course data to an app on this computer, outside the browser. assert.deepEqual(m.browser_specific_settings.gecko.data_collection_permissions, { required: ['none'], optional: ['websiteContent'] }); assert.equal(m.oauth2, undefined); - // identity for Drive's web sign-in (scripts/oauth-manifest.js), once its Web client is real; agent access asks for native messaging. - assert.deepEqual(m.optional_permissions, require('../lib/google.js').ready(require('../lib/google-config.js'), 'web') ? ['identity', 'nativeMessaging'] : ['nativeMessaging']); + assert.deepEqual(m.optional_permissions, ['nativeMessaging'], 'no identity while Drive is switched off; agent access asks for native messaging'); assert.ok(m.web_accessible_resources.every((e) => !('use_dynamic_url' in e))); // Code mods need Chrome: no sandbox page, CSP or files, so content.js never starts them. assert.equal(m.sandbox, undefined); diff --git a/test/gcal.test.js b/test/gcal.test.js index bff03d250..8800bb85f 100644 --- a/test/gcal.test.js +++ b/test/gcal.test.js @@ -397,6 +397,25 @@ withMock('worker: disconnect deletes the calendar when asked, revokes the grant, assert.equal(mock.calendars.size, 1, 'kept, as chosen'); }); +withMock('worker: Calendar\'s disconnect while Drive still holds the shared grant drops only Calendar\'s state: no revoke, Chrome\'s cache and identity stay', async (mock) => { + const w = worker(mock); + await w.send({ type: 'glaze:gcal', op: 'connect' }, EDITOR); + const cal = w.store.local.gcal.cal; + w.store.local.google = { email: 'student@school.edu', flow: 'chrome', at: NOW }; // Drive connected (lib/google.js) + assert.deepEqual(await w.send({ type: 'glaze:gcal', op: 'disconnect', drop: true }, EDITOR), { ok: true, revoked: false, had: false, shared: true }); + assert.ok(!mock.calendars.has(cal), 'the calendar still goes, as chosen'); + assert.deepEqual([mock.revoked, w.identity.cleared, w.identity.removed], [[], 0, []]); + assert.ok(!('gcal' in w.store.local) && !('gcalSync' in w.store.local)); + assert.deepEqual(w.store.local.google, { email: 'student@school.edu', flow: 'chrome', at: NOW }, 'Drive\'s record stays'); +}); + +test('Settings gives identity back after Calendar\'s Disconnect only when Drive doesn\'t share it', () => { + const src = read('options.js'); + const bye = src.slice(src.indexOf("$('#gcal-bye').addEventListener('close'"), src.indexOf("chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.gcal)")); + assert.match(bye, /if \(!r\.shared\) await chrome\.permissions\.remove\(\{ permissions: \['identity'\] \}\)/); + assert.doesNotMatch(bye.replace(/if \(!r\.shared\) await chrome\.permissions\.remove/, ''), /permissions\.remove/); +}); + // ---- the build-time switch (lib/gcal.js ON): off in the repo and the store package until Google verifies the app ---- test('switch off (the repo as it is): no settings section, no sync, no page script, even with a connection left from a dev build', async () => { assert.equal(G.ON, false, 'lib/gcal.js ON stays false until Google verifies the app'); @@ -439,7 +458,8 @@ test('the store package leaves Google sync out while the switch is off; scripts/ const packed = JSON.parse(execFileSync('unzip', ['-p', zip, 'manifest.json'])); const { oauth2 } = repo; // Calendar's scope stays out while its switch is off; what Drive needs (scripts/oauth-manifest.js) goes in. - assert.deepEqual(packed, require('../scripts/oauth-manifest.js').oauth(repo, { target: 'chrome', calendar: false, config: require('../lib/google-config.js') }), 'the store manifest: the repo\'s, with only what Drive needs from Google'); + const { oauth2: _o, optional_permissions: optional, ...rest } = repo, store = optional.filter((p) => p !== 'identity'); + assert.deepEqual(packed, store.length ? { ...rest, optional_permissions: store } : rest, 'the store manifest: the same, without oauth2 and identity while Calendar and Drive are both switched off'); assert.ok(!(packed.oauth2?.scopes || []).includes(G.SCOPE), 'no Calendar scope while the switch is off'); assert.match(execFileSync('unzip', ['-p', zip, 'lib/gcal.js']).toString(), /\n {2}const ON = false;\n/); const dev = JSON.parse(fs.readFileSync(path.join(dir, 'dev/manifest.json'))); diff --git a/test/google.test.js b/test/google.test.js index ca707aff4..96fc815f6 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -17,7 +17,7 @@ const BLANK = { ...C, chrome: 'PLACEHOLDER-chrome.apps.googleusercontent.com', w const REAL = { ...C, chrome: '111-chrome.apps.googleusercontent.com', web: '222-web.apps.googleusercontent.com', ios: '333-ios.apps.googleusercontent.com', apiKey: 'AIzaTestKey', appId: '123456789012' }; // A worker: flow, config, what Google answers (google(details) → the redirect URL; userinfo), which picker pages load. -const harness = ({ flow = 'chrome', config = REAL, granted = true, firefox = false, client = REAL.chrome, keep = false, up = [true, true], email = 'student@school.edu' } = {}) => { +const harness = ({ on = true, flow = 'chrome', config = REAL, granted = true, firefox = false, client = REAL.chrome, keep = false, up = [true, true], email = 'student@school.edu' } = {}) => { const local = {}, sync = {}; const log = { fetch: [], launch: [], auth: [], removed: [], cleared: 0, permRemoved: [], native: [] }; let clock = 1_000_000; @@ -65,7 +65,7 @@ const harness = ({ flow = 'chrome', config = REAL, granted = true, firefox = fal throw new Error(`unexpected request: ${url}`); }; const g = G.create({ - config, flow, firefox, + on, config, flow, firefox, identity: () => (granted ? identity : undefined), permissions: { contains: async ({ permissions }) => granted && permissions.join() === 'identity', remove: async (p) => { log.permRemoved.push(p); return true; } }, manifest: () => ({ oauth2: { client_id: client } }), @@ -365,10 +365,10 @@ test('the worker answers glaze:google from Settings only (pick from Glaze\'s own // ---- packaging: what each package's manifest carries for Google ---- const repo = require('../manifest.json'); test('packages: placeholders leave Drive out, so a build asks for no identity and carries no oauth2', () => { - const chrome = oauth(repo, { target: 'chrome', calendar: false, config: BLANK }); + const chrome = oauth(repo, { target: 'chrome', calendar: false, drive: true, config: BLANK }); assert.equal(chrome.oauth2, undefined); assert.ok(!(chrome.optional_permissions || []).includes('identity')); - const firefox = oauth(repo, { target: 'firefox', calendar: false, config: BLANK }); + const firefox = oauth(repo, { target: 'firefox', calendar: false, drive: true, config: BLANK }); assert.equal(firefox.oauth2, undefined); assert.ok(!(firefox.optional_permissions || []).includes('identity')); const { oauth2, optional_permissions: optional, ...rest } = repo; @@ -376,27 +376,69 @@ test('packages: placeholders leave Drive out, so a build asks for no identity an }); test('packages: real ids give Chrome the Drive client and its three scopes, without Calendar\'s while Calendar is off', () => { - const chrome = oauth(repo, { target: 'chrome', calendar: false, config: REAL }); + const chrome = oauth(repo, { target: 'chrome', calendar: false, drive: true, config: REAL }); assert.deepEqual(chrome.oauth2, { client_id: REAL.chrome, scopes: C.scopes }); assert.deepEqual(chrome.optional_permissions, repo.optional_permissions, 'identity stays optional'); assert.ok(!chrome.permissions.includes('identity')); - const both = oauth(repo, { target: 'chrome', calendar: true, config: REAL }); + const both = oauth(repo, { target: 'chrome', calendar: true, drive: true, config: REAL }); assert.deepEqual(both.oauth2.scopes, ['https://www.googleapis.com/auth/calendar.app.created', ...C.scopes]); - const cal = oauth(repo, { target: 'chrome', calendar: true, config: BLANK }); + const cal = oauth(repo, { target: 'chrome', calendar: true, drive: true, config: BLANK }); assert.deepEqual(cal.oauth2, repo.oauth2, 'Calendar alone: the manifest as it is'); - const web = oauth(repo, { target: 'chrome', calendar: false, config: { ...REAL, chrome: BLANK.chrome } }); + const web = oauth(repo, { target: 'chrome', calendar: false, drive: true, config: { ...REAL, chrome: BLANK.chrome } }); assert.equal(web.oauth2, undefined, 'only the Web client: Edge and Brave sign in, Chrome waits'); assert.ok(web.optional_permissions.includes('identity')); - const firefox = oauth(repo, { target: 'firefox', calendar: false, config: REAL }); + const firefox = oauth(repo, { target: 'firefox', calendar: false, drive: true, config: REAL }); assert.equal(firefox.oauth2, undefined); assert.ok(firefox.optional_permissions.includes('identity'), 'Firefox signs in with the Web client'); }); -test('packages: the shipped config gives Chrome Calendar\'s client with Drive\'s three scopes, and Firefox identity', () => { - const chrome = oauth(repo, { target: 'chrome', calendar: false, config: C }); +test('packages: switched on, the shipped config gives Chrome Calendar\'s client with Drive\'s three scopes, and Firefox identity', () => { + const chrome = oauth(repo, { target: 'chrome', calendar: false, drive: true, config: C }); assert.deepEqual(chrome.oauth2, { client_id: repo.oauth2.client_id, scopes: C.scopes }); assert.deepEqual(chrome.optional_permissions, repo.optional_permissions); - const firefox = oauth(repo, { target: 'firefox', calendar: false, config: C }); + const firefox = oauth(repo, { target: 'firefox', calendar: false, drive: true, config: C }); assert.equal(firefox.oauth2, undefined); assert.deepEqual(firefox.optional_permissions, repo.optional_permissions); }); + +// ---- the build-time switch (lib/google.js ON): off on this branch, as Calendar's is, until the release that ships Drive ---- +test('Drive is switched off: the worker refuses, Settings has no row, and the packages carry neither oauth2 nor identity', async () => { + assert.equal(G.ON, false); + const h = harness({ on: undefined }); + assert.deepEqual(await h.g.status(), { ok: false, why: 'off' }); + assert.deepEqual(await h.g.connect(), { ok: false, why: 'off' }); + assert.deepEqual(await h.g.pick(), { ok: false, why: 'off' }); + await assert.rejects(h.g.token(), (e) => e.why === 'off'); + assert.deepEqual([h.log.auth, h.log.launch, h.log.fetch, h.local], [[], [], [], {}]); + for (const target of ['chrome', 'firefox']) { + const m = oauth(repo, { target, calendar: false, drive: G.ON, config: C }); + assert.equal(m.oauth2, undefined, target); + assert.ok(!(m.optional_permissions || []).includes('identity'), target); + } + const src = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); + const at = src.indexOf("m?.type !== 'glaze:google'"); + assert.match(src.slice(at, src.indexOf('return true;', at)), /if \(!GZ_GOOGLE\.ON\) \{ reply\(\{ ok: false, why: 'off' \}\); return; \}/, 'the worker refuses every operation'); +}); + +test('scripts/dev-drive.sh: a dev copy with Drive on, Glaze\'s client and scopes, identity and the store key; the package and repo stay off', () => { + const { execFileSync } = require('node:child_process'); + const os = require('node:os'); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-drive-pkg-')); + try { + const keyFile = path.join(dir, 'key.txt'); + fs.writeFileSync(keyFile, require('node:crypto').randomBytes(294).toString('base64')); + const out = execFileSync(path.join(__dirname, '../scripts/dev-drive.sh'), [keyFile, path.join(dir, 'dev')], { env: { ...process.env, DIST: path.join(dir, 'dist') }, encoding: 'utf8', stdio: 'pipe' }); + assert.match(out, /extension ID [a-p]{32}/); + const zip = path.join(dir, 'dist', `glaze-v${repo.version}.zip`); + const packed = JSON.parse(execFileSync('unzip', ['-p', zip, 'manifest.json'])); + assert.ok(!packed.oauth2 && !(packed.optional_permissions || []).includes('identity'), 'the store package: off'); + assert.match(execFileSync('unzip', ['-p', zip, 'lib/google.js']).toString(), /\n {2}const ON = false;\n/); + const dev = JSON.parse(fs.readFileSync(path.join(dir, 'dev/manifest.json'))); + assert.deepEqual(dev.oauth2, { client_id: C.chrome, scopes: C.scopes }); + assert.deepEqual(dev.optional_permissions, repo.optional_permissions); + assert.equal(dev.key, fs.readFileSync(keyFile, 'utf8')); + assert.match(fs.readFileSync(path.join(dir, 'dev/lib/google.js'), 'utf8'), /\n {2}const ON = true;\n/); + assert.equal(G.ON, false, "the repo's switch is untouched"); + assert.ok(!('key' in repo)); + } finally { fs.rmSync(dir, { recursive: true, force: true }); } +}); From 641f219806f563e7bfa1fa35c32218d6053b64fa Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:04:05 -0400 Subject: [PATCH 103/907] Tell the page a viewer's full view before hiding the paper, so a compact frame doesn't fold to 40 px while the viewer builds --- test/writer-kinds.test.js | 16 ++++++++++++++++ writer-kinds.js | 11 ++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/test/writer-kinds.test.js b/test/writer-kinds.test.js index 25ac9c222..1b6f7bcfe 100644 --- a/test/writer-kinds.test.js +++ b/test/writer-kinds.test.js @@ -231,3 +231,19 @@ test('the page hears the full view before the paper is hidden for a viewer', asy assert.ok(await mounting, 'the viewer mounts'); assert.deepEqual(said.filter((m) => m.type === 'view').map((m) => m.full), [true], 'one full view, said once'); }); + +test('a viewer that fails gives the page back the compact view it had', async () => { + const said = [], el = () => ({ dataset: {}, append() {}, remove() {}, addEventListener() {} }), root = el(); + root.dataset.view = 'compact'; + const P = PA.open(store(), () => Date.now()); + await P.save(KEY, { kind: 'sheet', doc: null }); + const DK = require('../lib/doc-kinds.js'); + const ctx = vm.createContext({ console: { error() {} }, document: { documentElement: root, body: el(), head: { append: (l) => queueMicrotask(() => l.onload()) }, createElement: el }, + GZ_WRITER_PAPERS: { open: () => P }, GZ_DOC_KINDS: { KINDS: DK.KINDS, kindOf: DK.kindOf, settled: async () => {}, get: (id) => ({ ...DK.KINDS[id], view: async () => { throw new Error('no grid'); } }) }, + GZ_WRITER_FRAME: { t: (s) => s, say: (m) => said.push(m), on() {} }, GZ_WRITER_SETTINGS: { now: () => ({ sheets: true }) }, + GZ_WRITER_SUBMIT_LIB: L, GZ_WRITER_PAPER: { need: async () => {}, submit() {} } }); + vm.runInContext(fs.readFileSync(path.join(__dirname, '..', 'writer-kinds.js'), 'utf8'), ctx); + assert.equal(await ctx.GZ_WRITER_KINDS.mount('sheet', KEY, { key: () => KEY, context: () => ({}), paper: () => ({}) }), null); + assert.deepEqual(said.map((m) => `${m.type}:${m.full}`), ['view:true', 'view:false']); + assert.deepEqual({ ...root.dataset }, { view: 'compact' }, 'the paper shown again, compact'); +}); diff --git a/writer-kinds.js b/writer-kinds.js index 96745403b..fe62c93e8 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -44,9 +44,11 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { const mount = async (id, key, paper) => { const k = await kind(id).catch((e) => { console.error(e); return null; }); if (typeof k?.view !== 'function' || !key) return null; - const root = document.documentElement, el = document.createElement('div'); + const root = document.documentElement, el = document.createElement('div'), was = root.dataset.view; el.className = 'gk'; document.body.append(el); + // The page first: the hidden paper's next height report would fold a compact frame to 40 px while the viewer builds. + frame.say({ type: 'view', full: true }); root.dataset.doc = id; root.dataset.view = 'full'; let v = null; @@ -73,10 +75,13 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { submitB.addEventListener('click', () => P().submit?.(surface)); try { v = await k.view(key, { mount: el, t, title: () => paper.context()?.assignment?.title || '', tools: [submitB] }); - } catch (e) { console.error(e); el.remove(); delete root.dataset.doc; return null; } + } catch (e) { + console.error(e); el.remove(); delete root.dataset.doc; + if (was !== 'full') { if (was) root.dataset.view = was; else delete root.dataset.view; frame.say({ type: 'view', full: false }); } + return null; + } if (typeof P().submit === 'function') submitB.disabled = false; shown = { id, key, el, surface, paper, viewer: v }; - frame.say({ type: 'view', full: true }); return surface; }; const unmount = async () => { From c110c976afd9be5e3639c8569f87b14dce1e949f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:04:35 -0400 Subject: [PATCH 104/907] Build Drive off behind a switch with a dev copy to try it, keep a grant either feature still holds, and send framed Connect to Settings' own tab --- lib/background.js | 14 ++++++++++-- lib/google.js | 10 ++++++++- options.js | 6 ++++-- scripts/dev-drive.sh | 44 ++++++++++++++++++++++++++++++++++++++ scripts/oauth-manifest.js | 12 ++++++----- scripts/package-firefox.sh | 5 +++-- test/google.test.js | 2 +- 7 files changed, 80 insertions(+), 13 deletions(-) create mode 100755 scripts/dev-drive.sh diff --git a/lib/background.js b/lib/background.js index d88704a02..0255f5090 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3157,10 +3157,17 @@ const gcalOps = { return { ok: !!g?.cal }; }, // drop: also delete the Glaze calendar; if that fails, nothing changes. Then the grant is revoked, Chrome forgets its - // tokens and Glaze its state. → revoked: whether Google confirmed (false with no token to revoke: had). + // tokens and Glaze its state. → revoked: whether Google confirmed (false with no token to revoke: had). While Google Drive + // is connected (lib/google.js) it holds the same grant, and a revocation would end its access too: only Calendar's state + // goes, and shared tells Settings to keep the identity permission. disconnect: async (m) => { const g = await gcalState(); if (m.drop === true && g?.cal) await GZ_GCAL.remove(gcalEnv, g.cal); + if (typeof (await chrome.storage.local.get('google')).google?.email === 'string') { + await chrome.storage.local.remove(['gcal', 'gcalSync']); + serial(ensure); + return { ok: true, revoked: false, had: false, shared: true }; + } const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); @@ -3254,9 +3261,10 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { // pages. Loaded the first time one asks; nothing runs for someone who never connects. The token stays here. globalThis.addEventListener?.('install', () => importScripts('oauth.js', 'google-config.js', 'google.js')); let google = null; +const googleLib = () => { if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); return GZ_GOOGLE; }; const openGoogle = () => { if (!google) { - if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); + googleLib(); const protocol = new URL(chrome.runtime.getURL('')).protocol; google = GZ_GOOGLE.create({ config: GZ_GOOGLE_CONFIG, flow: GZ_GOOGLE.flowOf({ protocol, brands: navigator.userAgentData?.brands }), firefox: protocol === 'moz-extension:', @@ -3273,6 +3281,8 @@ const googlePage = (s) => s.id === chrome.runtime.id && typeof s.url === 'string chrome.runtime.onMessage.addListener((m, s, reply) => { if (m?.type !== 'glaze:google' || !['status', 'connect', 'disconnect', 'pick'].includes(m.op)) return; if (!(m.op === 'pick' ? googlePage(s) : fromEditor(s))) { reply({ ok: false, why: 'sender' }); return; } + googleLib(); + if (!GZ_GOOGLE.ON) { reply({ ok: false, why: 'off' }); return; } // the build-time switch (lib/google.js) Promise.resolve().then(() => openGoogle()[m.op]()).then(reply, () => reply({ ok: false, why: 'network' })); return true; }); diff --git a/lib/google.js b/lib/google.js index cd4d570c8..b73ec8df2 100644 --- a/lib/google.js +++ b/lib/google.js @@ -15,6 +15,10 @@ // storage (storage.local), fetch, native(message), keep() (Calendar sync holds identity too: Disconnect leaves it), now() }. // Classic script; module.exports for node. var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { + // The build-time switch, off until the release that ships Drive (and its privacy policy). Off: the worker refuses every + // operation, Settings shows no Google Drive row, and the packages carry no oauth2 key or identity for Drive + // (scripts/oauth-manifest.js). scripts/dev-drive.sh turns it on in its copy. + const ON = false; const O = typeof GZ_OAUTH !== 'undefined' ? GZ_OAUTH : require('./oauth.js'); const AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'; const USERINFO = 'https://openidconnect.googleapis.com/v1/userinfo'; @@ -73,6 +77,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const create = (env) => { const c = () => env.config; + const on = env.on ?? ON; // tests switch it on const now = env.now || Date.now; const id = () => env.identity(); let held = null; // the web flow's { token, exp }: this worker's memory only @@ -159,6 +164,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // → { ok: true, flow, email (null until connected) } or { ok: false, why: 'setup' (placeholders) | 'browser' (Safari, for now) } const status = async () => { + if (!on) return { ok: false, why: 'off' }; const flow = flowNow(); if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; if (!usable(flow)) return { ok: false, why: 'setup' }; @@ -184,6 +190,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { })); // The worker's token for a Drive request; interactive only for something the student pressed. const token = async ({ interactive = false } = {}) => { + if (!on) throw new Fail('off'); const g = await saved(); if (!g) throw new Fail('connect'); if (!usable(g.flow)) throw new Fail('setup'); @@ -223,6 +230,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { }; // From a click on a Glaze page. → { ok: true, files: [{ id, name, mimeType }] (none: cancelled) } or { ok: false, why } const pick = fails(async () => { + if (!on) return { ok: false, why: 'off' }; if (!pickable(c())) return { ok: false, why: 'setup' }; const g = await saved(); if (!g) return { ok: false, why: 'connect' }; @@ -238,6 +246,6 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { status, connect, token, account, disconnect, pick }; }; - return { AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; + return { ON, AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; })(); if (typeof module !== 'undefined') module.exports = GZ_GOOGLE; diff --git a/options.js b/options.js index c20604fcc..e1aaa887c 100644 --- a/options.js +++ b/options.js @@ -1148,8 +1148,8 @@ GZ_UI.load().then((s) => { if ($('#gcal-bye').returnValue !== 'ok') return $('#gcal-disconnect').focus(); const r = await gcalOp({ op: 'disconnect', drop: $('#gcal-drop').checked }); if (!r.ok) return hint(t("Couldn't delete the Glaze calendar, so nothing changed. Try again, or disconnect without deleting it."), true); - // Nothing stays behind: the permission goes too, until the next Connect asks again. - await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); + // Nothing stays behind: the permission goes too, until the next Connect asks again, unless Google Drive still uses it. + if (!r.shared) await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); gcalRender(null); $('#gcal-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), r.had && !r.revoked); @@ -1174,6 +1174,8 @@ GZ_UI.load().then((s) => { // Google's own screen, from the worker. $('#gdrive-connect').addEventListener('click', async (e) => { if (!e.isTrusted || gdBusy) return; + // Settings framed in a school's page may have no permissions API (Firefox): its own tab asks, at this section. + if (typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); gdBusy = true; try { const asked = chrome.permissions.request({ permissions: ['identity'] }); diff --git a/scripts/dev-drive.sh b/scripts/dev-drive.sh new file mode 100755 index 000000000..99c51fad2 --- /dev/null +++ b/scripts/dev-drive.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# A dev copy of the extension for trying Google Drive with Glaze's real clients (lib/google-config.js). Chrome's sign-in +# (getAuthToken with the "Chrome extension" client) and the Web client's redirect (https://.chromiumapp.org/) both need +# the Chrome Web Store item's ID, which Chrome gives an unpacked copy only when its manifest carries the store's public key +# ("key"). So: the store package (package.sh), unzipped into a folder, with Drive's build-time switch turned on +# (lib/google.js ON), the manifest's oauth2 client with Drive's scopes and the optional identity permission +# (scripts/oauth-manifest.js), and the key added, in that copy only. Never in the repo or the package (test/google.test.js). +# +# scripts/dev-drive.sh [folder, default dist/glaze-dev-drive] +# +# Then chrome://extensions → Developer mode → Load unpacked → that folder. Remove the store's Glaze from that Chrome +# profile first: two copies can't share one ID. +set -euo pipefail +cd "$(dirname "$0")/.." +key_file="${1:?usage: scripts/dev-drive.sh [folder]}" +out="${2:-dist/glaze-dev-drive}" + +./package.sh >/dev/null +version="$(node -p 'require("./manifest.json").version')" +rm -rf "$out" +mkdir -p "$out" +unzip -q "${DIST:-dist}/glaze-v$version.zip" -d "$out" +node - "$out" "$key_file" <<'JS' +const fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto'); +const [out, keyFile] = process.argv.slice(2); +const key = fs.readFileSync(keyFile, 'utf8').trim(); +const der = Buffer.from(key, 'base64'); +if (!der.length || der.toString('base64') !== key) throw new Error(`${keyFile}: not one line of base64`); // a public key, nothing else +const file = path.join(out, 'lib/google.js'), src = fs.readFileSync(file, 'utf8'); +if (!src.includes('\n const ON = false;\n')) throw new Error('lib/google.js: no ON switch'); +fs.writeFileSync(file, src.replace('\n const ON = false;\n', '\n const ON = true;\n')); +const { oauth } = require(path.resolve('scripts/oauth-manifest.js')); +const at = path.join(out, 'manifest.json'); +const manifest = oauth(JSON.parse(fs.readFileSync(at, 'utf8')), { + target: 'chrome', calendar: require(path.resolve('lib/gcal.js')).ON === true, drive: true, config: require(path.resolve('lib/google-config.js')), +}); +if (!manifest.oauth2) throw new Error('lib/google-config.js: the Chrome client is not set'); +manifest.key = key; +fs.writeFileSync(at, `${JSON.stringify(manifest, null, 2)}\n`); +// Chrome's extension ID: the key's SHA-256, first 32 hex digits, 0-f read as a-p. +const id = [...crypto.createHash('sha256').update(der).digest('hex').slice(0, 32)].map((c) => String.fromCharCode(97 + parseInt(c, 16))).join(''); +console.log(`${at}: extension ID ${id}`); +JS +echo "Load unpacked: $out" diff --git a/scripts/oauth-manifest.js b/scripts/oauth-manifest.js index c5442b1d0..a3fed28f5 100644 --- a/scripts/oauth-manifest.js +++ b/scripts/oauth-manifest.js @@ -1,15 +1,16 @@ // What a package's manifest carries for Google (package.sh, scripts/package-firefox.sh): the oauth2 key and the optional // identity permission, each only while a feature that uses it is on. Google Calendar sync: lib/gcal.js ON, off until Google -// verifies the app. Drive: lib/google-config.js, until its clients are real (lib/google.js ready). Safari carries neither +// verifies the app. Drive: lib/google.js ON, and then only once its clients in lib/google-config.js are real (ready). Safari carries neither // (scripts/safari-manifest.sh): its sign-in goes through the app. // node scripts/oauth-manifest.js → the manifest, to stdout const CALENDAR = 'https://www.googleapis.com/auth/calendar.app.created'; -// target 'chrome' (Chrome, Edge and other Chromium stores) or 'firefox'; calendar: lib/gcal.js ON; config: lib/google-config.js. -function oauth(manifest, { target, calendar, config }) { +// target 'chrome' (Chrome, Edge and other Chromium stores) or 'firefox'; calendar: lib/gcal.js ON; drive: lib/google.js ON; +// config: lib/google-config.js. +function oauth(manifest, { target, calendar, drive, config }) { const { ready } = require('../lib/google.js'); const m = structuredClone(manifest); - const chrome = target === 'chrome' && ready(config, 'chrome'), web = ready(config, 'web'); + const chrome = drive === true && target === 'chrome' && ready(config, 'chrome'), web = drive === true && ready(config, 'web'); const scopes = [...(target === 'chrome' && calendar ? [CALENDAR] : []), ...(chrome ? config.scopes : [])]; if (scopes.length) m.oauth2 = { client_id: chrome ? config.chrome : manifest.oauth2.client_id, scopes }; else delete m.oauth2; @@ -27,7 +28,8 @@ if (require.main === module) { const m = JSON.parse(require('node:fs').readFileSync(file, 'utf8')); // The Chrome package keeps Calendar's client while Calendar is on; Firefox's starts from that package. const calendar = require('../lib/gcal.js').ON === true; - process.stdout.write(`${JSON.stringify(oauth(m, { target, calendar, config: require('../lib/google-config.js') }), null, 2)}\n`); + const drive = require('../lib/google.js').ON === true; + process.stdout.write(`${JSON.stringify(oauth(m, { target, calendar, drive, config: require('../lib/google-config.js') }), null, 2)}\n`); } module.exports = { CALENDAR, oauth }; diff --git a/scripts/package-firefox.sh b/scripts/package-firefox.sh index 368ae6125..4b758d552 100644 --- a/scripts/package-firefox.sh +++ b/scripts/package-firefox.sh @@ -49,8 +49,9 @@ const imports = [...fs.readFileSync(background, 'utf8').matchAll(/importScripts\ .flatMap(([, args]) => [...args.matchAll(/'([^']+)'/g)].map(([, f]) => path.posix.join('lib', f))); m.background = { scripts: ['lib/firefox-background.js', ...new Set(imports), 'lib/background.js'] }; delete m.minimum_chrome_version; -// Google (scripts/oauth-manifest.js): no oauth2 key, which is getAuthToken's; identity only once Drive's Web client is real. -const google = require(path.join(process.cwd(), 'scripts/oauth-manifest.js')).oauth(m, { target: 'firefox', calendar: false, config: require(path.join(process.cwd(), 'lib/google-config.js')) }); +// Google (scripts/oauth-manifest.js): no oauth2 key, which is getAuthToken's; identity only for Drive, switched on (lib/google.js ON) +// with its Web client real. +const google = require(path.join(process.cwd(), 'scripts/oauth-manifest.js')).oauth(m, { target: 'firefox', calendar: false, drive: require(path.join(process.cwd(), 'lib/google.js')).ON === true, config: require(path.join(process.cwd(), 'lib/google-config.js')) }); for (const k of ['oauth2', 'optional_permissions']) if (k in google) m[k] = google[k]; else delete m[k]; for (const entry of m.web_accessible_resources || []) delete entry.use_dynamic_url; // Firefox has no declarativeContent (Chrome's lit button) and no addHostAccessRequest; AMO's linter flags both, the second diff --git a/test/google.test.js b/test/google.test.js index 96fc815f6..4879e1120 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -404,7 +404,7 @@ test('packages: switched on, the shipped config gives Chrome Calendar\'s client // ---- the build-time switch (lib/google.js ON): off on this branch, as Calendar's is, until the release that ships Drive ---- test('Drive is switched off: the worker refuses, Settings has no row, and the packages carry neither oauth2 nor identity', async () => { assert.equal(G.ON, false); - const h = harness({ on: undefined }); + const h = harness({ on: G.ON }); // the shipped switch assert.deepEqual(await h.g.status(), { ok: false, why: 'off' }); assert.deepEqual(await h.g.connect(), { ok: false, why: 'off' }); assert.deepEqual(await h.g.pick(), { ok: false, why: 'off' }); From 1056b209837111524535d03077d1092c32b0f873 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:04:38 -0400 Subject: [PATCH 105/907] Refresh the Safari resource mirror for Drive's build switch and the shared-grant and framed-Connect fixes --- .../Glaze Extension/Resources/lib/background.js | 14 ++++++++++++-- .../Glaze/Glaze Extension/Resources/lib/google.js | 10 +++++++++- safari/Glaze/Glaze Extension/Resources/options.js | 6 ++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index d88704a02..0255f5090 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3157,10 +3157,17 @@ const gcalOps = { return { ok: !!g?.cal }; }, // drop: also delete the Glaze calendar; if that fails, nothing changes. Then the grant is revoked, Chrome forgets its - // tokens and Glaze its state. → revoked: whether Google confirmed (false with no token to revoke: had). + // tokens and Glaze its state. → revoked: whether Google confirmed (false with no token to revoke: had). While Google Drive + // is connected (lib/google.js) it holds the same grant, and a revocation would end its access too: only Calendar's state + // goes, and shared tells Settings to keep the identity permission. disconnect: async (m) => { const g = await gcalState(); if (m.drop === true && g?.cal) await GZ_GCAL.remove(gcalEnv, g.cal); + if (typeof (await chrome.storage.local.get('google')).google?.email === 'string') { + await chrome.storage.local.remove(['gcal', 'gcalSync']); + serial(ensure); + return { ok: true, revoked: false, had: false, shared: true }; + } const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); @@ -3254,9 +3261,10 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { // pages. Loaded the first time one asks; nothing runs for someone who never connects. The token stays here. globalThis.addEventListener?.('install', () => importScripts('oauth.js', 'google-config.js', 'google.js')); let google = null; +const googleLib = () => { if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); return GZ_GOOGLE; }; const openGoogle = () => { if (!google) { - if (!globalThis.GZ_GOOGLE) importScripts('oauth.js', 'google-config.js', 'google.js'); + googleLib(); const protocol = new URL(chrome.runtime.getURL('')).protocol; google = GZ_GOOGLE.create({ config: GZ_GOOGLE_CONFIG, flow: GZ_GOOGLE.flowOf({ protocol, brands: navigator.userAgentData?.brands }), firefox: protocol === 'moz-extension:', @@ -3273,6 +3281,8 @@ const googlePage = (s) => s.id === chrome.runtime.id && typeof s.url === 'string chrome.runtime.onMessage.addListener((m, s, reply) => { if (m?.type !== 'glaze:google' || !['status', 'connect', 'disconnect', 'pick'].includes(m.op)) return; if (!(m.op === 'pick' ? googlePage(s) : fromEditor(s))) { reply({ ok: false, why: 'sender' }); return; } + googleLib(); + if (!GZ_GOOGLE.ON) { reply({ ok: false, why: 'off' }); return; } // the build-time switch (lib/google.js) Promise.resolve().then(() => openGoogle()[m.op]()).then(reply, () => reply({ ok: false, why: 'network' })); return true; }); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js index cd4d570c8..b73ec8df2 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -15,6 +15,10 @@ // storage (storage.local), fetch, native(message), keep() (Calendar sync holds identity too: Disconnect leaves it), now() }. // Classic script; module.exports for node. var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { + // The build-time switch, off until the release that ships Drive (and its privacy policy). Off: the worker refuses every + // operation, Settings shows no Google Drive row, and the packages carry no oauth2 key or identity for Drive + // (scripts/oauth-manifest.js). scripts/dev-drive.sh turns it on in its copy. + const ON = false; const O = typeof GZ_OAUTH !== 'undefined' ? GZ_OAUTH : require('./oauth.js'); const AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'; const USERINFO = 'https://openidconnect.googleapis.com/v1/userinfo'; @@ -73,6 +77,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const create = (env) => { const c = () => env.config; + const on = env.on ?? ON; // tests switch it on const now = env.now || Date.now; const id = () => env.identity(); let held = null; // the web flow's { token, exp }: this worker's memory only @@ -159,6 +164,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // → { ok: true, flow, email (null until connected) } or { ok: false, why: 'setup' (placeholders) | 'browser' (Safari, for now) } const status = async () => { + if (!on) return { ok: false, why: 'off' }; const flow = flowNow(); if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; if (!usable(flow)) return { ok: false, why: 'setup' }; @@ -184,6 +190,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { })); // The worker's token for a Drive request; interactive only for something the student pressed. const token = async ({ interactive = false } = {}) => { + if (!on) throw new Fail('off'); const g = await saved(); if (!g) throw new Fail('connect'); if (!usable(g.flow)) throw new Fail('setup'); @@ -223,6 +230,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { }; // From a click on a Glaze page. → { ok: true, files: [{ id, name, mimeType }] (none: cancelled) } or { ok: false, why } const pick = fails(async () => { + if (!on) return { ok: false, why: 'off' }; if (!pickable(c())) return { ok: false, why: 'setup' }; const g = await saved(); if (!g) return { ok: false, why: 'connect' }; @@ -238,6 +246,6 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { status, connect, token, account, disconnect, pick }; }; - return { AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; + return { ON, AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; })(); if (typeof module !== 'undefined') module.exports = GZ_GOOGLE; diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index c20604fcc..e1aaa887c 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1148,8 +1148,8 @@ GZ_UI.load().then((s) => { if ($('#gcal-bye').returnValue !== 'ok') return $('#gcal-disconnect').focus(); const r = await gcalOp({ op: 'disconnect', drop: $('#gcal-drop').checked }); if (!r.ok) return hint(t("Couldn't delete the Glaze calendar, so nothing changed. Try again, or disconnect without deleting it."), true); - // Nothing stays behind: the permission goes too, until the next Connect asks again. - await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); + // Nothing stays behind: the permission goes too, until the next Connect asks again, unless Google Drive still uses it. + if (!r.shared) await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); gcalRender(null); $('#gcal-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), r.had && !r.revoked); @@ -1174,6 +1174,8 @@ GZ_UI.load().then((s) => { // Google's own screen, from the worker. $('#gdrive-connect').addEventListener('click', async (e) => { if (!e.isTrusted || gdBusy) return; + // Settings framed in a school's page may have no permissions API (Firefox): its own tab asks, at this section. + if (typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); gdBusy = true; try { const asked = chrome.permissions.request({ permissions: ['identity'] }); From 98b0a6fd2fe107fd2dd507372c9be8e37d938fbc Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:04:53 -0400 Subject: [PATCH 106/907] Attach documents from Documents to an Ultra assignment's submission --- features/writer-submit.js | 68 +++++++++--- i18n/de.js | 9 +- i18n/es.js | 9 +- i18n/fr.js | 9 +- i18n/nl.js | 9 +- i18n/pt-BR.js | 9 +- lib/doc-files.js | 5 +- lib/writer-main.js | 9 ++ lib/writer-papers.js | 9 +- lib/writer-submit.js | 32 +++++- pdf-kind.js | 10 +- scripts/writer-submit-check.js | 119 ++++++++++++++++++++ sheet-kind.js | 7 +- slides-kind.js | 9 +- test/writer-papers-versions.test.js | 11 +- test/writer-submit.test.js | 38 +++++++ writer-kinds.js | 8 +- writer-paper.css | 8 ++ writer-paper.js | 64 ++++++++--- writer-submit.css | 15 +++ writer-submit.js | 166 ++++++++++++++++++++++++---- 21 files changed, 542 insertions(+), 81 deletions(-) diff --git a/features/writer-submit.js b/features/writer-submit.js index 14bb3f7d8..d574c3e43 100644 --- a/features/writer-submit.js +++ b/features/writer-submit.js @@ -15,6 +15,9 @@ // and the dispatch recorded in the click's own turn, with the paper's version marked Submitted; the sheet's Blackboard // editor clears an uncertain upload's (review). // A file the student submits as is (writer-import.js) takes the same steps from 1, its own name, type and bytes unchanged. +// The documents attached from Documents (the sheet's Attach from Documents) go in 1 too, each through handIn after the +// attempt's own file, Submit waiting until Ultra lists every one; the compact box's sheet sends them alone, Ultra's text +// box kept as the paper left it and the note added after it. // Ultra's own file area (enhance.writerUploads, on by default): one file dropped on it or chosen through its input goes to // the paper's frame instead, before Ultra hears it (in the capture phase), which asks what to do with it; Ultra // never starts that upload, so nothing of it needs undoing. More than one file, a file over 50 MB, Glaze's own attach and @@ -27,7 +30,7 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { const armed = new Map(); // box → whether it's still features/writer.js's const submitted = new WeakSet(); const runs = new WeakMap(), cancelled = new WeakMap(), tokens = new WeakMap(); - const attached = new WeakMap(); // box → exact bytes and the row produced by this upload + const attached = new WeakMap(); // box → each file's name → its exact bytes and the row its upload produced const written = new WeakSet(); // boxes a run has written into const imports = new WeakMap(); let passing = null, running = false, active = null, down = null, giving = false, X = null; @@ -160,7 +163,7 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { const attach = async (box, file, bytes, here) => { const area = L().areaOf(box.editor), panel = L().panelOf(box.editor); if (!area || !panel) return 'page'; - const old = attached.get(box), data = new Uint8Array(bytes); + const old = attached.get(box)?.get(file.name), data = new Uint8Array(bytes); if (old) { if (old.name !== file.name || old.bytes.length !== data.length || !old.bytes.every((b, i) => b === data[i])) return 'review'; // Reuse only the same bytes and the very row the completed upload produced. @@ -173,7 +176,9 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { const zone = !input && L().dropZone(area); if (!input && !zone) return 'upload'; const before = new Set(L().listings(area, file.name, visible)); - attached.set(box, { name: file.name, bytes: data.slice(), before }); // before dispatch: an exception can still leave an upload running + if (!attached.has(box)) attached.set(box, new Map()); + const entry = { name: file.name, bytes: data.slice(), before }; + attached.get(box).set(file.name, entry); // before dispatch: an exception can still leave an upload running if (input) { if (!give(input, file)) return 'review'; } else drop(zone, file); const shown = await until(() => { @@ -182,9 +187,19 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { const rows = L().listings(area, file.name, visible); return rows.length === 1 && !before.has(rows[0]) && 'listed'; }, 30000, 150, here); - if (shown === 'listed') attached.get(box).row = L().listings(area, file.name, visible)[0]; + if (shown === 'listed') entry.row = L().listings(area, file.name, visible)[0]; return !here() ? 'page' : shown === 'listed' ? null : shown === 'dialog' ? 'review' : 'attach'; }; + // The hand-in: one file from any source, { name, mime, bytes } (bytes an ArrayBuffer: a document's export, the attempt's + // own file, a Drive file), attached through Ultra's upload and waited on until Ultra lists it, while here() holds. + // → null, or why not ('large' over Blackboard's 50 MB, 'file' unreadable, else attach's). + const handIn = async (box, { name, mime, bytes } = {}, here = () => true) => { + const why = L().handInError({ name, mime, bytes }); + if (why) return why; + const buffer = bytes instanceof ArrayBuffer ? bytes : bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); + const file = new File([buffer], name, { type: mime, lastModified: Date.now() }); + return attach(box, file, buffer, here); + }; const sameText = (box, d) => { const want = d.map((o) => o.insert).join('').split('\n').slice(0, -1).map(squash).join('\n'); const have = [...box.editor.children].map((p) => squash(p.textContent)).join('\n'); @@ -193,6 +208,12 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { // What Ultra's own text box holds, line by line (a list's items each on its own), as the sheet shows it. const heldOf = (box) => L().lines([...box.editor.children].flatMap((c) => (/^(OL|UL)$/.test(c.tagName) ? [...c.children] : [c]).map((x) => x.textContent)).join('\n')).join('\n'); const flat = (t) => L().lines(t).map(squash).join('\n'); + // The compact box's sheet: the note after what Ultra's box holds, the box otherwise as the paper left it. + const tail = (box, note) => { const want = L().lines(note).map(squash), have = [...box.editor.children].map((p) => squash(p.textContent)); return want.length > 0 && have.slice(-want.length).join('\n') === want.join('\n'); }; + const add = (box, note) => { + const said = main('append', { id: box.id, text: L().lines(note).join('\n') }); + return said.some((r) => r?.ok) && tail(box, note) ? null : 'text'; + }; const write = (box, d) => { const said = main('set', { id: box.id, delta: d }); return said.some((r) => r?.ok) && sameText(box, d) ? null : 'text'; @@ -203,10 +224,16 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { runs.set(box, m.run); // kind: what the frame built (a paper's .docx, a PDF as saved, a deck as brought: writer-kinds.js), named for it. const name = m.name, bytes = m.bytes, key = m.key, asis = m.asis === true, kind = typeof m.kind === 'string' ? m.kind : 'paper'; - if (!key || key !== L().paperKey(box.context?.paper) || !(asis ? L().asIsName(name) && typeof m.mime === 'string' && m.mime.length <= 200 : L().safeName(name, kind)) || !(bytes instanceof ArrayBuffer) || !bytes.byteLength || bytes.byteLength > 50e6 || typeof m.note !== 'string' || m.note.length > 4000 || + // files: the documents attached beside it ({ name, mime, bytes } each, names unique); alone (no name, no bytes) only + // from the compact box's sheet, which adds the note after Ultra's text (append). + const files = Array.isArray(m.files) ? m.files : [], alone = name == null && bytes == null, append = alone && m.append === true; + const names = [...(alone ? [] : [name]), ...files.map((f) => f?.name)].map((n) => String(n).toLowerCase()); + if (files.length > L().MAX_ATTACH || files.some((f) => L().handInError(f) || !(f.bytes instanceof ArrayBuffer)) || new Set(names).size !== names.length || (alone && (!files.length || !append))) return fail(files.some((f) => L().handInError(f) === 'large') ? 'large' : 'page'); + if (!key || key !== L().paperKey(box.context?.paper) || !(alone || ((asis ? L().asIsName(name) && typeof m.mime === 'string' && m.mime.length <= 200 : L().safeName(name, kind)) && bytes instanceof ArrayBuffer && bytes.byteLength && bytes.byteLength <= 50e6)) || typeof m.note !== 'string' || m.note.length > L().MAX_NOTE || (m.text != null && (typeof m.text !== 'string' || m.text.length > 1e6)) || (m.held != null && (typeof m.held !== 'string' || m.held.length > 1e6)) || !Number.isFinite(m.until)) return fail('page'); running = true; - const user = box.context.paper.user, end = Math.min(m.until, Date.now() + 80e3); + // Each attached file has a minute more for its upload to be listed. + const user = box.context.paper.user, end = Math.min(m.until, Date.now() + 80e3 + 60e3 * files.length); const transaction = active = { box, run: m.run, cancelled: false, frame: box.frame, port: box.port }; const token = tokens.get(box) || crypto.randomUUID(); tokens.set(box, token); @@ -217,34 +244,39 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { // Ultra's text box goes as the sheet says: kept as it showed it (Keep), the note and text after it; else replaced by // them, or emptied. A run's own earlier write the sheet didn't show needs review. const held = heldOf(box), shown = flat(m.held ?? '') === flat(held), keep = m.keep === true && !!held, extra = L().delta({ note: m.note, text: m.text || '' }); - if (keep && !shown) return fail('page'); - if (!extra && !keep && written.has(box) && !shown) return fail('review'); - const d = keep && !extra ? null : L().delta({ kept: keep ? held : '', note: m.note, text: m.text || '' }) || (held ? [{ insert: '\n' }] : null); - let s = L().start({ text: !!d, asis }); + if (!append && keep && !shown) return fail('page'); + if (!append && !extra && !keep && written.has(box) && !shown) return fail('review'); + const d = append ? null : keep && !extra ? null : L().delta({ kept: keep ? held : '', note: m.note, text: m.text || '' }) || (held ? [{ insert: '\n' }] : null); + const note = append && L().lines(m.note).length ? m.note : ''; + let s = L().start({ text: !!d || !!note, asis }); if (!asis) s = L().advance(L().advance(s, { step: 'save' }), { step: 'build' }); const here = () => L().paperKey(box.context?.paper) === key && !transaction.cancelled && box.ready && box.frame === transaction.frame && box.frame?.isConnected && box.port === transaction.port && ok() && armed.has(box) && assignment(box) && Date.now() < end; if (!here()) return fail('page'); if (!await permit('claim')) return fail('claimed'); claimed = true; if (!here()) return fail('page'); + // A file an earlier run left in Ultra that this one doesn't send is the student's to review. + if ([...(attached.get(box)?.keys() || [])].some((n) => !names.includes(n.toLowerCase()))) return fail('review'); say(box, { type: 'submit:step', run: m.run, step: 'attach' }); - const file = new File([bytes], name, { type: asis ? m.mime : L().mimeOf(name), lastModified: Date.now() }); - const a = await attach(box, file, bytes, here); - if (a) return fail(a); + for (const f of [...(alone ? [] : [{ name, mime: asis ? m.mime : L().mimeOf(name), bytes }]), ...files]) { + const a = await handIn(box, f, here); + if (a) return fail(a); + } s = L().advance(s, { step: 'attach' }); if (!here()) return fail('page'); - if (d) { + if (d || note) { say(box, { type: 'submit:step', run: m.run, step: 'text' }); written.add(box); - const w = write(box, d); + const w = note ? add(box, note) : write(box, d); if (w) return fail(w); s = L().advance(s, { step: 'text' }); } say(box, { type: 'submit:step', run: m.run, step: 'submit' }); const b = await until(() => { const x = L().footer(box.editor).submit; return x && L().enabled(x) && x; }, 15000, 100, here); if (!b) return fail('submit'); - if (!here() || !L().may(s, 'submit') || !L().enabled(b) || b !== L().footer(box.editor).submit || !b.isConnected || L().listed(L().areaOf(box.editor), name, visible) !== 1) return fail('page'); - if (d ? !sameText(box, d) : flat(heldOf(box)) !== flat(held)) return fail('text'); + const area = L().areaOf(box.editor); + if (!here() || !L().may(s, 'submit') || !L().enabled(b) || b !== L().footer(box.editor).submit || !b.isConnected || [...(alone ? [] : [name]), ...files.map((f) => f.name)].some((n) => L().listed(area, n, visible) !== 1)) return fail('page'); + if (note ? !tail(box, note) : d ? !sameText(box, d) : flat(heldOf(box)) !== flat(held)) return fail('text'); // The worker records the dispatch in the click's own turn: nothing can fail between them, and a page Ultra's // navigation tears down after the click has already sent it. claimed = false; @@ -313,7 +345,7 @@ var GZ_WRITER_SUBMIT = globalThis.GZ_WRITER_SUBMIT || (() => { }; // armed(box): this step has the box's Ultra buttons (features/writer.js holds a press until it does). const press = (box, b, type = 'click') => { if (live().has(box) && box.full && ownerOf(b) === box) act(box, b, { type, preventDefault() {}, stopImmediatePropagation() {} }); }; - return { hear, press, passing: (button) => button === passing, armed: (box) => live().has(box) }; + return { hear, press, handIn, passing: (button) => button === passing, armed: (box) => live().has(box) }; })(); GZ_WRITER_SUBMIT; diff --git a/i18n/de.js b/i18n/de.js index dca4a403d..9dcbc03db 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3751,5 +3751,12 @@ "Don't allow": "Nicht erlauben", "Unnamed app": "Unbenannte App", "Glaze restarted before you answered.": "Glaze wurde neu gestartet, bevor du geantwortet hast.", - "Export .xlsm": ".xlsm exportieren" + "Export .xlsm": ".xlsm exportieren", + "Attach from Documents": "Aus Dokumenten anhängen", + "{name} could not be made.": "{name} konnte nicht erstellt werden.", + "{name} is larger than 50 MB.": "{name} ist größer als 50 MB.", + "Attach up to {n} documents.": "Hänge bis zu {n} Dokumente an.", + "Attached: {names}": "Angehängt: {names}", + "A file is larger than 50 MB. Nothing was submitted.": "Eine Datei ist größer als 50 MB. Es wurde nichts abgegeben.", + "A file could not be read. Nothing was submitted.": "Eine Datei konnte nicht gelesen werden. Es wurde nichts abgegeben." }; diff --git a/i18n/es.js b/i18n/es.js index 7568ab4f3..2c4921ebe 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3751,5 +3751,12 @@ "Don't allow": "No permitir", "Unnamed app": "App sin nombre", "Glaze restarted before you answered.": "Glaze se reinició antes de que respondieras.", - "Export .xlsm": "Exportar .xlsm" + "Export .xlsm": "Exportar .xlsm", + "Attach from Documents": "Adjuntar desde Documentos", + "{name} could not be made.": "No se pudo crear {name}.", + "{name} is larger than 50 MB.": "{name} supera los 50 MB.", + "Attach up to {n} documents.": "Adjunta hasta {n} documentos.", + "Attached: {names}": "Adjuntos: {names}", + "A file is larger than 50 MB. Nothing was submitted.": "Un archivo supera los 50 MB. No se entregó nada.", + "A file could not be read. Nothing was submitted.": "No se pudo leer un archivo. No se entregó nada." }; diff --git a/i18n/fr.js b/i18n/fr.js index 0ab81213c..763e170d8 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3751,5 +3751,12 @@ "Don't allow": "Ne pas autoriser", "Unnamed app": "App sans nom", "Glaze restarted before you answered.": "Glaze a redémarré avant votre réponse.", - "Export .xlsm": "Exporter .xlsm" + "Export .xlsm": "Exporter .xlsm", + "Attach from Documents": "Joindre depuis Documents", + "{name} could not be made.": "Impossible de créer {name}.", + "{name} is larger than 50 MB.": "{name} dépasse 50 Mo.", + "Attach up to {n} documents.": "Joignez jusqu’à {n} documents.", + "Attached: {names}": "Pièces jointes : {names}", + "A file is larger than 50 MB. Nothing was submitted.": "Un fichier dépasse 50 Mo. Rien n’a été remis.", + "A file could not be read. Nothing was submitted.": "Un fichier n’a pas pu être lu. Rien n’a été remis." }; diff --git a/i18n/nl.js b/i18n/nl.js index 9c1082c80..70f370624 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3751,5 +3751,12 @@ "Don't allow": "Niet toestaan", "Unnamed app": "Naamloze app", "Glaze restarted before you answered.": "Glaze is opnieuw gestart voordat je antwoordde.", - "Export .xlsm": ".xlsm exporteren" + "Export .xlsm": ".xlsm exporteren", + "Attach from Documents": "Bijvoegen uit Documenten", + "{name} could not be made.": "{name} kon niet worden gemaakt.", + "{name} is larger than 50 MB.": "{name} is groter dan 50 MB.", + "Attach up to {n} documents.": "Voeg maximaal {n} documenten toe.", + "Attached: {names}": "Bijgevoegd: {names}", + "A file is larger than 50 MB. Nothing was submitted.": "Een bestand is groter dan 50 MB. Er is niets ingeleverd.", + "A file could not be read. Nothing was submitted.": "Een bestand kon niet worden gelezen. Er is niets ingeleverd." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index aaa4cfd6f..425a85eca 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3751,5 +3751,12 @@ "Don't allow": "Não permitir", "Unnamed app": "App sem nome", "Glaze restarted before you answered.": "O Glaze reiniciou antes da sua resposta.", - "Export .xlsm": "Exportar .xlsm" + "Export .xlsm": "Exportar .xlsm", + "Attach from Documents": "Anexar de Documentos", + "{name} could not be made.": "Não foi possível criar {name}.", + "{name} is larger than 50 MB.": "{name} tem mais de 50 MB.", + "Attach up to {n} documents.": "Anexe até {n} documentos.", + "Attached: {names}": "Anexos: {names}", + "A file is larger than 50 MB. Nothing was submitted.": "Um arquivo tem mais de 50 MB. Nada foi enviado.", + "A file could not be read. Nothing was submitted.": "Não foi possível ler um arquivo. Nada foi enviado." }; diff --git a/lib/doc-files.js b/lib/doc-files.js index 46979dcf2..734c59f69 100644 --- a/lib/doc-files.js +++ b/lib/doc-files.js @@ -335,8 +335,8 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { return file; } finally { s.close(); } }; - // A ref → its bytes, each chunk decoded into the one buffer the file is. - const get = async (file) => { + // A ref → its bytes, each chunk decoded into the one buffer the file is (got(done, total) as they come). + const get = async (file, got = null) => { if (!ref(file)) throw fail('missing'); const s = session(), out = new Uint8Array(file.size); let at = 0; @@ -348,6 +348,7 @@ var GZ_DOC_FILES = globalThis.GZ_DOC_FILES || (() => { const part = unb64(m.data); if (at + part.length > out.length) throw fail('missing'); out.set(part, at); at += part.length; + try { got?.(at, out.length); } catch (e) { console.error(e); } s.send({ op: 'ack' }); } if (at !== out.length) throw fail('missing'); diff --git a/lib/writer-main.js b/lib/writer-main.js index 094e3db1c..924f19284 100644 --- a/lib/writer-main.js +++ b/lib/writer-main.js @@ -6,6 +6,8 @@ // gz-writer:set { id, delta } puts the Quill ops in as the student's edit (setContents, no clipboard heuristics in // between), so Ultra's Post, word count and draft follow it; the ops are checked first and only the compose formats // (lib/writer-schema.js delta) pass; +// gz-writer:append { id, text } adds plain paragraphs after what the box holds (a blank one between), its formats kept, +// as the student's edit; // gz-writer:get { id }; // each answered by gz-writer:done { id, ok, html, text }. // The box is the .ql-container whose data-gz-writer-id is `id`. Nothing here moves focus, and the page hears only what its @@ -50,6 +52,13 @@ if (!ops(m.delta)) throw new Error('delta'); q.setContents(m.delta, 'user'); }); + on('gz-writer:append', (q, m) => { + if (typeof m.text !== 'string' || !m.text.trim() || m.text.length > 20000 || /[\u0000-\u0009\u000b-\u001f\u007f]/.test(m.text)) throw new Error('text'); + // After the last line's newline, so the text takes none of that line's formats (a list item, a heading). + const n = q.getLength(), empty = n <= 1 && !q.getText().trim(); + if (empty) q.setContents([{ insert: `${m.text}\n` }], 'user'); + else q.updateContents([{ retain: n }, { insert: `\n${m.text}\n` }], 'user'); + }); on('gz-writer:get', (q) => { const s = q.getSelection?.(); return s && Number.isSafeInteger(s.index) && s.index >= 0 && Number.isSafeInteger(s.length) && s.length >= 0 ? { selection: { index: s.index, length: s.length } } : {}; diff --git a/lib/writer-papers.js b/lib/writer-papers.js index fdc075a9b..feb22f86d 100644 --- a/lib/writer-papers.js +++ b/lib/writer-papers.js @@ -139,6 +139,9 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { } return keep; }; + // What a submit attached beside the paper, as its version keeps it: at most 10 names, sizes and types. + const attachedOf = (list) => list.slice(0, 10).filter((a) => typeof a?.name === 'string' && a.name && Number.isSafeInteger(a.size) && a.size > 0) + .map((a) => ({ name: a.name.slice(0, 255), size: a.size, mime: typeof a.mime === 'string' ? a.mime.slice(0, 200) : '' })); const snapshot = (paper, reason, at) => { const doc = paper.doc, m = measure(doc); const file = paper.kind && paper.kind !== 'paper' ? Object.fromEntries(FILE_FIELDS.filter((f) => paper[f] !== undefined).map((f) => [f, paper[f]])) : {}; @@ -170,8 +173,9 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // Saves the paper under k, merging patch into what's stored; a record written before kinds is a paper, and its title // the assignment's until the student names it (named). reason: 'auto' (the default: autosave), 'sending' or // 'submit' (the paper itself kept), 'import' or 'restore' (the doc before it is kept), 'format', 'view' (the paper as - // the full writer left it, before the compact box writes it into Blackboard's). → { paper, version }. - const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null } = {}) => serial(k, async () => { + // the full writer left it, before the compact box writes it into Blackboard's). attached: on a 'sending' save, the files + // the submit attaches beside it ({ name, size, mime }), kept on its version. → { paper, version }. + const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null, attached = null } = {}) => serial(k, async () => { if (typeof k !== 'string' || !k) throw new Error('no paper key'); const written = Object.keys(parts || {}), dropped = [...(drop || [])]; if ([...written, ...dropped].some((n) => typeof n !== 'string' || !PART_NAME.test(n)) || (packed && typeof packed.gz !== 'string')) throw new Error('no such part'); @@ -237,6 +241,7 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { if (packed) version = await retain({ id: `${t.toString(36)}-${Math.random().toString(36).slice(2, 8)}`, at: t, reason: String(packed.reason || 'auto'), kind: paper.kind, cells: Number.isSafeInteger(packed.cells) ? packed.cells : 0, bytes: packed.gz.length, gz: packed.gz }); if (version) paper.lastVersion = t; + if (version && (reason === 'sending' || packed?.reason === 'sending') && Array.isArray(attached)) version.attached = attachedOf(attached); await claim(k, [paper, ...(history || [])], [patch]); // One storage write publishes the document, history and index together. A quota refusal cannot evict // a submitted version or expose a half-saved paper. The worker serializes this read/compute/write. diff --git a/lib/writer-submit.js b/lib/writer-submit.js index 74dacc205..766ab4749 100644 --- a/lib/writer-submit.js +++ b/lib/writer-submit.js @@ -45,6 +45,36 @@ var GZ_WRITER_SUBMIT_LIB = globalThis.GZ_WRITER_SUBMIT_LIB || (() => { const fileBytes = (f, bytes) => bytes instanceof ArrayBuffer && bytes.byteLength === f.size && !fileError(f); const kb = (bytes) => Math.max(1, Math.round((Number(bytes) || 0) / 1024)); + // ---- the hand-in: a file from any source ({ name, mime, bytes }: a document of Documents exported by its kind's file(), + // a Drive file) attached beside the attempt's own file, each one at most Blackboard's 50 MB, at most MAX_ATTACH of them. + const MAX_ATTACH = 10; + // → null, or why not: 'large' (over 50 MB), 'file' (no bytes, a name a file can't have, no type). + const handInError = (f) => { + const n = f?.bytes instanceof ArrayBuffer || ArrayBuffer.isView(f?.bytes) ? f.bytes.byteLength : -1; + if (n > MAX_FILE) return 'large'; + return n > 0 && asIsName(f.name) && typeof f.mime === 'string' && f.mime.length <= 200 ? null : 'file'; + }; + // A document's file name: Lastname_Title and its kind's extension, as the attempt's own file is named (a file of a type + // no kind has keeps its own name), made unique against the names taken (any case) with _2, _3… + const attachName = ({ surname = '', title = '', fallback = 'Document', name = '', taken = [] } = {}) => { + const ext = extOf(name), known = Object.hasOwn(EXT, ext); + const base = known ? fileName({ surname, title, fallback, ext }) : asIsName(name) ? name : fileName({ surname, title, fallback }); + const e = extOf(base), stem = e ? base.slice(0, -e.length) : base, used = new Set([...taken].map((x) => String(x).toLowerCase())); + if (!used.has(base.toLowerCase())) return base; + for (let i = 2; ; i++) { + const tail = `_${i}`, n = `${known ? stem.slice(0, 101 - tail.length) : stem}${tail}${e}`; + if (!used.has(n.toLowerCase())) return n; + } + }; + // The note to the instructor with the attached files named after it, on a line of their own (line: the words, as the + // sheet's language says them, with {names}). + const noteWith = (note, names = [], line = 'Attached: {names}') => { + const n = String(note ?? '').replace(/\s+$/, ''), list = [...names].filter(Boolean).join(', '); + return list ? [n, String(line).replace('{names}', list)].filter(Boolean).join('\n\n') : n; + }; + // What the note sent to the page may hold: the student's 4,000 characters and the files' names. + const MAX_NOTE = 4000 + MAX_ATTACH * 260; + // ---- a file the student brings (writer-import.js): a .docx asks Open in Writer or Submit as is, and a file of a kind // that opens in the frame (kinds: its switch on, its viewer here; writer-kinds.js) Open in Glaze or Submit as is; any // other file goes straight to the submit sheet as is. forFile: the registry's (lib/doc-kinds.js) where it is loaded. @@ -123,7 +153,7 @@ var GZ_WRITER_SUBMIT_LIB = globalThis.GZ_WRITER_SUBMIT_LIB || (() => { }; const may = (s, name) => step(s) === name; - return { MIME, EXT, SWITCH, switched, kindOf, mimeOf, shortTitle, fileName, safeName, paperKey, MAX_FILE, fileError, fileBytes, kb, route, asIsName, tag, pagesOf, PANEL, SUBMIT, SAVE, panelOf, areaOf, footer, actionOf, enabled, uploadInput, dropZone, listings, listed, lines, delta, STEPS, start, step, advance, may }; + return { MAX_ATTACH, handInError, attachName, noteWith, MAX_NOTE, MIME, EXT, SWITCH, switched, kindOf, mimeOf, shortTitle, fileName, safeName, paperKey, MAX_FILE, fileError, fileBytes, kb, route, asIsName, tag, pagesOf, PANEL, SUBMIT, SAVE, panelOf, areaOf, footer, actionOf, enabled, uploadInput, dropZone, listings, listed, lines, delta, STEPS, start, step, advance, may }; })(); if (typeof module !== 'undefined') module.exports = GZ_WRITER_SUBMIT_LIB; GZ_WRITER_SUBMIT_LIB; diff --git a/pdf-kind.js b/pdf-kind.js index dc487d8c9..243ca3349 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -46,7 +46,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { } }, // a record → its bytes, or null for none; a FileError when the store can't give them (missing, unavailable, cut). - get: async (rec) => { + get: async (rec, got = null) => { if (typeof rec?.file === 'string') { if (!rec.file || rec.file.length > 4 * Math.ceil(LEGACY / 3) || (rec.size != null && (!Number.isSafeInteger(rec.size) || rec.size <= 0 || rec.size > LEGACY))) return null; try { @@ -56,7 +56,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { } const F = await files(); if (!F.ref(rec?.file) || rec.file.size > STORE.max) return null; - try { return await F.client().get(rec.file); } catch (e) { throw new FileError(e.why); } + try { return await F.client().get(rec.file, got); } catch (e) { throw new FileError(e.why); } }, }; @@ -171,7 +171,11 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { STORE.get(rec).then((bytes) => bytes && read(bytes)).then(async (r) => { if (!r) return; const { task, doc } = r; try { const u = await picture(doc); drawn.set(at, u); img.src = u; } finally { await task.destroy().catch(() => {}); } }).catch(() => {}); return img; }; - const file = async (key) => { const rec = await papers().get(key), bytes = await STORE.get(rec); return bytes ? { name: nameOf(rec), mime: 'application/pdf', bytes } : null; }; + // progress (lib/doc-kinds.js progress): the bytes as they come from the file store. + const file = async (key, { progress = null } = {}) => { + const rec = await papers().get(key), bytes = await STORE.get(rec, progress && ((done, total) => progress({ stage: 'copy', done, total }))); + return bytes ? { name: nameOf(rec), mime: 'application/pdf', bytes } : null; + }; const exportFile = async (key) => { const f = await file(key); if (!f) throw new Error('no file'); diff --git a/scripts/writer-submit-check.js b/scripts/writer-submit-check.js index 6036201d5..3992cb9d7 100644 --- a/scripts/writer-submit-check.js +++ b/scripts/writer-submit-check.js @@ -972,6 +972,125 @@ const HARNESS = require('./ultra-attempt-harness.js'); await p.close(); await setFull(true); + // ---- Attach from Documents: a paper (.docx), a spreadsheet (.xlsx) and a PDF imported on Documents (papers.html, its + // own chooser), attached on the sheet beside the attempt's own file (the full writer) and alone (the compact box), one + // added twice and removed, one over 50 MB refused; Ultra's upload sees each with its name, type and size, the note + // names them, and the submitted version keeps them. + { + const L = require('../lib/writer-submit.js'), XL = require('../lib/sheet-xlsx.js'), SM = require('../lib/sheet-model.js'); + const setKinds = () => worker.evaluate(async () => { const w = (await chrome.storage.sync.get('writer')).writer || {}; await chrome.storage.sync.set({ writer: { ...w, sheets: true, pdf: true } }); }); + await setKinds(); + const book = SM.workbook(); + book.sheets[0].rows.set(0, new Map([[0, { t: 's', v: 'Trial' }], [1, { t: 's', v: 'Distance' }]])); + book.sheets[0].rows.set(1, new Map([[0, { t: 'n', v: 1 }], [1, { t: 'n', v: 4.25 }]])); + const xlsx = Buffer.from(await XL.write(book)), syllabus = fs.readFileSync(path.join(root, 'test/fixtures/import/syllabus.pdf')); + const docs = path.join(out, 'documents'); + fs.mkdirSync(docs, { recursive: true }); + fs.writeFileSync(path.join(docs, 'Lab notes.docx'), essay); fs.writeFileSync(path.join(docs, 'Trial data.xlsx'), xlsx); fs.writeFileSync(path.join(docs, 'Field trip.pdf'), syllabus); + const home = await context.newPage(); + watch(home, 'documents'); + await home.goto(`chrome-extension://${id}/papers.html`); + await until(() => home.evaluate(() => !document.getElementById('list').hidden && /\.pdf/.test(document.querySelector('#gd-file')?.accept || '') && /\.xlsx/.test(document.querySelector('#gd-file').accept)), 'Documents takes the three kinds'); + await home.setInputFiles('#gd-file', ['Lab notes.docx', 'Trial data.xlsx', 'Field trip.pdf'].map((f) => path.join(docs, f))); + const rowsOf = () => worker.evaluate(async () => ((await chrome.storage.local.get('writerPapers')).writerPapers || []).filter((r) => r.key.startsWith('doc:') && !r.trashed)); + const kinds = await until(async () => { const r = await rowsOf(); const by = Object.fromEntries(['paper', 'sheet', 'pdf'].map((k) => [k, r.find((x) => (x.kind || 'paper') === k)])); return Object.values(by).every(Boolean) && by; }, 'the three documents on Documents', 60000, rowsOf); + await home.close(); + const uploads = (p) => p.evaluate(() => __uploads.map(({ name, type, size, bytes }) => ({ name, type, size, bytes }))); + const rows = (fr) => fr.evaluate(() => [...document.querySelectorAll('.gs-att')].map((r) => ({ name: r.querySelector('.gs-name').textContent, size: r.querySelector('small').textContent, tag: r.querySelector('.gs-doc').textContent }))); + const ready = (fr, n, label) => until(async () => { const r = await rows(fr); return r.length === n && r.every((x) => / KB$/.test(x.size)) && await fr.evaluate(() => !document.querySelector('.gs-primary').disabled) && r; }, label, 60000, () => rows(fr)); + const picker = (fr) => until(() => fr.evaluate(() => !!document.querySelector('.gp-fromdocs-layer[data-open]')), 'the Documents picker'); + const pick = async (fr, key) => { + await picker(fr); + await tap(fr, `.gp-fromdocs-row[data-key="${key}"]`); await until(() => fr.evaluate(() => !document.querySelector('.gp-fromdocs-layer')), 'the picker closed and gone'); }; + const attachOne = async (fr, key, n) => { await tap(fr, '.gs-attach'); await pick(fr, key); return ready(fr, n, `${n} attached`); }; + const expect = (surname, taken, list) => { const names = []; for (const [row, ext] of list) names.push(L.attachName({ surname, title: row.title, fallback: 'Document', name: `x${ext}`, taken: [...taken, ...names] })); return names; }; + const sameFile = async (u, want) => { + if (want.ext === '.docx') { const z = DT.unzip(new Uint8Array(u.bytes)); return u.type === MIME && z.names.includes('word/document.xml'); } + if (want.ext === '.xlsx') return u.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' && (await XL.read(new Uint8Array(u.bytes))).sheets[0].rows.get(1).get(1).v === 4.25; + return u.type === 'application/pdf' && sha(u.bytes) === sha(syllabus); + }; + const kb = (n) => `${new Intl.NumberFormat('en-US').format(L.kb(n))} KB`; + const submitted = (key) => worker.evaluate(async (k) => ((await chrome.storage.local.get(`writerVersions:${k}`))[`writerVersions:${k}`] || []).find((v) => v.reason === 'submit') || null, key); + + // The full writer: the attempt's own .docx, then the three. + await worker.evaluate(async () => { const all = await chrome.storage.local.get(null); await chrome.storage.local.remove(Object.keys(all).filter((k) => k.startsWith('writerSubmit:'))); }); + ({ p, el, fr } = await openAttempt()); + await tap(fr, '.gp-submit'); + await sheetOpen(fr); + const main = await fr.evaluate(() => document.querySelector('.gs-file .gs-name').textContent), surname = main.split('_')[0]; + await tap(fr, '.gs-attach'); + await picker(fr); + const listed = await until(() => fr.evaluate((keys) => { const rs = keys.map((k) => document.querySelector(`.gp-fromdocs-row[data-key="${k}"]`)); return rs.every((r) => r?.querySelector('.gp-fromdocs-pic > *')) && rs.map((r) => r.querySelector('small').textContent.split(' · ')[0]); }, [kinds.paper.key, kinds.sheet.key, kinds.pdf.key]), 'each kind with its thumbnail', 30000); + check(listed.join('|') === 'Document|Spreadsheet|PDF' && await fr.evaluate(() => document.querySelector('#gp-fromdocs-h').textContent === 'Attach from Documents' && document.querySelector('.gs-layer').inert), `Attach from Documents lists every kind with its thumbnail over the inert sheet (${listed.join(', ')})`); + fs.writeFileSync(path.join(out, 'attach-picker.png'), await p.screenshot()); + await pick(fr, kinds.paper.key); + await ready(fr, 1, 'the paper attached'); + await attachOne(fr, kinds.sheet.key, 2); + await attachOne(fr, kinds.pdf.key, 3); + const twice = await attachOne(fr, kinds.sheet.key, 4); + const names = expect(surname, [main], [[kinds.paper, '.docx'], [kinds.sheet, '.xlsx'], [kinds.pdf, '.pdf']]); + const again = expect(surname, [main, ...names], [[kinds.sheet, '.xlsx']])[0]; + check(twice.map((r) => r.name).join('|') === [...names, again].join('|') && twice.map((r) => r.tag).join('|') === 'DOCX|XLSX|PDF|XLSX' && again.endsWith('_2.xlsx'), `each listed as Lastname_Title with its size, the same one twice de-duplicated (${twice.map((r) => `${r.name} ${r.size}`).join('; ')})`); + fs.writeFileSync(path.join(out, 'attach-sheet.png'), await p.screenshot()); + await tap(fr, '.gs-att:last-child .gs-att-x'); + const left = await ready(fr, 3, 'one removed'); + check(left.map((r) => r.name).join('|') === names.join('|') && await fr.evaluate(() => document.activeElement?.matches('.gs-att:last-child .gs-att-x')), 'Remove takes it off the list, focus to the row before'); + const before = (await uploads(p)).length; + await fr.fill('#gs-note', 'Here is my lab.'); + await tap(fr, '.gs-primary'); + await until(() => p.evaluate(() => !!document.getElementById('ultra-confirm')), 'submitted with attachments', 60000 * 4); + const up = (await uploads(p)).slice(before); + const wants = [{ name: main, ext: '.docx' }, ...names.map((n, i) => ({ name: n, ext: ['.docx', '.xlsx', '.pdf'][i] }))]; + const fits = await Promise.all(up.map((u, i) => wants[i] && u.name === wants[i].name && u.size === u.bytes.length && sameFile(u, wants[i]))); + check(up.length === 4 && fits.every(Boolean) && up.slice(1).every((u, i) => kb(u.size) === left[i].size) && await p.evaluate(() => __clicks.length === 1), `Ultra's upload sees the attempt's .docx, then each attached file with its name, type and listed size, Submit once (${up.map((u) => `${u.name} ${u.type} ${u.size}`).join('; ')})`); + const text = await p.evaluate(() => __text()); + check(text.includes('Here is my lab.') && text.includes(`Attached: ${names.join(', ')}`), `the note names the attachments (${JSON.stringify(text.slice(0, 200))})`); + const attemptKey = await fr.evaluate(() => GZ_WRITER_PAPER.current().key()); + const v = await until(() => submitted(attemptKey), 'the submitted version'); + check(JSON.stringify((v.attached || []).map((a) => [a.name, a.size, a.mime])) === JSON.stringify(up.slice(1).map((u) => [u.name, u.size, u.type])), `the submitted version keeps what was attached (${JSON.stringify(v.attached)})`); + await p.close(); + + // The compact box (Full writer off, the default): ⋯ → Attach from Documents, the files alone; one over 50 MB refused. + await setFull(false); + await worker.evaluate(async () => { const all = await chrome.storage.local.get(null); await chrome.storage.local.remove(Object.keys(all).filter((k) => k.startsWith('writerSubmit:') || k.startsWith('writerVersions:bb.'))); }); + ({ p, el, fr } = await openCompact()); + await until(() => fr.evaluate(() => document.documentElement.dataset.view === 'compact'), 'compact'); + await tap(fr, '#gw .ProseMirror'); await boxFocused(fr); await p.keyboard.press('End'); await p.keyboard.type(' Compact attach text.'); + await inUltra(p, fr, (x) => x.includes('Compact attach text.'), 'compact text in Ultra'); + const ultraBefore = await p.evaluate(() => __text()); + await tap(fr, '#more'); + await tap(fr, '#menu [role="menuitem"]:text-is("Attach from Documents")'); + await pick(fr, kinds.paper.key); + await sheetOpen(fr); + const one = await ready(fr, 1, 'compact: the paper attached'); + const shape = await fr.evaluate(() => ({ only: !!document.querySelector('.gs-sheet.gs-only'), prev: !!document.querySelector('.gs-prev'), keep: !!document.querySelector('#gs-keep')?.isConnected, paste: !!document.querySelector('.gs-toggle') })); + check(shape.only && !shape.prev && !shape.keep && !shape.paste, `compact: the sheet holds the files alone, no pages, Keep or paste switch (${JSON.stringify(shape)})`); + fs.writeFileSync(path.join(out, 'attach-compact.png'), await p.screenshot()); + // Over Blackboard's 50 MB: the PDF kind's export made larger in this frame. + await fr.evaluate(() => { const k = GZ_DOC_KINDS.get('pdf'); globalThis.__pdfFile = k.file; GZ_DOC_KINDS.register('pdf', { ...k, file: async () => ({ name: 'Big.pdf', mime: 'application/pdf', bytes: new Uint8Array(50e6 + 1) }) }); }); + await tap(fr, '.gs-attach'); await pick(fr, kinds.pdf.key); + const refused = await until(() => fr.evaluate(() => document.querySelector('.gs-err').textContent), 'the refusal'); + check(refused === `${kinds.pdf.title} is larger than 50 MB.` && (await rows(fr)).length === 1, `over 50 MB is refused with a plain reason ("${refused}")`); + await fr.evaluate(() => { const k = GZ_DOC_KINDS.get('pdf'); GZ_DOC_KINDS.register('pdf', { ...k, file: globalThis.__pdfFile }); }); + await attachOne(fr, kinds.sheet.key, 2); + const three = await attachOne(fr, kinds.pdf.key, 3); + const cnames = expect(surname, [], [[kinds.paper, '.docx'], [kinds.sheet, '.xlsx'], [kinds.pdf, '.pdf']]); + check(one[0].name === cnames[0] && three.map((r) => r.name).join('|') === cnames.join('|'), `compact: Lastname_Title, nothing taken by an own file (${cnames.join(', ')})`); + const cbefore = (await uploads(p)).length; + await tap(fr, '.gs-primary'); + await until(() => p.evaluate(() => !!document.getElementById('ultra-confirm')), 'compact: submitted with attachments', 60000 * 4); + const cup = (await uploads(p)).slice(cbefore); + const cfits = await Promise.all(cup.map((u, i) => u.name === cnames[i] && sameFile(u, { ext: ['.docx', '.xlsx', '.pdf'][i] }))); + const ctext = await p.evaluate(() => __text()); + check(cup.length === 3 && cfits.every(Boolean) && await p.evaluate(() => __clicks.length === 1), `compact: Ultra's upload sees the three files, Submit once (${cup.map((u) => `${u.name} ${u.type} ${u.size}`).join('; ')})`); + check(ctext.startsWith(ultraBefore.replace(/\n+$/, '')) && ctext.trim().endsWith(`Attached: ${cnames.join(', ')}`), `compact: Ultra's text box keeps the paper, the note naming the files after it (${JSON.stringify(ctext.slice(-160))})`); + const ckey = await fr.evaluate(() => GZ_WRITER_PAPER.current().key()); + const cv = await until(() => submitted(ckey), 'the compact submitted version'); + check((cv.attached || []).map((a) => a.name).join('|') === cnames.join('|'), 'compact: the submitted version keeps what was attached'); + await p.close(); + await setFull(true); + } + // ---- a test: no sheet, nothing caught p = await context.newPage(); watch(p, 'test'); diff --git a/sheet-kind.js b/sheet-kind.js index 61593b8bf..98508629f 100644 --- a/sheet-kind.js +++ b/sheet-kind.js @@ -134,7 +134,8 @@ const X = await codecs(), bytes = await X.write(wb), ext = extOf(wb); return { name: `${name}${ext}`, mime: ext === '.xlsm' ? XLSM : DK.KINDS.sheet.mime, bytes }; }; - const file = async (key) => { + const file = async (key, { progress = null } = {}) => { + progress?.({ stage: 'prepare' }); await need([...ENGINE, 'sheet-grid.js']); const got = await GZ_SHEET_DATA.read(get, key); return got ? fileOf(got.wb, got.rec.title) : null; @@ -150,12 +151,12 @@ const exportFile = async (key) => { const f = await file(key); if (f) download(f); }; // The frame's submit and kind-change checkpoints keep the complete workbook, as the grid's own Versions do. - const version = async (key, reason) => { + const version = async (key, reason, { attached = null } = {}) => { await need(ENGINE); const got = await GZ_SHEET_DATA.read(get, key); if (got?.rec.kind !== 'sheet') throw new Error('document kind changed'); const gz = await GZ_SHEET_DATA.pack(got.wb); - return papers().save(key, {}, { expectedKind: 'sheet', base: got.rec.revision, version: { reason, gz, cells: GZ_SHEET_DATA.measure(got.wb).cells } }); + return papers().save(key, {}, { expectedKind: 'sheet', base: got.rec.revision, version: { reason, gz, cells: GZ_SHEET_DATA.measure(got.wb).cells }, ...(attached && { attached }) }); }; // ---- the grid diff --git a/slides-kind.js b/slides-kind.js index ef175d587..c03319d8d 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -44,14 +44,14 @@ } }, // a record → its bytes, or null for none; a FileError when the store can't give them (missing, unavailable, cut). - get: async (rec) => { + get: async (rec, got = null) => { if (typeof rec?.file === 'string') { if (!rec.file.length || rec.file.length > 4 * Math.ceil(LEGACY / 3)) return null; try { const bytes = unb64(rec.file); return bytes.length <= LEGACY ? bytes : null; } catch { return null; } } const F = await files(); if (!F.ref(rec?.file) || rec.file.size > STORE.max) return null; - try { return await F.client().get(rec.file); } catch (e) { throw new FileError(e.why); } + try { return await F.client().get(rec.file, got); } catch (e) { throw new FileError(e.why); } }, }; let store = null; @@ -203,8 +203,9 @@ return key; }; // A deck as its file: the bytes imported, named by its title. → { name, mime, bytes } or null. - const file = async (key) => { - const rec = await papers().get(key), bytes = await STORE.get(rec); + // progress (lib/doc-kinds.js progress): the bytes as they come from the file store. + const file = async (key, { progress = null } = {}) => { + const rec = await papers().get(key), bytes = await STORE.get(rec, progress && ((done, total) => progress({ stage: 'copy', done, total }))); if (!bytes) return null; const ext = rec.ext === '.ppsx' ? '.ppsx' : '.pptx', name = String(rec.title || '').replace(/[\\/:*?"<>|\u0000-\u001f]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 120) || t('Slides'); return { name: `${name}${ext}`, mime: MIME[ext], bytes }; diff --git a/test/writer-papers-versions.test.js b/test/writer-papers-versions.test.js index ab545ebcf..affff2ca0 100644 --- a/test/writer-papers-versions.test.js +++ b/test/writer-papers-versions.test.js @@ -116,12 +116,15 @@ test('a restore the store refuses, or of a version gone meanwhile, says so and l test('Open from Documents lists no document in Trash', async () => { const fs = require('node:fs'), vm = require('node:vm'); const source = fs.readFileSync(require('node:path').join(__dirname, '../writer-paper.js'), 'utf8'); - const head = ' const fromDocsSheet = async () => {', from = source.indexOf(head), to = source.indexOf(' const layer = ', from); + const head = ' const fromDocsSheet = async ({ attach = false, back = null } = {}) => {', from = source.indexOf(head), to = source.indexOf(' const layer = ', from); assert.ok(from > 0 && to > from); const live = PA.newKey(), binned = PA.newKey(), older = PA.newKey(); const rows = [{ key: live, kind: 'paper', updated: 2 }, { key: binned, kind: 'paper', updated: 3, trashed: 4 }, { key: older, kind: 'paper', updated: 1 }, { key: K, kind: 'paper', updated: 5 }]; - const listed = await vm.runInNewContext(`(async () => {${source.slice(from + head.length, to)}\nreturn docs.map((r) => r.key); })()`, { - closeDocs() {}, docsTurn: 0, store: { list: async () => rows }, PA, root: { isConnected: true }, current: 1, api: 1, importing: false, + const sheet = PA.newKey(), pdf = PA.newKey(), pdfBinned = PA.newKey(); + rows.push({ key: sheet, kind: 'sheet', updated: 1.5 }, { key: pdf, kind: 'pdf', updated: 0.5 }, { key: pdfBinned, kind: 'pdf', updated: 6, trashed: 7 }); + const list = (attach) => vm.runInNewContext(`(async ({ attach = false, back = null } = {}) => {${source.slice(from + head.length, to)}\nreturn docs.map((r) => r.key); })({ attach: ${attach} })`, { + closeDocs() {}, docsTurn: 0, store: { list: async () => rows }, PA, root: { isConnected: true }, current: 1, api: 1, importing: false, KINDS: { paper: 'Document', sheet: 'Spreadsheet', pdf: 'PDF', slides: 'Slides' }, Promise, Object, Intl, document: { documentElement: { lang: 'en' } }, btn: () => ({}), h: () => ({}), icon: () => ({}), t: (k) => k, labelOf: () => '', ST: GZ_WRITER_STYLES, nf: String, toast() {}, console }); - assert.deepEqual(listed, [live, older]); + assert.deepEqual(await list(false), [live, older], 'Open from Documents: papers only'); + assert.deepEqual(await list(true), [live, sheet, older, pdf], 'Attach from Documents: every kind, none in Trash'); }); diff --git a/test/writer-submit.test.js b/test/writer-submit.test.js index 761d69b19..16dc2f826 100644 --- a/test/writer-submit.test.js +++ b/test/writer-submit.test.js @@ -140,3 +140,41 @@ test('review: listing requires an exact name, including a full title on visually row.textContent = 'Rivera_Ess…'; row.setAttribute('title', 'Rivera_Essay2.docx'); assert.equal(S.listed(area, 'Rivera_Essay2.docx'), 1, 'the full title proves a truncated listing'); }); + +// The hand-in (features/writer-submit.js handIn): a file from any source, { name, mime, bytes }, bounded by Blackboard's +// 50 MB per file; Documents' attachments named Lastname_Title as the attempt's own file is, never twice. +test('the hand-in takes { name, mime, bytes } up to 50 MB, from an ArrayBuffer or its view', () => { + const ok = { name: 'Rivera_Notes.docx', mime: S.MIME, bytes: new ArrayBuffer(10) }; + assert.equal(S.MAX_FILE, 50e6); + assert.equal(S.handInError(ok), null); + assert.equal(S.handInError({ ...ok, bytes: new Uint8Array(3) }), null); + assert.equal(S.handInError({ ...ok, bytes: { byteLength: 50e6 } }), 'file', 'not bytes at all'); + const big = { byteLength: 50e6 + 1 }; + Object.setPrototypeOf(big, ArrayBuffer.prototype); + assert.equal(S.handInError({ ...ok, bytes: big }), 'large', 'past 50 MB'); + for (const bad of [{ ...ok, bytes: new ArrayBuffer(0) }, { ...ok, name: '../x.docx' }, { ...ok, name: '' }, { ...ok, mime: null }, { ...ok, mime: 'x'.repeat(201) }, null, {}]) assert.equal(S.handInError(bad), 'file', JSON.stringify(bad)); + assert.equal(S.MAX_ATTACH, 10); +}); + +test('an attached document is Lastname_Title with its kind\'s extension, de-duplicated against the names taken', () => { + assert.equal(S.attachName({ surname: 'Rivera', title: 'Lab notes', name: 'Lab notes.docx' }), 'Rivera_LabNotes.docx'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Grades: term 1', name: 'Grades term 1.xlsx' }), 'Rivera_Grades.xlsx', 'the title before its subtitle'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Budget', name: 'Budget.xlsm' }), 'Rivera_Budget.xlsm'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Field trip', name: 'Field trip.pdf' }), 'Rivera_FieldTrip.pdf'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Week 3', name: 'Week 3.pptx' }), 'Rivera_Week3.pptx'); + assert.equal(S.attachName({ title: '', name: 'x.docx', fallback: 'Document' }), 'Document.docx'); + const taken = ['Rivera_Essay2.docx']; + assert.equal(S.attachName({ surname: 'Rivera', title: 'Essay 2', name: 'a.docx', taken }), 'Rivera_Essay2_2.docx', 'beside the attempt\'s own file'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Essay 2', name: 'a.docx', taken: ['rivera_essay2.DOCX', 'Rivera_Essay2_2.docx'] }), 'Rivera_Essay2_3.docx', 'any case'); + assert.equal(S.attachName({ surname: 'Rivera', title: 'Essay 2', name: 'a.pdf', taken }), 'Rivera_Essay2.pdf', 'another extension is another name'); + const long = S.attachName({ surname: 'x'.repeat(200), title: 'y'.repeat(200), name: 'a.docx', taken: [S.attachName({ surname: 'x'.repeat(200), title: 'y'.repeat(200), name: 'a.docx' })] }); + assert.ok(long.endsWith('_2.docx') && S.safeName(long, 'paper'), `still a built file's name (${long.length})`); + assert.equal(S.attachName({ name: 'Photo of lab.png', taken: ['photo of lab.png'] }), 'Photo of lab_2.png', 'a type no kind has keeps its own name'); +}); + +test('the note names the attached files on a line after it', () => { + assert.equal(S.noteWith('Here is my essay.', ['Rivera_Notes.docx', 'Rivera_Data.xlsx']), 'Here is my essay.\n\nAttached: Rivera_Notes.docx, Rivera_Data.xlsx'); + assert.equal(S.noteWith('', ['a.pdf'], 'Angehängt: {names}'), 'Angehängt: a.pdf'); + assert.equal(S.noteWith('Hi \n', []), 'Hi'); + assert.ok(S.MAX_NOTE >= 4000 + 10 * 255); +}); diff --git a/writer-kinds.js b/writer-kinds.js index c1049ae11..80826f7a1 100644 --- a/writer-kinds.js +++ b/writer-kinds.js @@ -59,10 +59,10 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { // A spreadsheet's file always waits for the .xlsx writer (sheet-kind.js fileOf): .xlsm when its record says so. kind: () => id, key: () => key, ext: () => (id === 'sheet' ? (ext === '.xlsm' ? '.xlsm' : '.xlsx') : ext), context: () => paper.context(), paper: () => paper.paper(), compact: () => false, blank: () => false, importing: () => busy, pages: () => v?.pages?.() || null, notify: (m) => v?.say?.(t(m)), focused: () => {}, resume: () => {}, look: () => null, - version: async (reason = 'submit') => { + version: async (reason = 'submit', { attached = null } = {}) => { if (!await surface.save()) return null; - if (typeof k.version === 'function') return k.version(key, reason).catch((e) => { console.error(e); return null; }); - return store().save(key, {}, { reason, expectedKind: id }).catch((e) => { console.error(e); return null; }); + if (typeof k.version === 'function') return k.version(key, reason, { attached }).catch((e) => { console.error(e); return null; }); + return store().save(key, {}, { reason, expectedKind: id, ...(attached && { attached }) }).catch((e) => { console.error(e); return null; }); }, save: async () => !!v && await owns() && await v.save() && await owns(), file: async () => { if (!await owns()) throw new Error('document kind changed'); const out = await v.file(); if (!await owns()) throw new Error('document kind changed'); return out; }, @@ -223,5 +223,5 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { } catch (e) { console.error(e); failed('error'); } }); - return { FRAME, opens, offers, mount, open, start, shown: () => shown?.id || null }; + return { FRAME, opens, offers, kind, mount, open, start, shown: () => shown?.id || null }; })(); diff --git a/writer-paper.css b/writer-paper.css index 8e5922e57..76de1f117 100644 --- a/writer-paper.css +++ b/writer-paper.css @@ -297,6 +297,14 @@ html[data-gz-motion="off"] .gp-fill, html[data-gz-no-trans] .gp-fill { transitio .gp-fromdocs-row:hover { border-color: var(--gz-accent); } .gp-fromdocs-row b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .gp-fromdocs-row small { font-size: 12px; color: var(--gz-ink-faint); } +.gp-fromdocs-thumbed { grid-template-columns: 56px minmax(0, 1fr); align-items: center; gap: 12px; } +.gp-fromdocs-thumbed > span:last-child { display: grid; gap: 2px; min-width: 0; } +/* Each kind's own thumbnail (its card's on the Documents home), drawn at about a quarter of its size. */ +.gp-fromdocs-pic { width: 56px; height: 72px; border-radius: 4px; overflow: hidden; background: var(--sheet, #fff); border: 1px solid var(--edge); display: flex; justify-content: center; align-items: flex-start; } +.gp-fromdocs-pic > * { zoom: .27; flex: none; } +.gp-fromdocs-pic .gs-thumb { width: 208px; box-shadow: none; border-radius: 0; } +.gp-fromdocs-pic .gq-thumb { width: 204px; display: block; } +.gp-fromdocs-pic .gs-thumb img { display: block; width: 100%; } .gp-fromdocs-layer button:focus-visible { outline: 2px solid var(--gz-accent); outline-offset: 2px; } /* papers.html */ diff --git a/writer-paper.js b/writer-paper.js index a4d36bb74..328c7139c 100644 --- a/writer-paper.js +++ b/writer-paper.js @@ -792,12 +792,12 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { const ours = async (p) => { writing++; try { return await p; } finally { if (!--writing && missed) { missed = false; adopt().catch((e) => console.error(e)); } } }; // base: the revision what's saved was typed on, when it isn't the one this editor last read (a recovered copy). let saves = Promise.resolve(), replacing = null; - const save = (reason = 'auto', base) => { - const next = saves.then(() => saveNow(reason, base)); + const save = (reason = 'auto', base, attached = null) => { + const next = saves.then(() => saveNow(reason, base, attached)); saves = next.catch(() => {}); return next; }; - const saveNow = async (reason = 'auto', base) => { + const saveNow = async (reason = 'auto', base, attached = null) => { // Imports write the replacement before showing it: a visibility/pagehide autosave must read the new editor, // never queue the outgoing document behind that write. The import's own pre-save runs before this barrier. if (replacing) await replacing; @@ -809,7 +809,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { emergency(); setState('Saving…'); try { - const rec = record(), r = await ours(store.save(key, rec, { reason, base: Number.isSafeInteger(base) ? base : paper?.revision ?? 0, rename: nameAt > nameSaved })); paper = r.paper; + const rec = record(), r = await ours(store.save(key, rec, { reason, base: Number.isSafeInteger(base) ? base : paper?.revision ?? 0, rename: nameAt > nameSaved, ...(attached && { attached }) })); paper = r.paper; nameSaved = nameAt; if (nameAt === nameEdits) { naming(paper); head(); } await keepPictures(rec.doc); @@ -1490,32 +1490,62 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { if (leaving.length) Promise.allSettled(leaving.map((a) => a.finished)).then(() => s.layer.remove()); else s.layer.remove(); for (const [el, was] of s.inert) el.inert = was; - if (back) opener().focus(); + s.done?.(null); + if (back) (s.back?.isConnected ? s.back : opener()).focus(); }; // What a sheet goes back to: the status bar's Versions, or the compact box's ⋯. const opener = () => (compact && document.getElementById('more')) || versionsB; + // Attach from Documents (the submit sheet's, attach): every kind of document, each with its thumbnail; the one picked + // is what it resolves to (its index row), null when the sheet closes without one. back: where focus goes after. + const KINDS = { paper: 'Document', sheet: 'Spreadsheet', pdf: 'PDF', slides: 'Slides' }; + const thumbOf = async (rec) => { + const kind = rec.kind || 'paper'; + if (kind === 'paper') { await need(['lib/writer-thumb.js']); return rec.doc ? GZ_WRITER_THUMB.render(GZ_WRITER_THUMB.firstPage(rec.doc), rec.format, { scale: 0.25 }) : null; } + const k = await globalThis.GZ_WRITER_KINDS?.kind(kind); + return k?.thumb?.(rec) || null; + }; + // The thumbnails, a dozen records at a time, while the same sheet is open. + const drawThumbs = async (docs, pics, turn) => { + for (let i = 0; i < docs.length && turn === docsTurn; i += 12) { + const batch = docs.slice(i, i + 12), got = await chrome.storage.local.get(batch.map((r) => `writerPaper:${r.key}`)).catch(() => ({})); + for (let j = 0; j < batch.length && turn === docsTurn; j++) { + const rec = got[`writerPaper:${batch[j].key}`]; + if (!rec) continue; + try { const el = await thumbOf(rec); if (el && turn === docsTurn) pics[i + j].replaceChildren(el); } catch (e) { console.error(e); } + } + } + }; // Each row: its title (the one given, else its first line as record() keeps it), format, words and date. - const fromDocsSheet = async () => { + const fromDocsSheet = async ({ attach = false, back = null } = {}) => { closeDocs(false); const turn = docsTurn; let docs; - try { docs = (await store.list()).filter((r) => PA.local(r.key) && r.kind === 'paper' && !PA.trashed(r)).sort((a, b) => (b.updated || 0) - (a.updated || 0)); } - catch (e) { console.error(e); if (turn === docsTurn && root.isConnected && current === api) { toast(t('Not saved. Try again in a moment.')); opener().focus(); } return; } - if (turn !== docsTurn || !root.isConnected || current !== api || importing || globalThis.GZ_WRITER_SUBMIT_SHEET?.opened()) return; + try { docs = (await store.list()).filter((r) => PA.local(r.key) && (attach ? Object.hasOwn(KINDS, r.kind) : r.kind === 'paper') && !PA.trashed(r)).sort((a, b) => (b.updated || 0) - (a.updated || 0)); } + catch (e) { console.error(e); if (turn === docsTurn && root.isConnected && (attach || current === api)) { toast(t('Not saved. Try again in a moment.')); (back || opener()).focus(); } return null; } + if (turn !== docsTurn || !root.isConnected || (!attach && (current !== api || importing || globalThis.GZ_WRITER_SUBMIT_SHEET?.opened()))) return null; const when = new Intl.DateTimeFormat(document.documentElement.lang || undefined, { dateStyle: 'medium' }); const x = btn({ class: 'gp-fromdocs-x', title: t('Close'), 'aria-label': t('Close'), onclick: () => closeDocs() }, icon('close')); + let resolve; + const picked = new Promise((r) => { resolve = r; }); + const pics = []; const picks = docs.map((r) => { const name = r.title || t('Untitled document'); - const meta = [labelOf(ST.get(r.format)), t('{n} words', { n: nf(r.words || 0) }), Number.isFinite(r.updated) ? when.format(r.updated) : ''].filter(Boolean).join(' · '); - return btn({ class: 'gp-fromdocs-row', 'aria-label': `${name} · ${meta}`, onclick: () => { closeDocs(false); fromDocs(r.key, name); } }, h('b', { text: name }), h('small', { text: meta })); + const meta = (attach ? [t(KINDS[r.kind]), r.kind === 'paper' ? t('{n} words', { n: nf(r.words || 0) }) : ''] : [labelOf(ST.get(r.format)), t('{n} words', { n: nf(r.words || 0) })]) + .concat(Number.isFinite(r.updated) ? when.format(r.updated) : '').filter(Boolean).join(' · '); + if (!attach) return btn({ class: 'gp-fromdocs-row', 'aria-label': `${name} · ${meta}`, onclick: () => { closeDocs(false); fromDocs(r.key, name); } }, h('b', { text: name }), h('small', { text: meta })); + const pic = h('span', { class: 'gp-fromdocs-pic', 'data-kind': r.kind, 'aria-hidden': 'true' }); + pics.push(pic); + return btn({ class: 'gp-fromdocs-row gp-fromdocs-thumbed', 'data-key': r.key, 'aria-label': `${name} · ${meta}`, onclick: () => { resolve(r); closeDocs(); } }, pic, h('span', {}, h('b', { text: name }), h('small', { text: meta }))); }); + const heading = attach ? t('Attach from Documents') : t('Open from Documents'); const layer = h('div', { class: 'gp-fromdocs-layer' }, h('div', { class: 'gp-fromdocs-dim', onclick: () => closeDocs() }), h('div', { class: 'gp-fromdocs', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'gp-fromdocs-h' }, - h('div', { class: 'gp-fromdocs-head' }, h('h2', { id: 'gp-fromdocs-h', text: t('Open from Documents') }), x), picks.length ? h('div', { class: 'gp-fromdocs-list' }, picks) : null)); + h('div', { class: 'gp-fromdocs-head' }, h('h2', { id: 'gp-fromdocs-h', text: heading }), x), picks.length ? h('div', { class: 'gp-fromdocs-list' }, picks) : null)); const inert = [...document.body.children].map((el) => [el, el.inert]); for (const [el] of inert) el.inert = true; document.body.append(layer); - docsSheet = { layer, inert }; + docsSheet = { layer, inert, back, done: resolve }; + if (attach) drawThumbs(docs, pics, turn).catch((e) => console.error(e)); layer.addEventListener('keydown', (e) => { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); closeDocs(); return; } if (picks.length && ['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(e.key)) { @@ -1535,7 +1565,11 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { // A fixed sheet is centred in the frame; Ultra may show only part of that frame. Bring the whole dialog into // the outer window too, including a keyboard opening while the status bar is below it. layer.querySelector('.gp-fromdocs').scrollIntoView({ block: 'center', inline: 'nearest' }); + return attach ? picked : null; }; + // The submit sheet's Attach from Documents (writer-submit.js), and the compact box's ⋯, which opens the sheet with it. + self.pick = ({ back = null } = {}) => fromDocsSheet({ attach: true, back }); + const attachFromDocs = async () => { const r = await fromDocsSheet({ attach: true }); if (r) globalThis.GZ_WRITER_SUBMIT_SHEET?.open(api, null, { attach: r }); }; // ---- what the page says: Ultra's names and the paper's key // The title field: what the student typed, else its derived name in its place, as wide as what it shows. @@ -1720,7 +1754,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { }; const api = { editor: ed, key: () => key, format: () => format, look, paper: () => paper, context: () => ctx, - docx, save, notify: (message) => toast(t(message)), version: (reason = 'submit') => save(reason), focus: setFocus, refresh: hooks, zoom: setZoom, importFile, importing: () => importing, + docx, save, notify: (message) => toast(t(message)), version: (reason = 'submit', { attached = null } = {}) => save(reason, undefined, attached), focus: setFocus, refresh: hooks, zoom: setZoom, importFile, importing: () => importing, // What the submit sheet attaches (writer-submit.js): the paper as its .docx. kind: 'paper', as another kind's surface // says its own (writer-kinds.js). kind: () => 'paper', file: async () => { const out = await docx(); return { bytes: out.bytes, mime: out.type || GZ_WRITER_SUBMIT_LIB.MIME, ext: '.docx' }; }, @@ -1906,7 +1940,7 @@ var GZ_WRITER_PAPER = globalThis.GZ_WRITER_PAPER || (() => { if (more) { more.before(said, compactArriveSay, fullSwitch()); const panel = async (name) => { await setView(true, { save: true }); if (!compact) { tabs[name].click(); tabs[name].focus(); } }; - const items = [['Checks', () => panel('checks')], ['Feedback', () => panel('feedback')], ['Cite', () => globalThis.GZ_WRITER_CITE?.open?.({ anchor: more })], ['Versions', () => versions(more)], ['Open from Documents', () => fromDocsSheet()], ['Documents', () => open2('papers.html')]] + const items = [['Checks', () => panel('checks')], ['Feedback', () => panel('feedback')], ['Cite', () => globalThis.GZ_WRITER_CITE?.open?.({ anchor: more })], ['Versions', () => versions(more)], ['Open from Documents', () => fromDocsSheet()], ...(globalThis.GZ_WRITER_SUBMIT_SHEET && globalThis.GZ_WRITER_FRAME?.kind?.() === 'assignment' ? [['Attach from Documents', () => attachFromDocs()]] : []), ['Documents', () => open2('papers.html')]] .map(([label, run]) => [host.item?.(t(label), run), label]); more.addEventListener('click', () => { for (const [b, label] of items) if (b) b.textContent = t(label); }, true); } diff --git a/writer-submit.css b/writer-submit.css index 4be305a3e..152cc42a8 100644 --- a/writer-submit.css +++ b/writer-submit.css @@ -62,3 +62,18 @@ .gs-big::before { content: ""; position: absolute; top: -1px; right: -1px; width: 34px; height: 34px; background: linear-gradient(225deg, var(--gz-surface-2) 50%, var(--edge) 50%); border-bottom-left-radius: 6px; } .gs-big .gs-doc { width: auto; height: auto; padding: 6px 12px; border-radius: 6px; font: 700 14px/1 var(--gz-font); letter-spacing: .04em; } .gs-clip { position: absolute; left: 16px; top: -12px; width: 22px; height: 40px; fill: none; stroke: var(--gz-ink-muted); stroke-width: 2; stroke-linecap: round; } + +/* The attached files (Attach from Documents): each its badge, name and size, and its remove button. */ +.gs-atts { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; min-width: 0; } +.gs-atts:empty { display: none; } +.gs-att { display: flex; align-items: center; gap: 10px; padding: 8px 8px 8px 12px; border: 1px solid var(--gz-line-strong); border-radius: 10px; font-size: 13px; min-width: 0; } +.gs-att > div { flex: 1; min-width: 0; } +.gs-att small { display: block; color: var(--gz-ink-faint); font-size: 12px; } +.gs-att-x { width: 28px; height: 28px; flex: none; border-radius: 8px; color: var(--gz-ink-muted); font-size: 18px; line-height: 1; } +.gs-att-x:hover { background: color-mix(in srgb, var(--gz-ink-faint) 16%, transparent); } +.gs-attach { justify-self: start; color: var(--gz-accent-hi); font-weight: 600; } +:root:not([data-dark]) .gs-attach { color: var(--gz-accent); } +.gs-sheet .gs-doc:is([data-kind="xlsx"], [data-kind="xlsm"], [data-kind="csv"], [data-kind="tsv"]) { background: #16a34a; } +.gs-sheet .gs-doc:is([data-kind="pptx"], [data-kind="ppsx"]) { background: #ea580c; } +/* The compact box's sheet: the files alone, no preview. */ +.gs-sheet.gs-only { width: min(520px, 100%); grid-template-columns: minmax(0, 1fr); } diff --git a/writer-submit.js b/writer-submit.js index 1459ef916..83ce76991 100644 --- a/writer-submit.js +++ b/writer-submit.js @@ -11,6 +11,12 @@ // (the PDF as saved, the deck as brought), named Lastname_Title with its own extension. // Text already in Ultra's text box (the page says, at open and after a failure) is shown with Keep, off: off, the note and // text replace it, or it's emptied; on, it goes as shown, the note and text after it. +// Attach from Documents: the student's documents of any kind (writer-paper.js pick), each exported by its kind's file() +// as it's picked (progress in its row), named Lastname_Title as the attempt's own file is, listed with its size and +// removable; up to 10, each at most Blackboard's 50 MB; the page hands each in after the attempt's own file, the note +// naming them and the 'sending' version keeping them (attached). add({ name, mime, bytes }) lists a file from elsewhere. +// The compact box's ⋯ → Attach from Documents opens the sheet with the files alone: Ultra's text box keeps the paper as +// the compact box wrote it, the note going after it. // frame → page: submit:arm (this paper owns the attempt), submit:held, submit:go { run, name, bytes, note, text, held, keep, // until, asis, mime, kind, version }, submit:saved { ok }, submit:review; page → frame: submit:open, submit:held { text }, // submit:save (Ultra's Save and Close), submit:step { run, step }, submit:done { run, ok, error }. A test's box is @@ -37,7 +43,28 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { review: 'Ultra may already have the file. Use Blackboard editor to review or remove it before submitting.', cancel: 'Submission stopped. Use Blackboard editor to review or remove any upload before submitting.', claimed: 'This attempt is already being submitted or needs review. Use Blackboard editor to continue.', + large: 'A file is larger than 50 MB. Nothing was submitted.', + file: 'A file could not be read. Nothing was submitted.', }; + // A document of Documents as its file, { name, mime, bytes }: a paper as its .docx (as papers.js exports it), another + // kind by its own file() (writer-kinds.js loads it), report hearing its progress (lib/doc-kinds.js progress). + const DOCX = ['lib/writer-color.js', 'lib/zip.js', 'lib/doctext.js', 'lib/writer-docx.js', 'lib/writer-objects-docx.js']; + let store = null; + const exportDoc = async (row, report) => { + const kind = row.kind || 'paper'; + if (kind === 'paper') { + report({ stage: 'prepare' }); + await P().need(DOCX); + const p = await (store ||= GZ_WRITER_PAPERS.open()).get(row.key); + if (!p?.doc) return null; + const f = GZ_WRITER_STYLES.get(p.format), surname = GZ_WRITER_FORMAT.surname(p.doc, f); + const out = GZ_WRITER_DOCX.exportDocx(p.doc, { format: f, surname, title: p.title }); + return { name: L().fileName({ surname, title: p.title, fallback: t('Paper') }), mime: out.type, bytes: out.bytes }; + } + const k = await globalThis.GZ_WRITER_KINDS?.kind(kind); + return typeof k?.file === 'function' ? k.file(row.key, { progress: report }) : null; + }; + const bufferOf = (b) => (b instanceof ArrayBuffer ? b : b.byteOffset === 0 && b.byteLength === b.buffer.byteLength ? b.buffer : b.slice().buffer); const h = (tag, props = {}, ...kids) => { const e = document.createElement(tag); for (const [k, v] of Object.entries(props)) { @@ -90,10 +117,11 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { for (const [el, was] of s.inert) el.inert = was; if (s.back?.isConnected) s.back.focus(); }; - // The compact box sends text through Ultra's own Submit: the sheet there is a brought file's only (writer-import.js). - const open = async (api = P()?.current(), file = null) => { - if (!api || api.importing?.() || frame.mode() !== 'paper' || frame.kind() !== 'assignment' || (!file && api.compact?.())) return; - if (sheet) { sheet.first.focus(); return; } + // The compact box sends text through Ultra's own Submit: the sheet there is a brought file's (writer-import.js), or the + // documents attached from Documents alone (opts.attach: the first one picked). + const open = async (api = P()?.current(), file = null, opts = {}) => { + if (!api || api.importing?.() || frame.mode() !== 'paper' || frame.kind() !== 'assignment' || (!file && api.compact?.() && !opts.attach)) return; + if (sheet) { if (opts.attach) sheet.addDoc?.(opts.attach); sheet.first.focus(); return; } const opening = ++openings; await P().need(['lib/writer-submit.js']); // The sheet names the assignment (its heading, the file's name, the .docx's title): it waits for the page to say @@ -103,7 +131,7 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { if (file) { const error = L().fileError(file); if (error) { api.notify?.(error); return; } } globalThis.GZ_WRITER_IMPORT?.close(); // other: a kind's viewer (writer-kinds.js), its file the one built. - const kindId = api.kind?.() || 'paper', other = !file && kindId !== 'paper'; + const kindId = api.kind?.() || 'paper', other = !file && kindId !== 'paper', only = !file && !!api.compact?.(); const ctx = api.context() || {}, paper = api.paper() || {}, view = api.editor?.view || null; const docNow = () => view?.state.doc ?? null, formatNow = () => JSON.stringify(api.format?.() ?? null); const snapshot = { doc: docNow(), format: formatNow(), key: api.key(), title: ctx.assignment?.title || paper.title || '' }; @@ -114,9 +142,9 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { const surname = (view && F?.surname(view.state.doc, api.format())) || ctx.user?.family || ''; // Whom the note is for: Ultra's name for them, else the one the student typed in the paper's heading. const instructor = String(ctx.instructor || (view && F?.read(view.state.doc, api.format())?.lines.instructor) || '').trim().slice(0, 80); - const name = file ? file.name : L().fileName({ surname, title, fallback: t('Paper'), ext: other ? api.ext?.() : '.docx' }); + const name = only ? null : file ? file.name : L().fileName({ surname, title, fallback: t('Paper'), ext: other ? api.ext?.() : '.docx' }); const pages = api.pages() || (other ? 0 : 1), issues = file || other ? 0 : globalThis.GZ_WRITER_ISSUES?.list?.().length || 0; - const kind = L().tag(name).toLowerCase(); + const kind = only ? '' : L().tag(name).toLowerCase(); const note = h('textarea', { id: 'gs-note', maxlength: '4000', rows: '4' }); const prefs = globalThis.GZ_WRITER_SETTINGS?.now()?.submit, remember = prefs?.rememberText !== false; @@ -130,16 +158,19 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { const cancel = h('button', { type: 'button', class: 'gs-btn', text: t('Cancel') }); const submit = h('button', { type: 'button', class: 'gs-btn gs-primary', text: t('Submit') }); const review = issues ? h('button', { type: 'button', class: 'gs-review', text: t('Review') }) : null; + const atts = h('ul', { class: 'gs-atts' }); + const attachB = typeof P()?.pick === 'function' ? h('button', { type: 'button', class: 'gs-attach', text: t('Attach from Documents') }) : null; const layer = h('div', { class: 'gs-layer' }, h('div', { class: 'gs-dim' }), - h('div', { class: `gs-sheet${file || other ? ' gs-asis' : ''}`, role: 'dialog', tabindex: '-1', 'aria-modal': 'true', 'aria-labelledby': 'gs-title' }, - h('div', { class: 'gs-prev' }, + h('div', { class: `gs-sheet${file || other || only ? ' gs-asis' : ''}${only ? ' gs-only' : ''}`, role: 'dialog', tabindex: '-1', 'aria-modal': 'true', 'aria-labelledby': 'gs-title' }, + only ? null : h('div', { class: 'gs-prev' }, file || other ? h('div', { class: 'gs-big', 'aria-hidden': 'true' }, clip(), h('span', { class: 'gs-doc', 'data-kind': kind, text: L().tag(name) })) : h('div', { class: 'gs-thumbs', 'aria-hidden': 'true' }, previews()), h('div', { class: 'gs-file' }, h('span', { class: 'gs-doc', 'data-kind': kind, 'aria-hidden': 'true', text: L().tag(name) }), h('div', {}, h('span', { class: 'gs-name', text: name }), size))), h('div', { class: 'gs-form' }, h('h2', { id: 'gs-title', text: title ? t('Submit {title}', { title }) : t('Submit') }), h('div', { class: 'gs-field' }, h('label', { for: 'gs-note', text: instructor ? t('Note to {name}', { name: instructor }) : t('Note to your instructor') }), note), - file || other ? null : h('label', { class: 'gs-toggle', for: 'gs-paste' }, h('span', { text: t('Also paste the text in the text box') }), sw), + file || other || only ? null : h('label', { class: 'gs-toggle', for: 'gs-paste' }, h('span', { text: t('Also paste the text in the text box') }), sw), + atts, attachB, review ? h('div', { class: 'gs-open' }, h('span', { class: 'gs-dot' }), h('span', { class: 'gs-grow', text: t('{n} checks open', { n: issues }) }), review) : null, err, native, h('div', { class: 'gs-foot' }, cancel, submit)))); @@ -159,7 +190,7 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { return api.file().then((out) => { built = { doc, format, key, out }; return out; }); }; if (file) globalThis.GZ_WRITER_IMPORT?.about(file).then((s) => { if (sheet === mine) size.textContent = s; }, () => {}); - else build().then((out) => { if (sheet === mine) size.textContent = [pages ? t('{n} pages', { n: pages }) : '', t('{n} KB', { n: nf(L().kb(out.bytes.length)) })].filter(Boolean).join(' · '); }, () => {}); + else if (!only) build().then((out) => { if (sheet === mine) size.textContent = [pages ? t('{n} pages', { n: pages }) : '', t('{n} KB', { n: nf(L().kb(out.bytes.length)) })].filter(Boolean).join(' · '); }, () => {}); sw.addEventListener('click', () => { if (!mine.busy) sw.setAttribute('aria-checked', String(sw.getAttribute('aria-checked') !== 'true')); }); keep.addEventListener('click', () => { if (!mine.busy) keep.setAttribute('aria-checked', String(keep.getAttribute('aria-checked') !== 'true')); }); @@ -167,6 +198,7 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { let held = ''; mine.held = (text) => { if (mine.busy || typeof text !== 'string' || text === held) return; + if (only) { held = text; return; } held = text; shown.value = text; keep.setAttribute('aria-checked', 'false'); @@ -175,6 +207,86 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { }; const ask = () => frame.say({ type: 'submit:held' }); ask(); + + // ---- the attached files: each { name, mime, bytes, size } once ready; making ones hold Submit + const list = []; + let making = 0; + const ready = () => { submit.disabled = mine.busy || making > 0 || (only && !list.some((a) => a.bytes)); if (attachB) attachB.disabled = mine.busy || list.length >= L().MAX_ATTACH; }; + const row = (label, tagText, kindTag) => { + const nameEl = h('span', { class: 'gs-name', text: label }), sizeEl = h('small', {}); + const x = h('button', { type: 'button', class: 'gs-att-x', 'aria-label': t('Remove {name}', { name: label }), title: t('Remove') }, '×'); + const el = h('li', { class: 'gs-att' }, h('span', { class: 'gs-doc', 'data-kind': kindTag, 'aria-hidden': 'true', text: tagText }), h('div', {}, nameEl, sizeEl), x); + atts.append(el); + return { el, nameEl, sizeEl, x }; + }; + const drop = (a, focus) => { + const i = list.indexOf(a); + if (i < 0 || mine.busy) return; + list.splice(i, 1); a.gone = true; a.ui.el.remove(); + if (focus) (list[Math.min(i, list.length - 1)]?.ui.x || attachB || note).focus(); + ready(); + }; + const take = (a, f) => { + a.name = L().attachName({ surname, title: a.title, fallback: t('Document'), name: f.name, taken: [name, ...list.filter((b) => b !== a && b.name).map((b) => b.name)].filter(Boolean) }); + a.mime = typeof f.mime === 'string' && f.mime ? f.mime : L().mimeOf(a.name) || 'application/octet-stream'; + a.bytes = f.bytes; a.size = f.bytes.byteLength; + a.ui.nameEl.textContent = a.name; + a.ui.el.querySelector('.gs-doc').textContent = L().tag(a.name); + a.ui.el.querySelector('.gs-doc').dataset.kind = L().tag(a.name).toLowerCase(); + a.ui.sizeEl.textContent = t('{n} KB', { n: nf(L().kb(a.size)) }); + a.ui.x.setAttribute('aria-label', t('Remove {name}', { name: a.name })); + }; + const refuse = (a, why) => { drop(a, false); err.textContent = why; }; + const room = () => { if (list.length < L().MAX_ATTACH) return true; err.textContent = t('Attach up to {n} documents.', { n: L().MAX_ATTACH }); return false; }; + // A document picked from Documents, exported here. + mine.addDoc = async (doc) => { + if (mine.busy || sheet !== mine || !doc?.key || !room()) return; + err.textContent = ''; + const title = doc.title || t('Untitled document'), a = { title }; + const tag = L().tag(`x.${{ paper: 'docx', sheet: 'xlsx', pdf: 'pdf', slides: 'pptx' }[doc.kind || 'paper'] || 'file'}`); + a.ui = row(title, tag, tag.toLowerCase()); + a.ui.x.addEventListener('click', () => drop(a, true)); + list.push(a); + making++; ready(); + try { + await P().need(['lib/doc-kinds.js']); + const id = doc.kind || 'paper'; + const report = GZ_DOC_KINDS.progress((p) => { if (p.end || p.failed || a.gone) return; const w = GZ_DOC_KINDS.said(p, id); if (w) a.ui.sizeEl.textContent = t(...w); }, id === 'pdf' || id === 'slides' ? ['copy'] : ['prepare']); + let f; + try { f = await exportDoc(doc, report); } catch (e) { console.error(e); f = null; } + report.end(); + if (a.gone || sheet !== mine) return; + if (!f || !(f.bytes?.byteLength > 0)) { refuse(a, t('{name} could not be made.', { name: title })); return; } + if (f.bytes.byteLength > L().MAX_FILE) { refuse(a, t('{name} is larger than 50 MB.', { name: title })); return; } + take(a, f); + } finally { making--; if (sheet === mine) ready(); } + }; + // A file from elsewhere (a Drive file), { name, mime, bytes }, listed as it is. + mine.add = (f) => { + if (mine.busy || sheet !== mine || !room()) return false; + const why = L().handInError({ ...f, bytes: f?.bytes }); + if (why) { err.textContent = why === 'large' ? t('{name} is larger than 50 MB.', { name: String(f?.name || '') }) : t(SAYS.file); return false; } + const a = { title: String(f.name).replace(/\.[^.]+$/, '') }; + a.ui = row(f.name, L().tag(f.name), L().tag(f.name).toLowerCase()); + a.ui.x.addEventListener('click', () => drop(a, true)); + list.push(a); + take(a, { ...f, bytes: f.bytes instanceof ArrayBuffer ? new Uint8Array(f.bytes) : f.bytes }); + ready(); + return true; + }; + attachB?.addEventListener('click', async () => { + if (mine.busy || !room()) return; + const doc = await P().pick({ back: attachB }); + if (doc && sheet === mine) mine.addDoc(doc); + }); + // What goes with the run: the files, the note naming them, and what the version keeps. + const extras = () => { + const ok = list.filter((a) => a.bytes), names = ok.map((a) => a.name); + return { files: ok.map((a) => ({ name: a.name, mime: a.mime, bytes: bufferOf(a.bytes) })), note: L().noteWith(note.value, names, t('Attached: {names}', { names: names.join(', ') })), + attached: ok.map((a) => ({ name: a.name, size: a.size, mime: a.mime })) }; + }; + ready(); + if (opts.attach) mine.addDoc(opts.attach); const cancelIt = () => { mine.cancelled = true; if (mine.busy) { @@ -211,25 +323,29 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { }); const lock = (on) => { mine.busy = on; - for (const x of [note, sw, keep, submit, review]) if (x) x.disabled = on; + for (const x of [note, sw, keep, submit, review, ...atts.querySelectorAll('button')]) if (x) x.disabled = on; + if (!on) ready(); else if (attachB) attachB.disabled = true; layer.querySelector('.gs-sheet').setAttribute('aria-busy', String(on)); submit.textContent = on ? t('Submitting…') : t('Submit'); if (on) cancel.focus(); }; // The page's steps, bounded: its answer, or 'page' after 85 seconds. + // Each attached file has a minute more. const send = (run, m) => new Promise((resolve) => { - const timer = setTimeout(() => resolve({ ok: false, error: 'page' }), 85000); + const more = 60000 * (m.files?.length || 0), timer = setTimeout(() => resolve({ ok: false, error: 'page' }), 85000 + more); waiting = { run, step: (st) => { if (st === 'attach') submit.textContent = t('Attaching…'); else submit.textContent = t('Submitting…'); }, done: (x) => { clearTimeout(timer); resolve(x); } }; - frame.say({ type: 'submit:go', run, ...m, until: Date.now() + 80000 }); + frame.say({ type: 'submit:go', run, ...m, until: Date.now() + 80000 + more }); }).finally(() => { if (waiting?.run === run) waiting = null; }); submit.addEventListener('click', async (e) => { - if (!e.isTrusted || mine.busy || mine.cancelled) return; + if (!e.isTrusted || mine.busy || mine.cancelled || making > 0) return; + const x = extras(); + if (only && !x.files.length) return; lock(true); err.textContent = ''; native.hidden = true; const run = mine.run = ++runs, paste = !file && !other && sw.getAttribute('aria-checked') === 'true', ultra = { held, keep: !!held && keep.getAttribute('aria-checked') === 'true' }; const text = paste ? api.editor.text() : null; if (remember && !file && !other) globalThis.GZ_WRITER_SETTINGS?.patch((w) => ({ ...w, submit: { ...w.submit, text: paste } })).catch((e) => console.error(e)); - let s = L().start({ text: !!L().delta({ note: note.value, text: text || '' }), asis: !!file }); + let s = L().start({ text: !!L().delta({ note: x.note, text: text || '' }), asis: !!file }); try { if (file) { // As is: the student's own file, byte for byte; the paper isn't touched. @@ -237,7 +353,7 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { let bytes; try { bytes = await file.arrayBuffer(); } catch (e) { console.error(e); throw 'page'; } if (mine.cancelled || sheet !== mine || P()?.current() !== api || api.key() !== snapshot.key || !L().fileBytes(file, bytes)) throw 'page'; - const r = await send(run, { key: snapshot.key, name, bytes, note: note.value, text: null, ...ultra, asis: true, mime: file.type || '' }); + const r = await send(run, { key: snapshot.key, name, bytes, note: x.note, text: null, ...ultra, asis: true, mime: file.type || '', files: x.files }); if (mine.cancelled || sheet !== mine) return; if (!r.ok) throw r.error || 'page'; mine.back = null; @@ -246,17 +362,26 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { return; } if (mine.cancelled || sheet !== mine || !same()) throw 'page'; - const sending = await api.version('sending'); + const sending = await api.version('sending', { attached: x.attached.length ? x.attached : null }); if (!sending) throw 'save'; if (mine.cancelled || sheet !== mine || !same()) throw 'page'; s = L().advance(s, { step: 'save' }); + if (only) { + // The files alone: Ultra's text box as the compact box wrote it, the note after it. + s = L().advance(s, { step: 'build' }); + const r = await send(run, { key: snapshot.key, note: x.note, text: null, ...ultra, keep: false, append: true, files: x.files, kind: 'paper', version: sending.version?.id ?? null }); + if (mine.cancelled || sheet !== mine) return; + if (!r.ok) throw r.error || 'page'; + mine.back = null; lock(false); close(); + return; + } let out; try { out = !other && built && built.doc === snapshot.doc && built.format === snapshot.format && built.key === snapshot.key ? built.out : await build(); } catch (e) { console.error(e); throw other ? 'made' : 'build'; } s = L().advance(s, { step: 'build' }); if (mine.cancelled || !same() || !L().may(s, 'attach') || sheet !== mine) throw 'page'; // A kind's file names its own extension (a spreadsheet with macros: .xlsm): the one sent says it too. const ext = /(\.[^./\\]+)$/.exec(String(out.name || ''))?.[1].toLowerCase(), sent = other && ext && L().kindOf(`x${ext}`) === kindId && L().kindOf(name) === kindId ? name.replace(/\.[^.]+$/, ext) : name; - const r = await send(run, { key: snapshot.key, name: sent, bytes: out.bytes.slice().buffer, note: note.value, text, ...ultra, kind: kindId, mime: out.mime || L().mimeOf(name), version: sending.version?.id ?? null }); + const r = await send(run, { key: snapshot.key, name: sent, bytes: out.bytes.slice().buffer, note: x.note, text, files: x.files, ...ultra, kind: kindId, mime: out.mime || L().mimeOf(name), version: sending.version?.id ?? null }); if (mine.cancelled || sheet !== mine) return; if (!r.ok) throw r.error || 'page'; mine.back = null; @@ -302,5 +427,6 @@ var GZ_WRITER_SUBMIT_SHEET = globalThis.GZ_WRITER_SUBMIT_SHEET || (() => { P().submit = (api) => open(api); frame.say({ type: 'submit:arm' }); }); - return { open, close, opened: () => !!sheet, busy: () => !!sheet?.busy }; + // add: a file from elsewhere ({ name, mime, bytes }) onto the open sheet's list (false when it isn't taken). + return { open, close, add: (f) => !!sheet?.add?.(f), opened: () => !!sheet, busy: () => !!sheet?.busy }; })(); From ed59683f2851e72c6f13d96ed4436c658ba85399 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:05:14 -0400 Subject: [PATCH 107/907] PDF pages panel: move, turn, delete, blank pages and another PDF's pages, applied by one Done that Undo takes back; off for signed files --- i18n/de.js | 11 +- i18n/es.js | 11 +- i18n/fr.js | 11 +- i18n/nl.js | 11 +- i18n/pt-BR.js | 11 +- lib/pdf-doc.js | 35 +++-- lib/pdf-pages.js | 230 +++++++++++++++++++++++++++++++ lib/pdf-tools.js | 10 +- lib/writer-papers.js | 5 +- pdf-kind.css | 16 +++ pdf-kind.js | 75 ++++++++-- scripts/pdf-check.js | 67 +++++++++ test/i18n.test.js | 4 +- test/pdf-pages.test.js | 94 +++++++++++++ test/writer-papers-files.test.js | 16 +++ 15 files changed, 571 insertions(+), 36 deletions(-) create mode 100644 lib/pdf-pages.js create mode 100644 test/pdf-pages.test.js diff --git a/i18n/de.js b/i18n/de.js index 179b033f8..7e4cd47e0 100644 --- a/i18n/de.js +++ b/i18n/de.js @@ -3756,5 +3756,14 @@ "Ellipse": "Ellipse", "Arrow": "Pfeil", "The owner of this PDF doesn't allow adding to it.": "Der Eigentümer dieses PDFs erlaubt keine Ergänzungen.", - "Draw": "Zeichnen" + "Draw": "Zeichnen", + "Rotate left": "Nach links drehen", + "Rotate right": "Nach rechts drehen", + "Blank page": "Leere Seite", + "Add PDF…": "PDF hinzufügen…", + "Page {n}": "Seite {n}", + "A PDF can have at most 500 pages.": "Ein PDF kann höchstens 500 Seiten haben.", + "This PDF is digitally signed: changing its pages would break the signature.": "Dieses PDF ist digital signiert: Eine Änderung der Seiten würde die Signatur ungültig machen.", + "The owner of this PDF does not allow changing its pages.": "Der Eigentümer dieses PDFs erlaubt keine Änderung der Seiten.", + "Pages cannot be added to a password-protected PDF, or taken from one.": "Einem passwortgeschützten PDF können keine Seiten hinzugefügt oder entnommen werden." }; diff --git a/i18n/es.js b/i18n/es.js index e164010f6..7f1f29e22 100644 --- a/i18n/es.js +++ b/i18n/es.js @@ -3756,5 +3756,14 @@ "Ellipse": "Elipse", "Arrow": "Flecha", "The owner of this PDF doesn't allow adding to it.": "El propietario de este PDF no permite añadirle nada.", - "Draw": "Dibujar" + "Draw": "Dibujar", + "Rotate left": "Girar a la izquierda", + "Rotate right": "Girar a la derecha", + "Blank page": "Página en blanco", + "Add PDF…": "Añadir PDF…", + "Page {n}": "Página {n}", + "A PDF can have at most 500 pages.": "Un PDF puede tener como máximo 500 páginas.", + "This PDF is digitally signed: changing its pages would break the signature.": "Este PDF tiene una firma digital: cambiar sus páginas invalidaría la firma.", + "The owner of this PDF does not allow changing its pages.": "El propietario de este PDF no permite cambiar sus páginas.", + "Pages cannot be added to a password-protected PDF, or taken from one.": "No se pueden añadir páginas a un PDF protegido con contraseña ni sacarlas de él." }; diff --git a/i18n/fr.js b/i18n/fr.js index 31599c7b4..8fde0938a 100644 --- a/i18n/fr.js +++ b/i18n/fr.js @@ -3756,5 +3756,14 @@ "Ellipse": "Ellipse", "Arrow": "Flèche", "The owner of this PDF doesn't allow adding to it.": "Le propriétaire de ce PDF n’autorise pas les ajouts.", - "Draw": "Dessiner" + "Draw": "Dessiner", + "Rotate left": "Faire pivoter à gauche", + "Rotate right": "Faire pivoter à droite", + "Blank page": "Page vierge", + "Add PDF…": "Ajouter un PDF…", + "Page {n}": "Page {n}", + "A PDF can have at most 500 pages.": "Un PDF peut contenir au plus 500 pages.", + "This PDF is digitally signed: changing its pages would break the signature.": "Ce PDF est signé numériquement : modifier ses pages invaliderait la signature.", + "The owner of this PDF does not allow changing its pages.": "Le propriétaire de ce PDF n’autorise pas la modification de ses pages.", + "Pages cannot be added to a password-protected PDF, or taken from one.": "Impossible d’ajouter des pages à un PDF protégé par mot de passe, ou d’en retirer." }; diff --git a/i18n/nl.js b/i18n/nl.js index 7d22aa470..3b1d9d62e 100644 --- a/i18n/nl.js +++ b/i18n/nl.js @@ -3756,5 +3756,14 @@ "Ellipse": "Ellips", "Arrow": "Pijl", "The owner of this PDF doesn't allow adding to it.": "De eigenaar van deze pdf staat geen toevoegingen toe.", - "Draw": "Tekenen" + "Draw": "Tekenen", + "Rotate left": "Linksom draaien", + "Rotate right": "Rechtsom draaien", + "Blank page": "Lege pagina", + "Add PDF…": "Pdf toevoegen…", + "Page {n}": "Pagina {n}", + "A PDF can have at most 500 pages.": "Een pdf kan maximaal 500 pagina's hebben.", + "This PDF is digitally signed: changing its pages would break the signature.": "Deze pdf is digitaal ondertekend: de pagina's wijzigen zou de handtekening ongeldig maken.", + "The owner of this PDF does not allow changing its pages.": "De eigenaar van deze pdf staat niet toe dat de pagina's worden gewijzigd.", + "Pages cannot be added to a password-protected PDF, or taken from one.": "Aan een met een wachtwoord beveiligde pdf kunnen geen pagina's worden toegevoegd of eruit worden gehaald." }; diff --git a/i18n/pt-BR.js b/i18n/pt-BR.js index 8c8e67c3b..540162e01 100644 --- a/i18n/pt-BR.js +++ b/i18n/pt-BR.js @@ -3756,5 +3756,14 @@ "Ellipse": "Elipse", "Arrow": "Seta", "The owner of this PDF doesn't allow adding to it.": "O proprietário deste PDF não permite adicionar nada a ele.", - "Draw": "Desenhar" + "Draw": "Desenhar", + "Rotate left": "Girar para a esquerda", + "Rotate right": "Girar para a direita", + "Blank page": "Página em branco", + "Add PDF…": "Adicionar PDF…", + "Page {n}": "Página {n}", + "A PDF can have at most 500 pages.": "Um PDF pode ter no máximo 500 páginas.", + "This PDF is digitally signed: changing its pages would break the signature.": "Este PDF tem assinatura digital: alterar as páginas invalidaria a assinatura.", + "The owner of this PDF does not allow changing its pages.": "O proprietário deste PDF não permite alterar as páginas.", + "Pages cannot be added to a password-protected PDF, or taken from one.": "Não é possível adicionar páginas a um PDF protegido por senha nem retirá-las dele." }; diff --git a/lib/pdf-doc.js b/lib/pdf-doc.js index c37956175..7e997b59b 100644 --- a/lib/pdf-doc.js +++ b/lib/pdf-doc.js @@ -87,6 +87,22 @@ var GZ_PDF_DOC = globalThis.GZ_PDF_DOC || (() => { return pages; }; + // The pages as layout leaves them → pdf.js's extractPages arguments, one entry per source and use: { source, + // includePages (its pages, ascending), pageIndices (each one's place in the result), rotations (each one's turn, by + // page) }. extractPages takes a source page once per entry, so a page used twice goes in a second entry of its source. + const entries = (pages) => { + const list = []; + pages.forEach((p, slot) => { + let entry = list.find((e) => e.source === p.source && !e.pages.has(p.page)); + if (!entry) list.push(entry = { source: p.source, pages: new Map() }); + entry.pages.set(p.page, { slot, turn: p.turn }); + }); + return list.map(({ source, pages: kept }) => { + const includePages = [...kept.keys()].sort((a, b) => a - b); + return { source, includePages, pageIndices: includePages.map((n) => kept.get(n).slot), rotations: Object.fromEntries(includePages.map((n) => [n, kept.get(n).turn])) }; + }); + }; + // A PDF of empty pages, one per [width, height] in points. const blankPdf = (sizes) => { const objects = ['<< /Type /Catalog /Pages 2 0 R >>', `<< /Type /Pages /Kids [${sizes.map((_, i) => `${i + 3} 0 R`).join(' ')}] /Count ${sizes.length} >>`, @@ -245,21 +261,10 @@ var GZ_PDF_DOC = globalThis.GZ_PDF_DOC || (() => { pages[i] = { ...p, page: sizes.length }; sizes.push(size || [612, 792]); } - // extractPages takes each source page once per entry, so a page used twice goes in a second entry of its source. - const entries = []; - pages.forEach((p, slot) => { - let entry = entries.find((e) => e.source === p.source && !e.pages.has(p.page)); - if (!entry) entries.push(entry = { source: p.source, pages: new Map() }); - entry.pages.set(p.page, { slot, turn: p.turn }); - }); const blank = sizes.length ? blankPdf(sizes) : null; - const out = await doc.extractPages(entries.map(({ source, pages: kept }) => { - const list = [...kept.keys()].sort((a, b) => a - b); - return { - document: source === 0 ? null : (source === 'blank' ? blank : others[source - 1].bytes).slice(), - includePages: list, pageIndices: list.map((n) => kept.get(n).slot), rotations: Object.fromEntries(list.map((n) => [n, kept.get(n).turn])), - }; - }), null, Math.min(L.bytes, L.merged)).catch((e) => { + const out = await doc.extractPages(entries(pages).map(({ source, ...pick }) => ({ + document: source === 0 ? null : (source === 'blank' ? blank : others[source - 1].bytes).slice(), ...pick, + })), null, Math.min(L.bytes, L.merged)).catch((e) => { if (e.message === 'PDF output exceeds byte limit') throw fail('PdfTooLargeError', `The PDF would be larger than ${mb(Math.min(L.bytes, L.merged))}.`); throw fail('PdfWriteError', 'Glaze could not write these pages.', { cause: e }); }); @@ -334,7 +339,7 @@ var GZ_PDF_DOC = globalThis.GZ_PDF_DOC || (() => { return { open, save, bake, thumbnail, text, hash }; }; - return { LIMITS, layout, use }; + return { LIMITS, layout, entries, use }; })(); if (typeof module !== 'undefined') module.exports = GZ_PDF_DOC; else GZ_PDF_DOC; diff --git a/lib/pdf-pages.js b/lib/pdf-pages.js new file mode 100644 index 000000000..60c889fda --- /dev/null +++ b/lib/pdf-pages.js @@ -0,0 +1,230 @@ +// The PDF viewer's pages panel (pdf-kind.js): the pages as tiles to move (drag, or Move up and Move down), turn, delete, +// a blank page and another PDF's pages to add; Done applies them all at once (lib/pdf-doc.js bake: pdf.js's +// extractPages with the rotations patch) as one step a single Undo takes back. The reducer is pure: what the student +// does (reduce) → the state's tiles → one list of bake's page operations (toOps). +// Classic script; module.exports for node (start, reduce, toOps, changed). +var GZ_PDF_PAGES = globalThis.GZ_PDF_PAGES || (() => { + const PAGES = 500, BYTES = 50e6; // lib/pdf-doc.js LIMITS.pages and .merged + const fail = (name, message) => Object.assign(new Error(message), { name }); + + // ---- the reducer. A tile: { id, source (0 the document, k the k-th merged file, 'blank'), page, turn (0 to 270, on top + // of the page's own) }. merges[k - 1]: { pages, bytes, name }. bytes: the document's size, for the merge limit. + const start = (count, { bytes = 0 } = {}) => ({ count, bytes, tiles: Array.from({ length: count }, (_, page) => ({ id: `0:${page}`, source: 0, page, turn: 0 })), merges: [], next: 0 }); + const inRange = (s, list) => Array.isArray(list) && list.length > 0 && list.every((i) => Number.isInteger(i) && i >= 0 && i < s.tiles.length); + const reduce = (s, a) => { + switch (a?.type) { + case 'move': { + const { from, to } = a; + if (!inRange(s, [from, to]) || from === to) return s; + const tiles = [...s.tiles], [t] = tiles.splice(from, 1); + tiles.splice(to, 0, t); + return { ...s, tiles }; + } + case 'rotate': { + if (!inRange(s, a.at) || !Number.isInteger(a.by) || a.by % 90 || !(a.by % 360)) return s; + const turned = new Set(a.at); + return { ...s, tiles: s.tiles.map((t, i) => (turned.has(i) ? { ...t, turn: (((t.turn + a.by) % 360) + 360) % 360 } : t)) }; + } + case 'remove': { + if (!inRange(s, a.at)) return s; + const gone = new Set(a.at), tiles = s.tiles.filter((_, i) => !gone.has(i)); + return tiles.length ? { ...s, tiles } : s; + } + case 'blank': { + const at = a.at ?? s.tiles.length; + if (!Number.isInteger(at) || at < 0 || at > s.tiles.length) return s; + if (s.tiles.length + 1 > PAGES) throw fail('PdfTooManyPagesError', `A PDF can have at most ${PAGES} pages.`); + const tiles = [...s.tiles]; + tiles.splice(at, 0, { id: `b${s.next}`, source: 'blank', page: 0, turn: 0 }); + return { ...s, tiles, next: s.next + 1 }; + } + case 'merge': { + const at = a.at ?? s.tiles.length; + if (!Number.isInteger(at) || at < 0 || at > s.tiles.length || !Number.isInteger(a.pages) || a.pages < 1) return s; + if (s.tiles.length + a.pages > PAGES) throw fail('PdfTooManyPagesError', `A PDF can have at most ${PAGES} pages.`); + if (s.merges.reduce((n, m) => n + m.bytes.length, s.bytes) + a.bytes.length > BYTES) throw fail('PdfTooLargeError', 'The PDF inputs exceed 50 MB.'); + const k = s.merges.length + 1, tiles = [...s.tiles]; + tiles.splice(at, 0, ...Array.from({ length: a.pages }, (_, page) => ({ id: `${k}:${page}`, source: k, page, turn: 0 }))); + return { ...s, tiles, merges: [...s.merges, { pages: a.pages, bytes: a.bytes, name: a.name || '' }] }; + } + default: return s; + } + }; + // The operations that lay the pages out as the tiles show (lib/pdf-doc.js layout): every merged file and blank page + // added at the end, the pages no tile keeps deleted, the rest put in the tiles' order, then each turn. + const toOps = (s) => { + const ops = [], offset = [0]; + let len = s.count; + for (const m of s.merges) { ops.push({ op: 'merge', bytes: m.bytes, at: len }); offset.push(len); len += m.pages; } + const blanks = new Map(); + for (const t of s.tiles) if (t.source === 'blank') { ops.push({ op: 'blank', at: len }); blanks.set(t.id, len++); } + const kept = s.tiles.map((t) => (t.source === 'blank' ? blanks.get(t.id) : offset[t.source] + t.page)); + const keep = new Set(kept), deleted = Array.from({ length: len }, (_, i) => i).filter((i) => !keep.has(i)); + if (deleted.length) ops.push({ op: 'delete', pages: deleted }); + const rank = new Map([...kept].sort((a, b) => a - b).map((i, r) => [i, r])), order = kept.map((i) => rank.get(i)); + if (order.some((r, i) => r !== i)) ops.push({ op: 'reorder', order }); + for (const by of [90, 180, 270]) { + const pages = s.tiles.flatMap((t, i) => (t.turn === by ? [i] : [])); + if (pages.length) ops.push({ op: 'rotate', pages, by }); + } + return ops; + }; + const changed = (s) => toOps(s).length > 0; + + // ---- the panel, over the viewer's pages. o: { P (pdf.mjs), doc, t, h, btn, mount, bytes (the file's size), allows + // ({ add, why }: whether other PDFs may come in, and why not), apply(state) → a promise (Done), close() }. + const SVG = 'http://www.w3.org/2000/svg'; + const ICONS = { left: 'M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5', right: 'M21 12a9 9 0 1 1-3-6.7L21 8M21 3v5h-5', up: 'm6 15 6-6 6 6', down: 'm6 9 6 6 6-6', trash: 'M4 7h16M10 11v6M14 11v6M6 7l1 13h10l1-13M9 7V4h6v3', blank: 'M6 3h9l4 4v14H6zM14 3v5h5', add: 'M12 5v14M5 12h14' }; + const icon = (d) => { const s = document.createElementNS(SVG, 'svg'); s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('class', 'gp-ico'); s.setAttribute('aria-hidden', 'true'); const p = document.createElementNS(SVG, 'path'); p.setAttribute('d', d); s.append(p); return s; }; + const panel = (o) => { + const { P, t, h, btn } = o, life = new AbortController(), signal = life.signal; + let s = start(o.doc.numPages, { bytes: o.bytes }), focus = 0, busy = false; + const sel = new Set(); + const docs = [o.doc]; // the document, then each merged file as pdf.js reads it (for its tiles' pictures) + const said = h('p', { class: 'gq-pages-said', role: 'status' }); + const tool = (act, label, d, run) => btn({ class: 'gq-ib', 'data-act': act, title: t(label), 'aria-label': t(label), onclick: run }, icon(d)); + const picked = () => s.tiles.flatMap((x, i) => (sel.has(x.id) ? [i] : [])), current = () => (picked().length ? picked() : s.tiles.length ? [Math.min(focus, s.tiles.length - 1)] : []); + const pick = h('input', { type: 'file', accept: '.pdf,application/pdf', hidden: true }); + const addB = tool('merge', 'Add PDF…', ICONS.add, () => pick.click()); + if (!o.allows.add) { addB.disabled = true; addB.title = t(o.allows.why || 'Pages cannot be added to a password-protected PDF, or taken from one.'); } + const doneB = btn({ class: 'gp-submit', 'data-act': 'done' }, t('Done')); + const bar = h('div', { class: 'gp-tool gq-pages-bar', role: 'toolbar', 'aria-label': t('Pages') }, + tool('rotate-left', 'Rotate left', ICONS.left, () => act({ type: 'rotate', at: current(), by: -90 })), + tool('rotate-right', 'Rotate right', ICONS.right, () => act({ type: 'rotate', at: current(), by: 90 })), + tool('up', 'Move up', ICONS.up, () => shift(-1)), tool('down', 'Move down', ICONS.down, () => shift(1)), + tool('delete', 'Delete', ICONS.trash, () => { const at = current(); act({ type: 'remove', at }); focus = Math.min(at[0] ?? 0, s.tiles.length - 1); sel.clear(); draw(); }), + tool('blank', 'Blank page', ICONS.blank, () => { act({ type: 'blank', at: (current().at(-1) ?? s.tiles.length - 1) + 1 }); }), + addB, h('span', { class: 'grow' }), said, btn({ class: 'gp-tbtn', 'data-act': 'cancel', onclick: () => close() }, t('Cancel')), doneB); + const grid = h('div', { class: 'gq-pages-grid', role: 'listbox', 'aria-multiselectable': 'true', 'aria-label': t('Pages') }); + const el = h('div', { class: 'gq-pagepanel', role: 'region', 'aria-label': t('Pages') }, bar, grid, pick); + const act = (a) => { + try { s = reduce(s, a); said.textContent = ''; } catch (e) { said.textContent = t(e.name === 'PdfTooManyPagesError' ? 'A PDF can have at most 500 pages.' : e.name === 'PdfTooLargeError' ? 'Choose a PDF smaller than 50 MB.' : e.message); } + draw(); + }; + // The chosen pages one place earlier or later, together. + const shift = (by) => { + const at = current().sort((a, b) => (by < 0 ? a - b : b - a)); + if (!at.length || (by < 0 ? at[0] === 0 : at[0] === s.tiles.length - 1)) return; + for (const i of at) s = reduce(s, { type: 'move', from: i, to: i + by }); + focus = Math.max(0, Math.min(s.tiles.length - 1, focus + by)); + draw(); + }; + // ---- pictures: each tile's page drawn once, small, as it comes into view; a blank page is white. + const drawn = new Map(), queue = []; + let drawing = false; + const paint = async () => { + if (drawing) return; + drawing = true; + try { + while (queue.length && !signal.aborted) { + const { tile, canvas } = queue.shift(); + if (drawn.get(tile.id) === canvas) continue; + const d = docs[tile.source]; + if (!d) continue; + const page = await d.getPage(tile.page + 1), one = page.getViewport({ scale: 1 }), scale = 112 / Math.max(one.width, one.height); + const viewport = page.getViewport({ scale: scale * (devicePixelRatio || 1) }); + canvas.width = Math.ceil(viewport.width); canvas.height = Math.ceil(viewport.height); + canvas.style.width = `${Math.round(one.width * scale)}px`; canvas.style.height = `${Math.round(one.height * scale)}px`; + await page.render({ canvas, viewport, background: '#fff', annotationMode: P.AnnotationMode.ENABLE_STORAGE }).promise.catch(() => {}); + drawn.set(tile.id, canvas); + } + } finally { drawing = false; } + }; + const seen = new IntersectionObserver((list) => { for (const e of list) if (e.isIntersecting) { const x = e.target; seen.unobserve(x); queue.push({ tile: x._tile, canvas: x.querySelector('canvas') }); } paint(); }, { root: grid, rootMargin: '200px' }); + const pictures = new Map(); // tile id → its canvas, kept across redraws + // ---- the tiles + let from = -1; + const draw = () => { + grid.replaceChildren(...s.tiles.map((tile, i) => { + let canvas = pictures.get(tile.id); + if (!canvas) { canvas = h('canvas', { class: tile.source === 'blank' ? 'gq-blank' : null }); pictures.set(tile.id, canvas); } + const fig = h('div', { class: 'gq-tile-pic' }, canvas); + fig.style.setProperty('--turn', `${tile.turn}deg`); + const x = h('div', { class: 'gq-tile', role: 'option', tabindex: i === focus ? '0' : '-1', draggable: 'true', 'data-id': tile.id, 'data-turn': String(tile.turn), 'aria-selected': String(sel.has(tile.id)), + 'aria-label': t('Page {n}', { n: i + 1 }) }, fig, h('span', { class: 'gq-tile-n', text: String(i + 1) })); + x._tile = tile; + if (tile.source !== 'blank' && !drawn.has(tile.id)) seen.observe(x); + return x; + })); + doneB.disabled = busy || !changed(s); + }; + const tileAt = (e) => e.target.closest?.('.gq-tile'); + const indexOf = (x) => [...grid.children].indexOf(x); + grid.addEventListener('click', (e) => { + const x = tileAt(e); if (!x) return; + const i = indexOf(x), id = x.dataset.id; + if (e.shiftKey) { const [a, b] = [Math.min(focus, i), Math.max(focus, i)]; for (let k = a; k <= b; k++) sel.add(s.tiles[k].id); } + else if (e.ctrlKey || e.metaKey) { if (sel.has(id)) sel.delete(id); else sel.add(id); } + else { sel.clear(); sel.add(id); } + focus = i; draw(); grid.children[focus]?.focus(); + }, { signal }); + grid.addEventListener('keydown', (e) => { + const x = tileAt(e); if (!x) return; + const i = indexOf(x), cols = Math.max(1, [...grid.children].filter((c) => c.offsetTop === grid.children[0].offsetTop).length); + const go = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -cols, ArrowDown: cols, Home: -i, End: s.tiles.length - 1 - i }[e.key]; + if (go !== undefined && !e.altKey) { + e.preventDefault(); + focus = Math.max(0, Math.min(s.tiles.length - 1, i + go)); + if (e.shiftKey) sel.add(s.tiles[focus].id); + draw(); grid.children[focus]?.focus(); + } else if (e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'ArrowRight' || e.key === 'ArrowDown')) { + e.preventDefault(); if (!sel.size) sel.add(s.tiles[i].id); shift(e.key === 'ArrowLeft' || e.key === 'ArrowUp' ? -1 : 1); grid.children[focus]?.focus(); + } else if (e.key === ' ' || e.key === 'Enter') { + e.preventDefault(); const id = s.tiles[i].id; if (sel.has(id)) sel.delete(id); else sel.add(id); draw(); grid.children[focus]?.focus(); + } else if (e.key === 'Delete' || e.key === 'Backspace') { + e.preventDefault(); const at = current(); act({ type: 'remove', at }); sel.clear(); focus = Math.min(at[0] ?? 0, s.tiles.length - 1); draw(); grid.children[focus]?.focus(); + } + }, { signal }); + // Drag a tile before or after another. + grid.addEventListener('dragstart', (e) => { const x = tileAt(e); if (!x) return; from = indexOf(x); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', x.dataset.id); }, { signal }); + grid.addEventListener('dragover', (e) => { if (from >= 0 && tileAt(e)) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } }, { signal }); + grid.addEventListener('drop', (e) => { + const x = tileAt(e); if (from < 0 || !x) return; + e.preventDefault(); + const r = x.getBoundingClientRect(), i = indexOf(x), after = e.clientX > r.left + r.width / 2; + let to = i + (after ? 1 : 0); + if (to > from) to--; + const was = from; from = -1; + if (to !== was) { s = reduce(s, { type: 'move', from: was, to }); focus = to; draw(); } + }, { signal }); + grid.addEventListener('dragend', () => { from = -1; }, { signal }); + // Another PDF's pages, after the chosen ones (or at the end). + pick.addEventListener('change', async () => { + const f = pick.files[0]; + pick.value = ''; + if (!f) return; + said.textContent = ''; + try { + if (f.size > BYTES) throw fail('PdfTooLargeError', ''); + const bytes = new Uint8Array(await f.arrayBuffer()); + const task = P.getDocument({ data: bytes.slice(), isEvalSupported: false, enableXfa: false, verbosity: 0, wasmUrl: o.wasmUrl }); + const d = await task.promise; + const next = reduce(s, { type: 'merge', at: (current().at(-1) ?? s.tiles.length - 1) + 1, pages: d.numPages, bytes, name: f.name }); + docs.push(d); s = next; draw(); + } catch (e) { + said.textContent = t(e.name === 'PdfTooManyPagesError' ? 'A PDF can have at most 500 pages.' : e.name === 'PdfTooLargeError' ? 'Choose a PDF smaller than 50 MB.' : 'That file could not be read.'); + } + }, { signal }); + doneB.addEventListener('click', async () => { + if (doneB.disabled) return; + busy = true; doneB.disabled = true; said.textContent = ''; + try { await o.apply(s); close(true); } catch (e) { console.error(e); said.textContent = t(e.say || 'Not saved. Try again in a moment.'); busy = false; draw(); } + }, { signal }); + el.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !busy) { e.preventDefault(); e.stopPropagation(); close(); } }, { signal }); + const close = (applied = false) => { + if (signal.aborted) return; + life.abort(); seen.disconnect(); el.remove(); + for (const d of docs.slice(1)) d.loadingTask.destroy().catch(() => {}); + for (const c of pictures.values()) c.width = c.height = 0; + o.close?.(applied); + }; + o.mount.append(el); + draw(); + grid.children[0]?.focus(); + return { el, close, state: () => s }; + }; + + return { PAGES, BYTES, start, reduce, toOps, changed, panel }; +})(); + +if (typeof module !== 'undefined') module.exports = GZ_PDF_PAGES; else GZ_PDF_PAGES; diff --git a/lib/pdf-tools.js b/lib/pdf-tools.js index e0347bff3..615fac1f2 100644 --- a/lib/pdf-tools.js +++ b/lib/pdf-tools.js @@ -132,11 +132,14 @@ var GZ_PDF_TOOLS = globalThis.GZ_PDF_TOOLS || (() => { if (!f) return; openMenu(sizeB, SIZES[f].map((n) => [String(n), () => { params[f].size = n; showParams(); push(); }, { 'data-size': String(n), role: 'menuitemradio', 'aria-checked': String(params[f].size === n) }])); }); - undoB.addEventListener('click', () => bus.dispatch('editingaction', { source: el, name: 'undo' })); + // One more step back after pdf.js's own: the viewer's (the pages panel's Done, extraUndo). + let extra = null, canUndo = false; + undoB.addEventListener('click', () => { if (canUndo || !extra) bus.dispatch('editingaction', { source: el, name: 'undo' }); else extra.run(); }); redoB.addEventListener('click', () => bus.dispatch('editingaction', { source: el, name: 'redo' })); bus.on('editingstateschanged', ({ details }) => { if (!on) return; - undoB.disabled = !details.hasSomethingToUndo; + canUndo = !!details.hasSomethingToUndo; + undoB.disabled = !canUndo && !extra; redoB.disabled = !details.hasSomethingToRedo; }, { signal }); @@ -163,6 +166,7 @@ var GZ_PDF_TOOLS = globalThis.GZ_PDF_TOOLS || (() => { for (const b of [...Object.values(buttons), colorB, sizeB]) { b.disabled = !on; if (!on) b.title = t(NO_TOOLS); } if (!on) { el.title = t(NO_TOOLS); el.dataset.off = ''; } showParams(); + undoB.disabled = !extra && !canUndo; ready(on); return on; }; @@ -241,7 +245,7 @@ var GZ_PDF_TOOLS = globalThis.GZ_PDF_TOOLS || (() => { pressed(); const dispose = () => { life.abort(); drag?.preview.remove(); }; - return { el, decide, commit, ready: settled, dispose, get tool() { return tool; } }; + return { el, decide, commit, ready: settled, dispose, extraUndo: (x) => { extra = x; undoB.disabled = false; }, get tool() { return tool; } }; }; return { SHAPES, INKS, MARKS, SIZES, HIGHLIGHTS, NO_TOOLS, shape, mount }; diff --git a/lib/writer-papers.js b/lib/writer-papers.js index b03bb7e9d..e5d414083 100644 --- a/lib/writer-papers.js +++ b/lib/writer-papers.js @@ -170,7 +170,8 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // Saves the paper under k, merging patch into what's stored; a record written before kinds is a paper, and its title // the assignment's until the student names it (named). reason: 'auto' (the default: autosave), 'sending' or // 'submit' (the paper itself kept), 'import' or 'restore' (the doc before it is kept), 'format', 'view' (the paper as - // the full writer left it, before the compact box writes it into Blackboard's). → { paper, version }. + // the full writer left it, before the compact box writes it into Blackboard's), 'edit' (a PDF's or deck's file before + // the one this save brings). → { paper, version }. const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null } = {}) => serial(k, async () => { if (typeof k !== 'string' || !k) throw new Error('no paper key'); const written = Object.keys(parts || {}), dropped = [...(drop || [])]; @@ -227,6 +228,8 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { if (whole) { version = await retain(snapshot(paper, reason, t)); if (reason === 'submit') paper.submitted = t; } else if (conflict) version = await retain(snapshot(prev, 'conflict', t)); else if ((reason === 'import' || reason === 'restore' || reason === 'format' || reason === 'view') && (prev?.doc || prev?.file)) version = await retain(snapshot(prev, reason, t)); + // A file's own edit kept (a PDF's pages changed, a viewer's first save): the file before it. + else if (reason === 'edit' && prev?.file && !sameFile(prev.file, paper.file)) version = await retain(snapshot(prev, reason, t)); else if (prev?.doc && patch.doc) { const a = measure(prev.doc), b = measure(patch.doc); const big = Math.abs(a.words - b.words) >= CAP.words || Math.abs(a.chars - b.chars) >= CAP.share * Math.max(a.chars, b.chars, 500); diff --git a/pdf-kind.css b/pdf-kind.css index bd3ebafc3..b7d8d24d5 100644 --- a/pdf-kind.css +++ b/pdf-kind.css @@ -79,3 +79,19 @@ dialog.gq-sign::backdrop { background: rgba(0,0,0,.35); } .gq-sign-said:empty { display: none; } .gq-sign-end { display: flex; justify-content: flex-end; gap: 8px; } dialog.gq-sign button:disabled { opacity: .45; cursor: default; } +/* The pages panel (lib/pdf-pages.js), over the pages while it is open. */ +.gq-pagepanel { position: absolute; inset: 0; z-index: 20; display: flex; flex-direction: column; background: var(--ground); } +.gq .gq-pages-bar { flex: none; border-bottom: 1px solid var(--edge); } +.gq-pages-said { margin: 0 8px; font-size: 13px; color: var(--gz-err); } +.gq-pages-said:empty { display: none; } +.gq-pages-grid { flex: 1; min-height: 0; overflow: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; padding: 16px; align-content: start; } +.gq-tile { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 8px; border-radius: 10px; cursor: grab; outline: none; } +.gq-tile:hover { background: var(--gz-line); } +.gq-tile[aria-selected="true"] { background: color-mix(in srgb, var(--gz-accent) 22%, transparent); } +.gq-tile:focus-visible { outline: 2px solid var(--gz-accent); outline-offset: -2px; } +.gq-tile-pic { width: 120px; height: 120px; display: grid; place-items: center; } +.gq-tile-pic canvas { background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35); transform: rotate(var(--turn, 0deg)); transition: transform .15s; } +:root[data-motion="off"] .gq-tile-pic canvas { transition: none; } +.gq-tile-pic canvas:not([width]), .gq-tile-pic canvas.gq-blank { width: 86px; height: 112px; } +.gq-tile-n { font-size: 12px; color: var(--gz-ink-muted); font-variant-numeric: tabular-nums; } +.gq[data-panel] .gq-tools { display: none; } diff --git a/pdf-kind.js b/pdf-kind.js index 3953dc6f0..c0738e1da 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -13,6 +13,9 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const UNREAD = 'That file could not be read.', TOO_BIG = GZ_DOC_KINDS.KINDS.pdf.big; const PAGES = 500; const TOO_BIG_SAVE = 'This PDF is too big to save.'; + // Why the pages can't change (lib/pdf-doc.js says the same). + const SIGNED = 'This PDF is digitally signed: changing its pages would break the signature.', LOCKED = 'The owner of this PDF does not allow changing its pages.'; + const PROTECTED = 'Pages cannot be added to a password-protected PDF, or taken from one.'; // ---- storage: the one place a PDF's bytes are kept and read back: the worker's file store (lib/doc-files.js, over its // port), the record pointing at them (file: { hash, size, type }), up to the kind's max. A record from before the store @@ -73,14 +76,15 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const GET = { wasmUrl: url('lib/pdfjs/'), isEvalSupported: false, enableXfa: false, verbosity: 0 }; let docLib = null; const pdfdoc = () => (typeof globalThis.GZ_PDF_DOC?.use === 'function' ? (docLib ||= pdfjs().then((P) => GZ_PDF_DOC.use(P, GET))) : null); - // bytes (a copy goes to the worker) → { task, doc }, or a RangeError(UNREAD) for a file that is no PDF, is locked, has - // too many pages, is pure XFA or has no first page to show (pdf.js's viewer would wait for it). + // bytes (a copy goes to the worker) → { task, doc, facts (lib/pdf-doc.js open's: signed, allows…; null without it) }, or a + // RangeError(UNREAD) for a file that is no PDF, is locked, has too many pages, is pure XFA or has no first page to show + // (pdf.js's viewer would wait for it). const read = async (bytes) => { const lib2 = pdfdoc(); - let task, doc; + let task, doc, facts = null; if (lib2) { const pdf = await lib2; - try { ({ doc } = await pdf.open(bytes)); task = doc.loadingTask; } catch { throw new RangeError(UNREAD); } + try { facts = await pdf.open(bytes); doc = facts.doc; task = doc.loadingTask; } catch { throw new RangeError(UNREAD); } } else { const P = await pdfjs(); task = P.getDocument({ ...GET, data: bytes.slice() }); @@ -93,7 +97,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { } await doc.getPage(1); } catch (e) { await task.destroy().catch(() => {}); throw e instanceof RangeError ? e : new RangeError(UNREAD); } - return { task, doc }; + return { task, doc, facts }; }; // Page 1 as a small picture (a data URL), as filled in: what the home's card shows (204 px wide, 408 on a 2× screen). // Try lib/pdf-doc.js's PNG (24 KB at most) once at full size, else a JPEG drawn here: a scan or a photo fits 24 KB of @@ -132,7 +136,7 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { }; const btn = (props, ...kids) => h('button', { type: 'button', ...props }, ...kids); const PATHS = { minus: 'M5 12h14', plus: 'M12 5v14M5 12h14', search: 'M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14zM20 20l-4-4', dots: 'M5 12h.01M12 12h.01M19 12h.01', caret: 'm6 9 6 6 6-6', - up: 'm6 15 6-6 6 6', down: 'm6 9 6 6 6-6', x: 'M6 6l12 12M18 6 6 18' }; + up: 'm6 15 6-6 6 6', down: 'm6 9 6 6 6-6', x: 'M6 6l12 12M18 6 6 18', pages: 'M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4zM13 13h7v7h-7z' }; const icon = (name) => { const s = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('class', 'gp-ico'); s.setAttribute('aria-hidden', 'true'); const p = document.createElementNS('http://www.w3.org/2000/svg', 'path'); p.setAttribute('d', PATHS[name]); s.append(p); return s; }; const nameOf = (rec) => `${String(rec?.title || '').replace(/[\\/:*?"<>|\u0000-\u001f]+/g, ' ').replace(/\s+/g, ' ').trim() || 'Document'}.pdf`; @@ -247,7 +251,9 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { // Every change the tools make saves as a filled field does (modified, below). let modified = () => {}; const tools = src ? null : GZ_PDF_TOOLS.mount({ P, viewer, bus, t, h, btn, openMenu: (...a) => openMenu(...a), modified: () => modified(), scroll, signatures: signs, say: (x) => say(x) }); - if (tools) findB.after(h('span', { class: 'gp-sepv' }), tools.el); + // Pages (lib/pdf-pages.js, loaded when first opened): off where the file can't take page changes, saying why. + const pagesB = src ? null : btn({ class: 'gq-ib', 'data-act': 'pages', title: t('Pages'), 'aria-label': t('Pages'), 'aria-haspopup': 'dialog', disabled: true }, icon('pages')); + if (tools) findB.after(h('span', { class: 'gp-sepv' }), pagesB, tools.el); l10n.translate(scroll); const refuse = (e) => { said.textContent = t(e instanceof FileError ? e.message : UNREAD); said.hidden = false; for (const b of [zoomOut, zoomB, zoomIn, findB, pageIn, saveB]) if (b) b.disabled = true; @@ -262,11 +268,11 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { if (f) into(key, f).then(() => location.reload(), (e) => { console.error(e); say(t(e instanceof RangeError ? e.message : UNREAD)); }); } }); ui.append(pick); - let bytes = null, task = null, doc = null; + let bytes = null, task = null, doc = null, facts = null; // A record from before the store moves into it as it is opened (the worker reads its base64 again). if (!src && typeof rec?.file === 'string') st.migrate?.(key).catch(() => {}); // A preview's bytes are read as a kept PDF's are (a copy: pdf.js takes what it is given; Save keeps the original). - try { bytes = src ? (src.bytes?.length ? src.bytes.slice() : null) : await STORE.get(rec); if (!bytes) throw new RangeError(UNREAD); ({ task, doc } = await read(bytes)); } catch (e) { refuse(e); } + try { bytes = src ? (src.bytes?.length ? src.bytes.slice() : null) : await STORE.get(rec); if (!bytes) throw new RangeError(UNREAD); ({ task, doc, facts } = await read(bytes)); } catch (e) { refuse(e); } const inited = new Promise((ok) => bus.on('pagesinit', ok, { once: true })); let drawTimer = 0, drawFailed = false; // pdf.js also emits pagerendered on a failed canvas draw. Only a successful first page may replace Blackboard; @@ -276,7 +282,10 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { else if (e.pageNumber === 1 && !e.cssTransform && !e.isDetailView) ok(true); }, { signal })); if (doc) { - bus.on('pagesinit', () => { viewer.currentScaleValue = 'page-width'; tools?.decide(doc); }); + bus.on('pagesinit', () => { + viewer.currentScaleValue = 'page-width'; tools?.decide(doc); + if (pagesB) { pagesB.disabled = !facts?.allows?.pages; if (pagesB.disabled && facts) pagesB.title = t(facts.signed ? SIGNED : LOCKED); } + }); viewer.setDocument(doc); links.setDocument(doc, null); } @@ -446,6 +455,52 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { ui.addEventListener('input', (e) => { if (e.target.closest?.('.annotationLayer')) modified(); }, { signal }); ui.addEventListener('change', (e) => { if (e.target.closest?.('.annotationLayer')) { modified(); flush(); } }, { signal }); } + // ---- the pages panel's Done: the file as saved, its page operations baked into new bytes in one pass (lib/pdf-doc.js + // bake), kept with the file before it as a version ('edit'), and the viewer opened again on them. That version is + // the one step Undo takes back (pagesUndo, kept for this tab across the reload). Its stages show as an import's do. + const UNDO = `gq-pages-undo:${key}`; + let pagesUndo = null; + try { const u = !src && JSON.parse(sessionStorage.getItem(UNDO) || 'null'); if (u?.id && u.revision === rec?.revision) pagesUndo = u; else sessionStorage.removeItem(UNDO); } catch { /* no session storage */ } + const PAGE_SAYS = { PdfSignedError: SIGNED, PdfLockedError: LOCKED, PdfProtectedError: PROTECTED, PdfTooManyPagesError: 'A PDF can have at most 500 pages.', PdfTooLargeError: 'Choose a PDF smaller than 50 MB.' }; + const applyPages = async (state) => { + const ops = GZ_PDF_PAGES.toOps(state); + if (!ops.length) return; + const progress = GZ_DOC_KINDS.progress((p) => { const w = !p.end && !p.failed && GZ_DOC_KINDS.said(p, 'pdf'); if (w) say(t(...w)); }, ['prepare', 'copy', 'save']); + try { + leaveNow(); + if (!await flush()) throw new Error('not saved'); + progress({ stage: 'prepare' }); + rec = await st.get(key); + const out = await (await pdfdoc()).bake(await STORE.get(rec), ops).catch((e) => { throw Object.assign(e, { say: PAGE_SAYS[e.name] }); }); + const back = await read(out.bytes); + let thumbUrl = null; + try { thumbUrl = await picture(back.doc).catch(() => null); } finally { await back.task.destroy().catch(() => {}); } + progress({ stage: 'copy', done: 0, total: out.bytes.length }); + const kept = await STORE.put(out.bytes, (done, total) => progress({ stage: 'copy', done, total })); + progress({ stage: 'save' }); + const { paper, version } = await st.save(key, { ...kept, pages: out.pages, ...(thumbUrl && { thumb: thumbUrl }) }, { expectedKind: 'pdf', reason: 'edit' }); + progress.end(); + try { if (version?.id) sessionStorage.setItem(UNDO, JSON.stringify({ id: version.id, revision: paper.revision })); } catch { /* the version stays in Versions */ } + location.reload(); + } catch (e) { progress.fail(e); say(''); throw e; } + }; + // Back to a kept version: the file as it is now kept first (lib/writer-papers.js restore), then the viewer opened on it. + const restoreTo = async (id) => { + leaveNow(); + if (!await flush()) return; + try { await st.restore(key, id); try { sessionStorage.removeItem(UNDO); } catch { /* none */ } location.reload(); } catch (e) { console.error(e); say(t('Not saved. Try again in a moment.')); } + }; + pagesUndo && tools?.extraUndo({ run: () => restoreTo(pagesUndo.id) }); + let panel = null; + pagesB?.addEventListener('click', async () => { + if (pagesB.disabled || panel || !doc) return; + await GZ_WRITER_PAPER.need(['lib/pdf-pages.js']); + leaveNow(); + ui.dataset.panel = 'pages'; + panel = GZ_PDF_PAGES.panel({ P, doc, t, h, btn, mount: ui.querySelector('.gq-work'), bytes: rec?.size || 0, wasmUrl: GET.wasmUrl, + allows: { add: !!facts?.allows?.add, why: facts?.encrypted ? PROTECTED : null }, apply: applyPages, + close: () => { panel = null; delete ui.dataset.panel; pagesB.focus(); } }); + }); back?.addEventListener('click', (e) => { leaveNow(); if (!dirty && !saving) return; diff --git a/scripts/pdf-check.js b/scripts/pdf-check.js index 7108a48fe..fef3a2053 100644 --- a/scripts/pdf-check.js +++ b/scripts/pdf-check.js @@ -669,6 +669,73 @@ const photo = () => { await openHome(); + // ---- the pages panel (lib/pdf-pages.js): a drag, a keyboard move, a turn, a delete, a blank page and another PDF's + // pages, all applied by one Done, the import's stages said as it goes; Undo takes the whole step back; a signed file + // keeps its pages. + const pagesPdf = path.join(tmp, 'Reordered.pdf'); + fs.writeFileSync(pagesPdf, fixture()); + await openHome(); + await page.setInputFiles('#gd-file', pagesPdf); + const pKey = await until(() => page.evaluate(() => [...document.querySelectorAll('.gd-card')].find((c) => c.querySelector('.gd-title')?.textContent === 'Reordered')?.dataset.key), 'the Reordered card'); + await page.goto(`${HOME}?pdf=${encodeURIComponent(pKey)}`); + await until(() => page.evaluate(() => document.querySelector('[data-act="pages"]')?.disabled === false), 'Pages on'); + const pBefore = await local(`writerPaper:${pKey}`); + await page.click('[data-act="pages"]'); + const tiles = () => page.evaluate(() => [...document.querySelectorAll('.gq-pagepanel .gq-tile')].map((x) => `${x.dataset.id}${x.dataset.turn !== '0' ? `@${x.dataset.turn}` : ''}`)); + await until(async () => (await tiles()).length === 3 && page.evaluate(() => document.querySelectorAll('.gq-pagepanel canvas[width]').length === 3), 'three tiles drawn'); + await shot('pages-1280.png'); + await page.locator('.gq-pagepanel .gq-tile >> nth=2').dragTo(page.locator('.gq-pagepanel .gq-tile >> nth=0'), { targetPosition: { x: 4, y: 60 } }); + await until(async () => (await tiles()).join() === '0:2,0:0,0:1', 'the drag'); + await page.locator('.gq-pagepanel .gq-tile >> nth=1').focus(); + await page.keyboard.press('Alt+ArrowRight'); + await until(async () => (await tiles()).join() === '0:2,0:1,0:0', 'the keyboard move'); + await page.click('.gq-pagepanel .gq-tile >> nth=0'); await page.click('.gq-pagepanel [data-act="rotate-right"]'); + await page.click('.gq-pagepanel .gq-tile >> nth=1'); await page.click('.gq-pagepanel [data-act="delete"]'); + await page.click('.gq-pagepanel .gq-tile >> nth=0'); await page.click('.gq-pagepanel [data-act="blank"]'); + const other = path.join(tmp, 'Other.pdf'); + fs.writeFileSync(other, Buffer.from(require('./pdf-fixtures.js').FIXTURES.three())); + await page.click('.gq-pagepanel .gq-tile >> nth=0'); + const otherChooser = page.waitForEvent('filechooser'); + await page.click('.gq-pagepanel [data-act="merge"]'); + await (await otherChooser).setFiles(other); + const laid = await until(async () => { const x = await tiles(); return x.length === 6 ? x : null; }, 'the merged pages'); + check(laid.join() === '0:2@90,1:0,1:1,1:2,b0,0:0', `the panel: drag, Alt+→, turn, delete, blank and another PDF's pages (${laid.join(' ')})`); + await page.evaluate(() => { const s = document.querySelector('.gp-top .gp-state'); new MutationObserver(() => { const l = JSON.parse(sessionStorage.getItem('pdfcheck-says') || '[]'); l.push(s.textContent); sessionStorage.setItem('pdfcheck-says', JSON.stringify(l)); }).observe(s, { childList: true, characterData: true, subtree: true }); }); + await page.click('.gq-pagepanel [data-act="done"]'); + const pAfter = await until(async () => { const r = await local(`writerPaper:${pKey}`); return r.pages === 6 && r.file.hash !== pBefore.file.hash ? r : null; }, 'Done saved', 30000); + await until(() => page.evaluate(() => document.querySelectorAll('.pdfViewer .page').length === 6 && !document.querySelector('.gq-pagepanel')), 'the viewer on the new pages'); + // What the saved file is, read back by pdf.js: each page's turn and words. + const readBack = (rec) => page.evaluate(async (f) => { + const P = await import(chrome.runtime.getURL('lib/pdfjs/pdf.mjs')); + await GZ_WRITER_PAPER.need(['lib/doc-files.js']); + const task = P.getDocument({ data: await GZ_DOC_FILES.client().get(f), isEvalSupported: false, verbosity: 0 }), d = await task.promise, out = []; + try { for (let i = 1; i <= d.numPages; i++) { const pg = await d.getPage(i); out.push(`${(await pg.getTextContent()).items.map((x) => x.str).join('').trim() || '∅'}${pg.rotate ? `@${pg.rotate}` : ''}`); } } finally { await task.destroy(); } + return out; + }, rec.file); + const baked = await readBack(pAfter), said = await page.evaluate(() => JSON.parse(sessionStorage.getItem('pdfcheck-says') || '[]')); + check(baked.join('|') === 'This is page 3@90|Alpha page one|Bravo page two|Charlie page three|∅|This is page 1' && said.includes('Preparing pages…') && said.some((x) => /^Copying/.test(x)) && said.includes('Saving…'), + `one Done applies them all: ${baked.join(' | ')}; said ${JSON.stringify([...new Set(said)])}`); + const pVersions = await local(`writerVersions:${pKey}`); + check(pVersions.some((v) => v.reason === 'edit' && v.file.hash === pBefore.file.hash && v.pages === 3), 'the file before Done is kept as a version'); + // Undo: the one step back. + await until(() => page.evaluate(() => document.querySelector('[data-act="undo"]')?.disabled === false), 'Undo on after Done'); + await page.click('[data-act="undo"]'); + const pUndone = await until(async () => { const r = await local(`writerPaper:${pKey}`); return r.file.hash === pBefore.file.hash ? r : null; }, 'the undo', 30000); + await until(() => page.evaluate(() => document.querySelectorAll('.pdfViewer .page').length === 3), 'the viewer on the pages before'); + check(pUndone.pages === 3 && (await readBack(pUndone)).join('|') === 'This is page 1|This is page 2|This is page 3', 'Undo takes the whole Done back to the file before it'); + + // ---- a signed file: Pages off, saying why. + const signedPdf = path.join(tmp, 'Signed form.pdf'); + fs.writeFileSync(signedPdf, Buffer.from(require('./pdf-fixtures.js').FIXTURES.signed())); + await openHome(); + await page.setInputFiles('#gd-file', signedPdf); + const sKey = await until(() => page.evaluate(() => [...document.querySelectorAll('.gd-card')].find((c) => c.querySelector('.gd-title')?.textContent === 'Signed form')?.dataset.key), 'the Signed form card'); + await page.goto(`${HOME}?pdf=${encodeURIComponent(sKey)}`); + await until(() => page.evaluate(() => !!document.querySelector('.pdfViewer .page canvas')), 'the signed file drawn'); + const signedPages = await until(() => page.evaluate(() => { const b = document.querySelector('[data-act="pages"]'); return b?.title !== 'Pages' ? { off: b.disabled, why: b.title } : null; }), 'Pages decided'); + check(signedPages.off && signedPages.why === 'This PDF is digitally signed: changing its pages would break the signature.', `a digitally signed file keeps its pages: Pages off, with the reason (${JSON.stringify(signedPages)})`); + await openHome(); + // A PDF whose first page can't be read (its page tree points at nothing) is refused at import; one already kept says // so in the viewer instead of an empty viewer waiting for pages that never come. const broken = Buffer.from(fixture().toString('latin1').replace(/\/Kids \[(\d+) 0 R/, '/Kids [99 0 R'), 'latin1'); diff --git a/test/i18n.test.js b/test/i18n.test.js index d5e086fd0..325143a52 100644 --- a/test/i18n.test.js +++ b/test/i18n.test.js @@ -64,7 +64,7 @@ const fromJs = (js, calls = /\b(?:t|tr)\s*\(/g) => { const JS = ['sharing/quick.js', 'lib/mcp/approve-page.js', 'features/welcome.js', 'lib/sessions-ui.js', 'content.js', 'content-ultra.js', 'content-canvas.js', 'options.js', 'popup.js', 'ui.js', 'lib/page.js', 'lib/petlibrary.js', 'lib/petsprites.js', 'lib/todo.js', 'lib/ultra.js', 'lib/editor-x.js', 'lib/editor-xbg.js', 'lib/editor-xmotion.js', 'lib/editor-pointer.js', 'lib/editor-xparts.js', 'lib/editor-mods.js', 'lib/editor-widget.js', 'lib/editor-navbar.js', 'lib/editor-ai.js', 'lib/editor-writer.js', 'lib/parts.js', 'lib/lookparts.js', ...fs.readdirSync(path.join(root, 'features')).filter((f) => f.endsWith('.js')).map((f) => `features/${f}`)]; // Glaze Writer's frame (writer.html, papers.html) and the libraries whose words it shows. lib/writer-docx.js's t() reads // a run's marks, so only its warnings and refusals are words (below). -const WRITER = ['writer.js', 'papers.js', 'sheet-kind.js', 'sheet-grid.js', 'lib/sheet-chart.js', 'pdf-kind.js', 'lib/pdf-tools.js', 'lib/pdf-sign.js', 'slides-kind.js', ...fs.readdirSync(root).filter((f) => /^writer-.+\.js$/.test(f)), ...fs.readdirSync(path.join(root, 'lib')).filter((f) => /^writer-.+\.js$/.test(f) && f !== 'writer-docx.js').map((f) => `lib/${f}`)]; +const WRITER = ['writer.js', 'papers.js', 'sheet-kind.js', 'sheet-grid.js', 'lib/sheet-chart.js', 'pdf-kind.js', 'lib/pdf-tools.js', 'lib/pdf-sign.js', 'lib/pdf-pages.js', 'slides-kind.js', ...fs.readdirSync(root).filter((f) => /^writer-.+\.js$/.test(f)), ...fs.readdirSync(path.join(root, 'lib')).filter((f) => /^writer-.+\.js$/.test(f) && f !== 'writer-docx.js').map((f) => `lib/${f}`)]; JS.push(...WRITER); // The capitalised strings of a `const NAME = …;` table (its labels and messages, not its ids, CSS or numbers). const tableOf = (f, ...names) => { @@ -88,7 +88,7 @@ const writerWords = [ ...tableOf('writer.js', 'LABELS'), ...tableOf('writer-cite.js', 'TYPES', 'FIELDS'), ...tableOf('writer-ai.js', 'STATUS'), ...tableOf('writer-submit.js', 'SAYS'), ...tableOf('writer-proof.js', 'LABEL'), // The PDF tools' names, colours and the reason a file refuses them (lib/pdf-tools.js). - ...tableOf('lib/pdf-tools.js', 'SHAPES', 'INKS', 'MARKS', 'TOOLS', 'NO_TOOLS'), + ...tableOf('lib/pdf-tools.js', 'SHAPES', 'INKS', 'MARKS', 'TOOLS', 'NO_TOOLS'), ...tableOf('pdf-kind.js', 'SIGNED', 'PROTECTED', 'PAGE_SAYS'), ...tableOf('writer-paper.js', 'SPACING', 'KIND', 'STYLES', 'REASONS'), ...tableOf('lib/writer-format.js', 'ROWS', 'names'), ...tableOf('lib/ai/tasks/writer-feedback.js', 'GRADE_REASONS'), ...tableOf('lib/ai/failure.js', 'REASONS', 'ERRORS'), // Labels handed to the paper chrome's helpers, which translate them. diff --git a/test/pdf-pages.test.js b/test/pdf-pages.test.js new file mode 100644 index 000000000..21b3beedf --- /dev/null +++ b/test/pdf-pages.test.js @@ -0,0 +1,94 @@ +// The pages panel's reducer (lib/pdf-pages.js): what the student does to the tiles (move, rotate, delete, a blank page, +// another PDF's pages) → one list of page operations for lib/pdf-doc.js bake, which lays them out (layout) and hands +// pdf.js's extractPages its arguments (entries). Pure: no pdf.js here. The panel on a page is scripts/pdf-check.js. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const R = require('../lib/pdf-pages.js'); +const D = require('../lib/pdf-doc.js'); + +const at = (pages) => pages.map((p) => `${p.source}:${p.page}${p.turn ? `@${p.turn}` : ''}`); +// What the tiles say the result is, as layout writes it. +const want = (s) => s.tiles.map((x) => `${x.source}:${x.source === 'blank' ? 0 : x.page}${x.turn ? `@${x.turn}` : ''}`); +const run = (s) => D.layout(s.count, R.toOps(s), s.merges.map((m) => m.pages)); + +test('a new state is the document as it is, and needs no operations', () => { + const s = R.start(3); + assert.deepEqual(at(s.tiles), ['0:0', '0:1', '0:2']); + assert.deepEqual(R.toOps(s), []); + assert.equal(R.changed(s), false); + assert.ok(new Set(s.tiles.map((x) => x.id)).size === 3, 'each tile its own id'); +}); + +test('move, rotate, delete, a blank page and a merged PDF: the operations lay the pages out as the tiles show', () => { + let s = R.start(4); + s = R.reduce(s, { type: 'move', from: 3, to: 0 }); // 3 0 1 2 + s = R.reduce(s, { type: 'rotate', at: [1], by: 90 }); // 0 turned + s = R.reduce(s, { type: 'rotate', at: [1, 2], by: 90 }); // 0 by 180, 1 by 90 + s = R.reduce(s, { type: 'remove', at: [3] }); // 3 0 1 + s = R.reduce(s, { type: 'blank', at: 1 }); // 3 b 0 1 + s = R.reduce(s, { type: 'merge', at: 2, pages: 2, bytes: new Uint8Array([1]), name: 'Other.pdf' }); // 3 b m0 m1 0 1 + s = R.reduce(s, { type: 'rotate', at: [3], by: -90 }); // m1 by 270 + assert.deepEqual(want(s), ['0:3', 'blank:0', '1:0', '1:1@270', '0:0@180', '0:1@90']); + assert.deepEqual(at(run(s)), want(s)); + assert.equal(R.changed(s), true); + const ops = R.toOps(s); + assert.equal(ops.filter((o) => o.op === 'merge').length, 1); + assert.ok(ops.find((o) => o.op === 'merge').bytes instanceof Uint8Array); +}); + +test('every order of moves and rotations comes out as the tiles show (random runs)', () => { + let seed = 11; + const rnd = (n) => ((seed = (seed * 1103515245 + 12345) & 0x7fffffff) % n); + for (let k = 0; k < 300; k++) { + let s = R.start(1 + rnd(6)); + for (let i = 0; i < 1 + rnd(8); i++) { + const n = s.tiles.length, pick = rnd(6); + if (pick === 0 && n > 1) s = R.reduce(s, { type: 'move', from: rnd(n), to: rnd(n) }); + else if (pick === 1) s = R.reduce(s, { type: 'rotate', at: [rnd(n)], by: [90, -90, 180][rnd(3)] }); + else if (pick === 2 && n > 1) s = R.reduce(s, { type: 'remove', at: [rnd(n)] }); + else if (pick === 3) s = R.reduce(s, { type: 'blank', at: rnd(n + 1) }); + else if (pick === 4) s = R.reduce(s, { type: 'merge', at: rnd(n + 1), pages: 1 + rnd(3), bytes: new Uint8Array(1) }); + } + assert.deepEqual(at(run(s)), want(s), JSON.stringify(R.toOps(s).map(({ bytes, ...o }) => o))); + } +}); + +test('the last page cannot go; moves and rotations out of range change nothing', () => { + const s = R.start(2); + const one = R.reduce(s, { type: 'remove', at: [0] }); + assert.equal(one.tiles.length, 1); + assert.equal(R.reduce(one, { type: 'remove', at: [0] }), one, 'a PDF keeps one page'); + assert.equal(R.reduce(s, { type: 'move', from: 0, to: 9 }), s); + assert.equal(R.reduce(s, { type: 'rotate', at: [5], by: 90 }), s); + assert.equal(R.reduce(s, { type: 'rotate', at: [0], by: 45 }), s); + assert.equal(R.reduce(s, { type: 'nothing' }), s); +}); + +test('limits: past 500 pages a merge is refused; the merged bytes count against 50 MB', () => { + const s = R.start(499); + assert.throws(() => R.reduce(s, { type: 'merge', at: 0, pages: 2, bytes: new Uint8Array(1) }), (e) => e.name === 'PdfTooManyPagesError'); + assert.throws(() => R.reduce(R.start(1, { bytes: 49e6 }), { type: 'merge', at: 0, pages: 1, bytes: { length: 2e6 } }), (e) => e.name === 'PdfTooLargeError'); + assert.equal(R.reduce(R.start(1, { bytes: 10e6 }), { type: 'merge', at: 1, pages: 1, bytes: { length: 2e6 } }).tiles.length, 2); +}); + +test('entries: the pages laid out → extractPages\'s arguments, each source page once per entry, its slot and turn', () => { + // 0:2, blank, 1:0@90, 0:0@180, 0:2 again (a second entry of the document). + const pages = [{ source: 0, page: 2, turn: 0 }, { source: 'blank', page: 0, turn: 0 }, { source: 1, page: 0, turn: 90 }, { source: 0, page: 0, turn: 180 }, { source: 0, page: 2, turn: 0 }]; + assert.deepEqual(D.entries(pages), [ + { source: 0, includePages: [0, 2], pageIndices: [3, 0], rotations: { 0: 180, 2: 0 } }, + { source: 'blank', includePages: [0], pageIndices: [1], rotations: { 0: 0 } }, + { source: 1, includePages: [0], pageIndices: [2], rotations: { 0: 90 } }, + { source: 0, includePages: [2], pageIndices: [4], rotations: { 2: 0 } }, + ]); +}); + +test('the reducer\'s operations through layout and entries: the panel\'s Done in one extractPages call', () => { + let s = R.start(3); + s = R.reduce(s, { type: 'merge', at: 3, pages: 1, bytes: new Uint8Array(1) }); + s = R.reduce(s, { type: 'move', from: 3, to: 0 }); + s = R.reduce(s, { type: 'rotate', at: [1], by: 90 }); + s = R.reduce(s, { type: 'remove', at: [2] }); + // m0, 0:0@90, 0:2 + const e = D.entries(run(s)); + assert.deepEqual(e, [{ source: 1, includePages: [0], pageIndices: [0], rotations: { 0: 0 } }, { source: 0, includePages: [0, 2], pageIndices: [1, 2], rotations: { 0: 90, 2: 0 } }]); +}); diff --git a/test/writer-papers-files.test.js b/test/writer-papers-files.test.js index db10be499..97b4fe7e2 100644 --- a/test/writer-papers-files.test.js +++ b/test/writer-papers-files.test.js @@ -178,3 +178,19 @@ test('a page\'s papers proxy asks the worker to migrate', async () => { assert.equal(P.reown, undefined, 'owners are the worker\'s'); } finally { delete globalThis.chrome; } }); + +test('a PDF saved with reason edit (the pages panel\'s Done, a viewer\'s first save) keeps the file before it; restore takes it back', async () => { + const { P, files, store, clock } = make(), k = key(); + const f1 = await files.put(bytes(500, 61), 'application/pdf'); + await P.save(k, { kind: 'pdf', file: f1, size: f1.size, pages: 3 }, { reason: 'import' }); + const f2 = await files.put(bytes(400, 62), 'application/pdf'); + clock.t += 1000; + const { paper, version } = await P.save(k, { file: f2, size: f2.size, pages: 2 }, { reason: 'edit', expectedKind: 'pdf' }); + assert.equal(paper.file.hash, f2.hash); + assert.ok(version && version.reason === 'edit' && version.file.hash === f1.hash && version.pages === 3, 'the file before, with its page count'); + clock.t += 1000; + const back = await P.restore(k, version.id); + assert.equal(back.paper.file.hash, f1.hash); + assert.equal(back.paper.pages, 3); + assert.ok(store.data[`writerVersions:${k}`].some((v) => v.reason === 'restore' && v.file.hash === f2.hash), 'what the restore replaced is a version too'); +}); From 7c7cfccb3fb89db69e6346d410ac7dbee5421922 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 15:06:48 -0400 Subject: [PATCH 108/907] Turn Google Calendar sync on in Chrome builds lib/gcal.js ON is true: the Chrome package keeps the oauth2 client and the optional identity permission; Firefox and Safari still leave both out. scripts/dev-gcal.sh now only adds the store's key. The privacy policy, the privacy summary, features and development docs describe the sync as it ships. --- CHANGELOG.md | 4 ++++ PRIVACY.md | 17 ++++++++--------- docs/development.md | 10 +++++++++- docs/features.md | 2 +- docs/privacy.md | 2 +- lib/gcal.js | 9 +++++---- package.sh | 4 ++-- scripts/dev-gcal.sh | 11 ++--------- test/audit-documentation.test.js | 23 +++++++++++++++-------- test/fetch-hosts.test.js | 4 ++-- test/gcal.test.js | 27 ++++++++++++++------------- 11 files changed, 63 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2487d0996..7602a4723 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ All notable changes to Glaze are documented here. The format follows ## [Unreleased] +### Added + +- Chrome: **Google Calendar sync** in Settings → Sharing → Google Calendar sync. Connect keeps a calendar named Glaze in your Google account in step with Export calendar (.ics), and touches nothing else there; Disconnect revokes access and can delete the calendar. Off until you connect; Firefox, Edge, Brave and Safari keep the export. + ## [4.0.0] — 2026-10-30 ### Added diff --git a/PRIVACY.md b/PRIVACY.md index 402d4384a..b845c48dd 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -376,7 +376,7 @@ not those unknown legacy copies or unrelated stickers. your "do on" days and focus sessions and your Gradescope work, with **course and assignment names**, links to them on your Canvas site and your tasks' notes, and the Canvas site's address in each event's identifier. Once saved, it is an ordinary file on your device; what happens to it after you import it is up to you and your calendar provider. -- **Google Calendar sync** (Chrome, off until you connect), in `chrome.storage.local`, on this device only: the ID of +- **Google Calendar sync** (Chrome, off until you connect), in `chrome.storage.local` (`gcal` and `gcalSync`), on this device only: the ID of the calendar named Glaze that Glaze made in your Google account (or that you linked with a code from another device), the Google account Google names as that calendar's owner (its email address, shown as "Connected to …"), when you connected, when each Canvas site and account last synced and how the last sync ended. For each event Glaze wrote: an @@ -751,8 +751,7 @@ you import) are bundled. It makes requests in these cases: isn't kept. If Glaze, the Gradescope switch or your Gradescope answer changes, or the dialog closes, while Canvas answers, no file is saved. With Gradescope in To Do on, the Gradescope work comes from what's already kept on this device; nothing is asked of Gradescope. -- **Google Calendar sync** (Chrome, off until you connect; not in released builds until Google verifies Glaze, and - those builds can't make any of these requests). The only destinations are Google's Calendar API at +- **Google Calendar sync** (Chrome, off until you connect). The only destinations are Google's Calendar API at `https://www.googleapis.com` and Google's token revocation at `https://oauth2.googleapis.com`; requests carry the access token Chrome gives Glaze, never cookies, and never follow a redirect. There is no Glaze server. - **Connect** (your click in Glaze's settings): Chrome asks for the `identity` permission and shows Google's own @@ -1071,10 +1070,8 @@ A site you add is kept in a list in the extension's local storage (`canvas`, `mo never in the LMS page. The reader page is web-accessible so an import can open it in a frame; it reads a file only with a one-use pass that Glaze's import gets from its own background worker, so a site that framed it itself can't make it read anything. -- `identity` (Chrome only, optional) — not requested by released builds while Google Calendar sync is switched off - at build time (`ON` in `lib/gcal.js`): the Chrome, Firefox and Safari packaging scripts remove it, with the OAuth - client, from the manifest they ship. In a build that enables the sync it is asked for only when you press Connect in - Google Calendar sync, and removed on Disconnect. It lets Chrome sign Glaze in to Google Calendar with the Google +- `identity` (Chrome only, optional) — asked for only when you press Connect in Google Calendar sync, and removed on + Disconnect. The Firefox and Safari packages leave it out, with the OAuth client. It lets Chrome sign Glaze in to Google Calendar with the Google account in your browser; Chrome shows no warning for it. Glaze never asks for `identity.email` and never sees your password. - `unlimitedStorage` — requested at install, so the browser doesn't clear your notes, pictures, attachments and the @@ -1132,12 +1129,14 @@ press it (see Glaze Writer above). Glaze never changes grades. ## Google user data -Google Calendar sync is unavailable in the current build while Google verification is pending. The following describes its use of Google data in a future build that enables this optional feature; calendar file export does not require it. +Google Calendar sync (Chrome, off until you connect) is the only part of Glaze that uses Google APIs. It asks for one +scope, `https://www.googleapis.com/auth/calendar.app.created`, and talks only to `www.googleapis.com` and +`oauth2.googleapis.com`, as described above; Export calendar (.ics) needs no Google account. Glaze's use and transfer of information received from Google APIs adheres to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements. Glaze uses Google Calendar access only to keep the Glaze calendar in step with your -Canvas work, as described above. It doesn't sell, share or use this data for advertising or to train AI models, no +Canvas or Blackboard work, as described above. It doesn't sell, share or use this data for advertising or to train AI models, no person reads it, and none of it goes to frostdev or anyone else. ## Hosting diff --git a/docs/development.md b/docs/development.md index f87ebeeff..9d1dd8682 100644 --- a/docs/development.md +++ b/docs/development.md @@ -117,7 +117,15 @@ aren't in the API; edit those in the dashboard. The publisher and item IDs defau ## Google Calendar sync -Google Calendar sync is unavailable in the current build while Google verification is pending. Keep the build-time switch in `lib/gcal.js` off until verification and an authorized release enable it. While off, the editor hides connection controls, the worker refuses sync operations, and packages omit the OAuth client and optional identity permission. Calendar export (.ics) remains available. +Google Calendar sync is on in Chrome builds since 4.0.1 (the build-time switch `ON` in `lib/gcal.js`). The Chrome package keeps the manifest's `oauth2` client (`calendar.app.created` scope) and the optional `identity` permission; the Firefox and Safari packages leave both out, because the sync needs Chrome's `identity.getAuthToken`. With the switch off, Settings hides the section, the worker refuses every operation and `package.sh` leaves both out of the Chrome package too. `test/gcal.test.js` runs it against a local mock of Google's API (`scripts/gcal-mock.js`). + +The OAuth client belongs to the Chrome Web Store item, so `getAuthToken` works only in a copy with the store's ID. An unpacked checkout has its own ID; for a copy with the store's, save the item's public key (Developer Dashboard → the item → Package → View public key, the base64 between the BEGIN and END lines, on one line) to a file outside the repo and run: + +```sh +scripts/dev-gcal.sh +``` + +Then load `dist/glaze-dev-gcal` unpacked, in a Chrome profile without the store's Glaze. The key never goes in `manifest.json` or the package. ## Safari diff --git a/docs/features.md b/docs/features.md index 2060164e8..74a0621e6 100644 --- a/docs/features.md +++ b/docs/features.md @@ -73,7 +73,7 @@ Notes **Back up or restore** opens **Settings → Privacy and data → Backup an - **Announcements in a pop-up (Canvas, Blackboard and Moodle, on by default)** — an announcement opens in Glaze's pop-up over the page instead of leaving it, from Better To Do, widgets, Ctrl+K search and each site's own lists: on Blackboard a course's Announcements and the Activity Stream; on Canvas Announcements, Recent Activity and the Dashboard; on Moodle the news forum and the Latest announcements block. The pop-up holds the title, any available course, author and date details, the post itself (made safe: no scripts, frames or styles, pictures only from your school's site, links in a new tab), its files, and **Open in Canvas**, **Open in Blackboard** or **Open in Moodle** for the site's own page. Opening an unread announcement marks it read on Canvas, as Canvas's page does, and on Moodle Moodle's own request marks its posts read where the forum tracks them automatically; Blackboard isn't marked, and a preview never marks anything. Ctrl- or ⌘-click, Shift-click or a middle click still opens the page in a new tab, and if the announcement can't be read the site's page opens instead. It's a real dialog: focus moves in, Escape or Close closes it and focus goes back to the link; it fits a phone-width window, larger text and right-to-left languages, and wears your Look. With **Pop-up animation → Genie**, it grows out of an on-screen link with Expressive motion and scales in with Subtle; **Fade** fades it in. Motion off or reduced, or **None**, makes it appear at once. Point at or focus an announcement in Better To Do or a site’s own list for a moment to preview its first lines (on Moodle, what the list shows); Escape hides the preview. **Settings → Course pages → Announcements** has **Open announcements** (in a pop-up or on the site's page) and **Preview on hover**; with **All settings**, **Pop-ups** sets each entry point (Better To Do, widgets and search; Blackboard; Canvas; Moodle) to Default, Pop-up or Site's page, its preview on or off, the preview's delay and lines, and **Pop-up animation** (Genie, Fade or None). On a Blackboard course's Announcements, **Mark all as read** ticks each unread announcement's own read circle in turn (scrolling, a click or a key stops it), and ticking one keeps your place in the list. - **Rubrics (Canvas and Blackboard Ultra, on by default)** — an assignment's rubric, with your published scores and feedback, read-only. On Ultra's assessment overview it replaces the rubric entry in Assessment Details when its columns fit, otherwise its rubric link opens it in the same pop-up; on Canvas the folded rubric's toggle opens the pop-up, and a rubric Canvas already shows stays as it is. The site's own view stays whenever Glaze can't read or recognize the rubric. **Settings → Course pages → Documents and quizzes → Rubrics**: Automatic, Pop-up or The site's own; with **All settings**, **Pop-ups → Show inline when there's room**. - **Export calendar (.ics) (Canvas and Blackboard)** — from Better To Do's Export button or Ctrl+K: one file of your Canvas or Blackboard deadlines, your own tasks with a due date, your "do on" days and focus sessions (as events at their times) and, if you show it, your Gradescope work, for the next 2, 4, 8 or 16 weeks or the rest of the term. Import it into Google Calendar, Outlook or Apple Calendar yourself; each item keeps the same identifier, so importing a newer file updates it where the calendar supports that. Made on your device and saved by your browser; the file holds your course and assignment names. -- **Google Calendar sync (Chrome, off until you connect)** — keeps a calendar named Glaze in your Google account up to date with the same events and choices as the export, from Glaze's settings. Chrome signs you in with Google; Glaze asks only to manage calendars it makes (`calendar.app.created`), so it can't see or touch your other calendars. Deadlines, tasks and classes mirror Canvas and your schedule: edit one in Google and Glaze writes it back, delete one and it stays deleted. Move or delete a "do on" day or focus session in Google and the plan follows in Better To Do. No reminders on the events. **Link another device** shows a code to paste on your other computer, so both keep the one calendar; **Disconnect** revokes Glaze's access and can delete the calendar. It syncs from Canvas and Blackboard pages (on open, every 15 minutes, and soon after your plans change); there's no Glaze server, and requests go only to Google. Edge, Brave and Safari use the export; Outlook sync isn't here. It isn't in released builds yet: it's switched off at build time until Google verifies Glaze. +- **Google Calendar sync (Chrome, off until you connect)** — keeps a calendar named Glaze in your Google account up to date with the same events and choices as the export, from Glaze's settings. Chrome signs you in with Google; Glaze asks only to manage calendars it makes (`calendar.app.created`), so it can't see or touch your other calendars. Deadlines, tasks and classes mirror Canvas and your schedule: edit one in Google and Glaze writes it back, delete one and it stays deleted. Move or delete a "do on" day or focus session in Google and the plan follows in Better To Do. No reminders on the events. **Link another device** shows a code to paste on your other computer, so both keep the one calendar; **Disconnect** revokes Glaze's access and can delete the calendar. It syncs from Canvas and Blackboard pages (on open, every 15 minutes, and soon after your plans change); there's no Glaze server, and requests go only to Google. Edge, Brave, Safari and Firefox use the export; Outlook sync isn't here. - **Tasks on course cards (Canvas and Blackboard, off by default)** — each Dashboard card (on Blackboard, each card on the Courses page) lists its course's next items (4 by default, up to 10, then how many more), soonest first, with the date and a status chip (submitted, graded, late, missing, excused); done items can stay crossed out. It follows Better To Do's timeframe and done rules and reuses its copy of your planner, on or off. The card grows to fit its list. - **Search in your courses (Canvas and Blackboard Ultra, on by default)** — from two characters in Ctrl/⌘K, find titles in your courses, this course first, then this term's, up to 24 results. Ultra searches this course and open courses in this term, including released public text of documents, announcements and discussion topics, without opening posts or marking anything read. Unknown or unsupported release conditions are omitted. Content results use Ultra's own redirect; announcements open in the pop-up or on the site's page, as chosen in settings; discussions open their course tool list. Answers are reused for five minutes; bounded stale entries are evicted on later cache use and all search data is cleared when search or Glaze is switched off. Typing again or closing cancels pending requests. Ultra asks at most three lists at a time, 30 search requests per query and two pages (up to 400 items) per list, across at most 100 selected courses, so very large courses or course lists may be incomplete. No search text or answers are stored. - **Canvas's document viewer** — PDFs and files in Canvas's viewer wear your palette (background, toolbar, thumbnails) with the pages left readable, or dimmed or inverted in a dark palette. **Pen, highlighter and eraser** (off by default): any color and size, undo and redo, clear page, export and import as a file, a page saved as a picture; kept on your device per site, account and file, never sent anywhere, and beside Canvas's own annotations, never over them. diff --git a/docs/privacy.md b/docs/privacy.md index 05b19ea84..a83214434 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -90,7 +90,7 @@ as do your stickers, your own sticker and pointer pictures and Look rotation's l the Looks gallery is a website you visit yourself; Glaze asks it only for the one background file or mod you choose there, when you click to use it (see [The Looks gallery](../PRIVACY.md#the-looks-gallery)). Canvas's own what-if scores and editor recovery remain with Canvas. Better To Do's own tasks and plans stay on your device too, with a two-minute copy of your Canvas planner items or Blackboard due items. A calendar file you export (.ics) is made on your device and saved by your browser; it holds your course and assignment names, and Glaze keeps no copy. -Google Calendar sync is unavailable in this build while Google verification is pending. In a future build that enables it, optional sync would send the same events to the Glaze calendar in your Google account and read changes there, directly between your browser and Google (`www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect); Chrome would keep the sign-in. Calendar file export remains available without connecting Google. +Google Calendar sync (Chrome, off until you connect) sends the same events to the Glaze calendar in your Google account and reads changes there, directly between your browser and Google (`www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect), with one scope, `calendar.app.created`, which can't see your other calendars. Chrome keeps the sign-in; Glaze keeps the calendar's ID, your account's address and what it wrote (`gcal`, `gcalSync`) on this device, and **Disconnect** deletes them and revokes access. Calendar file export works without connecting Google. Dashboard notes, the widgets' layout, the timer and the music link you type stay on your device. So do the notes app's notes, folders, pictures and attachments, in Glaze's own storage (not the school page's); its Canvas link picker reads your own Canvas site, and only a link and title are kept. A music widget contacts its player (Spotify, YouTube through youtube-nocookie.com, Apple Music) only after you press Load player. diff --git a/lib/gcal.js b/lib/gcal.js index 6a1872030..014aa2266 100644 --- a/lib/gcal.js +++ b/lib/gcal.js @@ -10,10 +10,11 @@ // guard nearest each request, after the token: it reads what the run rests on, checks it, and only then calls send(), // which issues the request with nothing awaited in between (or it throws a Fail, stopped) }. var GZ_GCAL = globalThis.GZ_GCAL || (() => { - // The build-time switch, off until Google verifies the app (docs/development.md, Google Calendar sync). Off: Glaze's settings show nothing - // about Google sync, the worker refuses every operation and runs nothing, and package.sh leaves the manifest's oauth2 - // client and optional identity permission out of the package. scripts/dev-gcal.sh turns it on in its copy. - const ON = false; + // The build-time switch, on since 4.0.1 (docs/development.md, Google Calendar sync). Off: Glaze's + // settings show nothing about Google sync, the worker refuses every operation and runs nothing, and package.sh leaves + // the manifest's oauth2 client and optional identity permission out of the package. Firefox and Safari leave them out + // either way: the sync needs Chrome's getAuthToken. + const ON = true; const API = 'https://www.googleapis.com/calendar/v3'; const REVOKE = 'https://oauth2.googleapis.com/revoke'; // The one scope: make secondary calendars, and see, change and delete events on those (S3 §2). diff --git a/package.sh b/package.sh index 94df0a9ae..7154850dc 100755 --- a/package.sh +++ b/package.sh @@ -17,8 +17,8 @@ chmod -R a+rX manifest.json content.js content-ultra.js content-canvas.js defaul zip -q -r -X "$OUT" manifest.json content.js content-ultra.js content-canvas.js defaults.js lib i18n _locales theme.css motion.css extras.css canvas.css moodle.css features \ popup.html popup.js options.html options.js preview.html reader.html reader.js writer.html writer.js writer.css writer-ai.js writer-ai.css writer-mcp.js writer-compose.js writer-compose.css writer-proof.js writer-proof.css writer-cite.js writer-cite.css writer-paper.js writer-paper.css writer-submit.js writer-submit.css writer-import.js writer-import.css writer-kinds.js writer-objects.js writer-objects.css writer-find.js writer-print.js writer-counts.js writer-edit.css writer-paste.js writer-ruler.js writer-ruler.css papers.html papers.js papers.css papers-motion.js sheet-kind.js sheet-grid.js sheet-data.js sheet.css pdf-kind.js pdf-kind.css slides-kind.js slides-kind.css sharing mods ui.js ui.css fonts icons assets/logo.svg assets/logo-dark.svg assets/art/pets assets/art/stickers assets/art/cursors assets/shaders -x '*.DS_Store' -# Google Calendar sync waits behind its build-time switch (lib/gcal.js ON) until Google verifies the app. While it's -# off, the packaged manifest carries neither its oauth2 client nor the optional identity permission it would ask for. +# Google Calendar sync's build-time switch (lib/gcal.js ON), on since 4.0.1. A build with it off ships a manifest with +# neither its oauth2 client nor the optional identity permission it would ask for. if [ "$(node -p 'require("./lib/gcal.js").ON')" != true ]; then stage="$(mktemp -d)" trap 'rm -rf "$stage"' EXIT diff --git a/scripts/dev-gcal.sh b/scripts/dev-gcal.sh index 4993a23e5..caf24a0df 100755 --- a/scripts/dev-gcal.sh +++ b/scripts/dev-gcal.sh @@ -2,8 +2,7 @@ # A dev copy of the extension for trying Google Calendar sync with the real OAuth client. That client (a "Chrome # extension" client, S3 §1) is tied to the Chrome Web Store item's ID, and Chrome gives an unpacked copy that ID only when # its manifest carries the store's public key ("key"). So: the store package (package.sh), unzipped into a folder, with -# Google sync's build-time switch turned on (lib/gcal.js ON, with the manifest's oauth2 client and optional identity -# permission back) and the key added, in that copy only. Never in manifest.json or the package (test/gcal.test.js checks). +# the key added, in that copy only. Never in manifest.json or the package (test/gcal.test.js checks). # # scripts/dev-gcal.sh [folder, default dist/glaze-dev-gcal] # @@ -24,15 +23,9 @@ import base64, hashlib, json, sys out, key_file = sys.argv[1], sys.argv[2] key = open(key_file).read().strip() der = base64.b64decode(key, validate=True) # a public key in base64, nothing else -gcal = f'{out}/lib/gcal.js' -src = open(gcal).read() -assert ' const ON = false;\n' in src or ' const ON = true;\n' in src, 'lib/gcal.js: no ON switch' -open(gcal, 'w').write(src.replace(' const ON = false;\n', ' const ON = true;\n', 1)) -repo = json.load(open('manifest.json')) path = f'{out}/manifest.json' manifest = json.load(open(path)) -manifest['oauth2'] = repo['oauth2'] -manifest['optional_permissions'] = sorted(set(manifest.get('optional_permissions', [])) | {'identity'}) +assert 'oauth2' in manifest and 'identity' in manifest.get('optional_permissions', []), 'the package has no Google sync (lib/gcal.js ON off?)' manifest['key'] = key open(path, 'w').write(json.dumps(manifest, indent=2) + '\n') # Chrome's extension ID: the key's SHA-256, first 32 hex digits, 0-f read as a-p. diff --git a/test/audit-documentation.test.js b/test/audit-documentation.test.js index 7adda2ac6..fa75a6409 100644 --- a/test/audit-documentation.test.js +++ b/test/audit-documentation.test.js @@ -31,10 +31,14 @@ for (const [name, subject, facts, anchor] of topics) { } const unavailable = /unavailable|not (?:yet )?available|not enabled|disabled|coming soon|future|not shipped|pending (?:verification|approval)|off until Google|when (?:available|supported)|where available/i; -for (const file of ['README.md', 'docs/privacy.md']) { - test(`GZ-04: ${file} does not advertise disabled Google Calendar sync as connectable`, () => { - if (G.ON) return; +for (const file of ['README.md', 'docs/privacy.md', 'PRIVACY.md', 'docs/features.md', 'docs/development.md']) { + test(`GZ-04: ${file} describes Google Calendar sync as the build has it`, () => { const claims = sentences(read(file)).filter((s) => /Google Calendar sync/i.test(s)); + if (G.ON) { + assert.deepEqual(claims.filter((s) => unavailable.test(s)), [], + 'Google Calendar sync is on in this build (Chrome, off until you connect); no sentence may call it unavailable or pending'); + return; + } const unqualified = claims.filter((s) => !unavailable.test(s)); assert.deepEqual(unqualified, [], 'Google Calendar sync is disabled at build time; current feature claims must disclose that or be removed'); @@ -92,11 +96,14 @@ test('BLD-5: the permission text matches the manifest and the packaging scripts' assert.ok(manifest.permissions.includes('unlimitedStorage')); assert.ok(!/doesn't ask for unlimited/i.test(policy)); assert.match(policy, /Glaze asks the browser for\s+`unlimitedStorage`/); - assert.equal(G.ON, false, 'Google Calendar sync is built off, so released builds omit identity'); - assert.match(code('package.sh'), /filter\(\(p\) => p !== "identity"\)/); - assert.match(code('scripts/package-firefox.sh'), /filter\(\(p\) => p !== 'identity'\)/); - assert.match(code('scripts/safari-manifest.sh'), /p != 'identity'/); - assert.match(policy, /`identity` \(Chrome only, optional\) — not requested by released builds/); + assert.equal(G.ON, true, 'Google Calendar sync is built on, so the Chrome package keeps identity and the OAuth client'); + assert.deepEqual(manifest.oauth2.scopes, [G.SCOPE]); + assert.ok(manifest.optional_permissions.includes('identity')); + assert.match(code('package.sh'), /if \[ "\$\(node -p 'require\("\.\/lib\/gcal\.js"\)\.ON'\)" != true \]; then[\s\S]*filter\(\(p\) => p !== "identity"\)/, 'Chrome strips them only from a build with the switch off'); + assert.match(code('scripts/package-firefox.sh'), /delete m\.oauth2;\nm\.optional_permissions = \(m\.optional_permissions \|\| \[\]\)\.filter\(\(p\) => p !== 'identity'\)/, 'Firefox always leaves them out'); + assert.match(code('scripts/safari-manifest.sh'), /manifest\.pop\('oauth2', None\)\noptional = \[p for p in manifest\.get\('optional_permissions', \[\]\) if p != 'identity'/, 'Safari always leaves them out'); + assert.match(policy, /`identity` \(Chrome only, optional\) — asked for only when you press Connect/); + assert.ok(!/not requested by released builds/.test(policy)); assert.match(policy, /Chrome sync \(or Firefox Sync\)/); assert.match(policy, /\*\*Music widget\*\* \(Canvas and Blackboard Ultra/); assert.ok(/music/.test(code('lib/widgets.js').match(/const TILES = \[[^\]]*\]/)[0]), 'the music tile is a widget on both sites'); diff --git a/test/fetch-hosts.test.js b/test/fetch-hosts.test.js index 6179ebe56..f9371fd65 100644 --- a/test/fetch-hosts.test.js +++ b/test/fetch-hosts.test.js @@ -87,8 +87,8 @@ const SITES = { { at: 'WebSocket: W = globalThis.WebSocket', kind: 'via', to: 'lib/sessions.js' }, ], 'lib/gcal.js': [ - { at: 'env.fetch(`${env.api || API}', kind: 'open', host: 'https://www.googleapis.com', why: 'Google Calendar API, CORS for any origin; behind GZ_GCAL.ON (off)' }, - { at: 'env.fetch(env.revoke || REVOKE', kind: 'open', host: 'https://oauth2.googleapis.com', why: 'token revocation, CORS for any origin; behind GZ_GCAL.ON (off)' }, + { at: 'env.fetch(`${env.api || API}', kind: 'open', host: 'https://www.googleapis.com', why: 'Google Calendar API, CORS for any origin; behind GZ_GCAL.ON (on; Chrome only)' }, + { at: 'env.fetch(env.revoke || REVOKE', kind: 'open', host: 'https://oauth2.googleapis.com', why: 'token revocation, CORS for any origin; behind GZ_GCAL.ON (on; Chrome only)' }, ], 'lib/fonts.js': [{ at: 'const get = (url, as) => fetch(url', kind: 'open', host: 'https://fonts.googleapis.com', hosts: ['https://fonts.googleapis.com', 'https://fonts.gstatic.com', 'https://cdn.jsdelivr.net'], why: 'Google Fonts CSS, fonts.gstatic.com and the jsDelivr catalog files answer every origin' }], 'options.js': [{ at: 'fetch(GZ_FONTS.cssUrl(name, false)', kind: 'open', host: 'https://fonts.googleapis.com', why: 'whether a typed Google font exists' }], diff --git a/test/gcal.test.js b/test/gcal.test.js index b57594fd2..bc57b2cdb 100644 --- a/test/gcal.test.js +++ b/test/gcal.test.js @@ -260,8 +260,9 @@ const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const read = (f) => fs.readFileSync(path.join(__dirname, '..', f), 'utf8'); -// on: the build-time switch (lib/gcal.js ON) turned on in the worker's copy, as scripts/dev-gcal.sh does. -const switched = (src, on) => (on ? src.replace(' const ON = false;\n', ' const ON = true;\n') : src); +// on: the build-time switch (lib/gcal.js ON) as the worker's copy has it; off is a build that leaves Google sync out. +const SWITCH = /\n {2}const ON = (?:true|false);\n/; +const switched = (src, on) => { assert.match(src, SWITCH); return src.replace(SWITCH, `\n const ON = ${on};\n`); }; // failSet(patch): a storage write that fails, as a full disk or a quota does. identity.hold: what getAuthToken waits on. const worker = (mock, { on = true, local = {}, sync = { enabled: true }, failSet = () => false } = {}) => { const store = { local, sync }; @@ -305,7 +306,7 @@ const worker = (mock, { on = true, local = {}, sync = { enabled: true }, failSet assert.ok(url.startsWith(`${G.API}/`) || url === G.REVOKE, `only Google's two addresses: ${url}`); return fetch(url.replace(G.API, mock.api).replace(G.REVOKE, mock.revoke), init); }, - importScripts: (...files) => { for (const f of files) vm.runInContext(switched(read(`lib/${f}`), on && f === 'gcal.js'), context); }, + importScripts: (...files) => { for (const f of files) vm.runInContext(f === 'gcal.js' ? switched(read(`lib/${f}`), on) : read(`lib/${f}`), context); }, chrome: { runtime: { id: 'glaze', getURL: (f = '') => `chrome-extension://x/${f}`, getManifest: () => require('../manifest.json'), onMessage: { addListener: (f) => listeners.push(f) }, onInstalled: { addListener: (f) => (listeners.installed ||= []).push(f) }, onStartup: { addListener() {} }, onConnect: { addListener() {} } }, @@ -397,9 +398,9 @@ withMock('worker: disconnect deletes the calendar when asked, revokes the grant, assert.equal(mock.calendars.size, 1, 'kept, as chosen'); }); -// ---- the build-time switch (lib/gcal.js ON): off in the repo and the store package until Google verifies the app ---- -test('switch off (the repo as it is): no settings section, no sync, no page script, even with a connection left from a dev build', async () => { - assert.equal(G.ON, false, 'lib/gcal.js ON stays false until Google verifies the app'); +// ---- the build-time switch (lib/gcal.js ON): on in the repo and the Chrome package since 4.0.1 ---- +test('switch off (a build that leaves it out): no settings section, no sync, no page script, even with a connection left', async () => { + assert.equal(G.ON, true, 'lib/gcal.js ON is on since 4.0.1'); // The section is hidden in the page itself, and only the switch shows it; the editor loads the switch. assert.match(read('options.html'), /
      -
      +
      More options

      Document review

      diff --git a/safari/Glaze/Glaze Extension/Resources/sheet-grid.js b/safari/Glaze/Glaze Extension/Resources/sheet-grid.js index ae5697849..0d4e58bde 100644 --- a/safari/Glaze/Glaze Extension/Resources/sheet-grid.js +++ b/safari/Glaze/Glaze Extension/Resources/sheet-grid.js @@ -278,7 +278,8 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { // o: { key, wb, rec, sigs, fresh, t, locale, papers, show (wb → a cell's shown text), local, fileOf (wb, title → { name, // mime, bytes }), download (that file), exportLabel, host }. host: an assignment's frame (writer-kinds.js view): { mount, // title, tools }, the grid in mount under the assignment's title with the frame's tools (Submit…) before ⋯, no way back to - // Documents. → { pages, say, ready, save (→ whether all is saved), file (→ { bytes, mime, ext, name }) }. + // Documents. memory: a workbook from elsewhere (sheet-kind.js show), shown as it came and never written: no saves, no versions, + // no other tab's writes, and read-only (writable below). → { pages, say, ready, save (→ whether all is saved), file (→ { bytes, mime, ext, name }) }. const mount = (o) => { const { key, t, papers } = o; const NF = GZ_SHEET_NUMFMT, CALC = GZ_SHEET_CALC, SD = GZ_SHEET_DATA, F = FX(); @@ -310,12 +311,14 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const freezeB = btn({ id: 'gx-freeze', class: 'gp-tbtn', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Freeze') }, icon('freeze'), h('span', { class: 'gx-lbl', text: t('Freeze') }), icon('caret')); const findB = tb('gx-find-b', t('Find'), 'find', { 'aria-pressed': 'false' }); const chartB = globalThis.GZ_SHEET_CHART ? btn({ id: 'gx-chart', class: 'gp-tbtn', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Chart') }, icon('chart'), h('span', { class: 'gx-lbl', text: t('Chart') }), icon('caret')) : null; - const versB = btn({ id: 'gx-versions', class: 'gp-tbtn', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Versions') }, icon('clock'), h('span', { class: 'gx-lbl', text: t('Versions') })); + const versB = o.memory ? null : btn({ id: 'gx-versions', class: 'gp-tbtn', 'aria-haspopup': 'menu', 'aria-expanded': 'false', title: t('Versions') }, icon('clock'), h('span', { class: 'gx-lbl', text: t('Versions') })); const moreB = tb('gx-more', t('More'), 'dots', { 'aria-haspopup': 'menu', 'aria-expanded': 'false' }); const sep = () => h('span', { class: 'gp-sepv' }); - const tool = h('div', { class: 'gp-tool gx-tool', role: 'toolbar', 'aria-label': t('Spreadsheet') }, undoB, redoB, sep(), fmtB, sep(), azB, zaB, freezeB, chartB, sep(), findB, h('span', { class: 'grow' }), versB, ...(host?.tools || []), moreB); + const edits = o.memory ? [] : [undoB, redoB, sep(), fmtB, sep(), azB, zaB, freezeB, chartB, sep()]; + const tool = h('div', { class: 'gp-tool gx-tool', role: 'toolbar', 'aria-label': t('Spreadsheet') }, ...edits, findB, h('span', { class: 'grow' }), versB, ...(host?.tools || []), moreB); const nameIn = h('input', { id: 'gx-name', class: 'gx-name', type: 'text', autocomplete: 'off', spellcheck: 'false', 'aria-label': t('Name box') }); const fx = h('input', { id: 'gx-formula', class: 'gx-fx', type: 'text', autocomplete: 'off', spellcheck: 'false', 'aria-label': t('Formula') }); + fx.readOnly = !!o.memory; const fbar = h('div', { class: 'gx-bar' }, nameIn, fx); const findQ = h('input', { id: 'gx-find-q', type: 'search', autocomplete: 'off', spellcheck: 'false', 'aria-label': t('Find'), placeholder: t('Find') }); const findN = h('span', { id: 'gx-find-n', class: 'gx-find-n', 'aria-live': 'polite' }); @@ -323,7 +326,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const findPrev = btn({ class: 'gp-tbtn gx-tb', 'aria-label': t('Previous'), title: t('Previous') }, icon('up')), findNext = btn({ class: 'gp-tbtn gx-tb', 'aria-label': t('Next'), title: t('Next') }, icon('down')); const repOne = btn({ id: 'gx-rep-one', class: 'gp-tbtn' }, t('Replace')), repAll = btn({ id: 'gx-rep-all', class: 'gp-tbtn' }, t('Replace all')); const findX = btn({ class: 'gp-tbtn gx-tb', 'aria-label': t('Close'), title: t('Close') }, icon('close')); - const findBar = h('div', { id: 'gx-find', class: 'gx-find', role: 'search', hidden: true }, findQ, findN, findPrev, findNext, repQ, repOne, repAll, findX); + const findBar = h('div', { id: 'gx-find', class: 'gx-find', role: 'search', hidden: true }, findQ, findN, findPrev, findNext, ...(o.memory ? [] : [repQ, repOne, repAll]), findX); const gridEl = h('div', { id: 'gx-grid', class: 'gx-grid', role: 'grid', tabindex: '0', 'aria-multiselectable': 'true' }); const pane = (cls, fr, fc) => { const inner = h('div', { class: 'gx-in' }), ov = h('div', { class: 'gx-ov', 'aria-hidden': 'true' }); const el = h('div', { class: `gx-pane gx-q ${cls}`, role: 'rowgroup' }, inner); inner.append(ov); return { el, inner, ov, fr, fc, rows: new Map() }; }; @@ -341,12 +344,21 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const addB = btn({ id: 'gx-add', class: 'gp-tbtn gx-tb', 'aria-label': t('Add sheet'), title: t('Add sheet') }, icon('plus')); const circ = h('span', { id: 'gx-circ', class: 'gx-circ' }), said = h('span', { class: 'gx-said', role: 'status' }), stats = h('span', { id: 'gx-sum', class: 'gx-sum' }); const status = h('div', { id: 'gx-status', class: 'gp-status gx-status' }, circ, said, h('span', { class: 'grow' }), stats); - const view = h('div', { class: 'gp gp-standalone gx' }, top, tool, fbar, findBar, h('div', { class: 'gx-wrap' }, gridEl, live), h('div', { class: 'gx-tabbar' }, addB, tabs), status); + const view = h('div', { class: 'gp gp-standalone gx' }, top, tool, fbar, findBar, h('div', { class: 'gx-wrap' }, gridEl, live), h('div', { class: 'gx-tabbar' }, o.memory ? null : addB, tabs), status); (host?.mount || document.body).append(view); // The one style read: the grid's font, for measuring text to fit a column. const css = getComputedStyle(gridEl), font = `${css.fontSize} ${css.fontFamily}`; let sayTimer = 0; const say = (text) => { said.textContent = text || ''; clearTimeout(sayTimer); if (text) sayTimer = setTimeout(() => { said.textContent = ''; }, 6000); }; + // The one gate for changes. A workbook from memory is read-only (Save to Documents makes the copy to edit): every path to + // the workbook asks here first (apply, undo and redo, a cell's commit, Replace), and so do the editor, a tab's rename, a + // paste and a number format before they open or build cells. + const writable = () => { + if (!o.memory) return true; + const text = t('Save to Documents to edit.'); + if (said.textContent !== text) say(text); + return false; + }; // ---- the sheet's room: its used range and a margin, grown as it is scrolled or reached (a page can't be 17.9M px) const rooms = new Map(); @@ -544,7 +556,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { if (marquee && marquee.sheet === S().id) kids.push(box(p, marquee.g, 'gx-marquee')); if (reachG) kids.push(box(p, reachG, 'gx-reach')); refs.forEach((x, i) => kids.push(box(p, x, 'gx-ref', REF_COLORS[i % REF_COLORS.length]))); - if (!editing && paneOf(Math.min(last.r1, ext.rows - 1), Math.min(last.c1, ext.cols - 1)) === p) { + if (!editing && !o.memory && paneOf(Math.min(last.r1, ext.rows - 1), Math.min(last.c1, ext.cols - 1)) === p) { const hEl = h('div', { class: 'gx-handle' }); hEl.style.left = `${lx(p, Math.min(last.c1, ext.cols - 1) + 1) - 4}px`; hEl.style.top = `${ly(p, Math.min(last.r1, ext.rows - 1) + 1) - 4}px`; kids.push(hEl); @@ -664,6 +676,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const refused = (e) => { if (!WHY[e?.code]) console.error(e); say(t(WHY[e?.code] || 'Not saved. Try again in a moment.')); }; // big: keep a version of the workbook as it was before (sort, a sheet deleted, a large paste). const apply = (op, { big = false, after = null } = {}) => { + if (!writable()) return null; if (big) nextVersion ||= SD.pack(wb).then((gz) => ({ reason: 'edit', gz, cells: SD.measure(wb).cells })); const before = { sheet: S().id, sel }; let res; @@ -684,6 +697,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { drawNow(true); }; const undo = (redo = false) => { + if (!writable()) return; const from = redo ? redos : undos, to = redo ? undos : redos, x = from.pop(); if (!x) return; if (!redo) undoBytes -= x.bytes; @@ -705,9 +719,10 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { let timer = 0, saving = null, again = false, lastVersion = rec?.lastVersion || 0, foreign = false; const busy = () => !!(timer || saving || dirty.size || dirtyAll || Object.keys(nextPatch).length || nextVersion || (editing && editing.surface.value !== editing.orig)); const pill = () => { state.textContent = busy() ? t('Saving…') : ''; }; - const schedule = () => { clearTimeout(timer); timer = setTimeout(() => { timer = 0; flush(); }, 700); pill(); }; + const schedule = () => { if (o.memory) { flush(); return; } clearTimeout(timer); timer = setTimeout(() => { timer = 0; flush(); }, 700); pill(); }; const flush = () => { clearTimeout(timer); timer = 0; + if (o.memory) { dirty.clear(); dirtyAll = false; nextPatch = {}; nextVersion = null; pill(); return Promise.resolve(); } if (saving) { again = true; return saving; } saving = (async () => { do { @@ -774,10 +789,10 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const next = ch[PAPER].newValue; if (saving) saving.then(() => theirs(next)); else theirs(next); }; - chrome.storage.onChanged.addListener(onStore); - addEventListener('pagehide', () => { if (busy()) { commit(); flush(); } }, { signal }); + if (!o.memory) chrome.storage.onChanged.addListener(onStore); + if (!o.memory) addEventListener('pagehide', () => { if (busy()) { commit(); flush(); } }, { signal }); // A tab closed while a save waits: the browser asks first, as for a PDF's unsaved form. - addEventListener('beforeunload', (e) => { if (busy()) { commit(); flush(); e.preventDefault(); e.returnValue = ''; } }, { signal }); + if (!o.memory) addEventListener('beforeunload', (e) => { if (busy()) { commit(); flush(); e.preventDefault(); e.returnValue = ''; } }, { signal }); document.addEventListener('visibilitychange', () => { if (document.hidden && busy()) flush(); }, { signal }); // Documents: what is unsaved goes first (a frame of a school's page replaces itself, as papers.js's links do). back?.addEventListener('click', (e) => { @@ -805,6 +820,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { editor.style.width = `${Math.min(Math.max(C.size(c) + 1, editor.value.length * 7.5 + 12), Math.max(120, vw - sx(c) - 4))}px`; }; const startEdit = (text, mode, surface = editor) => { + if (!writable()) return; const [r, c] = sel.active; reveal(r, c); editing = { r, c, sheet: S().id, mode, orig: editText(r, c), surface, point: null }; @@ -833,7 +849,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { scroller.scrollLeft = was?.sL || 0; scroller.scrollTop = was?.sT || 0; } } - if (text === x.orig) { stopEdit(); told(); drawNow(false); return true; } + if (text === x.orig || !writable()) { stopEdit(); told(); drawNow(false); return true; } let res; try { res = eng.edit(x.sheet, x.r, x.c, text); } catch (e) { refused(e); return false; } if (res.error) { say(t('There’s a problem with this formula.')); x.surface.focus(); return false; } @@ -947,7 +963,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { s.addEventListener('input', () => typed()); s.addEventListener('click', () => { if (editing) { editing.point = null; typed(); } }); } - fx.addEventListener('focus', () => { if (!editing) startEdit(editText(...sel.active), 'edit', fx); else if (editing.surface === editor) { editing.surface = fx; editor.hidden = true; } }); + fx.addEventListener('focus', () => { if (o.memory) return; if (!editing) startEdit(editText(...sel.active), 'edit', fx); else if (editing.surface === editor) { editing.surface = fx; editor.hidden = true; } }); // A sheet's tab pressed: show() decides. const blurTimers = new Set(); const afterBlur = (fn) => { const id = setTimeout(() => { blurTimers.delete(id); if (!gone) fn(); }, 0); blurTimers.add(id); }; @@ -984,18 +1000,19 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { return { r, c, ax, ay, area, x, y }; }; const edgeAt = (p) => { + if (o.memory) return null; if (p.area === 'col') { if (Math.abs(p.ax - C.offset(p.c + 1)) <= 4) return { axis: 'col', i: p.c }; if (p.c > 0 && Math.abs(p.ax - C.offset(p.c)) <= 4) return { axis: 'col', i: p.c - 1 }; } if (p.area === 'row') { if (Math.abs(p.ay - R.offset(p.r + 1)) <= 3) return { axis: 'row', i: p.r }; if (p.r > 0 && Math.abs(p.ay - R.offset(p.r)) <= 3) return { axis: 'row', i: p.r - 1 }; } return null; }; const onHandle = (p) => { - if (editing || p.area !== 'cell') return false; + if (o.memory || editing || p.area !== 'cell') return false; const g = sel.ranges[sel.ranges.length - 1], r1 = Math.min(g.r1, ext.rows - 1), c1 = Math.min(g.c1, ext.cols - 1); return Math.abs(p.x - sx(c1 + 1)) <= 6 && Math.abs(p.y - sy(r1 + 1)) <= 6; }; scroller.addEventListener('pointermove', (e) => { if (drag) return; - const p = hit(e), ed = edgeAt(p), ch = p.area === 'cell' && !editing ? charts?.hit(p.ax, p.ay) : null; + const p = hit(e), ed = edgeAt(p), ch = p.area === 'cell' && !editing && !o.memory ? charts?.hit(p.ax, p.ay) : null; scroller.style.cursor = ch ? (ch.handle ? 'nwse-resize' : 'move') : ed ? (ed.axis === 'col' ? 'col-resize' : 'row-resize') : onHandle(p) ? 'crosshair' : p.area === 'cell' ? 'cell' : 'default'; }); let drag = null; @@ -1020,7 +1037,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { e.preventDefault(); gridEl.focus({ preventScroll: true }); // A chart: chosen, and dragged to move it (or by its corner to size it). - const ch = p.area === 'cell' ? charts?.hit(p.ax, p.ay) : null; + const ch = p.area === 'cell' && !o.memory ? charts?.hit(p.ax, p.ay) : null; charts?.choose(ch?.id ?? null); if (ch) { drag = { kind: 'chart', ...ch, x: p.ax, y: p.ay }; scroller.setPointerCapture(e.pointerId); return; } const add = e.ctrlKey || e.metaKey; @@ -1109,6 +1126,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { fillOut(g, 'row', r - g.r1); }; const copyAlong = (axis) => { + if (!writable()) return; const g = sel.ranges[sel.ranges.length - 1], cells = []; if (axis === 'row') { if (g.r1 === g.r0) return; for (let c = g.c0; c <= g.c1; c++) { const x = cellAt(g.r0, c); for (let r = g.r0 + 1; r <= g.r1; r++) cells.push([r, c, x ? { ...x, ...(x.f !== undefined && { f: F.move(x.f, r - g.r0, 0) }) } : null]); } } else { if (g.c1 === g.c0) return; for (let r = g.r0; r <= g.r1; r++) { const x = cellAt(r, g.c0); for (let c = g.c0 + 1; c <= g.c1; c++) cells.push([r, c, x ? { ...x, ...(x.f !== undefined && { f: F.move(x.f, 0, c - g.c0) }) } : null]); } } @@ -1176,6 +1194,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { azB.addEventListener('click', () => { sort(false); gridEl.focus(); }); zaB.addEventListener('click', () => { sort(true); gridEl.focus(); }); const setFormat = (code) => { + if (!writable()) return false; try { NF.compile(NF.code(code)); } catch { say(t('That format can’t be used.')); return false; } const cells = [], seen = new Set(); const style = (x) => M.internStyle(wb, { ...(wb.styles[x?.s ?? 0] || {}), fmt: code }); @@ -1260,7 +1279,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const fileNow = async () => o.fileOf(wb, rec?.title || host?.title?.() || ''); toggle(moreB, () => [{ act: 'export', label: t(o.exportLabel(wb)), run: () => { flush(); fileNow().then(o.download, (e) => { console.error(e); say(t('Not saved. Try again in a moment.')); }); } }], t('More')); const when = new Intl.DateTimeFormat(o.locale || undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); - versB.addEventListener('click', async () => { + versB?.addEventListener('click', async () => { if (menu?.opener === versB) { closeMenu(true); return; } commit(); const list = (await papers.versions(key).catch(() => [])).filter((v) => (v.kind || 'paper') === 'sheet'); @@ -1268,6 +1287,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { openMenu(list.length ? list.map((v) => ({ act: 'restore', label: when.format(v.at), run: () => restore(v.id) })) : [{ act: 'none', label: t('No versions yet'), disabled: true, run: () => {} }], below(versB), { opener: versB, label: t('Versions') }); }); const restore = async (id) => { + if (!writable()) return; const v = await papers.version(key, id).catch(() => null), next = v?.gz ? await SD.unpack(v.gz).catch(() => null) : null; if (gone) return; if (!next) { say(t('That file could not be read.')); return; } @@ -1283,7 +1303,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const gridMenu = (at, area) => { const rows = whole('row') || area === 'row', cols = whole('col') || area === 'col'; const { n: nr } = lines('row'), { n: nc } = lines('col'); - const items = [ + const items = o.memory ? [{ act: 'copy', label: t('Copy'), run: () => { gridEl.focus(); document.execCommand('copy'); } }] : [ { act: 'cut', label: t('Cut'), run: () => { gridEl.focus(); document.execCommand('cut'); } }, { act: 'copy', label: t('Copy'), run: () => { gridEl.focus(); document.execCommand('copy'); } }, '-', ...(cols ? [] : [{ act: 'row-above', label: t(nr > 1 ? 'Insert {n} rows above' : 'Insert row above', { n: nr }), run: () => shape('insert', 'row', 'before') }, @@ -1333,6 +1353,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const tabOf = (el) => { const id = Number(el?.closest?.('.gx-tab')?.dataset.sheet); return Number.isInteger(id) ? wb.sheets.findIndex((s) => s.id === id) : -1; }; tabs.addEventListener('click', (e) => { const i = tabOf(e.target); if (i >= 0) { show(i); (editing ? editing.surface : gridEl).focus({ preventScroll: true }); } }); const renameTab = (i) => { + if (!writable()) return; const sh = wb.sheets[i], el = tabs.querySelector(`[data-sheet="${sh.id}"]`); if (!el) return; const input = h('input', { class: 'gx-tab-in', type: 'text', maxlength: '31', autocomplete: 'off', spellcheck: 'false', 'aria-label': t('Sheet name') }); @@ -1358,6 +1379,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { apply({ op: 'addSheet', sheet: clone, at: i + 1, active: i + 1 }); }; const tabMenu = (i, at) => { + if (o.memory) return; const s = wb.sheets[i], shown = wb.sheets.filter((x) => !x.keep?.hidden), hidden = wb.sheets.filter((x) => x.keep?.hidden); openMenu([ { act: 'rename', label: t('Rename'), run: () => renameTab(i) }, @@ -1382,7 +1404,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { // ---- find and replace in the shown sheet: its cells' shown text and formulas, case aside let hits = [], hitAt = -1; - const findOpen = (replace = false) => { findBar.hidden = false; findB.setAttribute('aria-pressed', 'true'); layout(); draw(true); (replace ? repQ : findQ).focus(); findQ.select(); }; + const findOpen = (replace = false) => { findBar.hidden = false; findB.setAttribute('aria-pressed', 'true'); layout(); draw(true); (replace && !o.memory ? repQ : findQ).focus(); findQ.select(); }; const findClose = () => { findBar.hidden = true; findB.setAttribute('aria-pressed', 'false'); hits = []; findN.textContent = ''; layout(); draw(true); gridEl.focus({ preventScroll: true }); }; const search = () => { const q = findQ.value.toLowerCase(); @@ -1395,7 +1417,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const step = (by) => { if (!hits.length) return; hitAt = (hitAt + by + hits.length) % hits.length; landHit(); }; const swap = (text) => { const q = findQ.value; return q ? text.replace(new RegExp(q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), () => repQ.value) : text; }; const replaceOne = () => { - if (hitAt < 0) return; + if (hitAt < 0 || !writable()) return; const [r, c] = hits[hitAt], was = editText(r, c), next = swap(was); // A match in the shown text alone (a format's $ or %) has nothing to replace: the next match is the one. if (next === was) { step(1); return; } @@ -1432,6 +1454,7 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { const copyOut = (e, cut) => { if (!onGrid()) return; e.preventDefault(); + if (cut && !writable()) return; const g = sel.ranges[sel.ranges.length - 1], u = used(S()), r1 = Math.min(g.r1, Math.max(u.r - 1, g.r0)), c1 = Math.min(g.c1, Math.max(u.c - 1, g.c0)); const rows = [], cells = []; for (let r = g.r0; r <= r1; r++) { const a = [], b = []; for (let c = g.c0; c <= c1; c++) { const x = cellAt(r, c); a.push({ text: x ? display(x, c).text : '' }); b.push(x ? { ...x } : null); } rows.push(a); cells.push(b); } @@ -1459,6 +1482,8 @@ var GZ_SHEET_GRID = globalThis.GZ_SHEET_GRID || (() => { document.addEventListener('paste', (e) => { if (!onGrid()) return; e.preventDefault(); + // Before its cells are built: a pasted format is added to the workbook's styles as they are. + if (!writable()) return; const text = e.clipboardData.getData('text/plain'), html = e.clipboardData.getData('text/html'); const g = sel.ranges[sel.ranges.length - 1], r0 = g.r0, c0 = g.c0, cells = []; let rows = 0, cols = 0; diff --git a/safari/Glaze/Glaze Extension/Resources/sheet-kind.js b/safari/Glaze/Glaze Extension/Resources/sheet-kind.js index 945262d29..c8c22cabf 100644 --- a/safari/Glaze/Glaze Extension/Resources/sheet-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/sheet-kind.js @@ -283,6 +283,52 @@ return v; }; const view = (key, host) => edit(key, { host }); + // A workbook's bytes from elsewhere (src { bytes, name, href }: a Canvas file the page read, writer-kinds.js), in the grid + // from memory (sheet-grid.js memory): read-only, nothing kept unless the student saves it. Open original (href) and Save + // to Documents (the file as it came, through importFile while Settings → Writer → Spreadsheets takes files; once saved, + // Open in Documents, the copy to edit) before ⋯. host: { mount, t, title, shown(), failed(reason), esc() }. → the grid's + // surface, or null. + const show = async (src, host) => { + const tt = host?.t || t, name = String(src?.name || '').slice(0, 200), bytes = src?.bytes?.length ? src.bytes : null; + const wb = bytes ? await workbookOf(new File([bytes], name || 'Spreadsheet.xlsx')).catch((e) => { console.warn(e); return null; }) : null; + if (!wb) { host?.failed?.(bytes ? 'read' : 'init'); return null; } + await need(CHARTS).catch((e) => console.error(e)); + const button = (act, label, cls) => h('button', { type: 'button', class: cls, 'data-act': act, text: label }); + const origB = /^https?:/.test(src.href || '') ? button('original', tt('Open original'), 'gp-tbtn') : null, saveB = button('save', tt('Save to Documents'), 'gp-submit'); + const v = GZ_SHEET_GRID.mount({ key: null, wb, rec: null, sigs: new Map(), fresh: false, memory: true, t: tt, locale, papers: null, show: shower, local: false, fileOf, download, + exportLabel: () => 'Export .xlsx', host: { ...host, title: () => name, tools: [origB, saveB].filter(Boolean) } }); + origB?.addEventListener('click', () => { window.open(src.href, '_blank', 'noopener,noreferrer'); }); + saveB.addEventListener('click', async () => { + if (saveB.disabled) return; + saveB.disabled = true; + v.say(tt('Saving…')); + const progress = DK.progress((p) => { if (!p.end && !p.failed) v.say(tt('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); + try { + const take = DK.get('sheet')?.importFile; + if (typeof take !== 'function') throw new Error('off'); + await need(['lib/writer-papers.js']); + const got = await take(new File([bytes], name || 'Spreadsheet.xlsx'), { progress }), key = typeof got === 'string' ? got : got?.key; + progress.end(); + v.say(tt('Saved to Documents')); + if (key) { + const openA = h('a', { class: 'gp-submit', 'data-act': 'open', href: `papers.html?${DK.KINDS.sheet.param}=${encodeURIComponent(key)}`, target: '_blank', rel: 'noopener', text: tt('Open in Documents') }); + saveB.replaceWith(openA); + openA.focus(); + } + } catch (e) { + const big = e instanceof RangeError && e.message === SAYS.size; + if (!big) console.error(e); + progress.fail(e); + v.say(tt(big ? SAYS.size : 'Not saved. Try again in a moment.')); + saveB.disabled = false; + } + }); + // Esc the grid didn't take (a cell, a menu or a field takes its own): back to the page that opened it. + document.addEventListener('keydown', (e) => { if (!e.defaultPrevented && e.key === 'Escape' && !e.target?.closest?.('input, textarea, select, [role="menu"]')) host?.esc?.(); }); + globalThis.GZ_PAPERS_MOTION?.shown('paper'); + host?.shown?.(); + return v; + }; // Opening, copying and exporting stay registered; creating and importing follow the switch, read as Settings shows it // (defaults.js: its default where none is stored). papers.html loads defaults.js on demand; writer.html has it already, @@ -290,7 +336,7 @@ const DEFAULTS = ['lib/color.js', 'lib/surfaces.js', 'lib/fonts.js', 'defaults.js']; const on = Promise.resolve().then(() => (typeof GZ_SANITIZE === 'undefined' ? need(DEFAULTS) : null)).then(() => chrome.storage.sync.get('writer')) .then((r) => GZ_SANITIZE({ writer: r?.writer }).writer.sheets === true, () => false); - DK.wait(on.then((yes) => DK.register('sheet', { edit, view, into, empty, thumb, file, exportFile, version, text, measure, copy, says: SAYS, exportLabel: 'Export .xlsx', exportLabelOf: (row) => labelOf(row?.ext), + DK.wait(on.then((yes) => DK.register('sheet', { edit, view, show, into, empty, thumb, file, exportFile, version, text, measure, copy, says: SAYS, exportLabel: 'Export .xlsx', exportLabelOf: (row) => labelOf(row?.ext), ...(yes && { importFile, create }) }))); globalThis.GZ_SHEET_KIND = Object.freeze({ shower, fileOf }); })(); diff --git a/safari/Glaze/Glaze Extension/Resources/slides-kind.css b/safari/Glaze/Glaze Extension/Resources/slides-kind.css index 2b5873263..4442cb839 100644 --- a/safari/Glaze/Glaze Extension/Resources/slides-kind.css +++ b/safari/Glaze/Glaze Extension/Resources/slides-kind.css @@ -73,3 +73,16 @@ dialog.gs-present::backdrop { background: #000; } .gs-find { grid-row: 2; grid-column: 1 / -1; min-width: 0; } .gs-canvas { padding: 12px; } } + +/* Print: the slides alone, a page each (its size and @page: slides-kind.js print, through the CSSOM), the notes below + with Print with notes; nothing of the Look around them */ +.gs-print { display: none; } +@media print { + :root[data-gs-print], :root[data-gs-print] body { height: auto !important; overflow: visible !important; background: #fff !important; } + :root[data-gs-print] > :not(head, body), :root[data-gs-print] body > :not(.gs-print) { display: none !important; } + :root[data-gs-print] body > .gs-print { display: block !important; } + .gs-print-page { box-sizing: border-box; overflow: hidden; break-after: page; break-inside: avoid; background: #fff; color: #000; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + .gs-print-page:last-child { break-after: auto; } + .gs-print[data-notes] .gs-stage { margin: 0 auto; box-shadow: 0 0 0 1px #000; } + .gs-print-notes { margin-top: 24px; white-space: pre-wrap; font: 12pt/1.4 Calibri, Carlito, sans-serif; overflow: hidden; } +} diff --git a/safari/Glaze/Glaze Extension/Resources/slides-kind.js b/safari/Glaze/Glaze Extension/Resources/slides-kind.js index c03319d8d..5217aef3d 100644 --- a/safari/Glaze/Glaze Extension/Resources/slides-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/slides-kind.js @@ -1,8 +1,8 @@ // Slide decks on the Documents page (papers.html). Settings → Writer → Slides (writer.slides, its default in defaults.js) permits // importing .pptx and .ppsx files; existing decks always remain available for viewing. Documents of the slides kind // (lib/doc-kinds.js), each card showing slide 1, and open in a viewer: a rail of thumbnails, the slide (lib/slides-render.js -// on lib/pptx.js's model), zoom, find in the slides' and notes' text, the speaker notes, and Present (a dialog, full screen -// where the browser allows, Rimeward's keys, hidden slides skipped). Its chrome wears the Look; the slide stays as authored. +// on lib/pptx.js's model), zoom, find in the slides' and notes' text, the speaker notes, Present (a dialog, full screen +// where the browser allows, Rimeward's keys, hidden slides skipped) and Print (a page per slide, notes optionally below). Its chrome wears the Look; the slide stays as authored. // Export gives back the bytes imported. A viewer, never an editor: the record only changes by the page's rename. // Storage (STORE): the deck's bytes in the worker's file store (lib/doc-files.js, over its port), the record pointing at // them (file: { hash, size, type }), up to the kind's max; a record from before the store holds them as base64, read as @@ -196,7 +196,8 @@ // A deck as a new standalone document, titled by its file's name. → its key. const importFile = async (file, { progress = null } = {}) => { if (!Number.isSafeInteger(file?.size) || file.size <= 0 || file.size > STORE.max) throw new RangeError(SAYS.size); - await need(LIBS); + // A deck shown from elsewhere saves from writer.html, which has no store of papers loaded until then. + await need([...LIBS, 'lib/writer-papers.js']); const fields = await bring(file, progress), title = stem(file.name); const key = GZ_WRITER_PAPERS.newKey(); await papers().save(key, { kind: 'slides', ...fields, named: !!title, title }, { reason: 'import' }); @@ -264,19 +265,22 @@ // host: an assignment's frame (writer-kinds.js view): { mount, t, title, tools }, the viewer in mount with the // assignment's title and the frame's tools (Submit…) before ⋯, no way back to Documents; → its surface. - // o: papers.js's { fresh } (a kind that opens a new document empty), or view's { host }. - const edit = async (key, { host = null } = {}) => { + // o: papers.js's { fresh } (a kind that opens a new document empty), or view's { host }, or show's { host, src }: a deck's + // bytes from elsewhere (src { bytes, name, href }: a Canvas file the page read, writer-kinds.js) opened from memory, nothing + // kept; Open original (href) and Save to Documents (the same bytes through importFile) beside ⋯. host then also has + // shown() once the slide is drawn, failed(reason), esc(). + const edit = async (key, { host = null, src = null } = {}) => { const root = document.documentElement; const life = new AbortController(), signal = life.signal; if (host) words = host.t || null; else root.dataset.mode = 'slides'; await need(LIBS); - const rec = await papers().get(key).catch(() => null); + const rec = src ? null : await papers().get(key).catch(() => null); // A record from before the store moves into it as it is opened (the worker reads its base64 again). if (typeof rec?.file === 'string') papers().migrate?.(key).catch(() => {}); let failed = null; - const bytes = await STORE.get(rec).catch((e) => { failed = e; return null; }); + const bytes = src ? (src.bytes?.length ? src.bytes : null) : await STORE.get(rec).catch((e) => { failed = e; return null; }); const deck = bytes ? await GZ_PPTX.read(bytes).catch((e) => { console.warn(e); return null; }) : null; - const Rr = R(), labels = LABELS(), title = host?.title?.() || rec?.title || t('Untitled document'), n = deck ? deck.slides.length : 0; + const Rr = R(), labels = LABELS(), title = host?.title?.() || rec?.title || src?.name || t('Untitled document'), n = deck ? deck.slides.length : 0; const media = deck ? await blobs(bytes, deck, new Set(deck.media.map((m) => m.path))) : new Map(); let cur = 0, zoom = 'fit', term = '', hits = [], hit = -1, menu = null, showing = null; let notesOpen = false; @@ -295,6 +299,8 @@ const findNext = btn({ id: 'gs-find-next', class: 'gs-btn', 'aria-label': t('Next'), title: t('Next') }, icon('down')); const notesB = btn({ id: 'gs-notes', class: 'gs-btn gs-label', 'aria-pressed': String(notesOpen) }, t('Notes')); const presentB = btn({ id: 'gs-present', class: 'gs-btn gs-label gs-go' }, icon('present'), h('span', { text: t('Present') })); + const origB = src && /^https?:/.test(src.href || '') ? btn({ class: 'gs-btn gs-label', 'data-act': 'original' }, t('Open original')) : null; + const saveB = src ? btn({ class: 'gs-btn gs-label', 'data-act': 'save', disabled: !deck }, t('Save to Documents')) : null; const moreB = btn({ class: 'gs-btn gs-more', 'aria-haspopup': 'menu', 'aria-expanded': 'false', 'aria-label': t('More'), title: t('More') }, icon('dots')); const canvasEl = h('div', { id: 'gs-canvas', class: 'gs-canvas', role: 'tabpanel', tabindex: '0' }); const notesEl = h('section', { id: 'gs-notes-pane', class: 'gs-notes', 'aria-label': t('Notes'), hidden: !notesOpen }); @@ -311,7 +317,7 @@ const view = h('div', { class: 'gp gp-standalone gs-view' }, h('header', { class: 'gp-top' }, host ? null : h('a', { href: 'papers.html', text: t('Documents') }), h('h1', { text: title }), h('span', { class: 'grow' }), state), h('div', { class: 'gs-tool' }, h('div', { class: 'gs-group' }, prevB, atEl, nextB), h('div', { class: 'gs-group' }, outB, zoomB, inB), - h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), moreB), + h('div', { class: 'gs-find', role: 'search' }, q, findN, findPrev, findNext), h('span', { class: 'grow' }), notesB, presentB, ...(host?.tools || []), origB, saveB, moreB), h('div', { class: 'gs-work' }, railEl, h('div', { class: 'gs-main' }, canvasEl, notesEl))); (host?.mount || document.body).append(view); if (!deck) { @@ -319,7 +325,7 @@ canvasEl.append(said); for (const b of [prevB, nextB, outB, zoomB, inB, q, findPrev, findNext, notesB, presentB]) b.disabled = true; // A file the store no longer has: Delete (to Trash, as Documents' own) or Import… it again, where this is a document of its own. - if (failed?.why === 'missing' && !host && GZ_WRITER_PAPERS.local(key)) { + if (failed?.why === 'missing' && !host && !src && GZ_WRITER_PAPERS.local(key)) { const pick = h('input', { type: 'file', accept: DK.KINDS.slides.accept.join(','), hidden: true, onchange: () => { const f = pick.files[0]; pick.value = ''; @@ -506,11 +512,59 @@ }; presentB.addEventListener('click', present); - // ---- ⋯: export in the deck's own format + // ---- Print…: the slides a show plays, each on a page of its own (GZ_SLIDES_RENDER.printPage, its @page through the + // CSSOM), with Print with notes each slide's notes below it on the deck's notes page; the pictures loaded before the + // browser's print dialog opens. Only the pages print (slides-kind.css @media print), never the Look's chrome. + let printing = null; + const PICTURE_WAIT = 15000; + const loaded = (el) => new Promise((ok) => { + const done = () => { clearTimeout(timer); el.setAttribute('data-gs-loaded', ''); ok(); }; + const timer = setTimeout(ok, PICTURE_WAIT); + el.addEventListener('load', done, { once: true }); + el.addEventListener('error', () => { clearTimeout(timer); ok(); }, { once: true }); + }); + const endPrint = () => { + if (!printing) return; + printing.box.remove(); + document.adoptedStyleSheets = document.adoptedStyleSheets.filter((x) => x !== printing.sheet); + root.removeAttribute('data-gs-print'); + document.title = printing.title; + printing = null; + }; + const print = async (withNotes = false) => { + if (!deck || printing) return; + const pg = Rr.printPage(deck, { notes: withNotes }), seq = Rr.visible(deck); + if (!pg || !seq.length) return; + const box = h('div', { class: 'gs-print', 'data-notes': withNotes, 'aria-hidden': 'true' }); + for (const i of seq) { + const s = deck.slides[i]; + box.append(h('section', { class: 'gs-print-page', 'data-slide': String(i) }, Rr.stage(s, deck, { document, media, labels, scale: pg.scale }), + withNotes && s.notes ? h('div', { class: 'gs-print-notes', text: s.notes }) : null)); + } + // Each picture heard from as it is drawn, before the pages join the document (a load can't come first). + const pictures = [...box.querySelectorAll('img, image')].map(loaded); + const sheet = new CSSStyleSheet(); + sheet.replaceSync(`@page { size: ${pg.w}px ${pg.h}px; margin: 0; } .gs-print-page { width: ${pg.w}px; height: ${pg.h}px; padding: ${pg.margin}px; }`); + printing = { box, sheet, title: document.title }; + document.title = title; + document.body.append(box); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + root.setAttribute('data-gs-print', withNotes ? 'notes' : 'slides'); + await Promise.all([...pictures, document.fonts?.ready]); + if (printing?.box !== box) return; + box.setAttribute('data-ready', ''); + addEventListener('afterprint', endPrint, { once: true, signal }); + window.print(); + }; + const notesAny = !!deck?.slides.some((s) => !s.hidden && s.notes); + + // ---- ⋯: print, and export in the deck's own format const closeMenu = (back) => { if (!menu) return; menu.remove(); menu = null; moreB.setAttribute('aria-expanded', 'false'); if (back) moreB.focus(); }; const openMenu = () => { menu = h('div', { class: 'pop gp-menu gs-menu', role: 'menu', 'aria-label': t('More') }, - btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'export', disabled: !bytes, onclick: () => { closeMenu(true); exportFile(key).catch((e) => console.warn(e)); } }, t('Export .pptx').replace('.pptx', rec?.ext === '.ppsx' ? '.ppsx' : '.pptx'))); + btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print', disabled: !deck, onclick: () => { closeMenu(true); print(false).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print…')), + notesAny ? btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'print-notes', onclick: () => { closeMenu(true); print(true).catch((e) => { console.warn(e); endPrint(); }); } }, t('Print with notes…')) : null, + src ? null : btn({ role: 'menuitem', tabindex: '-1', 'data-act': 'export', disabled: !bytes, onclick: () => { closeMenu(true); exportFile(key).catch((e) => console.warn(e)); } }, t('Export .pptx').replace('.pptx', rec?.ext === '.ppsx' ? '.ppsx' : '.pptx'))); document.body.append(menu); const r = moreB.getBoundingClientRect(); menu.style.setProperty('left', `${Math.max(4, Math.min(r.right - menu.offsetWidth, innerWidth - menu.offsetWidth - 4))}px`); @@ -534,6 +588,8 @@ addEventListener('keydown', (e) => { if (e.defaultPrevented || presenting || menu || !deck) return; if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'f') { e.preventDefault(); e.stopPropagation(); q.focus(); q.select(); } + // ⌘P / Ctrl+P: the slides, not the viewer around them. + if (view.contains(e.target) && (e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'p') { e.preventDefault(); e.stopPropagation(); print(false).catch((x) => { console.warn(x); endPrint(); }); } }, { capture: true, signal }); document.addEventListener('keydown', (e) => { if (e.defaultPrevented || presenting || menu || !deck) return; @@ -544,15 +600,41 @@ show(to); }, { signal }); + // A file from elsewhere: the original in a new tab; the deck into Documents as a new document, as an import makes one. + origB?.addEventListener('click', () => { window.open(src.href, '_blank', 'noopener,noreferrer'); }); + saveB?.addEventListener('click', async () => { + if (!deck || saveB.disabled) return; + saveB.disabled = true; + const say = (x) => { state.textContent = x; }; + say(t('Saving…')); + const progress = DK.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); + try { + // Only while Settings → Writer → Slides takes files, as Documents' own import. + if (typeof DK.get('slides')?.importFile !== 'function') throw new Error('off'); + const ext = /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx', name = /\.pp[ts]x$/i.test(src.name || '') ? src.name : `${src.name || t('Slides')}${ext}`; + await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); + progress.end(); + say(t('Saved to Documents')); + } catch (e) { + const big = e instanceof RangeError && e.message === SAYS.size; + if (!big) console.error(e); + progress.fail(e); + say(t(big ? SAYS.size : 'Not saved. Try again in a moment.')); + saveB.disabled = false; + } + }); + if (src) document.addEventListener('keydown', (e) => { if (!e.defaultPrevented && e.key === 'Escape' && !presenting && !menu) { e.preventDefault(); host?.esc?.(); } }, { signal }); + draw(); globalThis.GZ_PAPERS_MOTION?.shown('paper'); + if (src) { if (deck) host?.shown?.(); else host?.failed?.(bytes ? 'read' : 'init'); } // What an assignment's submit sheet sends (writer-submit.js): the deck as it was brought. return { pages: () => null, say: (text) => { if (state.textContent !== text) state.textContent = text || ''; }, ready: () => !!deck, save: async () => true, dispose: () => { - life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); + life.abort(); sizing.disconnect(); near.disconnect(); closeMenu(); presenting?.close(); endPrint(); for (const u of media.values()) { URL.revokeObjectURL(u); made.delete(u); } }, - file: async () => { const f = await file(key); if (!f) throw new Error('no file'); return { bytes: f.bytes, mime: f.mime, ext: rec?.ext === '.ppsx' ? '.ppsx' : '.pptx' }; } }; + file: async () => { if (src) return { bytes: src.bytes, mime: MIME[/\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx'], ext: /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx' }; const f = await file(key); if (!f) throw new Error('no file'); return { bytes: f.bytes, mime: f.mime, ext: rec?.ext === '.ppsx' ? '.ppsx' : '.pptx' }; } }; }; // A deck brought to an assignment (Open in Glaze, writer-kinds.js): the attempt's record becomes it, the paper it held // kept as a version first (the 'import' save), its title the assignment's. → the record. @@ -570,6 +652,6 @@ const DEFAULTS = ['lib/color.js', 'lib/surfaces.js', 'lib/fonts.js', 'defaults.js']; const sanitizer = () => (typeof GZ_SANITIZE === 'function' ? null : GZ_WRITER_PAPER.need(DEFAULTS)); const on = Promise.resolve().then(sanitizer).then(() => chrome.storage.sync.get('writer')).then((r) => GZ_SANITIZE({ writer: r?.writer }).writer.slides === true).catch(() => false); - DK.wait(on.then((yes) => DK.register('slides', { edit, view: (key, host) => edit(key, { host }), into, thumb, file, exportFile, text, ...(yes && { importFile }), measure, copy, says: SAYS, exportLabel: 'Export .pptx' }))); + DK.wait(on.then((yes) => DK.register('slides', { edit, view: (key, host) => edit(key, { host }), show: (src, host) => edit(null, { host, src: { ...src } }), into, thumb, file, exportFile, text, ...(yes && { importFile }), measure, copy, says: SAYS, exportLabel: 'Export .pptx' }))); globalThis.GZ_SLIDES_KIND = Object.freeze({ STORE }); })(); diff --git a/safari/Glaze/Glaze Extension/Resources/writer-kinds.js b/safari/Glaze/Glaze Extension/Resources/writer-kinds.js index d5b3a0e92..916f61529 100644 --- a/safari/Glaze/Glaze Extension/Resources/writer-kinds.js +++ b/safari/Glaze/Glaze Extension/Resources/writer-kinds.js @@ -211,20 +211,22 @@ var GZ_WRITER_KINDS = globalThis.GZ_WRITER_KINDS || (() => { }); // ---- a file shown from elsewhere (writer.js view mode; features/bbpreview.js: Blackboard's preview of an attachment, - // converted to a PDF, which the page read): init { kind: 'pdf', view: { bytes (an ArrayBuffer, transferred), name, href } } - // → the kind's show, nothing kept unless the student saves it to Documents. The page hears view:shown once page 1 is + // converted to a PDF, which the page read; features/canvasopen.js: a Canvas file the page read on a click): init { kind: + // 'pdf', 'slides' or 'sheet', view: { bytes (an ArrayBuffer, transferred), name, href } } → the kind's show, nothing kept unless the + // student saves it to Documents. The page hears view:shown once page 1 is // drawn, view:failed (with a reason) for anything else, so it keeps Blackboard's frame, and view:esc for an Esc the // viewer didn't take. + const SHOWN = ['pdf', 'slides', 'sheet']; frame.on('view', async (m) => { const v = m.view, failed = (reason) => frame.say({ type: 'view:failed', reason: String(reason || 'error') }); const bytes = v?.bytes instanceof ArrayBuffer && v.bytes.byteLength ? new Uint8Array(v.bytes) : null; - if (m.kind !== 'pdf' || !bytes) { failed('init'); return; } - const k = await kind('pdf').catch((e) => { console.error(e); return null; }); + if (!SHOWN.includes(m.kind) || !bytes) { failed('init'); return; } + const k = await kind(m.kind).catch((e) => { console.error(e); return null; }); if (typeof k?.show !== 'function') { failed('kind'); return; } const root = document.documentElement, el = document.createElement('div'); el.className = 'gk'; document.body.append(el); - root.dataset.doc = 'pdf'; + root.dataset.doc = m.kind; root.dataset.view = 'full'; const name = typeof v.name === 'string' ? v.name.slice(0, 200) : '', href = typeof v.href === 'string' && /^https?:/.test(v.href) ? v.href : ''; try { diff --git a/safari/Glaze/Glaze Extension/Resources/writer-paper.css b/safari/Glaze/Glaze Extension/Resources/writer-paper.css index 76de1f117..b208f5fd8 100644 --- a/safari/Glaze/Glaze Extension/Resources/writer-paper.css +++ b/safari/Glaze/Glaze Extension/Resources/writer-paper.css @@ -120,6 +120,7 @@ .gp-submit { padding: 7px 16px; border-radius: 999px; background: var(--gz-accent); color: var(--gz-accent-ink); font-weight: 700; font-size: 13px; white-space: nowrap; flex: none; } .gp button:disabled { cursor: default; opacity: .45; } .gp-submit:disabled { opacity: .55; } +a.gp-submit { text-decoration: none; } .gp button:focus-visible, .gp [tabindex]:focus-visible { outline: 2px solid var(--gz-accent); outline-offset: 1px; } .gp-ico { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; flex: none; } .gp-caret { width: 14px; height: 14px; } From 05c70a2c421e6e2bba0707bdd843cd18a0d3f064 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:23:57 -0400 Subject: [PATCH 205/907] A saved PDF or deck preview's Save becomes Open in Documents, the saved copy's link, as a spreadsheet's does --- pdf-kind.js | 8 +++++++- scripts/canvas-suite-check.js | 4 ++++ scripts/ultra-preview-check.js | 3 +++ slides-kind.js | 8 +++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pdf-kind.js b/pdf-kind.js index c74dd928a..74be4061f 100644 --- a/pdf-kind.js +++ b/pdf-kind.js @@ -378,9 +378,15 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const progress = GZ_DOC_KINDS.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); try { if (src.bytes.length > STORE.max) throw new RangeError(TOO_BIG_SAVE); - await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); + const key = await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); progress.end(); say(t('Saved to Documents')); + // The saved copy is the one to mark up: Save becomes its link, as a spreadsheet preview's does (sheet-kind.js). + if (typeof key === 'string' && key) { + const openA = h('a', { class: 'gp-submit', 'data-act': 'open', href: `papers.html?${GZ_DOC_KINDS.KINDS.pdf.param}=${encodeURIComponent(key)}`, target: '_blank', rel: 'noopener', text: t('Open in Documents') }); + saveB.replaceWith(openA); + openA.focus(); + } } catch (e) { const big = e instanceof RangeError && (e.message === TOO_BIG || e.message === TOO_BIG_SAVE); if (!big) console.error(e); diff --git a/scripts/canvas-suite-check.js b/scripts/canvas-suite-check.js index 27e26cd7f..a1a901c72 100644 --- a/scripts/canvas-suite-check.js +++ b/scripts/canvas-suite-check.js @@ -197,7 +197,11 @@ const WIKI = strip(fixture('canvas-wiki-page.html')).replace(/(
      fr.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent === 'Saved to Documents'), async () => `Saved to Documents (says "${await fr.evaluate(() => document.querySelector('.gp-top .gp-state')?.textContent)}"; errors ${JSON.stringify(errors)})`); const kept = await sw.evaluate(async () => { const all = await chrome.storage.local.get(null); return Object.entries(all).filter(([k, v]) => k.startsWith('writerPaper:') && v?.kind === 'slides').map(([, v]) => ({ title: v.title, slides: v.slides })); }); + const keptKey = await sw.evaluate(async () => Object.entries(await chrome.storage.local.get(null)).find(([k, v]) => k.startsWith('writerPaper:') && v?.kind === 'slides')?.[0].slice('writerPaper:'.length)); check(kept.length === 1 && kept[0].title === 'Week 3' && kept[0].slides === 10, `Save to Documents keeps the deck (${JSON.stringify(kept)})`); + const openDeck = await fr.evaluate(() => { const a = document.querySelector('[data-act="open"]'); return a && { tag: a.tagName, text: a.textContent, href: a.getAttribute('href'), save: !!document.querySelector('[data-act="save"]') }; }); + check(openDeck?.tag === 'A' && openDeck.text === 'Open in Documents' && openDeck.href === `papers.html?slides=${encodeURIComponent(keptKey)}` && !openDeck.save, + `Save to Documents becomes Open in Documents, the saved deck's link (${JSON.stringify(openDeck)})`); await page.click(rowB(80)); await until(() => page.evaluate(() => !document.querySelector('iframe.gz-co-frame')), 'the button again closes the viewer'); check(await page.getAttribute(rowB(80), 'aria-expanded') === 'false', 'the row\'s button again closes Glaze\'s viewer'); diff --git a/scripts/ultra-preview-check.js b/scripts/ultra-preview-check.js index d32b57885..927c6efb2 100644 --- a/scripts/ultra-preview-check.js +++ b/scripts/ultra-preview-check.js @@ -221,6 +221,9 @@ const quiet = async (fn, label, ms = 1500) => { const end = Date.now() + ms; whi check(saved.length === 2 && lab?.kind === 'pdf' && lab.size === pdfs[LAB.hash].length && lab.pages === 3, `Save to Documents adds one PDF record (${JSON.stringify(saved)})`); const index = await worker.evaluate(async () => (await chrome.storage.local.get('writerPapers')).writerPapers); check(JSON.stringify(index || {}).includes(lab.k.slice('writerPaper:'.length)), 'and it lists in Documents (the index row)'); + const openPdf = await fr.evaluate(() => { const a = document.querySelector('[data-act="open"]'); return a && { tag: a.tagName, text: a.textContent, href: a.getAttribute('href'), save: !!document.querySelector('[data-act="save"]') }; }); + check(openPdf?.tag === 'A' && openPdf.text === 'Open in Documents' && openPdf.href === `papers.html?pdf=${encodeURIComponent(lab.k.slice('writerPaper:'.length))}` && !openPdf.save, + `Save to Documents becomes Open in Documents, the saved PDF's link (${JSON.stringify(openPdf)})`); await set({ enhance: { docPages: 'plain' } }); // ---- the switch off: Blackboard's frames back; on with a preview open: Glaze's at once diff --git a/slides-kind.js b/slides-kind.js index 5217aef3d..36d9c0d66 100644 --- a/slides-kind.js +++ b/slides-kind.js @@ -612,9 +612,15 @@ // Only while Settings → Writer → Slides takes files, as Documents' own import. if (typeof DK.get('slides')?.importFile !== 'function') throw new Error('off'); const ext = /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx', name = /\.pp[ts]x$/i.test(src.name || '') ? src.name : `${src.name || t('Slides')}${ext}`; - await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); + const key = await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); progress.end(); say(t('Saved to Documents')); + // Save becomes the saved copy's link, as a spreadsheet preview's does (sheet-kind.js). + if (typeof key === 'string' && key) { + const openA = h('a', { class: 'gp-submit', 'data-act': 'open', href: `papers.html?${DK.KINDS.slides.param}=${encodeURIComponent(key)}`, target: '_blank', rel: 'noopener', text: t('Open in Documents') }); + saveB.replaceWith(openA); + openA.focus(); + } } catch (e) { const big = e instanceof RangeError && e.message === SAYS.size; if (!big) console.error(e); From 36a1f2a3345bd58ebf0297a672f9ee7b295605c6 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:29:47 -0400 Subject: [PATCH 206/907] Refresh the Safari resource mirror for the previews' Open in Documents --- safari/Glaze/Glaze Extension/Resources/pdf-kind.js | 8 +++++++- safari/Glaze/Glaze Extension/Resources/slides-kind.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js index c74dd928a..74be4061f 100644 --- a/safari/Glaze/Glaze Extension/Resources/pdf-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/pdf-kind.js @@ -378,9 +378,15 @@ var GZ_PDF_KIND = globalThis.GZ_PDF_KIND || (() => { const progress = GZ_DOC_KINDS.progress((p) => { if (!p.end && !p.failed) say(t('Saving… {n}%', { n: Math.floor(p.fraction * 10) * 10 })); }); try { if (src.bytes.length > STORE.max) throw new RangeError(TOO_BIG_SAVE); - await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); + const key = await importFile(new File([src.bytes], src.name || 'Document.pdf', { type: 'application/pdf' }), { progress }); progress.end(); say(t('Saved to Documents')); + // The saved copy is the one to mark up: Save becomes its link, as a spreadsheet preview's does (sheet-kind.js). + if (typeof key === 'string' && key) { + const openA = h('a', { class: 'gp-submit', 'data-act': 'open', href: `papers.html?${GZ_DOC_KINDS.KINDS.pdf.param}=${encodeURIComponent(key)}`, target: '_blank', rel: 'noopener', text: t('Open in Documents') }); + saveB.replaceWith(openA); + openA.focus(); + } } catch (e) { const big = e instanceof RangeError && (e.message === TOO_BIG || e.message === TOO_BIG_SAVE); if (!big) console.error(e); diff --git a/safari/Glaze/Glaze Extension/Resources/slides-kind.js b/safari/Glaze/Glaze Extension/Resources/slides-kind.js index 5217aef3d..36d9c0d66 100644 --- a/safari/Glaze/Glaze Extension/Resources/slides-kind.js +++ b/safari/Glaze/Glaze Extension/Resources/slides-kind.js @@ -612,9 +612,15 @@ // Only while Settings → Writer → Slides takes files, as Documents' own import. if (typeof DK.get('slides')?.importFile !== 'function') throw new Error('off'); const ext = /\.ppsx$/i.test(src.name || '') ? '.ppsx' : '.pptx', name = /\.pp[ts]x$/i.test(src.name || '') ? src.name : `${src.name || t('Slides')}${ext}`; - await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); + const key = await importFile(new File([src.bytes], name, { type: MIME[ext] }), { progress }); progress.end(); say(t('Saved to Documents')); + // Save becomes the saved copy's link, as a spreadsheet preview's does (sheet-kind.js). + if (typeof key === 'string' && key) { + const openA = h('a', { class: 'gp-submit', 'data-act': 'open', href: `papers.html?${DK.KINDS.slides.param}=${encodeURIComponent(key)}`, target: '_blank', rel: 'noopener', text: t('Open in Documents') }); + saveB.replaceWith(openA); + openA.focus(); + } } catch (e) { const big = e instanceof RangeError && e.message === SAYS.size; if (!big) console.error(e); From e721315ac6ce3298e1b323b5f4b2756ad5fb9680 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:33:19 -0400 Subject: [PATCH 207/907] Test that a Drive request disconnected while its token comes sends nothing --- test/drive.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/drive.test.js b/test/drive.test.js index df7de4098..d234a4b67 100644 --- a/test/drive.test.js +++ b/test/drive.test.js @@ -213,6 +213,12 @@ test('a request stops if Drive was connected to another account, or disconnected assert.deepEqual(await run(['a@school.edu', 'b@school.edu']), { r: 'connect', log: [] }); assert.deepEqual(await run(['a@school.edu', null]), { r: 'connect', log: [] }); assert.deepEqual(await run([null]), { r: 'connect', log: [] }); + // Disconnected while the token was on its way: nothing sent. + let gone = 'a@school.edu'; + const sent = []; + const late = D.create({ fetch: async (url) => { sent.push(url); return json(200, FILES.docx); }, token: async () => { gone = null; return 'tok'; }, account: async () => gone }); + assert.equal(await late.meta(FILES.docx.id).then(() => 'ok', (e) => e.why), 'connect'); + assert.deepEqual(sent, []); // Mid-upload: the save's update finds another account and sends nothing. let who = 'a@school.edu'; const log = []; From a8d621eae466584117f70008ff99aecf758ccd03 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:33:30 -0400 Subject: [PATCH 208/907] Check Drive's account again once the token is in hand, and ask a page's Drive status again after a change it may have missed --- lib/drive.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/drive.js b/lib/drive.js index 34c959a56..9014d3fb0 100644 --- a/lib/drive.js +++ b/lib/drive.js @@ -77,6 +77,11 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // connected to another account or none before any of its calls), boundary() } const create = (env) => { const bound = env.account ? Promise.resolve().then(env.account).catch(() => null) : null; + const still = async () => { + if (!bound) return; + const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); + if (!was || now !== was) throw new Fail('connect'); + }; const send = (url, init) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); const reason = async (r) => { const j = await r.json().catch(() => null); @@ -91,11 +96,9 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { const call = async (url, init = {}) => { for (let i = 0; ; i++) { let t, r; - if (bound) { - const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); - if (!was || now !== was) throw new Fail('connect'); - } + await still(); try { t = await env.token({ interactive: true }); } catch (e) { throw new Fail(WHY.includes(e?.why) ? e.why : 'auth'); } + await still(); // a Disconnect while the token came: Calendar's grant may keep it valid try { r = await send(url, { ...init, headers: { authorization: `Bearer ${t}`, ...init.headers } }); } catch { throw new Fail('network'); } if (r.ok) return r; if (r.status === 401 && i === 0) { @@ -289,14 +292,20 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { if (typeof document === 'undefined' || !globalThis.chrome?.runtime?.sendMessage) return null; // A framed Documents is answered once its document has claimed its pass: until then 'sender', asked again after 0.5, // 1, 2, 4 and 8 seconds. - let connected = false, asking = null; + // A change while one is asked is asked again once it answers, its answer then set aside as stale. + let connected = false, asking = null, again = false; const shown = new Set(); - const ask = (n = 0) => (asking ||= chrome.runtime.sendMessage({ type: 'glaze:drive', op: 'status' }).catch(() => null).then((r) => { - asking = null; - if (r?.why === 'sender' && n < 5) setTimeout(() => ask(n + 1), 500 * 2 ** n); - const on = r?.connected === true; - if (on !== connected) { connected = on; for (const f of shown) try { f(); } catch (e) { console.error(e); } } - })); + const ask = (n = 0) => { + if (asking) { again = true; return asking; } + asking = chrome.runtime.sendMessage({ type: 'glaze:drive', op: 'status' }).catch(() => null).then((r) => { + asking = null; + if (again) { again = false; ask(); return; } + if (r?.why === 'sender' && n < 5) setTimeout(() => ask(n + 1), 500 * 2 ** n); + const on = r?.connected === true; + if (on !== connected) { connected = on; for (const f of shown) try { f(); } catch (e) { console.error(e); } } + }); + return asking; + }; ask(); chrome.storage?.onChanged?.addListener((c, where) => { if (where === 'local' && 'google' in c) ask(); }); const locale = (() => { try { return chrome.i18n.getUILanguage(); } catch { return 'en'; } })(); From 073eb3c8ac7aed55c41a40927bb1f66a0b0ac8ba Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:33:35 -0400 Subject: [PATCH 209/907] Refresh the Safari resource mirror for Drive's second account check --- .../Glaze Extension/Resources/lib/drive.js | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/drive.js b/safari/Glaze/Glaze Extension/Resources/lib/drive.js index 34c959a56..9014d3fb0 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/drive.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/drive.js @@ -77,6 +77,11 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // connected to another account or none before any of its calls), boundary() } const create = (env) => { const bound = env.account ? Promise.resolve().then(env.account).catch(() => null) : null; + const still = async () => { + if (!bound) return; + const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); + if (!was || now !== was) throw new Fail('connect'); + }; const send = (url, init) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); const reason = async (r) => { const j = await r.json().catch(() => null); @@ -91,11 +96,9 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { const call = async (url, init = {}) => { for (let i = 0; ; i++) { let t, r; - if (bound) { - const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); - if (!was || now !== was) throw new Fail('connect'); - } + await still(); try { t = await env.token({ interactive: true }); } catch (e) { throw new Fail(WHY.includes(e?.why) ? e.why : 'auth'); } + await still(); // a Disconnect while the token came: Calendar's grant may keep it valid try { r = await send(url, { ...init, headers: { authorization: `Bearer ${t}`, ...init.headers } }); } catch { throw new Fail('network'); } if (r.ok) return r; if (r.status === 401 && i === 0) { @@ -289,14 +292,20 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { if (typeof document === 'undefined' || !globalThis.chrome?.runtime?.sendMessage) return null; // A framed Documents is answered once its document has claimed its pass: until then 'sender', asked again after 0.5, // 1, 2, 4 and 8 seconds. - let connected = false, asking = null; + // A change while one is asked is asked again once it answers, its answer then set aside as stale. + let connected = false, asking = null, again = false; const shown = new Set(); - const ask = (n = 0) => (asking ||= chrome.runtime.sendMessage({ type: 'glaze:drive', op: 'status' }).catch(() => null).then((r) => { - asking = null; - if (r?.why === 'sender' && n < 5) setTimeout(() => ask(n + 1), 500 * 2 ** n); - const on = r?.connected === true; - if (on !== connected) { connected = on; for (const f of shown) try { f(); } catch (e) { console.error(e); } } - })); + const ask = (n = 0) => { + if (asking) { again = true; return asking; } + asking = chrome.runtime.sendMessage({ type: 'glaze:drive', op: 'status' }).catch(() => null).then((r) => { + asking = null; + if (again) { again = false; ask(); return; } + if (r?.why === 'sender' && n < 5) setTimeout(() => ask(n + 1), 500 * 2 ** n); + const on = r?.connected === true; + if (on !== connected) { connected = on; for (const f of shown) try { f(); } catch (e) { console.error(e); } } + }); + return asking; + }; ask(); chrome.storage?.onChanged?.addListener((c, where) => { if (where === 'local' && 'google' in c) ask(); }); const locale = (() => { try { return chrome.i18n.getUILanguage(); } catch { return 'en'; } })(); From aa05d8a98515428418145c9402da758ecfe2c9e2 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:36:39 -0400 Subject: [PATCH 210/907] Test drop(token): a token Google refused is forgotten in both flows, and the next one is fresh --- test/google.test.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/google.test.js b/test/google.test.js index 713d0d189..47328b580 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -295,6 +295,30 @@ test('disconnect revokes the grant, clears the cached tokens, gives identity bac } }); +test('drop(token): a token Google answered 401 to is forgotten, and the next token() is a fresh one, silently', async () => { + const w = harness({ flow: 'web' }); + await w.g.connect(); + assert.equal(await w.g.token(), 'web-tok-1'); + await w.g.drop('someone-else'); + assert.equal(await w.g.token(), 'web-tok-1', 'another token: the held one stays'); + await w.g.drop('web-tok-1'); + const checks = w.log.fetch.filter((f) => f.url === G.USERINFO).length; + assert.equal(await w.g.token(), 'web-tok-2', 'web: not the dead token again'); + assert.equal(w.log.launch.at(-1).interactive, false, 'renewed silently'); + assert.equal(w.log.fetch.filter((f) => f.url === G.USERINFO).length, checks + 1, 'and the new one is checked for the account'); + + const c = harness(); + await c.g.connect(); + const t = await c.g.token(); + await c.g.drop(t); + assert.deepEqual(c.log.removed, [t], 'Chrome forgets it from its cache'); + const checks2 = c.log.fetch.filter((f) => f.url === G.USERINFO).length; + assert.notEqual(await c.g.token(), t); + assert.equal(c.log.fetch.filter((f) => f.url === G.USERINFO).length, checks2 + 1); + assert.equal(c.log.auth.at(-1).interactive, false); + await harness().g.drop('x'); // not connected: nothing to do, and no throw +}); + test('a token for another Google account than the one connected is refused, dropped, never used or revoked', async () => { for (const flow of ['chrome', 'web']) { const h = harness({ flow }); From ae674c9fa1e5b1eed9c5d18818b89a6c758cb677 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:36:45 -0400 Subject: [PATCH 211/907] Add drop(token) to Drive's sign-in, so a refused token is forgotten and the next one is fresh in both flows --- lib/google.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/google.js b/lib/google.js index 57fe22ae3..09ce791ba 100644 --- a/lib/google.js +++ b/lib/google.js @@ -11,7 +11,8 @@ // web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow // (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). // native Safari, through the app (native(); not wired yet). -// The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with +// API: status(), connect() (from a click), token({ interactive }), drop(token) (one Google refused), account(), disconnect(), +// pick(). The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with // launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and // the files picked (picked()). Requests carry no cookies, follow no redirect and skip the cache. // env: { config, flow, firefox, identity() (chrome.identity, which exists once the permission is held), permissions, manifest(), @@ -215,6 +216,15 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return ours(g, interactive); }; const account = async () => (await saved())?.email || null; + // A token Google answered 401 to: forgotten with its account check (Chrome's cache, or this worker's held one), so the + // next token() gets a fresh one, silently where it can. A Drive request's one retry calls this, then token(). + const forget = async (t) => { + if (typeof t !== 'string' || !t) return; + checked.delete(t); + const g = await saved(); + if (g?.flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token: t })).catch(() => {}); + else if (held?.token === t) held = null; + }; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and // the identity permission dropped, the account forgotten. While Calendar sync holds the grant too (keep), revoking would // end its access as well (a revocation covers every scope the project was granted): only Drive's own state goes. @@ -270,7 +280,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { ok: true, files: picked(url, state) }; }); - return { status, connect, token, account, disconnect, pick }; + return { status, connect, token, drop: forget, account, disconnect, pick }; }; return { ON, AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; From 707dae0a52d894db1b5cb30636cb5376a74d0f78 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:36:48 -0400 Subject: [PATCH 212/907] Refresh the Safari resource mirror for drop(token) --- .../Glaze/Glaze Extension/Resources/lib/google.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js index 57fe22ae3..09ce791ba 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -11,7 +11,8 @@ // web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow // (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). // native Safari, through the app (native(); not wired yet). -// The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with +// API: status(), connect() (from a click), token({ interactive }), drop(token) (one Google refused), account(), disconnect(), +// pick(). The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with // launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and // the files picked (picked()). Requests carry no cookies, follow no redirect and skip the cache. // env: { config, flow, firefox, identity() (chrome.identity, which exists once the permission is held), permissions, manifest(), @@ -215,6 +216,15 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return ours(g, interactive); }; const account = async () => (await saved())?.email || null; + // A token Google answered 401 to: forgotten with its account check (Chrome's cache, or this worker's held one), so the + // next token() gets a fresh one, silently where it can. A Drive request's one retry calls this, then token(). + const forget = async (t) => { + if (typeof t !== 'string' || !t) return; + checked.delete(t); + const g = await saved(); + if (g?.flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token: t })).catch(() => {}); + else if (held?.token === t) held = null; + }; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and // the identity permission dropped, the account forgotten. While Calendar sync holds the grant too (keep), revoking would // end its access as well (a revocation covers every scope the project was granted): only Drive's own state goes. @@ -270,7 +280,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { ok: true, files: picked(url, state) }; }); - return { status, connect, token, account, disconnect, pick }; + return { status, connect, token, drop: forget, account, disconnect, pick }; }; return { ON, AUTH, USERINFO, REVOKE, DRIVE, APP, Fail, real, ready, pickable, flowOf, authUrl, pickerUrl, grant, picked, create }; From 3ec5d42372ef008215f1f5b67d214b399f0dd527 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:39:23 -0400 Subject: [PATCH 213/907] Test that the worker's Drive forgets a refused token through the sign-in's drop --- test/drive.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/drive.test.js b/test/drive.test.js index d234a4b67..fb62b5a4c 100644 --- a/test/drive.test.js +++ b/test/drive.test.js @@ -335,3 +335,10 @@ test('the port: a page that sends more than it said, or goes away, uploads nothi await new Promise((r) => setTimeout(r, 20)); assert.equal(h.log.fetch.length, 0); }); + +test('the worker\'s Drive forgets a refused token through the sign-in\'s drop, which covers both flows', () => { + const src = require('node:fs').readFileSync(require('node:path').join(__dirname, '../lib/background.js'), 'utf8'); + const at = src.indexOf('drive: D.create({'), call = src.slice(at, src.indexOf('}),', at)); + assert.match(call, /drop: \(token\) => openGoogle\(\)\.drop\(token\)/); + assert.doesNotMatch(call, /removeCachedAuthToken/); +}); From 23b7828ef0a30471f9a43b3819d7a09d42dae2c6 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:39:28 -0400 Subject: [PATCH 214/907] Retry Drive's 401 with the sign-in's drop(token), so the web flow's refused token goes too --- lib/background.js | 2 +- lib/drive.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/background.js b/lib/background.js index 505807ef1..4f33ab2ae 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3319,7 +3319,7 @@ chrome.runtime.onConnect.addListener((p) => { D.serve(p, { allowed: async () => googleLib().ON && await drivePage(s), drive: D.create({ fetch: (u, init) => fetch(u, init), token: (o) => openGoogle().token(o), account: () => openGoogle().account(), - drop: (token) => chrome.identity?.removeCachedAuthToken?.({ token }) }), + drop: (token) => openGoogle().drop(token) }), record: { get: async (k) => { if (!P().valid(k)) return null; diff --git a/lib/drive.js b/lib/drive.js index 9014d3fb0..7c7f873d8 100644 --- a/lib/drive.js +++ b/lib/drive.js @@ -3,7 +3,7 @@ // kind's import does (a Google Doc, Sheet or Slides deck exported as .docx, .xlsx or .pptx); Save to Drive puts a // document's exported file in My Drive, or a paper as a Google Doc, and remembers it on the record (drive: { id, as }) so a // later save updates that file while it is still there, Glaze may edit it and it is still that type, else makes a new one. -// The Drive v3 client (create) runs in the worker, where the token is (lib/google.js token; drop forgets one Drive refused): +// The Drive v3 client (create) runs in the worker, where the token is (lib/google.js token; its drop forgets one Drive refused): // requests carry no cookies, follow no redirect, skip the cache, and after a 401 ask once more with a fresh token. A file // larger than its kind opens (lib/doc-kinds.js max) is refused before its bytes come, by the metadata's size where Drive gives // the file's own (not a Google file's, whose export differs), else as they stream. Failures are fixed reasons (WHY). @@ -72,7 +72,7 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { return { kind, name, mime, size, as, max }; }; - // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (forget one Drive answered 401 to), account() (the + // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (lib/google.js drop: forget one Drive answered 401 to), account() (the // connected account, lib/google.js account: one client serves one request, which stops as 'connect' if Drive is // connected to another account or none before any of its calls), boundary() } const create = (env) => { From af0bffaad1cfacf4822cd8178a8ef50360d530ed Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:39:48 -0400 Subject: [PATCH 215/907] Test a paper save that records a field without moving Last edited (touch: false) --- test/writer-papers-touch.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/writer-papers-touch.test.js diff --git a/test/writer-papers-touch.test.js b/test/writer-papers-touch.test.js new file mode 100644 index 000000000..3135f4feb --- /dev/null +++ b/test/writer-papers-touch.test.js @@ -0,0 +1,28 @@ +// A save that records something about a document without changing it (Save to Google Drive's drive: { id, as }, +// lib/drive.js): touch: false keeps its Last edited, and so its place in Documents' list; the revision still moves on. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const PA = require('../lib/writer-papers.js'); +globalThis.GZ_WRITER_STYLES = require('../lib/writer-styles.js'); + +const store = () => { + const data = {}; + return { data, get: async (k) => ({ [k]: data[k] === undefined ? undefined : JSON.parse(JSON.stringify(data[k])) }), set: async (o) => { for (const [k, v] of Object.entries(o)) data[k] = JSON.parse(JSON.stringify(v)); }, remove: async (ks) => { for (const k of ks) delete data[k]; } }; +}; +const doc = (text) => ({ type: 'doc', content: [{ type: 'paragraph', content: [{ type: 'text', text }] }] }); + +test('touch: false records a field without moving Last edited; a save without it still does', async () => { + const clock = { t: 1000 }, s = store(), P = PA.open(s, () => clock.t); + const k = 'doc:00000000-0000-4000-8000-000000000001'; + await P.save(k, { doc: doc('An essay.') }); + clock.t = 5000; + const { paper, version } = await P.save(k, { drive: { id: '1NeW_f1', as: 'file' } }, { changed: false, touch: false }); + assert.equal(paper.updated, 1000); + assert.equal(paper.revision, 2); + assert.deepEqual(paper.drive, { id: '1NeW_f1', as: 'file' }); + assert.equal(version, null); + assert.equal(s.data.writerPapers.find((r) => r.key === k).updated, 1000, 'the index row keeps its place'); + clock.t = 9000; + assert.equal((await P.save(k, { doc: doc('An essay, longer.') })).paper.updated, 9000); + assert.deepEqual((await P.get(k)).drive, { id: '1NeW_f1', as: 'file' }, 'an edit keeps the Drive file'); +}); From 4c5d5d9f4f20dbbc461e17c2bad9524bfb0480d9 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:39:55 -0400 Subject: [PATCH 216/907] Keep a document's Last edited when Save to Google Drive records its Drive file --- lib/background.js | 2 +- lib/writer-papers.js | 5 +++-- scripts/drive-files-check.js | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/background.js b/lib/background.js index 4f33ab2ae..6b5505d37 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3328,7 +3328,7 @@ chrome.runtime.onConnect.addListener((p) => { }, set: (k, drive) => (brokerTurn ||= GZ_CARDS.queue())(async () => { if (!(await chrome.storage.local.get(P().KEYS.PAPER + k))[P().KEYS.PAPER + k]) return false; - await openPapers().save(k, { drive }, { changed: false }); + await openPapers().save(k, { drive }, { changed: false, touch: false }); return true; }), }, diff --git a/lib/writer-papers.js b/lib/writer-papers.js index b03bb7e9d..2c3c724b0 100644 --- a/lib/writer-papers.js +++ b/lib/writer-papers.js @@ -171,7 +171,8 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // the assignment's until the student names it (named). reason: 'auto' (the default: autosave), 'sending' or // 'submit' (the paper itself kept), 'import' or 'restore' (the doc before it is kept), 'format', 'view' (the paper as // the full writer left it, before the compact box writes it into Blackboard's). → { paper, version }. - const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null } = {}) => serial(k, async () => { + // touch: false keeps Last edited (updated): a field recorded about the document, not a change to it (lib/drive.js drive). + const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null, touch = true } = {}) => serial(k, async () => { if (typeof k !== 'string' || !k) throw new Error('no paper key'); const written = Object.keys(parts || {}), dropped = [...(drop || [])]; if ([...written, ...dropped].some((n) => typeof n !== 'string' || !PART_NAME.test(n)) || (packed && typeof packed.gz !== 'string')) throw new Error('no such part'); @@ -181,7 +182,7 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // the kind; the editor's session copy still holds its unsaved document or workbook. if (prev && patch.kind && patch.kind !== (prev.kind || 'paper') && !['import', 'restore'].includes(reason)) throw new Error('document kind changed'); const cleared = patch.kind && patch.kind !== (prev?.kind || 'paper') ? Object.fromEntries([...FILE_FIELDS, ...(prev?.kind === 'sheet' ? ['book', 'grid', 'cells', 'sheets'] : [])].map((f) => [f, null])) : {}; - const paper = { ...(prev || { key: k, created: t }), ...cleared, ...patch, key: k, updated: t, revision: (prev?.revision || 0) + 1 }; + const paper = { ...(prev || { key: k, created: t }), ...cleared, ...patch, key: k, updated: touch === false && prev ? prev.updated : t, revision: (prev?.revision || 0) + 1 }; // An autosave carries the name it last read. Only an explicit title edit can replace a later rename. if (patch.doc && prev && base != null && base < (prev.titled ?? prev.revision ?? 0) && !rename) { paper.title = prev.title; paper.named = prev.named; diff --git a/scripts/drive-files-check.js b/scripts/drive-files-check.js index 57645cdb7..de2a9d8fd 100644 --- a/scripts/drive-files-check.js +++ b/scripts/drive-files-check.js @@ -213,6 +213,7 @@ const HOSTS = ['www.googleapis.com', 'openidconnect.googleapis.com', 'oauth2.goo const rec = await home.evaluate(async (k) => (await chrome.storage.local.get(`writerPaper:${k}`))[`writerPaper:${k}`], key); return { first, asked: log.drive.slice(m), drive: rec.drive, rec }; }; + const edited = await home.evaluate(async (k) => (await chrome.storage.local.get(`writerPaper:${k}`))[`writerPaper:${k}`].updated, river); let r = await save(river, 'file'); ok(r.first === 'file', 'a paper offers a Word file first'); ok(r.asked.length === 1 && r.asked[0].method === 'POST' && r.asked[0].path === '/upload/drive/v3/files' && r.asked[0].query.uploadType === 'multipart' && r.asked[0].query.fields === 'id,name,mimeType', @@ -222,6 +223,7 @@ const HOSTS = ['www.googleapis.com', 'openidconnect.googleapis.com', 'oauth2.goo ok(r.asked[0].auth === `Bearer ${TOKEN}` && !r.asked[0].cookie, 'with the token and no cookie'); const first = r.drive?.id; ok(r.drive?.as === 'file' && /^1NeW_/.test(first), `the record remembers the Drive file (${JSON.stringify(r.drive)})`); + ok(r.rec.updated === edited, 'and its Last edited stays as it was'); await home.screenshot({ path: path.join(out, 'drive-3-saved-file.png') }); r = await save(river, 'file'); From db68e55760bc53ef92c01e9830ddf8707053f29e Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:40:00 -0400 Subject: [PATCH 217/907] Refresh the Safari resource mirror for drop(token) and touch: false --- safari/Glaze/Glaze Extension/Resources/lib/background.js | 4 ++-- safari/Glaze/Glaze Extension/Resources/lib/drive.js | 4 ++-- safari/Glaze/Glaze Extension/Resources/lib/writer-papers.js | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 505807ef1..6b5505d37 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3319,7 +3319,7 @@ chrome.runtime.onConnect.addListener((p) => { D.serve(p, { allowed: async () => googleLib().ON && await drivePage(s), drive: D.create({ fetch: (u, init) => fetch(u, init), token: (o) => openGoogle().token(o), account: () => openGoogle().account(), - drop: (token) => chrome.identity?.removeCachedAuthToken?.({ token }) }), + drop: (token) => openGoogle().drop(token) }), record: { get: async (k) => { if (!P().valid(k)) return null; @@ -3328,7 +3328,7 @@ chrome.runtime.onConnect.addListener((p) => { }, set: (k, drive) => (brokerTurn ||= GZ_CARDS.queue())(async () => { if (!(await chrome.storage.local.get(P().KEYS.PAPER + k))[P().KEYS.PAPER + k]) return false; - await openPapers().save(k, { drive }, { changed: false }); + await openPapers().save(k, { drive }, { changed: false, touch: false }); return true; }), }, diff --git a/safari/Glaze/Glaze Extension/Resources/lib/drive.js b/safari/Glaze/Glaze Extension/Resources/lib/drive.js index 9014d3fb0..7c7f873d8 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/drive.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/drive.js @@ -3,7 +3,7 @@ // kind's import does (a Google Doc, Sheet or Slides deck exported as .docx, .xlsx or .pptx); Save to Drive puts a // document's exported file in My Drive, or a paper as a Google Doc, and remembers it on the record (drive: { id, as }) so a // later save updates that file while it is still there, Glaze may edit it and it is still that type, else makes a new one. -// The Drive v3 client (create) runs in the worker, where the token is (lib/google.js token; drop forgets one Drive refused): +// The Drive v3 client (create) runs in the worker, where the token is (lib/google.js token; its drop forgets one Drive refused): // requests carry no cookies, follow no redirect, skip the cache, and after a 401 ask once more with a fresh token. A file // larger than its kind opens (lib/doc-kinds.js max) is refused before its bytes come, by the metadata's size where Drive gives // the file's own (not a Google file's, whose export differs), else as they stream. Failures are fixed reasons (WHY). @@ -72,7 +72,7 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { return { kind, name, mime, size, as, max }; }; - // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (forget one Drive answered 401 to), account() (the + // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (lib/google.js drop: forget one Drive answered 401 to), account() (the // connected account, lib/google.js account: one client serves one request, which stops as 'connect' if Drive is // connected to another account or none before any of its calls), boundary() } const create = (env) => { diff --git a/safari/Glaze/Glaze Extension/Resources/lib/writer-papers.js b/safari/Glaze/Glaze Extension/Resources/lib/writer-papers.js index b03bb7e9d..2c3c724b0 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/writer-papers.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/writer-papers.js @@ -171,7 +171,8 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // the assignment's until the student names it (named). reason: 'auto' (the default: autosave), 'sending' or // 'submit' (the paper itself kept), 'import' or 'restore' (the doc before it is kept), 'format', 'view' (the paper as // the full writer left it, before the compact box writes it into Blackboard's). → { paper, version }. - const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null } = {}) => serial(k, async () => { + // touch: false keeps Last edited (updated): a field recorded about the document, not a change to it (lib/drive.js drive). + const save = (k, patch = {}, { reason = 'auto', base = seen.get(k), rename = false, expectedKind, parts = null, drop = null, changed: own, version: packed = null, touch = true } = {}) => serial(k, async () => { if (typeof k !== 'string' || !k) throw new Error('no paper key'); const written = Object.keys(parts || {}), dropped = [...(drop || [])]; if ([...written, ...dropped].some((n) => typeof n !== 'string' || !PART_NAME.test(n)) || (packed && typeof packed.gz !== 'string')) throw new Error('no such part'); @@ -181,7 +182,7 @@ var GZ_WRITER_PAPERS = globalThis.GZ_WRITER_PAPERS || (() => { // the kind; the editor's session copy still holds its unsaved document or workbook. if (prev && patch.kind && patch.kind !== (prev.kind || 'paper') && !['import', 'restore'].includes(reason)) throw new Error('document kind changed'); const cleared = patch.kind && patch.kind !== (prev?.kind || 'paper') ? Object.fromEntries([...FILE_FIELDS, ...(prev?.kind === 'sheet' ? ['book', 'grid', 'cells', 'sheets'] : [])].map((f) => [f, null])) : {}; - const paper = { ...(prev || { key: k, created: t }), ...cleared, ...patch, key: k, updated: t, revision: (prev?.revision || 0) + 1 }; + const paper = { ...(prev || { key: k, created: t }), ...cleared, ...patch, key: k, updated: touch === false && prev ? prev.updated : t, revision: (prev?.revision || 0) + 1 }; // An autosave carries the name it last read. Only an explicit title edit can replace a later rename. if (patch.doc && prev && base != null && base < (prev.titled ?? prev.revision ?? 0) && !rename) { paper.title = prev.title; paper.named = prev.named; From 982b76cd9fea9127deff2830f97eacd456dcc53b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:46:13 -0400 Subject: [PATCH 218/907] Google sync: events Glaze removed come back when selected again, deletions found on a linked device are kept, and every listing asks for deleted events --- PRIVACY.md | 3 +- features/calexport.js | 4 +- lib/gcal.js | 30 +++++++++----- test/calexport.test.js | 7 ++++ test/gcal.test.js | 91 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 122 insertions(+), 13 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index b845c48dd..91f751323 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -383,7 +383,8 @@ not those unknown legacy copies or unrelated stickers. event ID (a SHA-256 hash of the Canvas site, your Canvas user id and the event's identifier), which Canvas account and, for a "do on" day or focus session, which Better To Do item it belongs to, that plan's date or time, when the event ends, a hash of what was written and Google's version tag for it; Google's sync cursor; and the IDs of deadline - events you deleted in Google Calendar, so Glaze doesn't put them back. No sign-in token: Chrome keeps that itself. + events you deleted in Google Calendar, so Glaze doesn't put them back, including deleted event IDs not yet matched + to an export, and markers for events Glaze removed while unselected. No sign-in token: Chrome keeps that itself. **Disconnect** deletes all of it. - **Gradescope in To Do** (off by default), in `chrome.storage.local`, on this device only: which Gradescope account it's from (your name on Gradescope, and a SHA-256 hash of your Gradescope email address, never the address itself), diff --git a/features/calexport.js b/features/calexport.js index 6086622e2..45b11033a 100644 --- a/features/calexport.js +++ b/features/calexport.js @@ -21,15 +21,17 @@ var GZ_CAL_UI = globalThis.GZ_CAL_UI || (() => { const pages = async (path) => { const out = []; let url = location.origin + path; + let complete = false; for (let i = 0; url && i < 10; i++) { const r = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json+canvas-string-ids, application/json' } }); if (!r.ok) throw new Error(String(r.status)); const j = JSON.parse((await r.text()).replace(/^while\(1\);/, '')); if (Array.isArray(j)) out.push(...j); const next = r.headers.get('link')?.match(/<([^>]+)>;\s*rel="next"/)?.[1]; + complete = !next; // A continuation we cannot safely follow is still an unread page. url = next && new URL(next, location.origin).origin === location.origin ? next : null; } - return { items: out, complete: !url }; + return { items: out, complete }; }; // What the file may hold, read afresh from storage: Glaze on, the Gradescope switch and this Canvas account's answer diff --git a/lib/gcal.js b/lib/gcal.js index 20fcd138c..94edf787b 100644 --- a/lib/gcal.js +++ b/lib/gcal.js @@ -101,12 +101,13 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { // The sync state kept on this device (storage.local gcalSync), cleaned: Google's cursor (sync); for each event Glaze // wrote, by its id (map): its Canvas account (s), kind (k: 'on', 'session' or ''), item (i) and plan (v), when it ends - // (t), a hash of what was written (h) and Google's etag for it (e); gone: deadlines the student deleted in Google, never - // written again. + // (t), a hash of what was written (h) and Google's etag for it (e); removed marks events Glaze removed while unselected. + // gone: deadlines the student deleted in Google, never written again; dead: deletions not yet matched to an export. const fresh = (raw) => ({ sync: typeof raw?.sync === 'string' ? raw.sync : null, map: raw?.map && typeof raw.map === 'object' && !Array.isArray(raw.map) ? raw.map : {}, - gone: Array.isArray(raw?.gone) ? raw.gone.filter((x) => typeof x === 'string').slice(-1000) : [], + gone: Array.isArray(raw?.gone) ? raw.gone.filter((x) => typeof x === 'string') : [], + dead: Array.isArray(raw?.dead) ? raw.dead.filter((x) => typeof x === 'string') : [], }); // Whether a plan (x: a map entry, or a prepared event: kind k, item i, plan v) is what Better To Do holds (lib/todo.js @@ -133,7 +134,7 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (let i = 0; ; i++) { if (i > 50) throw new Fail('http'); let r; - try { r = await call('GET', path, { query: { maxResults: '2500', ...(cursor ? { syncToken: cursor } : { showDeleted: 'true' }), ...(page && { pageToken: page }) } }); } catch (e) { + try { r = await call('GET', path, { query: { maxResults: '2500', showDeleted: 'true', ...(cursor && { syncToken: cursor }), ...(page && { pageToken: page }) } }); } catch (e) { // An expired cursor: everything again (Google's sync guide). if (e.status === 410 && cursor) { cursor = null; page = null; got = []; continue; } throw e; @@ -146,10 +147,12 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { // Google, it's written over; deleted there, it stays deleted. A "do on" day or a focus session is the student's // own plan: moved in Google, the plan moves here; deleted there, the plan goes. Each only while the plan here is // still what Glaze last wrote (still()): a change made here since is newer, and goes to Google instead. - const edits = [], skip = new Set(), dead = new Set(); + const edits = [], skip = new Set(), dead = new Set(st.dead); for (const ev of got) { const m = st.map[ev?.id]; if (!m && ev?.status === 'cancelled' && typeof ev.id === 'string') dead.add(ev.id); // deleted before this device wrote it + if (ev?.status !== 'cancelled') dead.delete(ev?.id); + if (m?.removed) continue; // Glaze removed an unselected event; this is not a student's deletion or plan edit if (!m || ev.etag === m.e) continue; // not Glaze's, or Glaze's own write if (ev.status === 'cancelled') { delete st.map[ev.id]; @@ -173,7 +176,9 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { } } if (edits.length && await plans(edits) !== true) throw new Fail('local'); - st.gone = st.gone.slice(-1000); + // The cursor spans every school account and export range. Keep unclassified deletions with it, even when this + // batch has none of their events; a later account/range can then recognize its deleted deadlines after a restart. + st.dead = [...dead]; await save(); // 3. What changed here: new and changed events written, ones gone from the range deleted, past ones left as history. const want = new Map(); @@ -186,12 +191,15 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (const w of want.values()) if (w.k && !holds(d, w)) skip.add(w.id); } // A deadline this device never wrote that Google holds deleted: the student deleted it (another device's copy). - for (const w of want.values()) if (!w.k && !st.map[w.id] && dead.has(w.id) && !st.gone.includes(w.id)) st.gone.push(w.id); - st.gone = st.gone.slice(-1000); + for (const w of want.values()) if (dead.has(w.id)) { + if (!w.k && !st.map[w.id] && !st.gone.includes(w.id)) st.gone.push(w.id); + dead.delete(w.id); + } + st.dead = [...dead]; const gone = new Set(st.gone); const ops = [...want.values()].filter((w) => !gone.has(w.id) && !skip.has(w.id) && st.map[w.id]?.h !== w.h); for (const [id, m] of Object.entries(st.map)) { - if (m?.s !== src || want.has(id) || skip.has(id)) continue; + if (m?.s !== src || want.has(id) || skip.has(id) || m.removed) continue; if (typeof m.t === 'number' ? m.t <= from : !(m.t > first)) delete st.map[id]; // Gone from a batch the page couldn't read whole (whole: false, Ultra's calendar cut short): not proof it's gone. else if (whole === true) ops.push(id); // only a read the page says was whole deletes (an omitted flag deletes nothing) @@ -200,7 +208,9 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (const op of ops.slice(0, WRITES)) { if (typeof op === 'string') { await call('DELETE', `${path}/${op}`, { ok: [404, 410] }); - delete st.map[op]; + // Keep the deletion's provenance across cursor reads and worker restarts, so selecting this source again can + // restore it, including if its due date is later rescheduled. Its empty hash schedules a write when wanted. + st.map[op] = { ...st.map[op], removed: true, h: '' }; } else { // An id Google already has (another device's event, or one this device lost track of) is updated instead. let r = st.map[op.id] ? null : await call('POST', path, { body: { id: op.id, ...op.body }, ok: [409] }); diff --git a/test/calexport.test.js b/test/calexport.test.js index 31c30718c..059bd4af9 100644 --- a/test/calexport.test.js +++ b/test/calexport.test.js @@ -221,6 +221,13 @@ test('review: a planner read cut short at ten pages is not whole; one that ends assert.equal((await ended.go()).whole, true); }); +test('review: a planner continuation on another origin is not followed and is not a whole read', async () => { + const p = page({ next: () => 'https://other.test/api/v1/planner/items?page=2' }); + assert.equal((await p.go(() => true, { icsPlan: false })).whole, false); + assert.equal(p.asked.length, 1); + assert.ok(p.asked.every((url) => new URL(url).origin === 'https://canvas.test')); +}); + test('review: an export with nothing changed saves Gradescope work and the overdue item\'s plan, after asking further back', async () => { const p = page(); const r = await p.go(); diff --git a/test/gcal.test.js b/test/gcal.test.js index 155941836..54a9d1262 100644 --- a/test/gcal.test.js +++ b/test/gcal.test.js @@ -101,7 +101,7 @@ withMock('incremental: nothing changed costs one read; one changed item is one u const r = await G.sync(e, cal, st, { src: SRC, events: [], from: later, first: '2026-10-21', whole: true }); assert.ok(mock.live(cal).some((x) => x.summary === 'BIO 101') && mock.live(cal).some((x) => x.summary === 'Lab 1 (revised)'), 'past events stay'); assert.equal(r.wrote, 0); - assert.equal(Object.keys(st.map).length, 0, 'and are forgotten here'); + assert.ok(Object.values(st.map).every((m) => m.removed), 'past events are forgotten; only Glaze removal markers remain'); }); withMock('conflict rule: an edited deadline is written over, a deleted one stays deleted; a moved or deleted plan moves or clears the plan here', async (mock) => { @@ -186,6 +186,19 @@ withMock('an expired cursor (410) lists everything again and carries on', async assert.deepEqual(mock.log.map(([m, p]) => [m, /syncToken/.test(p)]), [['GET', true], ['GET', false]]); }); +withMock('sync query: full and incremental listings preserve showDeleted, including after a cursor expires', async (mock) => { + const e = env(mock); + const { cal } = await G.connect(e); + const st = G.fresh(); + await run(e, cal, st); + await run(e, cal, st); + mock.expire(); + await run(e, cal, st); + const listings = mock.log.filter(([m, p]) => m === 'GET' && p.includes('/events?')); + assert.ok(listings.some(([, p]) => p.includes('syncToken='))); + for (const [, p] of listings) assert.equal(new URL(p, mock.api).searchParams.get('showDeleted'), 'true', p); +}); + withMock('link another device: the code opens the same calendar, and that device\'s events land on the same ones, not duplicates', async (mock) => { const a = env(mock); const { cal } = await G.connect(a); @@ -225,6 +238,82 @@ withMock('link another device: a deadline deleted in Google stays deleted on the assert.equal(mock.live(cal).length, 4); }); +withMock('export choices: events Glaze removed return when selected again, including after a worker restart', async (mock) => { + const e = env(mock); + const { cal } = await G.connect(e); + let st = G.fresh(); + await run(e, cal, st); + const omitted = EVENTS.filter((x) => x.summary !== 'Lab 1' && !x.plan); + await run(e, cal, st, omitted); + assert.equal(mock.live(cal).length, 2); + st = G.fresh(JSON.parse(JSON.stringify(st))); + const edits = []; + await run(e, cal, st, EVENTS, { plans: async (x) => { edits.push(...x); return true; } }); + assert.deepEqual(mock.live(cal).map((x) => x.summary).sort(), EVENTS.map((x) => x.summary).sort()); + assert.deepEqual(edits, [], 'Glaze removing an unselected plan is not a student deleting that plan'); + assert.deepEqual(st.gone, [], 'only student deletions suppress deadlines'); + await run(e, cal, st, omitted); + mock.log.length = 0; + await run(e, cal, st, omitted); + assert.equal(writes(mock).length, 0, 'an acknowledged Glaze removal is not deleted again'); + st = G.fresh(JSON.parse(JSON.stringify(st))); + await run(e, cal, st); + assert.equal(mock.live(cal).length, EVENTS.length, 'restoring also works after reading the deletion cursor'); + await run(e, cal, st, omitted); + const later = Date.parse('2026-10-25T04:00:00Z'); + await G.sync(e, cal, st, { src: SRC, events: [], from: later, first: '2026-10-25', whole: true }); + st = G.fresh(JSON.parse(JSON.stringify(st))); + mock.expire(); + await G.sync(e, cal, st, { src: SRC, events: [{ ...EVENTS[0], start: later + DAY }], from: later, first: '2026-10-25', whole: true }); + assert.ok(mock.live(cal).some((x) => x.summary === 'Lab 1'), 'a rescheduled deadline also returns after its old date and a full cursor refresh'); +}); + +withMock('link another device: deleted deadlines survive a sync for another school account and a worker restart', async (mock) => { + const a = env(mock); + const { cal } = await G.connect(a); + await run(a, cal, G.fresh()); + const lab = mock.live(cal).find((x) => x.summary === 'Lab 1').id; + mock.del(cal, lab); + const b = env(mock); + let st = G.fresh(); + let saved; + await G.sync(b, cal, st, { src: 'https://other.test/7', events: [], from: FROM, first: FIRST, whole: true }, { + save: async () => { saved = JSON.parse(JSON.stringify(st)); }, + }); + st = G.fresh(saved); + await run(b, cal, st); + assert.ok(!mock.live(cal).some((x) => x.id === lab), 'advancing the shared calendar cursor must not lose another account deletion'); + assert.ok(st.gone.includes(lab)); +}); + +withMock('link another device: a deleted deadline outside the first export survives a later range expansion', async (mock) => { + const a = env(mock); + const { cal } = await G.connect(a); + await run(a, cal, G.fresh()); + const lab = mock.live(cal).find((x) => x.summary === 'Lab 1').id; + mock.del(cal, lab); + const b = env(mock); + let saved; + const st = G.fresh(); + await assert.rejects(run(b, cal, st, [], { save: async () => { saved = JSON.parse(JSON.stringify(st)); throw new Error('worker stopped'); } }), /worker stopped/); + await run(b, cal, G.fresh(saved)); + assert.ok(!mock.live(cal).some((x) => x.id === lab), 'the deletion must be durable whenever its cursor is saved'); +}); + +withMock('deletion history: discovering another deleted deadline does not evict an earlier student deletion', async (mock) => { + const e = env(mock); + const { cal } = await G.connect(e); + await run(e, cal, G.fresh(), EVENTS.slice(0, 2)); + const lab = mock.live(cal).find((x) => x.summary === 'Lab 1').id; + for (const x of mock.live(cal)) mock.del(cal, x.id); + let st = G.fresh({ gone: [lab, ...Array.from({ length: 999 }, (_, i) => String(i).padStart(64, '0'))] }); + await run(e, cal, st, EVENTS.slice(0, 2)); + assert.equal(mock.live(cal).length, 0, 'an older deletion must not be recreated to make room for a newly discovered one'); + st = G.fresh(JSON.parse(JSON.stringify(st))); + await run(e, cal, st, EVENTS.slice(0, 2)); + assert.equal(mock.live(cal).length, 0, 'the full deletion history survives a worker restart'); +}); + withMock('disconnect: the calendar deleted if asked, and the grant revoked with the token in the body, not the address', async (mock) => { const e = env(mock); const { cal } = await G.connect(e); From 28732fc645406ec59690b2550e83d6e6494e009f Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:46:13 -0400 Subject: [PATCH 219/907] Refresh the Safari resource mirror for Google sync's removal markers --- .../Resources/features/calexport.js | 4 ++- .../Glaze Extension/Resources/lib/gcal.js | 30 ++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/features/calexport.js b/safari/Glaze/Glaze Extension/Resources/features/calexport.js index 6086622e2..45b11033a 100644 --- a/safari/Glaze/Glaze Extension/Resources/features/calexport.js +++ b/safari/Glaze/Glaze Extension/Resources/features/calexport.js @@ -21,15 +21,17 @@ var GZ_CAL_UI = globalThis.GZ_CAL_UI || (() => { const pages = async (path) => { const out = []; let url = location.origin + path; + let complete = false; for (let i = 0; url && i < 10; i++) { const r = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json+canvas-string-ids, application/json' } }); if (!r.ok) throw new Error(String(r.status)); const j = JSON.parse((await r.text()).replace(/^while\(1\);/, '')); if (Array.isArray(j)) out.push(...j); const next = r.headers.get('link')?.match(/<([^>]+)>;\s*rel="next"/)?.[1]; + complete = !next; // A continuation we cannot safely follow is still an unread page. url = next && new URL(next, location.origin).origin === location.origin ? next : null; } - return { items: out, complete: !url }; + return { items: out, complete }; }; // What the file may hold, read afresh from storage: Glaze on, the Gradescope switch and this Canvas account's answer diff --git a/safari/Glaze/Glaze Extension/Resources/lib/gcal.js b/safari/Glaze/Glaze Extension/Resources/lib/gcal.js index 20fcd138c..94edf787b 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/gcal.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/gcal.js @@ -101,12 +101,13 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { // The sync state kept on this device (storage.local gcalSync), cleaned: Google's cursor (sync); for each event Glaze // wrote, by its id (map): its Canvas account (s), kind (k: 'on', 'session' or ''), item (i) and plan (v), when it ends - // (t), a hash of what was written (h) and Google's etag for it (e); gone: deadlines the student deleted in Google, never - // written again. + // (t), a hash of what was written (h) and Google's etag for it (e); removed marks events Glaze removed while unselected. + // gone: deadlines the student deleted in Google, never written again; dead: deletions not yet matched to an export. const fresh = (raw) => ({ sync: typeof raw?.sync === 'string' ? raw.sync : null, map: raw?.map && typeof raw.map === 'object' && !Array.isArray(raw.map) ? raw.map : {}, - gone: Array.isArray(raw?.gone) ? raw.gone.filter((x) => typeof x === 'string').slice(-1000) : [], + gone: Array.isArray(raw?.gone) ? raw.gone.filter((x) => typeof x === 'string') : [], + dead: Array.isArray(raw?.dead) ? raw.dead.filter((x) => typeof x === 'string') : [], }); // Whether a plan (x: a map entry, or a prepared event: kind k, item i, plan v) is what Better To Do holds (lib/todo.js @@ -133,7 +134,7 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (let i = 0; ; i++) { if (i > 50) throw new Fail('http'); let r; - try { r = await call('GET', path, { query: { maxResults: '2500', ...(cursor ? { syncToken: cursor } : { showDeleted: 'true' }), ...(page && { pageToken: page }) } }); } catch (e) { + try { r = await call('GET', path, { query: { maxResults: '2500', showDeleted: 'true', ...(cursor && { syncToken: cursor }), ...(page && { pageToken: page }) } }); } catch (e) { // An expired cursor: everything again (Google's sync guide). if (e.status === 410 && cursor) { cursor = null; page = null; got = []; continue; } throw e; @@ -146,10 +147,12 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { // Google, it's written over; deleted there, it stays deleted. A "do on" day or a focus session is the student's // own plan: moved in Google, the plan moves here; deleted there, the plan goes. Each only while the plan here is // still what Glaze last wrote (still()): a change made here since is newer, and goes to Google instead. - const edits = [], skip = new Set(), dead = new Set(); + const edits = [], skip = new Set(), dead = new Set(st.dead); for (const ev of got) { const m = st.map[ev?.id]; if (!m && ev?.status === 'cancelled' && typeof ev.id === 'string') dead.add(ev.id); // deleted before this device wrote it + if (ev?.status !== 'cancelled') dead.delete(ev?.id); + if (m?.removed) continue; // Glaze removed an unselected event; this is not a student's deletion or plan edit if (!m || ev.etag === m.e) continue; // not Glaze's, or Glaze's own write if (ev.status === 'cancelled') { delete st.map[ev.id]; @@ -173,7 +176,9 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { } } if (edits.length && await plans(edits) !== true) throw new Fail('local'); - st.gone = st.gone.slice(-1000); + // The cursor spans every school account and export range. Keep unclassified deletions with it, even when this + // batch has none of their events; a later account/range can then recognize its deleted deadlines after a restart. + st.dead = [...dead]; await save(); // 3. What changed here: new and changed events written, ones gone from the range deleted, past ones left as history. const want = new Map(); @@ -186,12 +191,15 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (const w of want.values()) if (w.k && !holds(d, w)) skip.add(w.id); } // A deadline this device never wrote that Google holds deleted: the student deleted it (another device's copy). - for (const w of want.values()) if (!w.k && !st.map[w.id] && dead.has(w.id) && !st.gone.includes(w.id)) st.gone.push(w.id); - st.gone = st.gone.slice(-1000); + for (const w of want.values()) if (dead.has(w.id)) { + if (!w.k && !st.map[w.id] && !st.gone.includes(w.id)) st.gone.push(w.id); + dead.delete(w.id); + } + st.dead = [...dead]; const gone = new Set(st.gone); const ops = [...want.values()].filter((w) => !gone.has(w.id) && !skip.has(w.id) && st.map[w.id]?.h !== w.h); for (const [id, m] of Object.entries(st.map)) { - if (m?.s !== src || want.has(id) || skip.has(id)) continue; + if (m?.s !== src || want.has(id) || skip.has(id) || m.removed) continue; if (typeof m.t === 'number' ? m.t <= from : !(m.t > first)) delete st.map[id]; // Gone from a batch the page couldn't read whole (whole: false, Ultra's calendar cut short): not proof it's gone. else if (whole === true) ops.push(id); // only a read the page says was whole deletes (an omitted flag deletes nothing) @@ -200,7 +208,9 @@ var GZ_GCAL = globalThis.GZ_GCAL || (() => { for (const op of ops.slice(0, WRITES)) { if (typeof op === 'string') { await call('DELETE', `${path}/${op}`, { ok: [404, 410] }); - delete st.map[op]; + // Keep the deletion's provenance across cursor reads and worker restarts, so selecting this source again can + // restore it, including if its due date is later rescheduled. Its empty hash schedules a write when wanted. + st.map[op] = { ...st.map[op], removed: true, h: '' }; } else { // An id Google already has (another device's event, or one this device lost track of) is updated instead. let r = st.map[op.id] ? null : await call('POST', path, { body: { id: op.id, ...op.body }, ok: [409] }); From 24d33b0c5cdcb5f3669edb104ffb3f7fcd814869 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:49:46 -0400 Subject: [PATCH 220/907] Keep a newer Google grant safe from a superseded sign-in, a stale refresh and a replaced app session --- .../Glaze/Glaze Extension/GlazeGoogle.swift | 62 +++-- safari/Glaze/Glaze/GlazeGoogleSignIn.swift | 32 ++- test/google-native-races.test.js | 238 ++++++++++++++++++ 3 files changed, 308 insertions(+), 24 deletions(-) create mode 100644 test/google-native-races.test.js diff --git a/safari/Glaze/Glaze Extension/GlazeGoogle.swift b/safari/Glaze/Glaze Extension/GlazeGoogle.swift index 9af126760..05a9dd32d 100644 --- a/safari/Glaze/Glaze Extension/GlazeGoogle.swift +++ b/safari/Glaze/Glaze Extension/GlazeGoogle.swift @@ -159,12 +159,17 @@ nonisolated enum GlazeGoogle { /// The extension's side: sign-ins started and finished, tokens minted, the grant revoked. actor Engine { struct Pending: Codable { let state: String, verifier: String, at: Double } - struct Saved: Codable { let refresh: String, sub: String, email: String } + struct Saved: Codable, Equatable { let refresh: String, sub: String, email: String } private let vault: Vault, slot: Slot?, transport: Transport, now: @Sendable () -> Date private var held: [String: (token: String, expires: Date, sub: String)] = [:] - /// Bumped by connect and disconnect: an answer that arrives after either, across an await, is dropped. + /// Bumped by Connect, Disconnect and committing a grant: older answers across an await are dropped. private var generation: [String: Int] = [:] + /// Only an explicit Disconnect may revoke a late exchange. Google's revocation also invalidates newer grants + /// for the same account/client, so a superseding Connect must only discard the old answer. + private var disconnected: Set = [] + private var revoking = 0 + private var revocationWaiters: [CheckedContinuation] = [] private func bump(_ profile: String) { generation[profile, default: 0] += 1 } init(vault: Vault, slot: Slot?, transport: Transport, now: @escaping @Sendable () -> Date = { Date() }) { @@ -180,11 +185,19 @@ nonisolated enum GlazeGoogle { return ["ok": true, "email": s?.email ?? NSNull(), "sub": s?.sub ?? NSNull()] } - func connect(profile: String, hint: String?) throws -> SignIn { + func connect(profile: String, hint: String?) async throws -> SignIn { guard let slot else { throw Fail.setup } if let hint, !validEmail(hint) { throw Fail.request } guard let state = random(), let verifier = random() else { throw Fail.setup } bump(profile) + disconnected.remove(profile) + let started = generation[profile, default: 0] + // A revoke already sent to Google cannot be canceled. Do not start another authorization until it ends. + // This is global: Google's revocation is account/client-wide, across Safari profiles too. + while revoking > 0 { + await withCheckedContinuation { revocationWaiters.append($0) } + guard generation[profile, default: 0] == started else { throw Fail.signin } + } if let old = load(Pending.self, "pending." + profile) { slot.remove(old.state) } slot.sweep(olderThan: now().addingTimeInterval(-pendingFor)) guard store(Pending(state: state, verifier: verifier, at: now().timeIntervalSince1970), "pending." + profile) else { throw Fail.setup } @@ -214,12 +227,14 @@ nonisolated enum GlazeGoogle { "grant_type": "authorization_code", "redirect_uri": redirect]) let g: Grant do { g = try grant(status: status, body: body, now: now()) } catch Fail.revoked { throw Fail.auth } // a spent code - guard generation[profile, default: 0] == started else { // disconnected or restarted meanwhile: this grant goes too - if let r = g.refresh { _ = try? await post(revokeURL, ["token": r]) } + guard generation[profile, default: 0] == started else { + if disconnected.contains(profile), let r = g.refresh { _ = await revoke(r) } throw Fail.signin } guard let refresh = g.refresh, let account = g.account else { throw Fail.auth } guard store(Saved(refresh: refresh, sub: account.sub, email: account.email), "refresh." + profile) else { throw Fail.setup } + // A refresh can start after Connect but before this exchange finishes. It belongs to the previous grant. + bump(profile) held[profile] = (g.access, g.expires, account.sub) return account } @@ -231,7 +246,8 @@ nonisolated enum GlazeGoogle { held[profile] = nil let started = generation[profile, default: 0] let (status, body) = try await post(tokenURL, ["client_id": client, "grant_type": "refresh_token", "refresh_token": s.refresh]) - guard generation[profile, default: 0] == started else { throw Fail.signin } // disconnected meanwhile + guard generation[profile, default: 0] == started, + load(Saved.self, "refresh." + profile) == s else { throw Fail.signin } let g: Grant do { g = try grant(status: status, body: body, now: now()) } catch Fail.revoked { vault.remove("refresh." + profile); throw Fail.revoked } catch Fail.scope { vault.remove("refresh." + profile); throw Fail.revoked } // Drive's access was taken back @@ -243,14 +259,28 @@ nonisolated enum GlazeGoogle { func disconnect(profile: String) async -> (revoked: Bool, had: Bool) { bump(profile) + disconnected.insert(profile) let s = load(Saved.self, "refresh." + profile) if let p = load(Pending.self, "pending." + profile) { slot?.remove(p.state) } vault.remove("refresh." + profile) vault.remove("pending." + profile) held[profile] = nil guard let s else { return (false, false) } - let r = try? await post(revokeURL, ["token": s.refresh]) - return (r?.0 == 200, true) + return (await revoke(s.refresh), true) + } + + private func revoke(_ refresh: String) async -> Bool { + revoking += 1 + defer { + revoking -= 1 + if revoking == 0 { + let waiters = revocationWaiters + revocationWaiters.removeAll() + for waiter in waiters { waiter.resume() } + } + } + let r = try? await post(revokeURL, ["token": refresh]) + return r?.0 == 200 } private func post(_ url: URL, _ form: [String: String]) async throws -> (Int, Data) { @@ -350,15 +380,21 @@ nonisolated enum GlazeGoogle { } /// Google's endpoints only: a form POST, no cookies, no cache, no redirects followed. - final class HTTP: NSObject, Transport, URLSessionTaskDelegate, @unchecked Sendable { - private lazy var session: URLSession = { + final class HTTP: Transport { + private let session: URLSession + init() { let c = URLSessionConfiguration.ephemeral c.httpCookieStorage = nil c.urlCache = nil c.requestCachePolicy = .reloadIgnoringLocalCacheData c.timeoutIntervalForRequest = 15 - return URLSession(configuration: c, delegate: self, delegateQueue: nil) - }() + session = URLSession(configuration: c, delegate: Redirects(), delegateQueue: nil) + } + /// Kept separate so the session does not retain its transport and construction needs no mutable/lazy property. + private final class Redirects: NSObject, URLSessionTaskDelegate, Sendable { + func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { completionHandler(nil) } + } func post(_ url: URL, form: [String: String]) async throws -> (Int, Data) { var r = URLRequest(url: url) r.httpMethod = "POST" @@ -367,8 +403,6 @@ nonisolated enum GlazeGoogle { let (data, response) = try await session.data(for: r) return ((response as? HTTPURLResponse)?.statusCode ?? 0, data) } - func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, - newRequest request: URLRequest, completionHandler: @escaping (URLRequest?) -> Void) { completionHandler(nil) } } /// application/x-www-form-urlencoded, keys sorted. diff --git a/safari/Glaze/Glaze/GlazeGoogleSignIn.swift b/safari/Glaze/Glaze/GlazeGoogleSignIn.swift index aa02d2e2e..0a1adb289 100644 --- a/safari/Glaze/Glaze/GlazeGoogleSignIn.swift +++ b/safari/Glaze/Glaze/GlazeGoogleSignIn.swift @@ -20,35 +20,47 @@ import UIKit final class GlazeGoogleSignIn: NSObject, ASWebAuthenticationPresentationContextProviding { static let shared = GlazeGoogleSignIn() private var session: ASWebAuthenticationSession? - private var state: String? + private struct Active { + let id: UUID, state: String, slot: GlazeGoogle.GroupSlot + let done: @MainActor () -> Void + } + private var active: Active? /// Whether url is the sign-in URL's scheme. done runs once this sign-in ends (at once if none started). @discardableResult func open(_ url: URL, done: @escaping @MainActor () -> Void = {}) -> Bool { guard url.scheme?.lowercased() == GlazeGoogle.appScheme else { return false } guard let s = GlazeGoogle.signIn(from: url), let slot = GlazeGoogle.GroupSlot.shared() else { done(); return true } - if let old = state { session?.cancel(); answer(slot, old, ["error": "cancel"]) } + if let old = active { complete(old.id, ["error": "cancel"], cancel: true) } + let id = UUID() let session = ASWebAuthenticationSession(url: GlazeGoogle.authURL(s), callbackURLScheme: GlazeGoogle.callbackScheme) { [weak self] url, error in Task { @MainActor in let canceled = (error as? ASWebAuthenticationSessionError)?.code == .canceledLogin - self?.answer(slot, s.state, url.map { ["url": $0.absoluteString] } ?? ["error": canceled ? "cancel" : "auth"]) - if self?.state == s.state { self?.session = nil; self?.state = nil } - done() + self?.complete(id, url.map { ["url": $0.absoluteString] } ?? ["error": canceled ? "cancel" : "auth"]) } } session.presentationContextProvider = self session.prefersEphemeralWebBrowserSession = false self.session = session - state = s.state + active = Active(id: id, state: s.state, slot: slot, done: done) if !session.start() { - answer(slot, s.state, ["error": "auth"]) - self.session = nil - state = nil - done() + complete(id, ["error": "auth"]) } return true } + /// Programmatic cancel need not call AuthenticationServices' completion. Retire ownership before cancel/done so + /// delayed callbacks cannot answer twice, overwrite a newer reply, or decrement the DMG app's sign-in count twice. + private func complete(_ id: UUID, _ reply: [String: String], cancel: Bool = false) { + guard let old = active, old.id == id else { return } + let session = self.session + active = nil + self.session = nil + answer(old.slot, old.state, reply) + if cancel { session?.cancel() } + old.done() + } + private func answer(_ slot: GlazeGoogle.GroupSlot, _ state: String, _ reply: [String: String]) { if let data = try? JSONSerialization.data(withJSONObject: reply) { _ = slot.put(state, data) } } diff --git a/test/google-native-races.test.js b/test/google-native-races.test.js new file mode 100644 index 000000000..428decbf9 --- /dev/null +++ b/test/google-native-races.test.js @@ -0,0 +1,238 @@ +// Deterministic Safari Google auth races. The Swift source runs with memory storage and a suspended transport; +// the app session test substitutes only AuthenticationServices and app-group discovery, never starting an app or network. +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const S = require('./helpers/swift-native.cjs'); +const root = path.join(__dirname, '..'); +const google = fs.readFileSync(path.join(root, 'safari/Glaze/Glaze Extension/GlazeGoogle.swift'), 'utf8'); +const signIn = fs.readFileSync(path.join(root, 'safari/Glaze/Glaze/GlazeGoogleSignIn.swift'), 'utf8'); +const skip = !S.supported; +const fixtures = String.raw` +final class Memory: GlazeGoogle.Vault, @unchecked Sendable { + private let lock = NSLock() + private var items: [String: Data] = [:] + func read(_ key: String) -> Data? { lock.withLock { items[key] } } + func write(_ key: String, _ data: Data) -> Bool { lock.withLock { items[key] = data }; return true } + func remove(_ key: String) { _ = lock.withLock { items.removeValue(forKey: key) } } +} +actor Google: GlazeGoogle.Transport { + var sent: [[String: String]] = [] + var pending: [Int: CheckedContinuation<(Int, Data), Error>] = [:] + var holdRevokes = false + func suspendRevocation() { holdRevokes = true } + func post(_ url: URL, form: [String: String]) async throws -> (Int, Data) { + let index = sent.count + sent.append(form.merging(["url": url.absoluteString]) { a, _ in a }) + if url == GlazeGoogle.revokeURL && !holdRevokes { return (200, Data()) } + return try await withCheckedThrowingContinuation { pending[index] = $0 } + } + func waitFor(_ n: Int) async { while sent.count < n { await Task.yield() } } + func answer(_ n: Int, _ body: [String: String], status: Int = 200) { + pending.removeValue(forKey: n)!.resume(returning: (status, try! JSONSerialization.data(withJSONObject: body))) + } +} +typealias G = GlazeGoogle +let now = Date(timeIntervalSince1970: 2_000_000_000) +let vault = Memory(), transport = Google() +let slot = G.GroupSlot(folder: URL(fileURLWithPath: CommandLine.arguments[1])) +let engine = G.Engine(vault: vault, slot: slot, transport: transport, now: { now }) +func grant(_ name: String) -> [String: String] { + let claims: [String: Any] = ["iss": "https://accounts.google.com", "aud": G.client, + "exp": now.timeIntervalSince1970 + 3600, "sub": "student", "email": "student@example.edu"] + let id = "e30." + G.base64url(try! JSONSerialization.data(withJSONObject: claims)) + ".c2ln" + return ["access_token": "access-" + name, "token_type": "Bearer", "scope": G.drive, "refresh_token": "refresh-" + name, "id_token": id] +} +func start(_ profile: String = "p") async throws -> G.SignIn { + let s = try await engine.connect(profile: profile, hint: nil) + _ = slot.put(s.state, try! JSONSerialization.data(withJSONObject: ["url": G.redirect + "?state=" + s.state + "&code=" + s.state])) + return s +} +func reason(_ f: () async throws -> Any) async -> String { + do { _ = try await f(); return "ok" } catch let e as G.Fail { return e.rawValue } catch { return "other" } +} +func emit(_ result: [String: Any]) { print(String(data: try! JSONSerialization.data(withJSONObject: result), encoding: .utf8)!) } +`; +async function run(program, { app = false } = {}) { + const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-google-races-')); + const group = path.join(temp, 'group'); + fs.mkdirSync(group); + let proc; + try { + const file = path.join(temp, 'Race.swift'); + const appSource = app ? signIn.replace(/\bASWebAuthenticationSession\b/g, 'TestSession') + .replace(/\bASWebAuthenticationPresentationContextProviding\b/g, 'TestPresentation') + .replace('GlazeGoogle.GroupSlot.shared()', 'testSlot') : ''; + fs.writeFileSync(file, google + fixtures + (app ? sessions + appSource : '') + program); + proc = S.run(file, [group], { flags: ['-swift-version', '5', ...S.FEATURES, '-strict-concurrency=complete', ...(app ? ['-default-isolation', 'MainActor'] : [])] }); + const result = await proc.next(); + assert.equal(await proc.done, 0, proc.stderr()); + assert.doesNotMatch(proc.stderr(), /warning:|error:/); + return result; + } finally { + if (proc) await proc.stop(); + fs.rmSync(temp, { recursive: true, force: true }); + } +} + +test('a superseded Connect drops its late grant without revoking the newer connection', { skip }, async () => { + const r = await run(String.raw` +_ = try await start() +let old = Task { await reason { try await engine.finish(profile: "p", wait: 0) } } +await transport.waitFor(1) +_ = try await start() +let fresh = Task { try await engine.finish(profile: "p", wait: 0) } +await transport.waitFor(2) +await transport.answer(1, grant("new")) +_ = try await fresh.value +await transport.answer(0, grant("old")) +let oldResult = await old.value +let token = try await engine.token(profile: "p", sub: nil) +emit(["old": oldResult, "token": token.token, "revokes": await transport.sent.filter { $0["url"] == G.revokeURL.absoluteString }.count]) +`); + assert.deepEqual(r, { old: 'signin', token: 'access-new', revokes: 0 }); +}); + +test('Connect waits for an already dispatched revocation before starting a new authorization', { skip }, async () => { + const r = await run(String.raw` +_ = vault.write("refresh.p", try! JSONEncoder().encode(G.Engine.Saved(refresh: "refresh-old", sub: "student", email: "student@example.edu"))) +await transport.suspendRevocation() +let disconnect = Task { await engine.disconnect(profile: "p") } +await transport.waitFor(1) +actor Progress { + var started = false, connected = false + func start() { started = true } + func connect() { connected = true } +} +let progress = Progress() +let connect = Task { + await progress.start() + _ = try await engine.connect(profile: "p", hint: nil) + await progress.connect() +} +while !(await progress.started) { await Task.yield() } +// Let the actor process Connect while the revocation response is deliberately held. +try await Task.sleep(nanoseconds: 20_000_000) +let before = await progress.connected +await transport.answer(0, [:]) +_ = await disconnect.value +try await connect.value +emit(["beforeRevoked": before, "afterRevoked": await progress.connected]) +`); + assert.deepEqual(r, { beforeRevoked: false, afterRevoked: true }); +}); + +for (const response of ['invalid_grant', 'scope', 'success']) { + test(`a refresh started during Connect cannot replace or delete the completed grant: ${response}`, { skip }, async () => { + const r = await run(String.raw` +_ = vault.write("refresh.p", try! JSONEncoder().encode(G.Engine.Saved(refresh: "refresh-old", sub: "student", email: "student@example.edu"))) +_ = try await start() +let old = Task { await reason { try await engine.token(profile: "p", sub: nil) } } +await transport.waitFor(1) +let fresh = Task { try await engine.finish(profile: "p", wait: 0) } +await transport.waitFor(2) +await transport.answer(1, grant("new")) +_ = try await fresh.value +` + (response === 'invalid_grant' ? 'await transport.answer(0, ["error": "invalid_grant"], status: 400)' : response === 'scope' + ? 'await transport.answer(0, grant("old").merging(["scope": "openid email"]) { _, b in b })' + : 'await transport.answer(0, grant("old"))') + String.raw` +let oldResult = await old.value +let saved = vault.read("refresh.p").flatMap { try? JSONDecoder().decode(G.Engine.Saved.self, from: $0) } +let token = await reason { try await engine.token(profile: "p", sub: nil) } +emit(["old": oldResult, "refresh": saved?.refresh ?? "", "tokenResult": token, "status": await engine.status(profile: "p")]) +`); + assert.equal(r.old, 'signin'); + assert.equal(r.refresh, 'refresh-new'); + assert.equal(r.tokenResult, 'ok'); + assert.deepEqual(r.status, { ok: true, sub: 'student', email: 'student@example.edu' }); + }); +} + +const sessions = String.raw` +import AuthenticationServices +import AppKit +let testSlot: G.GroupSlot? = slot +@MainActor protocol TestPresentation: AnyObject { + func presentationAnchor(for session: TestSession) -> ASPresentationAnchor +} +@MainActor final class TestSession { + static var created: [TestSession] = [] + static var starts = true + let completion: @Sendable (URL?, Error?) -> Void + var presentationContextProvider: (any TestPresentation)? + var prefersEphemeralWebBrowserSession = false + var cancels = 0 + init(url: URL, callbackURLScheme: String?, completionHandler: @escaping @Sendable (URL?, Error?) -> Void) { + completion = completionHandler + Self.created.append(self) + } + func start() -> Bool { Self.starts } + func cancel() { cancels += 1 } // Programmatic cancel need not call completion. + func complete(_ url: URL?) { completion(url, nil) } +} +func settle() async { for _ in 0..<20 { await Task.yield() } } +`; + +test('replacing an app sign-in completes the displaced caller once and ignores its late callback', { skip }, async () => { + const r = await run(String.raw` +let manager = GlazeGoogleSignIn() +let s = G.SignIn(state: String(repeating: "s", count: 43), challenge: String(repeating: "c", count: 43), hint: nil) +var first = 0, second = 0 +_ = manager.open(G.signInURL(s)) { first += 1 } +let old = TestSession.created[0] +_ = manager.open(G.signInURL(s)) { second += 1 } +let displaced = first +let current = TestSession.created[1] +current.complete(URL(string: G.redirect + "?state=" + s.state + "&code=current")) +await settle() +old.complete(URL(string: G.redirect + "?state=" + s.state + "&code=old")) +await settle() +let reply = slot.take(s.state).flatMap { try? JSONSerialization.jsonObject(with: $0) } as? [String: String] +emit(["displaced": displaced, "first": first, "second": second, "cancels": old.cancels, "reply": reply ?? [:]]) +`, { app: true }); + assert.deepEqual(r, { displaced: 1, first: 1, second: 1, cancels: 1, + reply: { url: 'com.googleusercontent.apps.1071735898350-h5i3v4abnj2o4btvn270vq9689585eeh:/oauth2redirect?state=' + 's'.repeat(43) + '&code=current' } }); +}); + +test('a failed session start completes once even if AuthenticationServices later calls back', { skip }, async () => { + const r = await run(String.raw` +let manager = GlazeGoogleSignIn() +let s = G.SignIn(state: String(repeating: "s", count: 43), challenge: String(repeating: "c", count: 43), hint: nil) +var done = 0 +TestSession.starts = false +_ = manager.open(G.signInURL(s)) { done += 1 } +TestSession.created[0].complete(URL(string: G.redirect + "?state=" + s.state + "&code=late")) +await settle() +let reply = slot.take(s.state).flatMap { try? JSONSerialization.jsonObject(with: $0) } as? [String: String] +emit(["done": done, "reply": reply ?? [:]]) +`, { app: true }); + assert.deepEqual(r, { done: 1, reply: { error: 'auth' } }); +}); + +test('concurrent native requests share one initialized HTTP session without a lazy-initialization race', { skip }, async () => { + const r = await run(String.raw` +extension GlazeGoogle.HTTP { func testSession() -> URLSession { session } } +final class Sessions: @unchecked Sendable { + let lock = NSLock() + var all: [ObjectIdentifier: URLSession] = [:] + var redirect = "unset" + func add(_ session: URLSession) { lock.withLock { all[ObjectIdentifier(session)] = session } } + func redirected(_ request: URLRequest?) { lock.withLock { redirect = request == nil ? "refused" : "followed" } } +} +let http = G.HTTP(), sessions = Sessions() +// Touch only session construction: no URLSession tasks or network requests are started. +DispatchQueue.concurrentPerform(iterations: 32) { _ in sessions.add(http.testSession()) } +let count = sessions.all.count +let session = http.testSession() +let request = URLRequest(url: G.tokenURL), task = session.dataTask(with: G.tokenURL) +(session.delegate as? URLSessionTaskDelegate)?.urlSession?(session, task: task, + willPerformHTTPRedirection: HTTPURLResponse(url: G.tokenURL, statusCode: 302, httpVersion: nil, headerFields: nil)!, + newRequest: request, completionHandler: { sessions.redirected($0) }) +task.cancel() +for session in sessions.all.values { session.invalidateAndCancel() } +emit(["sessions": count, "redirect": sessions.redirect]) +`); + assert.deepEqual(r, { sessions: 1, redirect: 'refused' }); +}); From 522196320482eb2c23f153ff9e9d5e13daadbdff Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:51:01 -0400 Subject: [PATCH 221/907] Test that concurrent saves share one Drive file and that a revoked sender, a closed page or an account change stops a transfer --- test/drive.test.js | 134 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/test/drive.test.js b/test/drive.test.js index fb62b5a4c..892a80907 100644 --- a/test/drive.test.js +++ b/test/drive.test.js @@ -317,22 +317,25 @@ test('the port: a page saves a document, the worker remembers its Drive file and assert.equal(h.log.fetch.length, 0); }); -test('the port: a page that sends more than it said, or goes away, uploads nothing', async () => { +test('the port: a page that sends more than it said, or goes away, uploads nothing', { timeout: 5000 }, async () => { const records = new Map([['doc:1', null]]); const record = { get: async (k) => (records.has(k) ? { drive: records.get(k) } : null), set: async () => true }; const h = harness([]); let [page, worker] = ports(); D.serve(worker, { drive: h.drive, record, allowed: async () => true }); - const got = []; - page.onMessage.addListener((m) => { got.push(m); if (m.type === 'more') page.postMessage({ op: 'chunk', data: D.b64(new Uint8Array(20)) }); }); + const refused = new Promise((resolve) => page.onMessage.addListener((m) => { + if (m.type === 'more') page.postMessage({ op: 'chunk', data: D.b64(new Uint8Array(20)) }); + if (m.type === 'fail') resolve(m); + })); page.postMessage({ op: 'save', key: 'doc:1', name: 'a.pdf', mime: MIME.pdf, size: 10, as: 'file' }); - await new Promise((r) => setTimeout(r, 20)); - assert.equal(got.at(-1).type, 'fail'); assert.equal(got.at(-1).why, 'bad'); + assert.equal((await refused).why, 'bad'); + page.disconnect(); [page, worker] = ports(); D.serve(worker, { drive: h.drive, record, allowed: async () => true }); + const disconnected = new Promise((resolve) => worker.onDisconnect.addListener(resolve)); page.onMessage.addListener((m) => { if (m.type === 'more') page.disconnect(); }); page.postMessage({ op: 'save', key: 'doc:1', name: 'a.pdf', mime: MIME.pdf, size: 10, as: 'file' }); - await new Promise((r) => setTimeout(r, 20)); + await disconnected; assert.equal(h.log.fetch.length, 0); }); @@ -342,3 +345,122 @@ test('the worker\'s Drive forgets a refused token through the sign-in\'s drop, w assert.match(call, /drop: \(token\) => openGoogle\(\)\.drop\(token\)/); assert.doesNotMatch(call, /removeCachedAuthToken/); }); + +test('concurrent saves of one document reuse the first saved Drive file', async () => { + let rec = { drive: null }, made = 0; + const targets = []; + const record = { get: async () => structuredClone(rec), set: async (_k, drive) => { rec = { drive }; } }; + const drive = { save: async ({ id }) => { + targets.push(id); + // Both tabs finish collecting their bytes before the first upload answers. + await new Promise((r) => setImmediate(r)); + return { id: id || `made-${++made}`, fresh: false }; + } }; + const save = () => { + const [page, worker] = ports(); + D.serve(worker, { drive, record }); + return D.client(() => page).save({ key: 'doc:concurrent', name: 'Essay.docx', mime: MIME.docx, bytes: new Uint8Array([1]), as: 'file' }); + }; + const results = await Promise.all([save(), save()]); + assert.equal(made, 1, 'two tabs must not create independent remote copies'); + assert.deepEqual(targets, [null, 'made-1']); + assert.equal(results[0].id, results[1].id); + assert.equal(rec.drive.id, results[0].id); +}); + +test('a sender revoked while uploading bytes cannot publish them to Drive', async () => { + const [page, worker] = ports(); + let allowed = true, uploads = 0, recorded = false; + D.serve(worker, { + allowed: async () => allowed, + record: { get: async () => ({ drive: null }), set: async () => { recorded = true; } }, + drive: { save: async () => { uploads++; return { id: 'unexpected', fresh: false }; } }, + }); + await fails(D.client(() => page).save({ key: 'doc:revoked', name: 'Essay.docx', mime: MIME.docx, bytes: new Uint8Array([1]), as: 'file' }, () => { allowed = false; }), 'sender'); + assert.equal(uploads, 0); + assert.equal(recorded, false); +}); + +test('a sender revoked after a download chunk receives no further bytes', async () => { + const n = D.CHUNK * 2, h = harness([json(200, { ...FILES.pdf, size: String(n) }), stream(n, D.CHUNK).res]); + const [page, worker] = ports(); + let allowed = true, received = 0; + D.serve(worker, { drive: h.drive, allowed: async () => allowed }); + await fails(D.client(() => page).open(FILES.pdf.id, ALL, ({ done }) => { + received = done; + if (done) allowed = false; + }), 'sender'); + assert.equal(received, D.CHUNK); +}); + +test('a sender revoked during token acquisition sends no Drive request', async () => { + const [page, worker] = ports(); + let allowed = true, sent = 0; + const permitted = async () => allowed; + const drive = D.create({ + allowed: permitted, account: async () => 'student@school.edu', + token: async () => { allowed = false; return 'tok'; }, + fetch: async () => { sent++; return json(200, SAVED.file); }, + }); + D.serve(worker, { drive, allowed: permitted, record: { get: async () => ({ drive: null }), set: async () => {} } }); + await fails(D.client(() => page).save({ key: 'doc:auth', name: 'Essay.docx', mime: MIME.docx, bytes: new Uint8Array([1]), as: 'file' }), 'sender'); + assert.equal(sent, 0); +}); + +test('the worker cancels an upload when its page disconnects while the token is pending', async () => { + let release, sent = 0; + const pending = new Promise((resolve) => { release = resolve; }); + const key = 'doc:00000000-0000-4000-8000-000000000001'; + const w = require('./helpers/worker-env.cjs')({ + store: { sync: { version: 2, enabled: true, enhance: {} }, local: { ultra: [], [`writerPaper:${key}`]: { key, kind: 'paper' } }, session: {} }, + globals: { + GZ_DRIVE: D, GZ_GOOGLE_CONFIG: {}, + GZ_GOOGLE: { ON: true, flowOf: () => 'chrome', create: () => ({ account: async () => 'student@school.edu', token: async () => pending }) }, + GZ_DOC_FILES: { idb: () => null, store: () => ({}) }, + }, + fetch: async () => { sent++; return json(200, SAVED.file); }, + }); + const c = w.connect(D.PORT, { id: 'glaze', url: 'chrome-extension://x/papers.html', frameId: 0, tab: { id: 7 } }); + c.say({ op: 'save', key, name: 'Essay.docx', mime: MIME.docx, size: 1, as: 'file' }); + await w.settle(); + assert.equal(c.port.posted.at(-1).type, 'more'); + c.say({ op: 'chunk', data: D.b64(new Uint8Array([1])) }); + await w.settle(); + c.say({ op: 'end' }); + await w.settle(); + c.close(); + release('tok'); + await w.settle(); + assert.equal(sent, 0, 'a port that went away cannot resume an upload after sign-in'); + assert.equal(w.store.local[`writerPaper:${key}`].drive, undefined); +}); + +test('a download stops delivering bytes when its Google account changes mid-stream', async () => { + for (const next of [null, 'other@school.edu']) { + let who = 'student@school.edu', calls = 0, received = 0; + const body = stream(8, 4); + const drive = D.create({ + account: async () => who, token: async () => 'tok', + fetch: async () => ++calls === 1 ? json(200, { ...FILES.pdf, size: '8' }) : body.res, + }); + await fails(drive.open(FILES.pdf.id, ALL, { bytes: (b) => { received += b.length; who = next; } }), 'connect'); + assert.equal(received, 4, 'no further bytes reach the page after Disconnect or an account switch'); + assert.equal(body.seen.cancelled, true); + } +}); + +test('Disconnect stops a download even inside a large network chunk', async () => { + let who = 'student@school.edu', calls = 0, received = 0; + const n = 2 * D.CHUNK, body = stream(n, n); + const drive = D.create({ + account: async () => who, token: async () => 'tok', + fetch: async () => ++calls === 1 ? json(200, { ...FILES.pdf, size: String(n) }) : body.res, + }); + const [page, worker] = ports(); + D.serve(worker, { drive }); + await fails(D.client(() => page).open(FILES.pdf.id, ALL, ({ done }) => { + received = done; + if (done) who = null; + }), 'connect'); + assert.equal(received, D.CHUNK, 'only the chunk acknowledged before Disconnect was delivered'); +}); From 883919a778c3ef9098faa03f7507906cf5ee8d7b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:51:01 -0400 Subject: [PATCH 222/907] Serialize a document's Drive saves and stop a transfer once its sender, page or account goes, checked at each chunk --- lib/background.js | 7 ++++-- lib/drive.js | 60 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/lib/background.js b/lib/background.js index 6b5505d37..aec9e012d 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3316,10 +3316,13 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { chrome.runtime.onConnect.addListener((p) => { if (p.name !== 'glaze:drive') return; const s = p.sender || {}, D = driveLib(), P = () => (openPapers(), GZ_WRITER_PAPERS); + let live = true; + p.onDisconnect.addListener(() => { live = false; }); + const allowed = async () => googleLib().ON && await drivePage(s) && live; D.serve(p, { - allowed: async () => googleLib().ON && await drivePage(s), + allowed, drive: D.create({ fetch: (u, init) => fetch(u, init), token: (o) => openGoogle().token(o), account: () => openGoogle().account(), - drop: (token) => openGoogle().drop(token) }), + drop: (token) => openGoogle().drop(token), allowed }), record: { get: async (k) => { if (!P().valid(k)) return null; diff --git a/lib/drive.js b/lib/drive.js index 7c7f873d8..86869f47e 100644 --- a/lib/drive.js +++ b/lib/drive.js @@ -74,13 +74,15 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (lib/google.js drop: forget one Drive answered 401 to), account() (the // connected account, lib/google.js account: one client serves one request, which stops as 'connect' if Drive is - // connected to another account or none before any of its calls), boundary() } + // connected to another account or none before any of its calls), allowed() (the current sender), boundary() } const create = (env) => { const bound = env.account ? Promise.resolve().then(env.account).catch(() => null) : null; const still = async () => { - if (!bound) return; - const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); - if (!was || now !== was) throw new Fail('connect'); + if (bound) { + const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); + if (!was || now !== was) throw new Fail('connect'); + } + if (env.allowed && !await env.allowed()) throw new Fail('sender'); }; const send = (url, init) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); const reason = async (r) => { @@ -129,10 +131,16 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { for (;;) { let x; try { x = await reader.read(); } catch { throw new Fail('network'); } - if (x.done) break; - total += x.value.length; - if (total > p.max) throw new Fail('size'); - await on.bytes?.(x.value, total); + if (x.done) { await still(); break; } + if (total + x.value.length > p.max) throw new Fail('size'); + // A network read can contain several port chunks. Recheck after each acknowledgement, so Disconnect + // cannot release the rest of that read to the page merely because its fetch was already under way. + for (let i = 0; i < x.value.length; i += CHUNK) { + await still(); + const b = x.value.subarray(i, i + CHUNK); + total += b.length; + await on.bytes?.(b, total); + } } } catch (e) { reader.cancel().catch(() => {}); throw e; } return total; @@ -174,23 +182,40 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // save: { type: 'more' }…, { type: 'saved', id, fresh }; either: { type: 'fail', why }. // o: { drive (create), allowed() (the sender, checked again before the request), record: { get(key) → { drive } or null // when there is no such document, set(key, drive) } } + // Tabs share a document's remote target. Keep the target read, upload and pointer write in one turn, without holding + // the paper broker's queue during a network request. Other documents can still transfer independently. + const saving = new Map(); + const saveTurn = (key, run) => { + const p = (saving.get(key) || Promise.resolve()).then(run); + const tail = p.catch(() => {}); + saving.set(key, tail); + tail.then(() => { if (saving.get(key) === tail) saving.delete(key); }); + return p; + }; const serve = (port, { drive, allowed = async () => true, record = null }) => { let gone = false, waiting = null, begun = false; const post = (m) => { if (gone) return; try { port.postMessage(m); } catch { gone = true; } }; const next = () => new Promise((ok, no) => { if (gone) no(new Fail('cancel')); else waiting = { ok, no }; }); + const active = async () => { + if (gone) throw new Fail('cancel'); + if (!await allowed()) throw new Fail('sender'); + if (gone) throw new Fail('cancel'); + }; port.onDisconnect.addListener(() => { gone = true; const w = waiting; waiting = null; w?.no(new Fail('cancel')); }); const open = async (m) => { const KS = kinds().KINDS; if (!Array.isArray(m.takes) || !m.takes.length || m.takes.length > 8 || !m.takes.every((k) => typeof k === 'string' && Object.hasOwn(KS, k))) throw new Fail('bad'); let parts = [], held = 0; const flush = async () => { + await active(); const c = join(parts, held); parts = []; held = 0; post({ type: 'chunk', data: b64(c) }); if ((await next()).op !== 'more') throw new Fail('bad'); + await active(); }; const size = await drive.open(m.id, m.takes, { - head: (h) => post({ type: 'file', ...h }), + head: async (h) => { await active(); post({ type: 'file', ...h }); }, bytes: async (b) => { for (let i = 0; i < b.length;) { const k = Math.min(CHUNK - held, b.length - i); @@ -222,18 +247,23 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { post({ type: 'more' }); } if (got !== m.size) throw new Fail('bad'); - const was = rec.drive?.as === m.as && typeof rec.drive.id === 'string' ? rec.drive.id : null; - const r = await drive.save({ name: m.name, mime: m.mime, bytes: join(parts, got), as: m.as, id: was }); - // Saved even if the record can't take the id (deleted meanwhile): the next save makes a new file. - await Promise.resolve().then(() => record.set(m.key, { id: r.id, as: m.as })).catch(() => {}); - return { type: 'saved', id: r.id, fresh: r.fresh }; + return saveTurn(m.key, async () => { + const current = await record.get(m.key); + if (!current) throw new Fail('missing'); + await active(); + const was = current.drive?.as === m.as && typeof current.drive.id === 'string' ? current.drive.id : null; + const r = await drive.save({ name: m.name, mime: m.mime, bytes: join(parts, got), as: m.as, id: was }); + // Saved even if the record can't take the id (deleted meanwhile): the next save makes a new file. + await Promise.resolve().then(() => record.set(m.key, { id: r.id, as: m.as })).catch(() => {}); + return { type: 'saved', id: r.id, fresh: r.fresh }; + }); }; port.onMessage.addListener((m) => { if (m?.op === 'ping') return; if (begun) { const w = waiting; waiting = null; w?.ok(m && typeof m === 'object' ? m : {}); return; } begun = true; (async () => { - if (!await allowed()) throw new Fail('sender'); + await active(); if (m?.op === 'open') return open(m); if (m?.op === 'save') return save(m); throw new Fail('bad'); From 9d7b630f81cd4da80e02cdeeea7b6f051777f52b Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:51:06 -0400 Subject: [PATCH 223/907] Refresh the Safari resource mirror for Drive's transfer checks --- .../Resources/lib/background.js | 7 ++- .../Glaze Extension/Resources/lib/drive.js | 60 ++++++++++++++----- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 6b5505d37..aec9e012d 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3316,10 +3316,13 @@ chrome.runtime.onMessage.addListener((m, s, reply) => { chrome.runtime.onConnect.addListener((p) => { if (p.name !== 'glaze:drive') return; const s = p.sender || {}, D = driveLib(), P = () => (openPapers(), GZ_WRITER_PAPERS); + let live = true; + p.onDisconnect.addListener(() => { live = false; }); + const allowed = async () => googleLib().ON && await drivePage(s) && live; D.serve(p, { - allowed: async () => googleLib().ON && await drivePage(s), + allowed, drive: D.create({ fetch: (u, init) => fetch(u, init), token: (o) => openGoogle().token(o), account: () => openGoogle().account(), - drop: (token) => openGoogle().drop(token) }), + drop: (token) => openGoogle().drop(token), allowed }), record: { get: async (k) => { if (!P().valid(k)) return null; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/drive.js b/safari/Glaze/Glaze Extension/Resources/lib/drive.js index 7c7f873d8..86869f47e 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/drive.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/drive.js @@ -74,13 +74,15 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // env: { fetch, token({ interactive }) (lib/google.js token), drop(token) (lib/google.js drop: forget one Drive answered 401 to), account() (the // connected account, lib/google.js account: one client serves one request, which stops as 'connect' if Drive is - // connected to another account or none before any of its calls), boundary() } + // connected to another account or none before any of its calls), allowed() (the current sender), boundary() } const create = (env) => { const bound = env.account ? Promise.resolve().then(env.account).catch(() => null) : null; const still = async () => { - if (!bound) return; - const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); - if (!was || now !== was) throw new Fail('connect'); + if (bound) { + const was = await bound, now = await Promise.resolve().then(env.account).catch(() => null); + if (!was || now !== was) throw new Fail('connect'); + } + if (env.allowed && !await env.allowed()) throw new Fail('sender'); }; const send = (url, init) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); const reason = async (r) => { @@ -129,10 +131,16 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { for (;;) { let x; try { x = await reader.read(); } catch { throw new Fail('network'); } - if (x.done) break; - total += x.value.length; - if (total > p.max) throw new Fail('size'); - await on.bytes?.(x.value, total); + if (x.done) { await still(); break; } + if (total + x.value.length > p.max) throw new Fail('size'); + // A network read can contain several port chunks. Recheck after each acknowledgement, so Disconnect + // cannot release the rest of that read to the page merely because its fetch was already under way. + for (let i = 0; i < x.value.length; i += CHUNK) { + await still(); + const b = x.value.subarray(i, i + CHUNK); + total += b.length; + await on.bytes?.(b, total); + } } } catch (e) { reader.cancel().catch(() => {}); throw e; } return total; @@ -174,23 +182,40 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { // save: { type: 'more' }…, { type: 'saved', id, fresh }; either: { type: 'fail', why }. // o: { drive (create), allowed() (the sender, checked again before the request), record: { get(key) → { drive } or null // when there is no such document, set(key, drive) } } + // Tabs share a document's remote target. Keep the target read, upload and pointer write in one turn, without holding + // the paper broker's queue during a network request. Other documents can still transfer independently. + const saving = new Map(); + const saveTurn = (key, run) => { + const p = (saving.get(key) || Promise.resolve()).then(run); + const tail = p.catch(() => {}); + saving.set(key, tail); + tail.then(() => { if (saving.get(key) === tail) saving.delete(key); }); + return p; + }; const serve = (port, { drive, allowed = async () => true, record = null }) => { let gone = false, waiting = null, begun = false; const post = (m) => { if (gone) return; try { port.postMessage(m); } catch { gone = true; } }; const next = () => new Promise((ok, no) => { if (gone) no(new Fail('cancel')); else waiting = { ok, no }; }); + const active = async () => { + if (gone) throw new Fail('cancel'); + if (!await allowed()) throw new Fail('sender'); + if (gone) throw new Fail('cancel'); + }; port.onDisconnect.addListener(() => { gone = true; const w = waiting; waiting = null; w?.no(new Fail('cancel')); }); const open = async (m) => { const KS = kinds().KINDS; if (!Array.isArray(m.takes) || !m.takes.length || m.takes.length > 8 || !m.takes.every((k) => typeof k === 'string' && Object.hasOwn(KS, k))) throw new Fail('bad'); let parts = [], held = 0; const flush = async () => { + await active(); const c = join(parts, held); parts = []; held = 0; post({ type: 'chunk', data: b64(c) }); if ((await next()).op !== 'more') throw new Fail('bad'); + await active(); }; const size = await drive.open(m.id, m.takes, { - head: (h) => post({ type: 'file', ...h }), + head: async (h) => { await active(); post({ type: 'file', ...h }); }, bytes: async (b) => { for (let i = 0; i < b.length;) { const k = Math.min(CHUNK - held, b.length - i); @@ -222,18 +247,23 @@ var GZ_DRIVE = globalThis.GZ_DRIVE || (() => { post({ type: 'more' }); } if (got !== m.size) throw new Fail('bad'); - const was = rec.drive?.as === m.as && typeof rec.drive.id === 'string' ? rec.drive.id : null; - const r = await drive.save({ name: m.name, mime: m.mime, bytes: join(parts, got), as: m.as, id: was }); - // Saved even if the record can't take the id (deleted meanwhile): the next save makes a new file. - await Promise.resolve().then(() => record.set(m.key, { id: r.id, as: m.as })).catch(() => {}); - return { type: 'saved', id: r.id, fresh: r.fresh }; + return saveTurn(m.key, async () => { + const current = await record.get(m.key); + if (!current) throw new Fail('missing'); + await active(); + const was = current.drive?.as === m.as && typeof current.drive.id === 'string' ? current.drive.id : null; + const r = await drive.save({ name: m.name, mime: m.mime, bytes: join(parts, got), as: m.as, id: was }); + // Saved even if the record can't take the id (deleted meanwhile): the next save makes a new file. + await Promise.resolve().then(() => record.set(m.key, { id: r.id, as: m.as })).catch(() => {}); + return { type: 'saved', id: r.id, fresh: r.fresh }; + }); }; port.onMessage.addListener((m) => { if (m?.op === 'ping') return; if (begun) { const w = waiting; waiting = null; w?.ok(m && typeof m === 'object' ? m : {}); return; } begun = true; (async () => { - if (!await allowed()) throw new Fail('sender'); + await active(); if (m?.op === 'open') return open(m); if (m?.op === 'save') return save(m); throw new Fail('bad'); From 855068759ed7fd5ecdaff1eeedab7ed990ca88f0 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:58:01 -0400 Subject: [PATCH 224/907] Bring the Safari builder's native Google flow into lib/google.js --- lib/google.js | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/lib/google.js b/lib/google.js index 09ce791ba..2c693e437 100644 --- a/lib/google.js +++ b/lib/google.js @@ -10,7 +10,9 @@ // (the packaged manifest's oauth2). // web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow // (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). -// native Safari, through the app (native(); not wired yet). +// native Safari: the extension's handler (safari/Glaze/Glaze Extension/GlazeGoogle.swift) holds the refresh token in the +// Keychain and mints tokens; the sign-in page runs in the Glaze app, opened by the handler (macOS) or by a tab +// the student taps (iOS, env.open). Connect answers why: 'pending' until the app has answered; Settings asks again. // API: status(), connect() (from a click), token({ interactive }), drop(token) (one Google refused), account(), disconnect(), // pick(). The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with // launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and @@ -101,16 +103,29 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { try { return await f(...a); } catch (e) { if (e instanceof Fail) return { ok: false, why: e.why }; throw e; } }; - // Safari: the app signs in. TODO(Safari): the extension handler (Swift) answers { type: 'google-auth', url } by running - // ASWebAuthenticationSession on url with the iOS client's callback scheme and replying { url } with the URL it came back - // to; the iOS client takes the code flow with PKCE (lib/oauth.js challenge, code). Until then config.native stays false. - const native = async (url) => { - const r = await Promise.resolve().then(() => env.native({ type: 'google-auth', url })).catch(() => null); - if (typeof r?.url !== 'string') throw new Fail('cancel'); - return r.url; + // Safari's handler: one google.* message, its answer or null. + const native = (message) => Promise.resolve().then(() => env.native(message)).catch(() => null); + let started = false; // a native sign-in under way, this worker's memory only + const nativeConnect = async () => { + if (!started) { + const r = await native({ type: 'google.connect' }); + if (r?.ok !== true || typeof r.open !== 'string') throw new Fail(r?.why === 'setup' ? 'setup' : 'auth'); + if (!r.opened) await Promise.resolve().then(() => env.open(r.open)).catch(() => { throw new Fail('auth'); }); + started = true; + } + const r = await native({ type: 'google.finish', wait: 15000 }); + if (r?.why === 'pending') throw new Fail('pending'); + started = false; + if (r?.ok !== true || typeof r.email !== 'string' || typeof r.sub !== 'string') throw new Fail(['cancel', 'state', 'scope', 'network'].includes(r?.why) ? r.why : 'auth'); + return { email: r.email, sub: r.sub }; + }; + const nativeToken = async (g) => { + const r = await native({ type: 'google.token', sub: g.sub }); + if (r?.ok === true && typeof r.token === 'string' && r.token) return r.token; + if (r?.why === 'revoked') await env.storage.remove('google'); // signin can be a race with a reconnect: keep the account + throw new Fail(r?.why === 'account' ? 'account' : r?.why === 'revoked' || r?.why === 'signin' ? 'connect' : 'network'); }; const launch = async (url, interactive) => { - if (env.flow === 'native') return native(url); const i = id(); if (typeof i?.launchWebAuthFlow !== 'function') throw new Fail('permission'); // Firefox's schema takes only these two keys, and reads the redirect from the url's redirect_uri. @@ -170,6 +185,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // g's token, once Google says it is g's account's. Another account's is forgotten (Chrome's cache, this worker's // memory) and refused: 'account'. const ours = async (g, interactive) => { + if (g.flow === 'native') return nativeToken(g); // the handler binds its refresh token to g.sub const t = await tokenFor(g.flow, interactive, g.email); if (checked.get(t) === g.sub) return t; const { sub } = await who(g.flow, t); @@ -185,14 +201,19 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const flow = flowNow(); if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; if (!usable(flow)) return { ok: false, why: 'setup' }; + if (flow === 'native' && (await native({ type: 'google.status' }))?.ok !== true) return { ok: false, why: 'setup' }; // no app group return { ok: true, flow, email: (await saved())?.email || null }; }; // From Settings' click, once the page holds the identity permission. → { ok, email } or { ok: false, why } const connect = () => turn(fails(async () => { const s = await status(); if (!s.ok) return s; + if (s.flow === 'native') { + const { email, sub } = await nativeConnect(); + await env.storage.set({ google: { email, sub, flow: 'native', at: now() } }); + return { ok: true, email }; + } if (!await Promise.resolve(env.permissions.contains({ permissions: ['identity'] })).catch(() => false)) return { ok: false, why: 'permission' }; - if (s.flow === 'native') return { ok: false, why: 'browser' }; let flow = s.flow, token; held = null; if (flow === 'chrome') { @@ -239,6 +260,12 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { await env.storage.remove('google'); return { ok: true, revoked: false, had: false }; } + if (g?.flow === 'native') { + const r = await native({ type: 'google.disconnect' }); + started = false; + await env.storage.remove('google'); + return { ok: true, revoked: r?.revoked === true, had: true }; + } const t = g && usable(g.flow) ? await ours(g, false).catch(() => null) : null; const r = t && await send(REVOKE, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), From 9790b10d878e1482e2715cb95c77f3eee962e114 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 16:58:28 -0400 Subject: [PATCH 225/907] Test the DMG app's Google sign-in URL scheme, the worker's tab opener, and Settings' native Connect --- scripts/google-check.js | 16 ++++++++++++++++ test/google.test.js | 6 ++++++ test/safari-update-native.test.js | 6 ++++++ 3 files changed, 28 insertions(+) diff --git a/scripts/google-check.js b/scripts/google-check.js index 3070b9160..293fdcf29 100644 --- a/scripts/google-check.js +++ b/scripts/google-check.js @@ -278,6 +278,22 @@ const HOSTS = ['accounts.google.com', 'openidconnect.googleapis.com', 'oauth2.go await own.close(); await lms.close(); + // Safari's native flow, the worker's answers stood in for: Settings asks for no identity permission (Safari has none), and + // asks again while the app's sign-in is pending. + await sw.evaluate(() => { + globalThis.__gzNative = { status: google.status, connect: google.connect, connects: 0 }; + google.status = async () => ({ ok: true, flow: 'native', email: __gzNative.connects >= 3 ? 'student@icloud.com' : null }); + google.connect = async () => (++__gzNative.connects < 3 ? { ok: false, why: 'pending' } : { ok: true, email: 'student@icloud.com' }); + }); + await open(); + await until(() => row.isVisible(), 'the row, native'); + await page.locator('#gdrive-connect').click(); + await until(async () => (await page.locator('#gdrive-who').textContent()) === 'student@icloud.com', 'connected, native', 30000); + ok(await sw.evaluate(() => __gzNative.connects) === 3, 'native: Settings asks again while the sign-in is pending, until it\'s done'); + ok(JSON.stringify(await page.evaluate(() => __gz.asked)) === '[]', 'and asks for no identity permission'); + ok(!(await page.locator('#hint').textContent()).includes("didn't connect"), 'and shows no failure while it waits'); + await sw.evaluate(() => { google.status = __gzNative.status; google.connect = __gzNative.connect; }); + // Who may ask: a page Glaze doesn't trust for this (the pop-up) is refused. const popup = await ctx.newPage(); await popup.goto(`chrome-extension://${id}/popup.html`); diff --git a/test/google.test.js b/test/google.test.js index 47328b580..98e59df82 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -453,6 +453,12 @@ test('pick: frostdev.io first, its GitHub Pages copy when it doesn\'t load, the assert.deepEqual(await harness().g.pick(), { ok: false, why: 'connect' }); }); +test('the worker gives lib/google.js a way to open the sign-in tab the student taps (iOS), from the worker', () => { + const src = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); + const env = src.slice(src.indexOf('google = GZ_GOOGLE.create({'), src.indexOf('});', src.indexOf('google = GZ_GOOGLE.create({'))); + assert.match(env, /open: \(url\) => chrome\.tabs\.create\(\{ url \}\)/); +}); + test('the worker answers glaze:google from Settings only (pick from Glaze\'s own pages), loading lib/google.js on first use', () => { const src = fs.readFileSync(path.join(__dirname, '..', 'lib/background.js'), 'utf8'); assert.match(src, /globalThis\.addEventListener\?\.\('install', \(\) => importScripts\('oauth\.js', 'google-config\.js', 'google\.js'\)\)/); diff --git a/test/safari-update-native.test.js b/test/safari-update-native.test.js index bd60d222f..2510ff551 100644 --- a/test/safari-update-native.test.js +++ b/test/safari-update-native.test.js @@ -54,6 +54,12 @@ test('DMG staging is repeatable and isolates Sparkle from the checked-in Mac/iOS const scheme = /static let start = "([a-z.-]+):\/\/start"/.exec(fs.readFileSync(path.join(root, 'safari/Glaze/Glaze Extension/GlazeAgentBridge.swift'), 'utf8'))[1]; assert.equal(scheme, 'io.frostdev.glaze-agent'); assert.deepEqual(info.CFBundleURLTypes[1], { CFBundleURLName: scheme, CFBundleURLSchemes: [scheme] }); + // Google Drive's sign-in: the extension's handler opens the app at ://signin (GlazeGoogle.swift), and the + // staged Info.plist, whose URL types the script replaces, keeps the scheme the app's own Info.plist declares. + const google = /static let appScheme = "([a-z.-]+)"/.exec(fs.readFileSync(path.join(root, 'safari/Glaze/Glaze Extension/GlazeGoogle.swift'), 'utf8'))[1]; + assert.equal(google, 'io.frostdev.glaze-google'); + assert.deepEqual(info.CFBundleURLTypes[2], { CFBundleURLName: google, CFBundleURLSchemes: [google] }); + assert.equal(info.CFBundleURLTypes.length, 3); const GROUPS = 'com.apple.security.application-groups'; const appEntitlements = plist(path.join(staged, 'Glaze/Release.entitlements')), extensionEntitlements = plist(path.join(staged, 'Glaze Extension/Release.entitlements')); assert.deepEqual([appEntitlements[GROUPS], appEntitlements['com.apple.security.app-sandbox']], [['22AAZFMTV8.io.frostdev.Glaze'], undefined]); From d647b5b6401dfdf36b40fc8d6f9c5ee7f555d12c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:00:49 -0400 Subject: [PATCH 226/907] Connect through the Glaze app on Safari, open its sign-in tab from the worker, and give the DMG app its Google URL scheme --- lib/background.js | 2 ++ lib/google-config.js | 4 ++-- options.js | 17 ++++++++++++----- scripts/stage-safari-dmg.py | 5 ++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/background.js b/lib/background.js index 3992b3c2b..c09d07cce 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3276,6 +3276,8 @@ const openGoogle = () => { identity: () => chrome.identity, permissions: chrome.permissions, manifest: () => chrome.runtime.getManifest(), storage: chrome.storage.local, fetch: (u, init) => fetch(u, init), native: (message) => chrome.runtime.sendNativeMessage(GZ_GOOGLE.APP, message), + // iOS: the sign-in tab the student taps (the worker opens it, never a framed page). + open: (url) => chrome.tabs.create({ url }), // Calendar sync signs in with the same permission and Chrome's same cache: while it's connected, Disconnect leaves both. keep: async () => GZ_GCAL.ON && !!(await chrome.storage.local.get('gcal')).gcal?.cal, }); diff --git a/lib/google-config.js b/lib/google-config.js index ee2f856d8..8654a3c41 100644 --- a/lib/google-config.js +++ b/lib/google-config.js @@ -12,13 +12,13 @@ var GZ_GOOGLE_CONFIG = globalThis.GZ_GOOGLE_CONFIG || { // redirect URIs: https://jmmaeoajomodnbnlmdikjooalmcilooh.chromiumapp.org/ (the store item, which Edge and Brave install // too) and Firefox's http://127.0.0.1/mozoauth2/3a273a94baf709b1c4095e46230f7ab56b7ad073. web: '1071735898350-r3pnkbe9c38i9j5lvkggf84lfhb6pasj.apps.googleusercontent.com', - // "iOS" client, bundle io.frostdev.Glaze: Safari, through the app (lib/google.js native; not wired yet). Its callback + // "iOS" client, bundle io.frostdev.Glaze: Safari, through the extension's handler and the app (lib/google.js native). Its callback // scheme: com.googleusercontent.apps.1071735898350-h5i3v4abnj2o4btvn270vq9689585eeh. ios: '1071735898350-h5i3v4abnj2o4btvn270vq9689585eeh.apps.googleusercontent.com', // The Picker's browser API key and the Cloud project number (Picker setAppId, which lets drive.file open what's picked). apiKey: 'AIzaSyAJaEwp76_apj8iixjxdeqGcrYNDJuRg-A', appId: '1071735898350', - // Safari: true once the app answers 'google-auth' (lib/google.js native). + // Safari: true once the owner has signed in through the app in real Safari (lib/google.js native). native: false, scopes: ['https://www.googleapis.com/auth/drive.file', 'openid', 'email'], // The picker page, then its copy: Glaze works without frostdev.io. diff --git a/options.js b/options.js index f6d118fd6..cb3401bf2 100644 --- a/options.js +++ b/options.js @@ -1162,7 +1162,9 @@ GZ_UI.load().then((s) => { // Hidden until Google's clients are set up for this browser (the worker says why: 'setup' or 'browser'). { const gdOp = (op) => new Promise((res) => chrome.runtime.sendMessage({ type: 'glaze:google', op }, (r) => res(chrome.runtime.lastError ? { ok: false, why: 'network' } : r || { ok: false }))); + let gdFlow = ''; const gdRender = (s) => { + gdFlow = s?.flow || ''; $('#gdrive').hidden = !s?.ok; $('#gdrive-who').textContent = s?.email || ''; $('#gdrive-connect').hidden = !!s?.email; @@ -1172,16 +1174,21 @@ GZ_UI.load().then((s) => { gdLoad(); let gdBusy = false; // Inside the student's click: the browser's identity permission first (optional; nothing is asked before this), then - // Google's own screen, from the worker. + // Google's own screen, from the worker. Safari signs in through the Glaze app and has no identity permission: Connect + // answers 'pending' until the app is done (each answer waits a while), and Settings asks again. $('#gdrive-connect').addEventListener('click', async (e) => { if (!e.isTrusted || gdBusy) return; + const native = gdFlow === 'native'; // Settings framed in a school's page may have no permissions API (Firefox): its own tab asks, at this section. - if (typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); + if (!native && typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); gdBusy = true; try { - const asked = chrome.permissions.request({ permissions: ['identity'] }); - if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); - const r = await gdOp('connect'); + if (!native) { + const asked = chrome.permissions.request({ permissions: ['identity'] }); + if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); + } + let r = await gdOp('connect'); + for (let n = 0; native && r.why === 'pending' && n < 40; n++) r = await gdOp('connect'); if (r.ok) { await gdLoad(); $('#gdrive-disconnect').focus(); return hint(t('Connected.')); } hint(r.why === 'scope' ? t('Connect again and allow Google Drive access.') : r.why === 'cancel' ? '' : t("Google Drive didn't connect. Try again in a moment."), r.why !== 'cancel'); } finally { gdBusy = false; } diff --git a/scripts/stage-safari-dmg.py b/scripts/stage-safari-dmg.py index 9e55d016d..a3f4dffb9 100644 --- a/scripts/stage-safari-dmg.py +++ b/scripts/stage-safari-dmg.py @@ -138,7 +138,10 @@ def target(name): 'CFBundleURLSchemes': ['io.frostdev.glaze-update']}, # GlazeAgentBridge.start: the extension opens the app hidden for agents. {'CFBundleURLName': 'io.frostdev.glaze-agent', - 'CFBundleURLSchemes': ['io.frostdev.glaze-agent']}]) + 'CFBundleURLSchemes': ['io.frostdev.glaze-agent']}, + # GlazeGoogle.appScheme: the extension opens the app's Google Drive sign-in. + {'CFBundleURLName': 'io.frostdev.glaze-google', + 'CFBundleURLSchemes': ['io.frostdev.glaze-google']}]) with info.open('wb') as f: plistlib.dump(i, f) # Exact version plus immutable revision, reviewed together on every Sparkle bump. From 67858e19dde6509756bf11d72a3a7ceece89dce2 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:00:51 -0400 Subject: [PATCH 227/907] Refresh the Safari resource mirror for Drive's native sign-in --- .../Resources/lib/background.js | 2 + .../Resources/lib/google-config.js | 4 +- .../Glaze Extension/Resources/lib/google.js | 47 +++++++++++++++---- .../Glaze Extension/Resources/options.js | 17 +++++-- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index 3992b3c2b..c09d07cce 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3276,6 +3276,8 @@ const openGoogle = () => { identity: () => chrome.identity, permissions: chrome.permissions, manifest: () => chrome.runtime.getManifest(), storage: chrome.storage.local, fetch: (u, init) => fetch(u, init), native: (message) => chrome.runtime.sendNativeMessage(GZ_GOOGLE.APP, message), + // iOS: the sign-in tab the student taps (the worker opens it, never a framed page). + open: (url) => chrome.tabs.create({ url }), // Calendar sync signs in with the same permission and Chrome's same cache: while it's connected, Disconnect leaves both. keep: async () => GZ_GCAL.ON && !!(await chrome.storage.local.get('gcal')).gcal?.cal, }); diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google-config.js b/safari/Glaze/Glaze Extension/Resources/lib/google-config.js index ee2f856d8..8654a3c41 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google-config.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google-config.js @@ -12,13 +12,13 @@ var GZ_GOOGLE_CONFIG = globalThis.GZ_GOOGLE_CONFIG || { // redirect URIs: https://jmmaeoajomodnbnlmdikjooalmcilooh.chromiumapp.org/ (the store item, which Edge and Brave install // too) and Firefox's http://127.0.0.1/mozoauth2/3a273a94baf709b1c4095e46230f7ab56b7ad073. web: '1071735898350-r3pnkbe9c38i9j5lvkggf84lfhb6pasj.apps.googleusercontent.com', - // "iOS" client, bundle io.frostdev.Glaze: Safari, through the app (lib/google.js native; not wired yet). Its callback + // "iOS" client, bundle io.frostdev.Glaze: Safari, through the extension's handler and the app (lib/google.js native). Its callback // scheme: com.googleusercontent.apps.1071735898350-h5i3v4abnj2o4btvn270vq9689585eeh. ios: '1071735898350-h5i3v4abnj2o4btvn270vq9689585eeh.apps.googleusercontent.com', // The Picker's browser API key and the Cloud project number (Picker setAppId, which lets drive.file open what's picked). apiKey: 'AIzaSyAJaEwp76_apj8iixjxdeqGcrYNDJuRg-A', appId: '1071735898350', - // Safari: true once the app answers 'google-auth' (lib/google.js native). + // Safari: true once the owner has signed in through the app in real Safari (lib/google.js native). native: false, scopes: ['https://www.googleapis.com/auth/drive.file', 'openid', 'email'], // The picker page, then its copy: Glaze works without frostdev.io. diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js index 09ce791ba..2c693e437 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -10,7 +10,9 @@ // (the packaged manifest's oauth2). // web Edge, Brave, other Chromium browsers and Firefox: launchWebAuthFlow, the "Web application" client's implicit flow // (response_type=token), its state checked (lib/oauth.js reply), renewed silently (prompt=none, interactive:false). -// native Safari, through the app (native(); not wired yet). +// native Safari: the extension's handler (safari/Glaze/Glaze Extension/GlazeGoogle.swift) holds the refresh token in the +// Keychain and mints tokens; the sign-in page runs in the Glaze app, opened by the handler (macOS) or by a tab +// the student taps (iOS, env.open). Connect answers why: 'pending' until the app has answered; Settings asks again. // API: status(), connect() (from a click), token({ interactive }), drop(token) (one Google refused), account(), disconnect(), // pick(). The Picker (pick): Google's Picker on Glaze's picker page, frostdev.io's, else its GitHub Pages copy, opened with // launchWebAuthFlow, the access token and a state in the fragment; the page comes back to the redirect URL with the state and @@ -101,16 +103,29 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { try { return await f(...a); } catch (e) { if (e instanceof Fail) return { ok: false, why: e.why }; throw e; } }; - // Safari: the app signs in. TODO(Safari): the extension handler (Swift) answers { type: 'google-auth', url } by running - // ASWebAuthenticationSession on url with the iOS client's callback scheme and replying { url } with the URL it came back - // to; the iOS client takes the code flow with PKCE (lib/oauth.js challenge, code). Until then config.native stays false. - const native = async (url) => { - const r = await Promise.resolve().then(() => env.native({ type: 'google-auth', url })).catch(() => null); - if (typeof r?.url !== 'string') throw new Fail('cancel'); - return r.url; + // Safari's handler: one google.* message, its answer or null. + const native = (message) => Promise.resolve().then(() => env.native(message)).catch(() => null); + let started = false; // a native sign-in under way, this worker's memory only + const nativeConnect = async () => { + if (!started) { + const r = await native({ type: 'google.connect' }); + if (r?.ok !== true || typeof r.open !== 'string') throw new Fail(r?.why === 'setup' ? 'setup' : 'auth'); + if (!r.opened) await Promise.resolve().then(() => env.open(r.open)).catch(() => { throw new Fail('auth'); }); + started = true; + } + const r = await native({ type: 'google.finish', wait: 15000 }); + if (r?.why === 'pending') throw new Fail('pending'); + started = false; + if (r?.ok !== true || typeof r.email !== 'string' || typeof r.sub !== 'string') throw new Fail(['cancel', 'state', 'scope', 'network'].includes(r?.why) ? r.why : 'auth'); + return { email: r.email, sub: r.sub }; + }; + const nativeToken = async (g) => { + const r = await native({ type: 'google.token', sub: g.sub }); + if (r?.ok === true && typeof r.token === 'string' && r.token) return r.token; + if (r?.why === 'revoked') await env.storage.remove('google'); // signin can be a race with a reconnect: keep the account + throw new Fail(r?.why === 'account' ? 'account' : r?.why === 'revoked' || r?.why === 'signin' ? 'connect' : 'network'); }; const launch = async (url, interactive) => { - if (env.flow === 'native') return native(url); const i = id(); if (typeof i?.launchWebAuthFlow !== 'function') throw new Fail('permission'); // Firefox's schema takes only these two keys, and reads the redirect from the url's redirect_uri. @@ -170,6 +185,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // g's token, once Google says it is g's account's. Another account's is forgotten (Chrome's cache, this worker's // memory) and refused: 'account'. const ours = async (g, interactive) => { + if (g.flow === 'native') return nativeToken(g); // the handler binds its refresh token to g.sub const t = await tokenFor(g.flow, interactive, g.email); if (checked.get(t) === g.sub) return t; const { sub } = await who(g.flow, t); @@ -185,14 +201,19 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const flow = flowNow(); if (flow === 'native' && c().native !== true) return { ok: false, why: 'browser' }; if (!usable(flow)) return { ok: false, why: 'setup' }; + if (flow === 'native' && (await native({ type: 'google.status' }))?.ok !== true) return { ok: false, why: 'setup' }; // no app group return { ok: true, flow, email: (await saved())?.email || null }; }; // From Settings' click, once the page holds the identity permission. → { ok, email } or { ok: false, why } const connect = () => turn(fails(async () => { const s = await status(); if (!s.ok) return s; + if (s.flow === 'native') { + const { email, sub } = await nativeConnect(); + await env.storage.set({ google: { email, sub, flow: 'native', at: now() } }); + return { ok: true, email }; + } if (!await Promise.resolve(env.permissions.contains({ permissions: ['identity'] })).catch(() => false)) return { ok: false, why: 'permission' }; - if (s.flow === 'native') return { ok: false, why: 'browser' }; let flow = s.flow, token; held = null; if (flow === 'chrome') { @@ -239,6 +260,12 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { await env.storage.remove('google'); return { ok: true, revoked: false, had: false }; } + if (g?.flow === 'native') { + const r = await native({ type: 'google.disconnect' }); + started = false; + await env.storage.remove('google'); + return { ok: true, revoked: r?.revoked === true, had: true }; + } const t = g && usable(g.flow) ? await ours(g, false).catch(() => null) : null; const r = t && await send(REVOKE, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ token: t }).toString(), diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index f6d118fd6..cb3401bf2 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1162,7 +1162,9 @@ GZ_UI.load().then((s) => { // Hidden until Google's clients are set up for this browser (the worker says why: 'setup' or 'browser'). { const gdOp = (op) => new Promise((res) => chrome.runtime.sendMessage({ type: 'glaze:google', op }, (r) => res(chrome.runtime.lastError ? { ok: false, why: 'network' } : r || { ok: false }))); + let gdFlow = ''; const gdRender = (s) => { + gdFlow = s?.flow || ''; $('#gdrive').hidden = !s?.ok; $('#gdrive-who').textContent = s?.email || ''; $('#gdrive-connect').hidden = !!s?.email; @@ -1172,16 +1174,21 @@ GZ_UI.load().then((s) => { gdLoad(); let gdBusy = false; // Inside the student's click: the browser's identity permission first (optional; nothing is asked before this), then - // Google's own screen, from the worker. + // Google's own screen, from the worker. Safari signs in through the Glaze app and has no identity permission: Connect + // answers 'pending' until the app is done (each answer waits a while), and Settings asks again. $('#gdrive-connect').addEventListener('click', async (e) => { if (!e.isTrusted || gdBusy) return; + const native = gdFlow === 'native'; // Settings framed in a school's page may have no permissions API (Firefox): its own tab asks, at this section. - if (typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); + if (!native && typeof chrome.permissions?.request !== 'function') return GZ_SITE.elsewhere ? GZ_SITE.elsewhere(e.currentTarget) : hint(t("Glaze needs the browser's sign-in permission to connect."), true); gdBusy = true; try { - const asked = chrome.permissions.request({ permissions: ['identity'] }); - if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); - const r = await gdOp('connect'); + if (!native) { + const asked = chrome.permissions.request({ permissions: ['identity'] }); + if (!await Promise.resolve(asked).catch(() => false)) return hint(t("Glaze needs the browser's sign-in permission to connect."), true); + } + let r = await gdOp('connect'); + for (let n = 0; native && r.why === 'pending' && n < 40; n++) r = await gdOp('connect'); if (r.ok) { await gdLoad(); $('#gdrive-disconnect').focus(); return hint(t('Connected.')); } hint(r.why === 'scope' ? t('Connect again and allow Google Drive access.') : r.why === 'cancel' ? '' : t("Google Drive didn't connect. Try again in a moment."), r.why !== 'cancel'); } finally { gdBusy = false; } From 3197b08722f4019fdf86c93c403aa959bfa5612c Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:11:38 -0400 Subject: [PATCH 228/907] Stop stale token answers after a connection change, end Safari's pending sign-ins on Disconnect, forget refused Safari tokens, keep a late exchange from revoking a newer grant, and release identity in the shared grant queue --- lib/background.js | 2 + lib/google.js | 30 +++-- options.js | 3 +- .../Glaze/Glaze Extension/GlazeGoogle.swift | 18 ++- test/gcal.test.js | 37 +++++- test/google-native-races.test.js | 52 ++++++++ test/google.test.js | 113 +++++++++++++++++- 7 files changed, 237 insertions(+), 18 deletions(-) diff --git a/lib/background.js b/lib/background.js index c09d07cce..94a803801 100644 --- a/lib/background.js +++ b/lib/background.js @@ -3176,6 +3176,8 @@ const gcalOps = { const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); + // Release inside grantTurn, before another Connect can acquire identity. A delayed Settings reply owns no grant. + await Promise.resolve().then(() => chrome.permissions.remove({ permissions: ['identity'] })).catch(() => {}); await chrome.storage.local.remove(['gcal', 'gcalSync']); serial(ensure); return { ok: true, revoked, had: !!token }; diff --git a/lib/google.js b/lib/google.js index 2c693e437..f5aea0f86 100644 --- a/lib/google.js +++ b/lib/google.js @@ -89,6 +89,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { let held = null; // the web flow's { token, exp }: this worker's memory only const checked = new Map(); // token → the account (sub) its userinfo named: this worker's memory only let gen = 0; // moves on at each Connect and Disconnect: a Pick that started before one stops + const current = (at) => { if (gen !== at) throw new Fail('connect'); }; let queue = Promise.resolve(); const turn = (f) => { const p = queue.then(f); queue = p.catch(() => {}); return p; }; const send = (url, init = {}) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); @@ -120,9 +121,11 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { email: r.email, sub: r.sub }; }; const nativeToken = async (g) => { + const at = gen; const r = await native({ type: 'google.token', sub: g.sub }); + current(at); if (r?.ok === true && typeof r.token === 'string' && r.token) return r.token; - if (r?.why === 'revoked') await env.storage.remove('google'); // signin can be a race with a reconnect: keep the account + if (r?.why === 'revoked') await env.storage.remove('google'); throw new Fail(r?.why === 'account' ? 'account' : r?.why === 'revoked' || r?.why === 'signin' ? 'connect' : 'network'); }; const launch = async (url, interactive) => { @@ -158,13 +161,17 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // hint: the connected account, for a silent renewal first; without one only an interactive sign-in can answer. const webToken = async (interactive, hint) => { if (held && held.exp - EARLY > now()) return held.token; + const at = gen; held = null; const once = async (prompt, inter) => { + current(at); const state = O.random(), back = redirect(); - return grant(await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter), state, now()); + const answer = await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter); + current(at); + return grant(answer, state, now()); }; let g = null; - if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state') throw e; return null; }); + if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state' || e.why === 'connect') throw e; return null; }); else if (!interactive) throw new Fail('auth'); g ||= await once(hint ? '' : 'select_account', true); held = g; @@ -173,7 +180,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const tokenFor = (flow, interactive, hint) => (flow === 'chrome' ? chromeToken(interactive) : webToken(interactive, hint)); const drop = async (flow, token) => { if (flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token })).catch(() => {}); - else held = null; + else if (held?.token === token) held = null; }; const who = async (flow, token) => { const r = await send(USERINFO, { headers: { authorization: `Bearer ${token}` } }).catch(() => null); @@ -186,9 +193,12 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // memory) and refused: 'account'. const ours = async (g, interactive) => { if (g.flow === 'native') return nativeToken(g); // the handler binds its refresh token to g.sub + const at = gen; const t = await tokenFor(g.flow, interactive, g.email); + current(at); if (checked.get(t) === g.sub) return t; const { sub } = await who(g.flow, t); + current(at); if (checked.size > 50) checked.clear(); checked.set(t, sub); if (sub !== g.sub) { await drop(g.flow, t); throw new Fail('account'); } @@ -210,6 +220,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { if (!s.ok) return s; if (s.flow === 'native') { const { email, sub } = await nativeConnect(); + gen++; await env.storage.set({ google: { email, sub, flow: 'native', at: now() } }); return { ok: true, email }; } @@ -231,10 +242,14 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // The worker's token for a Drive request; interactive only for something the student pressed. const token = async ({ interactive = false } = {}) => { if (!on) throw new Fail('off'); + const at = gen; const g = await saved(); + current(at); if (!g) throw new Fail('connect'); if (!usable(g.flow)) throw new Fail('setup'); - return ours(g, interactive); + const t = await ours(g, interactive); + current(at); + return t; }; const account = async () => (await saved())?.email || null; // A token Google answered 401 to: forgotten with its account check (Chrome's cache, or this worker's held one), so the @@ -244,6 +259,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { checked.delete(t); const g = await saved(); if (g?.flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token: t })).catch(() => {}); + else if (g?.flow === 'native') await native({ type: 'google.drop', token: t }); else if (held?.token === t) held = null; }; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and @@ -260,11 +276,11 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { await env.storage.remove('google'); return { ok: true, revoked: false, had: false }; } - if (g?.flow === 'native') { + if (env.flow === 'native' || g?.flow === 'native') { const r = await native({ type: 'google.disconnect' }); started = false; await env.storage.remove('google'); - return { ok: true, revoked: r?.revoked === true, had: true }; + return { ok: true, revoked: r?.revoked === true, had: !!g || r?.had === true }; } const t = g && usable(g.flow) ? await ours(g, false).catch(() => null) : null; const r = t && await send(REVOKE, { diff --git a/options.js b/options.js index cb3401bf2..020ec3de4 100644 --- a/options.js +++ b/options.js @@ -1149,8 +1149,7 @@ GZ_UI.load().then((s) => { if ($('#gcal-bye').returnValue !== 'ok') return $('#gcal-disconnect').focus(); const r = await gcalOp({ op: 'disconnect', drop: $('#gcal-drop').checked }); if (!r.ok) return hint(t("Couldn't delete the Glaze calendar, so nothing changed. Try again, or disconnect without deleting it."), true); - // Nothing stays behind: the permission goes too, until the next Connect asks again, unless Google Drive still uses it. - if (!r.shared) await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); + // The worker released identity in its grant queue unless Drive still holds it. gcalRender(null); $('#gcal-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), r.had && !r.revoked); diff --git a/safari/Glaze/Glaze Extension/GlazeGoogle.swift b/safari/Glaze/Glaze Extension/GlazeGoogle.swift index 05a9dd32d..6876ba44c 100644 --- a/safari/Glaze/Glaze Extension/GlazeGoogle.swift +++ b/safari/Glaze/Glaze Extension/GlazeGoogle.swift @@ -17,6 +17,7 @@ // { type: 'google.token', sub? } → { ok: true, token, expiresAt (ms since 1970) } | { ok: false, why: 'signin' | // 'account' (sub isn't the connected account's) | 'network' | 'revoked' (Google refused the refresh token: forgotten) } // { type: 'google.disconnect' } → { ok: true, revoked, had }: the grant revoked with its refresh token, everything forgotten +// { type: 'google.drop', token } → { ok: true }: forget this refused access token, retaining the refresh token // Any other failure is { ok: false, why: 'request' | 'setup' }. Nothing here logs; replies carry fixed reasons only. // @@ -168,6 +169,9 @@ nonisolated enum GlazeGoogle { /// Only an explicit Disconnect may revoke a late exchange. Google's revocation also invalidates newer grants /// for the same account/client, so a superseding Connect must only discard the old answer. private var disconnected: Set = [] + // Revocation covers the client across profiles. A late exchange must not revoke an authorization begun later + // in another profile, just as it must not revoke a superseding Connect in its own profile. + private var authorization: String? private var revoking = 0 private var revocationWaiters: [CheckedContinuation] = [] private func bump(_ profile: String) { generation[profile, default: 0] += 1 } @@ -190,6 +194,7 @@ nonisolated enum GlazeGoogle { if let hint, !validEmail(hint) { throw Fail.request } guard let state = random(), let verifier = random() else { throw Fail.setup } bump(profile) + authorization = state disconnected.remove(profile) let started = generation[profile, default: 0] // A revoke already sent to Google cannot be canceled. Do not start another authorization until it ends. @@ -228,7 +233,7 @@ nonisolated enum GlazeGoogle { let g: Grant do { g = try grant(status: status, body: body, now: now()) } catch Fail.revoked { throw Fail.auth } // a spent code guard generation[profile, default: 0] == started else { - if disconnected.contains(profile), let r = g.refresh { _ = await revoke(r) } + if disconnected.contains(profile), authorization == p.state, let r = g.refresh { _ = await revoke(r) } throw Fail.signin } guard let refresh = g.refresh, let account = g.account else { throw Fail.auth } @@ -257,6 +262,10 @@ nonisolated enum GlazeGoogle { return (g.access, g.expires) } + func drop(profile: String, token: String) { + if held[profile]?.token == token { held[profile] = nil } + } + func disconnect(profile: String) async -> (revoked: Bool, had: Bool) { bump(profile) disconnected.insert(profile) @@ -418,13 +427,14 @@ nonisolated enum GlazeGoogle { /// A google.* message, its fields checked before anything awaits. struct Request: Sendable { - let type: String, profile: String, hint: String?, sub: String?, wait: TimeInterval + let type: String, profile: String, hint: String?, sub: String?, token: String?, wait: TimeInterval init?(_ message: Any?, profile: UUID?) { guard let m = message as? [String: Any], let type = m["type"] as? String, type.hasPrefix("google.") else { return nil } self.type = type self.profile = profile?.uuidString ?? "default" hint = m["hint"] as? String sub = m["sub"] as? String + token = m["token"] as? String wait = ((m["wait"] as? NSNumber)?.doubleValue ?? 0) / 1000 } } @@ -450,6 +460,10 @@ nonisolated enum GlazeGoogle { case "google.disconnect": let d = await engine.disconnect(profile: r.profile) return ["ok": true, "revoked": d.revoked, "had": d.had] + case "google.drop": + guard let token = r.token, !token.isEmpty, token.utf8.count <= 4096 else { return fail(.request) } + await engine.drop(profile: r.profile, token: token) + return ["ok": true] default: return fail(.request) } } catch let f as Fail { return fail(f) } catch { return fail(.auth) } diff --git a/test/gcal.test.js b/test/gcal.test.js index a722cb2ea..9828245ea 100644 --- a/test/gcal.test.js +++ b/test/gcal.test.js @@ -298,6 +298,7 @@ const worker = (mock, { on = true, drive = false, local = {}, sync = { enabled: removeCachedAuthToken: async ({ token }) => { identity.removed.push(token); }, clearAllCachedAuthTokens: async () => { identity.cleared++; }, }; + const permissions = { contains: async () => true, remove: async () => true }; const context = vm.createContext({ URL, URLSearchParams, console, TextEncoder, TextDecoder, structuredClone, Promise, Date, AbortController, crypto: globalThis.crypto, atob, btoa, navigator: {}, setTimeout: (f, ms) => setTimeout(f, Math.min(ms, 1)), clearTimeout, @@ -312,7 +313,7 @@ const worker = (mock, { on = true, drive = false, local = {}, sync = { enabled: onInstalled: { addListener: (f) => (listeners.installed ||= []).push(f) }, onStartup: { addListener() {} }, onConnect: { addListener() {} } }, storage: { local: area('local', store.local), sync: area('sync', store.sync), onChanged: { addListener: (f) => heard.push(f) } }, identity, - permissions: { contains: async () => true }, // the student granted every host (Chrome) + permissions, // the student granted every host (Chrome) tabs: { query: async () => [] }, scripting: { getRegisteredContentScripts: async () => registered, @@ -325,7 +326,7 @@ const worker = (mock, { on = true, drive = false, local = {}, sync = { enabled: const send = (m, sender) => new Promise((res) => { for (const f of listeners) f(structuredClone(m), sender, (r) => res(structuredClone(r))); }); // change(): a change made elsewhere (the popup, the editor), heard as Chrome tells it. const change = (name, patch) => { const c = Object.fromEntries(Object.keys(patch).map((k) => [k, { oldValue: store[name][k], newValue: patch[k] }])); Object.assign(store[name], patch); fire(name, c); }; - return { store, identity, asked, send, hooks, change, registered: () => registered, install: () => { for (const f of listeners.installed) f(); } }; + return { store, identity, permissions, asked, send, hooks, change, registered: () => registered, install: () => { for (const f of listeners.installed) f(); } }; }; // What the page sends with its batch (lib/calexport.js consent), worked out from the worker's storage as it is now. const D = require('../defaults.js'); @@ -424,6 +425,33 @@ withMock('worker: Calendar\'s and Drive\'s Disconnects at once, in either order: } }); +test('Calendar releases identity before replying so Settings cannot remove a later Drive connection permission', async () => { + const w = worker({ api: G.API, revoke: G.REVOKE }, { local: { gcal: { gen: 'g', cal: 'calendar', last: {} } } }); + w.identity.getAuthToken = async () => { throw new Error('signed out'); }; + let permissionHeld = true, close, removed = 0; + w.permissions.remove = async () => { removed++; permissionHeld = false; return true; }; + const nodes = { + '#gcal-bye': { returnValue: 'ok', addEventListener: (_, f) => { close = f; } }, + '#gcal-drop': { checked: false }, '#gcal-connect': { focus() {} }, + }; + const source = read('options.js'); + const start = source.indexOf("$('#gcal-bye').addEventListener('close'"); + const end = source.indexOf('chrome.storage.onChanged.addListener', start); + vm.runInNewContext(source.slice(start, end), { + $: (s) => nodes[s], chrome: { permissions: w.permissions }, t: (s) => s, hint() {}, gcalRender() {}, + gcalOp: async (m) => { + const result = await w.send({ type: 'glaze:gcal', ...m }, EDITOR); + // A queued Drive Connect completes before the reply reaches Settings. + permissionHeld = true; + w.store.local.google = { email: 'student@school.edu', sub: '1', flow: 'chrome', at: NOW }; + return result; + }, + }); + await close(); + assert.equal(removed, 1, 'the worker releases the old permission'); + assert.equal(permissionHeld, true, 'the old Calendar reply must not remove Drive\'s new permission'); +}); + test('Settings\' Calendar sign-in asks Chrome for Calendar\'s scope only', () => { const src = read('options.js'); const sign = src.slice(src.indexOf('const gcalSignIn = async () => {'), src.indexOf('// Only a person\'s click or key, while the page has their activation')); @@ -432,11 +460,10 @@ test('Settings\' Calendar sign-in asks Chrome for Calendar\'s scope only', () => assert.doesNotMatch(sign, /getManifest\(\)\.oauth2/); }); -test('Settings gives identity back after Calendar\'s Disconnect only when Drive doesn\'t share it', () => { +test('Settings leaves Calendar identity cleanup to the worker grant queue', () => { const src = read('options.js'); const bye = src.slice(src.indexOf("$('#gcal-bye').addEventListener('close'"), src.indexOf("chrome.storage.onChanged.addListener((changes, area) => { if (area === 'local' && changes.gcal)")); - assert.match(bye, /if \(!r\.shared\) await chrome\.permissions\.remove\(\{ permissions: \['identity'\] \}\)/); - assert.doesNotMatch(bye.replace(/if \(!r\.shared\) await chrome\.permissions\.remove/, ''), /permissions\.remove/); + assert.doesNotMatch(bye, /permissions\.remove/); }); // ---- the build-time switch (lib/gcal.js ON): off in the repo and the store package until Google verifies the app ---- diff --git a/test/google-native-races.test.js b/test/google-native-races.test.js index 428decbf9..5ccb921ab 100644 --- a/test/google-native-races.test.js +++ b/test/google-native-races.test.js @@ -95,6 +95,36 @@ emit(["old": oldResult, "token": token.token, "revokes": await transport.sent.fi assert.deepEqual(r, { old: 'signin', token: 'access-new', revokes: 0 }); }); +test('native drop forgets only the refused access token and renews with the saved grant', { skip }, async () => { + const r = await run(String.raw` +_ = vault.write("refresh.p", try! JSONEncoder().encode(G.Engine.Saved(refresh: "refresh-old", sub: "student", email: "student@example.edu"))) +let first = Task { try await engine.token(profile: "p", sub: "student") } +await transport.waitFor(1) +await transport.answer(0, grant("old")) +let old = try await first.value +let request = G.Request(["type": "google.drop", "token": "someone-else"], profile: nil)! +let ignored = await G.reply(to: request, engine: engine) +if ignored["ok"] as? Bool != true { emit(["drop": false]); exit(0) } +// Use this profile's public native-message path as the worker does. +let profile = UUID() +_ = vault.write("refresh." + profile.uuidString, try! JSONEncoder().encode(G.Engine.Saved(refresh: "refresh-new", sub: "student", email: "student@example.edu"))) +let held = Task { try await engine.token(profile: profile.uuidString, sub: "student") } +await transport.waitFor(2) +await transport.answer(1, grant("held")) +let heldToken = try await held.value +_ = await G.reply(to: G.Request(["type": "google.drop", "token": "someone-else"], profile: profile)!, engine: engine) +let same = try await engine.token(profile: profile.uuidString, sub: "student") +let dropped = await G.reply(to: G.Request(["type": "google.drop", "token": heldToken.token], profile: profile)!, engine: engine) +let fresh = Task { try await engine.token(profile: profile.uuidString, sub: "student") } +await transport.waitFor(3) +await transport.answer(2, grant("fresh")) +let next = try await fresh.value +emit(["drop": dropped["ok"] as? Bool == true, "same": same.token, "next": next.token, + "refresh": await transport.sent.last!["refresh_token"]!, "oldProfile": old.token]) +`); + assert.deepEqual(r, { drop: true, same: 'access-held', next: 'access-fresh', refresh: 'refresh-new', oldProfile: 'access-old' }); +}); + test('Connect waits for an already dispatched revocation before starting a new authorization', { skip }, async () => { const r = await run(String.raw` _ = vault.write("refresh.p", try! JSONEncoder().encode(G.Engine.Saved(refresh: "refresh-old", sub: "student", email: "student@example.edu"))) @@ -124,6 +154,28 @@ emit(["beforeRevoked": before, "afterRevoked": await progress.connected]) assert.deepEqual(r, { beforeRevoked: false, afterRevoked: true }); }); +for (const early of [false, true]) test(`a disconnected profile cannot revoke a newer authorization in another profile: newer Connect ${early ? 'before' : 'after'} the old exchange`, { skip }, async () => { + const r = await run(String.raw` +let oldSignIn = try await start("old") +` + (early ? '_ = try await start("new")\n' : '') + String.raw` +// The fixed test clock is later than file mtimes; deliver the callback after both Connects have swept old files. +_ = slot.put(oldSignIn.state, try! JSONSerialization.data(withJSONObject: ["url": G.redirect + "?state=" + oldSignIn.state + "&code=old"])) +let old = Task { await reason { try await engine.finish(profile: "old", wait: 0) } } +await transport.waitFor(1) +_ = await engine.disconnect(profile: "old") +` + (!early ? '_ = try await start("new")\n' : '') + String.raw` +let fresh = Task { try await engine.finish(profile: "new", wait: 0) } +await transport.waitFor(2) +await transport.answer(1, grant("new")) +_ = try await fresh.value +await transport.answer(0, grant("old")) +let result = await old.value +let token = try await engine.token(profile: "new", sub: nil) +emit(["old": result, "token": token.token, "revokes": await transport.sent.filter { $0["url"] == G.revokeURL.absoluteString }.count]) +`); + assert.deepEqual(r, { old: 'signin', token: 'access-new', revokes: 0 }); +}); + for (const response of ['invalid_grant', 'scope', 'success']) { test(`a refresh started during Connect cannot replace or delete the completed grant: ${response}`, { skip }, async () => { const r = await run(String.raw` diff --git a/test/google.test.js b/test/google.test.js index 98e59df82..ffdcc79ff 100644 --- a/test/google.test.js +++ b/test/google.test.js @@ -17,7 +17,7 @@ const BLANK = { ...C, chrome: 'PLACEHOLDER-chrome.apps.googleusercontent.com', w const REAL = { ...C, chrome: '111-chrome.apps.googleusercontent.com', web: '222-web.apps.googleusercontent.com', ios: '333-ios.apps.googleusercontent.com', apiKey: 'AIzaTestKey', appId: '123456789012' }; // A worker: flow, config, what Google answers (google(details) → the redirect URL; userinfo), which picker pages load. -const harness = ({ on = true, flow = 'chrome', config = REAL, granted = true, firefox = false, client = REAL.chrome, keep = false, up = [true, true], email = 'student@school.edu' } = {}) => { +const harness = ({ on = true, flow = 'chrome', config = REAL, granted = true, firefox = false, client = REAL.chrome, keep = false, up = [true, true], email = 'student@school.edu', native = async () => null } = {}) => { const local = {}, sync = {}; const who = { sub: '1', email }; // the account Google's userinfo names for any token: changed to model another account const owners = {}, gate = { picker: null }; // gate.picker: what the picker page's HEAD waits on @@ -75,7 +75,8 @@ const harness = ({ on = true, flow = 'chrome', config = REAL, granted = true, fi manifest: () => ({ oauth2: { client_id: client } }), storage: area(local), fetch, - native: async (m) => { log.native.push(m); return null; }, + native: async (m) => { log.native.push(m); return native(m); }, + open: async () => {}, keep: async () => keep, now: () => clock, }); @@ -373,6 +374,114 @@ test('Disconnect stops a Pick under way: no picker opens with the token, and no } }); +test('a delayed token cannot survive Disconnect or replace a reconnected account', async () => { + for (const flow of ['chrome', 'web']) for (const reconnect of [false, true]) { + const h = harness({ flow, keep: true }); + await h.g.connect(); + h.tick(3600 * 1000); + let release, entered; + const waiting = new Promise((r) => { entered = r; }); + const gate = new Promise((r) => { release = r; }); + const method = flow === 'web' ? 'answer' : 'getAuthToken'; + const original = h.identity[method]; + h.identity[method] = async (d) => { + h.identity[method] = original; + const answer = await original(d); + entered(); + await gate; + return answer; + }; + const late = assert.rejects(h.g.token(), (e) => e.why === 'connect'); + await waiting; + await h.g.disconnect(); + if (reconnect) { + Object.assign(h.who, { sub: 'B', email: 'b@school.edu' }); + await h.g.connect(); + } + release(); + await late; + if (reconnect) assert.equal(typeof await h.g.token(), 'string', `${flow}: the new account still works`); + else await assert.rejects(h.g.token(), (e) => e.why === 'connect'); + } +}); + +test('Disconnect stops a failed silent renewal from opening another sign-in', async () => { + const h = harness({ flow: 'web', keep: true }); + await h.g.connect(); + h.tick(3600 * 1000); + let release, entered; + const waiting = new Promise((r) => { entered = r; }); + const original = h.identity.answer; + h.identity.answer = async () => { + h.identity.answer = original; + entered(); + await new Promise((r) => { release = r; }); + throw new Error('silent renewal needs interaction'); + }; + const picking = h.g.pick(); + await waiting; + await h.g.disconnect(); + release(); + assert.deepEqual(await picking, { ok: false, why: 'connect' }); + assert.equal(h.log.launch.length, 2, 'only Connect and the silent renewal; no prompt after Disconnect'); +}); + +test('native Disconnect cancels a pending sign-in even before an account is saved', async () => { + const h = harness({ flow: 'native', config: { ...REAL, native: true }, native: async (m) => { + if (m.type === 'google.status') return { ok: true }; + if (m.type === 'google.connect') return { ok: true, open: 'io.frostdev.glaze-google://signin', opened: true }; + if (m.type === 'google.finish') return { ok: false, why: 'pending' }; + if (m.type === 'google.disconnect') return { ok: true, revoked: false, had: false }; + throw new Error(m.type); + } }); + assert.deepEqual(await h.g.connect(), { ok: false, why: 'pending' }); + assert.deepEqual(await h.g.disconnect(), { ok: true, revoked: false, had: false }); + assert.equal(h.log.native.filter((m) => m.type === 'google.disconnect').length, 1); + await h.g.connect(); + assert.equal(h.log.native.filter((m) => m.type === 'google.connect').length, 2, 'the old pending attempt is not resumed'); + assert.deepEqual(h.log.permRemoved, [], 'Safari has no identity permission'); +}); + +test('a stale native revoked response cannot erase a newer connection', async () => { + let release, entered, account = { sub: 'A', email: 'a@school.edu' }; + const waiting = new Promise((r) => { entered = r; }); + const h = harness({ flow: 'native', config: { ...REAL, native: true }, native: async (m) => { + if (m.type === 'google.status') return { ok: true }; + if (m.type === 'google.connect') return { ok: true, open: 'io.frostdev.glaze-google://signin', opened: true }; + if (m.type === 'google.finish') return { ok: true, ...account }; + if (m.type === 'google.token') { + entered(); + return new Promise((r) => { release = r; }); + } + throw new Error(m.type); + } }); + await h.g.connect(); + const late = assert.rejects(h.g.token(), (e) => e.why === 'connect'); + await waiting; + account = { sub: 'B', email: 'b@school.edu' }; + assert.equal((await h.g.connect()).ok, true); + release({ ok: false, why: 'revoked' }); + await late; + assert.equal(await h.g.account(), 'b@school.edu'); +}); + +test('native drop forwards the refused token so the handler can renew it', async () => { + let token = 'native-old'; + const h = harness({ flow: 'native', config: { ...REAL, native: true }, native: async (m) => { + if (m.type === 'google.status') return { ok: true }; + if (m.type === 'google.connect') return { ok: true, open: 'io.frostdev.glaze-google://signin', opened: true }; + if (m.type === 'google.finish') return { ok: true, sub: 'A', email: 'a@school.edu' }; + if (m.type === 'google.token') return { ok: true, token }; + if (m.type === 'google.drop') { if (m.token === token) token = 'native-new'; return { ok: true }; } + throw new Error(m.type); + } }); + await h.g.connect(); + await h.g.drop('someone-else'); + assert.equal(await h.g.token(), 'native-old'); + await h.g.drop('native-old'); + assert.equal(await h.g.token(), 'native-new'); +}); + test('Disconnect after a worker restart, when silent renewal fails, says Google may still hold the grant', async () => { const h = harness({ flow: 'web' }); await h.g.connect(); From 166ecab77609e5a81caeec212adb3951a23613f9 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:11:38 -0400 Subject: [PATCH 229/907] Refresh the Safari resource mirror for the review fixes --- .../Resources/lib/background.js | 2 ++ .../Glaze Extension/Resources/lib/google.js | 30 ++++++++++++++----- .../Glaze Extension/Resources/options.js | 3 +- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/safari/Glaze/Glaze Extension/Resources/lib/background.js b/safari/Glaze/Glaze Extension/Resources/lib/background.js index c09d07cce..94a803801 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/background.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/background.js @@ -3176,6 +3176,8 @@ const gcalOps = { const token = await gcalEnv.token().catch(() => null); const revoked = !!token && await GZ_GCAL.revoke(gcalEnv, token); await Promise.resolve().then(() => chrome.identity.clearAllCachedAuthTokens()).catch(() => {}); + // Release inside grantTurn, before another Connect can acquire identity. A delayed Settings reply owns no grant. + await Promise.resolve().then(() => chrome.permissions.remove({ permissions: ['identity'] })).catch(() => {}); await chrome.storage.local.remove(['gcal', 'gcalSync']); serial(ensure); return { ok: true, revoked, had: !!token }; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/google.js b/safari/Glaze/Glaze Extension/Resources/lib/google.js index 2c693e437..f5aea0f86 100644 --- a/safari/Glaze/Glaze Extension/Resources/lib/google.js +++ b/safari/Glaze/Glaze Extension/Resources/lib/google.js @@ -89,6 +89,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { let held = null; // the web flow's { token, exp }: this worker's memory only const checked = new Map(); // token → the account (sub) its userinfo named: this worker's memory only let gen = 0; // moves on at each Connect and Disconnect: a Pick that started before one stops + const current = (at) => { if (gen !== at) throw new Fail('connect'); }; let queue = Promise.resolve(); const turn = (f) => { const p = queue.then(f); queue = p.catch(() => {}); return p; }; const send = (url, init = {}) => env.fetch(url, { ...init, credentials: 'omit', redirect: 'error', cache: 'no-store' }); @@ -120,9 +121,11 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { return { email: r.email, sub: r.sub }; }; const nativeToken = async (g) => { + const at = gen; const r = await native({ type: 'google.token', sub: g.sub }); + current(at); if (r?.ok === true && typeof r.token === 'string' && r.token) return r.token; - if (r?.why === 'revoked') await env.storage.remove('google'); // signin can be a race with a reconnect: keep the account + if (r?.why === 'revoked') await env.storage.remove('google'); throw new Fail(r?.why === 'account' ? 'account' : r?.why === 'revoked' || r?.why === 'signin' ? 'connect' : 'network'); }; const launch = async (url, interactive) => { @@ -158,13 +161,17 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // hint: the connected account, for a silent renewal first; without one only an interactive sign-in can answer. const webToken = async (interactive, hint) => { if (held && held.exp - EARLY > now()) return held.token; + const at = gen; held = null; const once = async (prompt, inter) => { + current(at); const state = O.random(), back = redirect(); - return grant(await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter), state, now()); + const answer = await launch(authUrl(c(), { redirect: back, state, prompt, hint }), inter); + current(at); + return grant(answer, state, now()); }; let g = null; - if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state') throw e; return null; }); + if (hint) g = await once('none', false).catch((e) => { if (!interactive || e.why === 'state' || e.why === 'connect') throw e; return null; }); else if (!interactive) throw new Fail('auth'); g ||= await once(hint ? '' : 'select_account', true); held = g; @@ -173,7 +180,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { const tokenFor = (flow, interactive, hint) => (flow === 'chrome' ? chromeToken(interactive) : webToken(interactive, hint)); const drop = async (flow, token) => { if (flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token })).catch(() => {}); - else held = null; + else if (held?.token === token) held = null; }; const who = async (flow, token) => { const r = await send(USERINFO, { headers: { authorization: `Bearer ${token}` } }).catch(() => null); @@ -186,9 +193,12 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // memory) and refused: 'account'. const ours = async (g, interactive) => { if (g.flow === 'native') return nativeToken(g); // the handler binds its refresh token to g.sub + const at = gen; const t = await tokenFor(g.flow, interactive, g.email); + current(at); if (checked.get(t) === g.sub) return t; const { sub } = await who(g.flow, t); + current(at); if (checked.size > 50) checked.clear(); checked.set(t, sub); if (sub !== g.sub) { await drop(g.flow, t); throw new Fail('account'); } @@ -210,6 +220,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { if (!s.ok) return s; if (s.flow === 'native') { const { email, sub } = await nativeConnect(); + gen++; await env.storage.set({ google: { email, sub, flow: 'native', at: now() } }); return { ok: true, email }; } @@ -231,10 +242,14 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { // The worker's token for a Drive request; interactive only for something the student pressed. const token = async ({ interactive = false } = {}) => { if (!on) throw new Fail('off'); + const at = gen; const g = await saved(); + current(at); if (!g) throw new Fail('connect'); if (!usable(g.flow)) throw new Fail('setup'); - return ours(g, interactive); + const t = await ours(g, interactive); + current(at); + return t; }; const account = async () => (await saved())?.email || null; // A token Google answered 401 to: forgotten with its account check (Chrome's cache, or this worker's held one), so the @@ -244,6 +259,7 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { checked.delete(t); const g = await saved(); if (g?.flow === 'chrome') await Promise.resolve().then(() => id()?.removeCachedAuthToken({ token: t })).catch(() => {}); + else if (g?.flow === 'native') await native({ type: 'google.drop', token: t }); else if (held?.token === t) held = null; }; // The grant revoked (with a token from the cache or a silent renewal, never a prompt), the browser's cached tokens and @@ -260,11 +276,11 @@ var GZ_GOOGLE = globalThis.GZ_GOOGLE || (() => { await env.storage.remove('google'); return { ok: true, revoked: false, had: false }; } - if (g?.flow === 'native') { + if (env.flow === 'native' || g?.flow === 'native') { const r = await native({ type: 'google.disconnect' }); started = false; await env.storage.remove('google'); - return { ok: true, revoked: r?.revoked === true, had: true }; + return { ok: true, revoked: r?.revoked === true, had: !!g || r?.had === true }; } const t = g && usable(g.flow) ? await ours(g, false).catch(() => null) : null; const r = t && await send(REVOKE, { diff --git a/safari/Glaze/Glaze Extension/Resources/options.js b/safari/Glaze/Glaze Extension/Resources/options.js index cb3401bf2..020ec3de4 100644 --- a/safari/Glaze/Glaze Extension/Resources/options.js +++ b/safari/Glaze/Glaze Extension/Resources/options.js @@ -1149,8 +1149,7 @@ GZ_UI.load().then((s) => { if ($('#gcal-bye').returnValue !== 'ok') return $('#gcal-disconnect').focus(); const r = await gcalOp({ op: 'disconnect', drop: $('#gcal-drop').checked }); if (!r.ok) return hint(t("Couldn't delete the Glaze calendar, so nothing changed. Try again, or disconnect without deleting it."), true); - // Nothing stays behind: the permission goes too, until the next Connect asks again, unless Google Drive still uses it. - if (!r.shared) await chrome.permissions.remove({ permissions: ['identity'] }).catch(() => {}); + // The worker released identity in its grant queue unless Drive still holds it. gcalRender(null); $('#gcal-connect').focus(); hint(r.had && !r.revoked ? t("Disconnected on this device. Google didn't confirm it removed Glaze's access: you can remove it at myaccount.google.com/connections.") : t('Disconnected.'), r.had && !r.revoked); From 607538cd1e40132f326f6b579c4597244cf60de2 Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:13:55 -0400 Subject: [PATCH 230/907] Hold the worker to never handing a site's grant back, now that Calendar's Disconnect releases Google's identity permission there --- test/allow.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/allow.test.js b/test/allow.test.js index 8f188e19e..1fe7bdda0 100644 --- a/test/allow.test.js +++ b/test/allow.test.js @@ -291,7 +291,10 @@ test('taken back: a site\'s registration goes while its host isn\'t held, its ap // Firefox lets a student take back a built-in host too: Glaze stops asking its pages' frames for nothing, and removes nothing. await w.revoke('https://*.instructure.com/*'); assert.deepEqual(w.removedGrants, []); - assert.doesNotMatch(read('lib/background.js'), /permissions\.remove\(/, 'the worker never hands a grant back'); + // Sites' grants: the worker never hands one back. Its one removal is Google's identity API permission, released when + // Calendar's Disconnect is the last holder of their shared grant (inside the grant queue, so a later Connect keeps it). + const removals = [...read('lib/background.js').matchAll(/permissions\.remove\(([^)]*)\)/g)].map((m) => m[1].replace(/\s+/g, ' ').trim()); + assert.deepEqual(removals, ["{ permissions: ['identity'] }"], 'the worker never hands a site\'s grant back'); }); test('a listed site registers only while its host is held (every grant is checked, the built-in ones too); Safari\'s holds them all', async () => { From a3e9dc94a3beb20597ad5f5ae970abecc92688ac Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:16:13 -0400 Subject: [PATCH 231/907] Name Drive's stored account id and Safari's Keychain refresh token in the privacy texts --- PRIVACY.md | 15 ++++++++------- docs/privacy.md | 2 +- test/privacy-claims.test.js | 11 +++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index cadda1761..bc1ebe91d 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -387,10 +387,11 @@ not those unknown legacy copies or unrelated stickers. **Disconnect** deletes all of it. - **Google Drive** (off until you connect), unavailable in this build while Google verification is pending. In a future build that enables it, `chrome.storage.local` holds one record, `google`, on this device only: the email address of - the Google account you connected (shown as the connected account), which sign-in flow Glaze used and when you - connected (`{ email, flow, at }`). No access token: Chrome keeps it in its own sign-in cache; Edge, Brave and Firefox - hold it only in the memory of Glaze's background worker; Safari will keep it in the Keychain. **Disconnect** deletes - the record. + the Google account you connected (shown as the connected account), its Google account id (`sub`, used to keep access + bound to that account), which sign-in flow Glaze used and when you connected (`{ email, sub, flow, at }`). No access + token is stored there: Chrome keeps it in its own sign-in cache; Edge, Brave and Firefox hold it only in the memory + of Glaze's background worker. Safari's access tokens stay only in native extension memory; its refresh token stays + in the extension's own Keychain, on this device only. **Disconnect** deletes the record and Safari's Keychain grant. - **Gradescope in To Do** (off by default), in `chrome.storage.local`, on this device only: which Gradescope account it's from (your name on Gradescope, and a SHA-256 hash of your Gradescope email address, never the address itself), which Canvas or Blackboard course each of your Gradescope courses goes with (by its Gradescope course id and short name, or "none of @@ -792,9 +793,9 @@ you import) are bundled. It makes requests in these cases: sign-in and consent for three scopes: `https://www.googleapis.com/auth/drive.file`, `openid` and `email`. With `drive.file` Glaze can reach only the Drive files it creates and the files you pick in Google's Picker; it can't list or open anything else in your Drive. The email address is used to show which account is connected. Glaze never - stores the access token: Chrome keeps it in its own sign-in cache (`getAuthToken`); Edge, Brave and Firefox hold it - only in the background worker's memory (`launchWebAuthFlow`); Safari will use the system's sign-in sheet and keep it - in the Keychain. + stores the access token in extension storage: Chrome keeps it in its own sign-in cache (`getAuthToken`); Edge, Brave + and Firefox hold it only in the background worker's memory (`launchWebAuthFlow`). Safari uses the Glaze app's system + sign-in sheet, holds access tokens in native extension memory and keeps its refresh token in the extension's Keychain. - **The picker page**: to pick a file, Glaze opens `https://frostdev.io/glaze/drive-picker/`, or its copy at `https://frostdev-ops.github.io/glaze-drive-picker/` when frostdev.io doesn't answer. The page loads Google's Picker. The access token reaches it in the address's fragment, which browsers don't send to servers, and the page diff --git a/docs/privacy.md b/docs/privacy.md index 0a3de7353..15977df7e 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -93,7 +93,7 @@ the Looks gallery is a website you visit yourself; Glaze asks it only for the on Better To Do's own tasks and plans stay on your device too, with a two-minute copy of your Canvas planner items or Blackboard due items. A calendar file you export (.ics) is made on your device and saved by your browser; it holds your course and assignment names, and Glaze keeps no copy. Google Calendar sync is unavailable in this build while Google verification is pending. In a future build that enables it, optional sync would send the same events to the Glaze calendar in your Google account and read changes there, directly between your browser and Google (`www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect); Chrome would keep the sign-in. Calendar file export remains available without connecting Google. -Google Drive is unavailable in this build while Google verification is pending. In a future build that enables it, Connect in Settings asks Google for `drive.file`, `openid` and `email`: Glaze can reach only the Drive files it creates or the ones you pick, and the email shows which account is connected. Open from Drive, Save to Drive, Sync Documents (off by default; a Glaze folder in your Drive) and Submit from Drive go directly between your browser and Google (`accounts.google.com`, `openidconnect.googleapis.com`, `www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect). To pick a file, Glaze opens a static page, `https://frostdev.io/glaze/drive-picker/` or its copy `https://frostdev-ops.github.io/glaze-drive-picker/`, that loads Google's Picker; the token reaches it in the address's fragment, which browsers don't send to servers, so those hosts see only an ordinary page request. Glaze stores the connected account's email, sign-in flow and time, never the token, and nothing goes to Frostdev. Glaze's use of Google data adheres to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements. +Google Drive is unavailable in this build while Google verification is pending. In a future build that enables it, Connect in Settings asks Google for `drive.file`, `openid` and `email`: Glaze can reach only the Drive files it creates or the ones you pick, and the email shows which account is connected. Open from Drive, Save to Drive, Sync Documents (off by default; a Glaze folder in your Drive) and Submit from Drive go directly between your browser and Google (`accounts.google.com`, `openidconnect.googleapis.com`, `www.googleapis.com`, and `oauth2.googleapis.com` to revoke access on Disconnect). To pick a file, Glaze opens a static page, `https://frostdev.io/glaze/drive-picker/` or its copy `https://frostdev-ops.github.io/glaze-drive-picker/`, that loads Google's Picker; the token reaches it in the address's fragment, which browsers don't send to servers, so those hosts see only an ordinary page request. Glaze stores the connected account's email, Google account id, sign-in flow and time in extension storage. Chrome keeps access tokens in its sign-in cache; Edge, Brave and Firefox hold them only in background worker memory. Safari holds access tokens only in native extension memory and keeps its refresh token in the extension's own Keychain on this device; Disconnect deletes that grant. Nothing goes to Frostdev. Glaze's use of Google data adheres to the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the Limited Use requirements. Dashboard notes, the widgets' layout, the timer and the music link you type stay on your device. So do the notes app's notes, folders, pictures and attachments, in Glaze's own storage (not the school page's); its Canvas link picker reads your own Canvas site, and only a link and title are kept. A music widget contacts its player (Spotify, YouTube through youtube-nocookie.com, Apple Music) only after you press Load player. diff --git a/test/privacy-claims.test.js b/test/privacy-claims.test.js index 1a05b91f5..693a77ac7 100644 --- a/test/privacy-claims.test.js +++ b/test/privacy-claims.test.js @@ -63,6 +63,17 @@ test('optional Google Drive is disclosed in both privacy texts, with its scope, assert.match(google, /Google Drive access \(`drive\.file`/); }); +test('Google Drive storage disclosures include the account id and Safari refresh token', () => { + const stored = policy.slice(policy.indexOf('- **Google Drive**'), policy.indexOf('- **Gradescope in To Do**')); + assert.match(stored, /\{ email, sub, flow, at \}/); + for (const text of [stored, read('docs/privacy.md')]) { + assert.match(text, /refresh token[^.]*Keychain/i); + assert.match(text, /access tokens?[^.]*memory/i); + } + assert.match(read('docs/privacy.md'), /Google account id/); + assert.doesNotMatch(policy, /Safari will (?:keep it|use[^.]*keep it)\s+in the Keychain/); +}); + const languages = { '': /account identifiers/, de: /Kontokennungen/, es: /identificadores de escuela, curso y cuenta/, fr: /identifiants d’établissement, de cours et de compte/, nl: /account-ID/, 'pt-BR': /identificadores de escola, curso e conta/, From da631843ce7bbb993382098adbf1ae01498d45ac Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:24:43 -0400 Subject: [PATCH 232/907] Move the extension into extension/, scripts into checks/ and tools/, tests into areas --- {ci => .github/pins}/chromium.pins | 0 {ci => .github/pins}/ffmpeg.pins | 0 {ci => .github/pins}/playwright.pins | 0 .../agents}/agent-link-check.js | 0 .../agents}/mcp-approve-check.js | 0 .../agents}/mcp-conformance-check.js | 0 .../agents}/mcp-refresh-check.js | 0 .../agents}/mcp-windows-check.js | 0 .../agents}/mcp-writer-check.js | 0 {scripts => checks/core}/download-check.js | 0 .../core}/widgets-builder-check.js | 0 .../core}/widgets-sizes-check.js | 0 {scripts => checks/courses}/allow-check.js | 0 .../courses}/announce-check-sites.js | 0 {scripts => checks/courses}/announce-check.js | 0 .../courses}/announce-read-check.js | 0 .../courses}/canvas-boards-check.js | 0 .../courses}/docreview-check.js | 0 .../courses}/docviewer-check.js | 0 {scripts => checks/courses}/rubric-check.js | 0 {scripts => checks/courses}/stream-check.js | 0 .../courses}/stream-runtime-check.js | 0 .../ultra-assignment-attempt-check.js | 0 .../courses}/ultra-boards-check.js | 0 .../courses}/ultra-cards-check.js | 0 .../courses}/ultra-folders-check.js | 0 .../courses}/ultra-live-frames-check.js | 0 .../courses}/ultra-perf-check.js | 0 .../courses}/ultra-preview-check.js | 0 .../courses}/ultra-test-attempt-check.js | 0 .../documents}/documents-ai-check.js | 0 .../documents}/documents-check.js | 0 .../documents}/documents-entry-check.js | 0 .../documents}/documents-page-check.js | 0 {scripts => checks/documents}/pdf-check.js | 0 {scripts => checks/documents}/sheets-check.js | 0 {scripts => checks/documents}/slides-check.js | 0 .../firefox}/agent-link-firefox-check.mjs | 0 {scripts => checks/firefox}/firefox-check.mjs | 0 .../looks}/glass-remaining-check.js | 0 {scripts => checks/looks}/lookparts-check.js | 0 .../looks}/looks-catalog-check.js | 0 {scripts => checks/looks}/media-check.js | 0 {scripts => checks/looks}/media-cost-check.js | 0 {scripts => checks/looks}/motion-check.js | 0 .../looks}/motion-default-check.js | 0 {scripts => checks/looks}/motionx-check.js | 0 {scripts => checks/looks}/pointer-check.js | 0 {scripts => checks/looks}/rules-check.js | 0 {scripts => checks/looks}/scene-check.html | 0 {scripts => checks/looks}/scene-check.mjs | 0 {scripts => checks/looks}/scene-page-check.js | 0 {scripts => checks/looks}/surfaces-check.js | 0 {scripts => checks/mocks}/frame-tap.js | 0 {scripts => checks/mocks}/gcal-mock.js | 0 .../mocks}/ultra-attempt-harness.js | 0 {scripts => checks/mocks}/ultra-mock.js | 0 .../mocks}/ultra-offcanvas-fixture.js | 0 {scripts => checks/mods}/blocklist-check.js | 0 {scripts => checks/mods}/modbundle-check.js | 0 {scripts => checks/mods}/mods-api-check.js | 0 .../mods}/mods-channel-check.js | 0 .../mods}/mods-combined-check.js | 0 {scripts => checks/mods}/mods-parts-check.js | 0 .../mods}/mods-sandbox-check.js | 0 .../mods}/mods-section-check.js | 0 {scripts => checks/mods}/mods-tools-check.js | 0 .../navigation}/coursetabs-sketch-check.js | 0 .../navigation}/ctab-background-check.js | 0 .../navigation}/ctab-palette-check.js | 0 .../navigation}/ctab-panel-close-check.js | 0 .../navigation}/ctab-strip-glass-check.js | 0 .../navigation}/nav-layout-check.js | 0 .../navigation}/navbar-canvas-check.js | 0 .../navigation}/navbar-check.js | 0 .../navigation}/navbar-moodle-check.js | 0 .../navigation}/sidebar-assessment-check.js | 0 .../navigation}/sidebar-canvas-check.js | 0 .../navigation}/sidebar-participants-check.js | 0 .../navigation}/sidebar-tabs-check.js | 0 .../navigation}/sidebar-test-attempt-check.js | 0 .../navigation}/sidebar-ultra-check.js | 0 .../navigation}/sidebar-ultra-tabs-check.js | 0 .../ultra-contents-drawer-check.js | 0 .../navigation}/ultra-course-bar-check.js | 0 .../navigation}/ultra-offcanvas-check.js | 0 .../ultra-panel-background-check.js | 0 .../navigation}/ultra-panels-check.js | 0 .../navigation}/ultra-tabs-check.js | 0 .../planner}/gradescope-switch-check.js | 0 {scripts => checks/planner}/import-check.js | 0 {scripts => checks/planner}/import-truth.js | 0 .../planner}/whatif-ultra-check.js | 0 .../platform}/background-pages-check.js | 0 .../platform}/background-shell-check.js | 0 {scripts => checks/platform}/lit-check.js | 0 .../platform}/register-update-check.js | 0 .../platform}/registered-hosts-check.js | 0 .../platform}/transition-guard-check.js | 0 .../content-navigation-safari-check.py | 0 {scripts => checks/settings}/backup-check.js | 0 .../settings}/editor-x-check.js | 0 .../settings}/embedded-settings-check.js | 0 {scripts => checks/settings}/popup-check.js | 0 .../settings}/popup-motion-check.js | 0 .../settings}/settings-agents-check.js | 0 .../settings}/settings-ai-check.js | 0 .../settings}/settings-ia-check.js | 0 .../settings}/settings-wiring-check.js | 0 .../settings}/settings-writer-check.js | 0 {scripts => checks/settings}/sites-check.js | 0 {scripts => checks/settings}/welcome-check.js | 0 .../settings}/xparts-editor-check.js | 0 {scripts => checks/sharing}/share-check.js | 0 .../writer}/writer-adversarial-check.js | 0 {scripts => checks/writer}/writer-ai-check.js | 0 {scripts => checks/writer}/writer-check.js | 0 .../writer}/writer-cite-check.js | 0 .../writer}/writer-completion-check.js | 0 .../writer}/writer-compose-check.js | 0 .../writer}/writer-core-check.js | 0 .../writer}/writer-dark-check.js | 0 .../writer}/writer-edit-check.js | 0 .../writer}/writer-format-check.js | 0 .../writer}/writer-huge-check.js | 0 .../writer}/writer-keyboard-check.js | 0 .../writer}/writer-kinds-check.js | 0 .../writer}/writer-look-check.js | 0 .../writer}/writer-objects-check.js | 0 .../writer}/writer-paper-check.js | 0 .../writer}/writer-paper-regression-check.js | 0 .../writer}/writer-proof-check.js | 0 .../writer}/writer-sources-check.js | 0 .../writer}/writer-submit-check.js | 0 .../_locales}/de/messages.json | 0 .../_locales}/en/messages.json | 0 .../_locales}/es/messages.json | 0 .../_locales}/fr/messages.json | 0 .../_locales}/nl/messages.json | 0 .../_locales}/pt_BR/messages.json | 0 {assets => extension/assets}/art/README.md | 0 .../assets}/art/contact-sheet.png | Bin .../assets}/art/cursors/aqua/default.png | Bin .../assets}/art/cursors/aqua/pointer.png | Bin .../assets}/art/cursors/aurora/default.png | Bin .../assets}/art/cursors/aurora/default.svg | 0 .../assets}/art/cursors/aurora/default@2x.png | Bin .../assets}/art/cursors/aurora/pointer.png | Bin .../assets}/art/cursors/aurora/pointer.svg | 0 .../assets}/art/cursors/aurora/pointer@2x.png | Bin .../assets}/art/cursors/clear/default.png | Bin .../assets}/art/cursors/clear/default.svg | 0 .../assets}/art/cursors/clear/default@2x.png | Bin .../assets}/art/cursors/clear/pointer.png | Bin .../assets}/art/cursors/clear/pointer.svg | 0 .../assets}/art/cursors/clear/pointer@2x.png | Bin .../assets}/art/cursors/frostdev/default.png | Bin .../assets}/art/cursors/frostdev/default.svg | 0 .../art/cursors/frostdev/default@2x.png | Bin .../assets}/art/cursors/frostdev/pointer.png | Bin .../assets}/art/cursors/frostdev/pointer.svg | 0 .../art/cursors/frostdev/pointer@2x.png | Bin .../assets}/art/cursors/ghost/default.png | Bin .../assets}/art/cursors/ghost/default.svg | 0 .../assets}/art/cursors/ghost/default@2x.png | Bin .../assets}/art/cursors/ghost/pointer.png | Bin .../assets}/art/cursors/ghost/pointer.svg | 0 .../assets}/art/cursors/ghost/pointer@2x.png | Bin .../assets}/art/cursors/lagoon/default.png | Bin .../assets}/art/cursors/lagoon/default.svg | 0 .../assets}/art/cursors/lagoon/default@2x.png | Bin .../assets}/art/cursors/lagoon/pointer.png | Bin .../assets}/art/cursors/lagoon/pointer.svg | 0 .../assets}/art/cursors/lagoon/pointer@2x.png | Bin .../assets}/art/cursors/lava/default.png | Bin .../assets}/art/cursors/lava/default.svg | 0 .../assets}/art/cursors/lava/default@2x.png | Bin .../assets}/art/cursors/lava/pointer.png | Bin .../assets}/art/cursors/lava/pointer.svg | 0 .../assets}/art/cursors/lava/pointer@2x.png | Bin .../assets}/art/cursors/lilac/default.png | Bin .../assets}/art/cursors/lilac/pointer.png | Bin .../assets}/art/cursors/neon/default.png | Bin .../assets}/art/cursors/neon/default.svg | 0 .../assets}/art/cursors/neon/default@2x.png | Bin .../assets}/art/cursors/neon/pointer.png | Bin .../assets}/art/cursors/neon/pointer.svg | 0 .../assets}/art/cursors/neon/pointer@2x.png | Bin .../assets}/art/cursors/pearl/default.png | Bin .../assets}/art/cursors/pearl/pointer.png | Bin .../assets}/art/cursors/pumpkin/default.png | Bin .../assets}/art/cursors/pumpkin/default.svg | 0 .../art/cursors/pumpkin/default@2x.png | Bin .../assets}/art/cursors/pumpkin/pointer.png | Bin .../assets}/art/cursors/pumpkin/pointer.svg | 0 .../art/cursors/pumpkin/pointer@2x.png | Bin .../assets}/art/cursors/quill/default.png | Bin .../assets}/art/cursors/quill/default.svg | 0 .../assets}/art/cursors/quill/default@2x.png | Bin .../assets}/art/cursors/quill/pointer.png | Bin .../assets}/art/cursors/quill/pointer.svg | 0 .../assets}/art/cursors/quill/pointer@2x.png | Bin .../assets}/art/cursors/rose/default.png | Bin .../assets}/art/cursors/rose/default.svg | 0 .../assets}/art/cursors/rose/default@2x.png | Bin .../assets}/art/cursors/rose/pointer.png | Bin .../assets}/art/cursors/rose/pointer.svg | 0 .../assets}/art/cursors/rose/pointer@2x.png | Bin .../assets}/art/cursors/terminal/default.png | Bin .../assets}/art/cursors/terminal/default.svg | 0 .../art/cursors/terminal/default@2x.png | Bin .../assets}/art/cursors/terminal/pointer.png | Bin .../assets}/art/cursors/terminal/pointer.svg | 0 .../art/cursors/terminal/pointer@2x.png | Bin .../assets}/art/pet-reactions.md | 0 .../assets}/art/pets/bunny/reactions.webp | Bin .../assets}/art/pets/bunny/sprites.webp | Bin .../assets}/art/pets/cat/reactions.webp | Bin .../assets}/art/pets/cat/sprites.webp | Bin .../assets}/art/pets/ember/pet.json | 0 .../assets}/art/pets/ember/reactions.webp | Bin .../assets}/art/pets/ember/spritesheet.webp | Bin .../assets}/art/pets/glaze/pet.json | 0 .../assets}/art/pets/glaze/reactions.webp | Bin .../assets}/art/pets/glaze/spritesheet.webp | Bin .../assets}/art/pets/luna/pet.json | 0 .../assets}/art/pets/luna/reactions.webp | Bin .../assets}/art/pets/luna/spritesheet.webp | Bin .../assets}/art/pets/mochi/pet.json | 0 .../assets}/art/pets/mochi/reactions.webp | Bin .../assets}/art/pets/mochi/spritesheet.webp | Bin .../assets}/art/pets/pebble/pet.json | 0 .../assets}/art/pets/pebble/reactions.webp | Bin .../assets}/art/pets/pebble/spritesheet.webp | Bin .../assets}/art/pets/rime/pet.json | 0 .../assets}/art/pets/rime/reactions.webp | Bin .../assets}/art/pets/rime/spritesheet.webp | Bin .../assets}/art/pets/sprout/pet.json | 0 .../assets}/art/pets/sprout/reactions.webp | Bin .../assets}/art/pets/sprout/spritesheet.webp | Bin .../art/pets/study-buddy/reactions.webp | Bin .../assets}/art/pets/study-buddy/sprites.webp | Bin .../art/stickers/cozy-autumn/acorn.webp | Bin .../art/stickers/cozy-autumn/candle.webp | Bin .../stickers/cozy-autumn/cinnamon-bun.webp | Bin .../art/stickers/cozy-autumn/cozy-sock.webp | Bin .../stickers/cozy-autumn/folded-blanket.webp | Bin .../stickers/cozy-autumn/knitted-mittens.webp | Bin .../art/stickers/cozy-autumn/maple-leaf.webp | Bin .../art/stickers/cozy-autumn/mushroom.webp | Bin .../art/stickers/cozy-autumn/oak-leaf.webp | Bin .../art/stickers/cozy-autumn/pumpkin.webp | Bin .../art/stickers/cozy-autumn/scarf.webp | Bin .../cozy-autumn/steaming-cocoa-mug.webp | Bin .../art/stickers/critters/bumblebee.webp | Bin .../art/stickers/critters/gentle-otter.webp | Bin .../art/stickers/critters/lilac-hedgehog.webp | Bin .../art/stickers/critters/little-mouse.webp | Bin .../art/stickers/critters/mint-frog.webp | Bin .../art/stickers/critters/penguin-chick.webp | Bin .../art/stickers/critters/red-panda.webp | Bin .../art/stickers/critters/round-owl.webp | Bin .../art/stickers/critters/seal-pup.webp | Bin .../art/stickers/critters/sitting-bunny.webp | Bin .../art/stickers/critters/small-fox.webp | Bin .../critters/study-buddy-bandana.webp | Bin .../stickers/critters/study-buddy-happy.webp | Bin .../stickers/critters/study-buddy-sleepy.webp | Bin .../art/stickers/critters/study-buddy.webp | Bin .../art/stickers/critters/teal-turtle.webp | Bin .../assets}/art/stickers/habitat/kelp.webp | Bin .../assets}/art/stickers/habitat/pet-bed.webp | Bin .../art/stickers/habitat/sandcastle.webp | Bin .../assets}/art/stickers/habitat/shelf.webp | Bin .../art/stickers/habitat/toy-ball.webp | Bin .../art/stickers/habitat/yarn-ball.webp | Bin .../assets}/art/stickers/space-sky/comet.webp | Bin .../art/stickers/space-sky/constellation.webp | Bin .../art/stickers/space-sky/crescent-moon.webp | Bin .../stickers/space-sky/earthlike-planet.webp | Bin .../art/stickers/space-sky/fluffy-cloud.webp | Bin .../art/stickers/space-sky/full-moon.webp | Bin .../art/stickers/space-sky/rainbow.webp | Bin .../art/stickers/space-sky/ringed-planet.webp | Bin .../art/stickers/space-sky/rocket.webp | Bin .../art/stickers/space-sky/shooting-star.webp | Bin .../assets}/art/stickers/space-sky/sun.webp | Bin .../art/stickers/space-sky/telescope.webp | Bin .../art/stickers/study/blank-sticky-note.webp | Bin .../assets}/art/stickers/study/checkmark.webp | Bin .../art/stickers/study/closed-books.webp | Bin .../assets}/art/stickers/study/coffee.webp | Bin .../assets}/art/stickers/study/desk-lamp.webp | Bin .../art/stickers/study/graduation-cap.webp | Bin .../assets}/art/stickers/study/hourglass.webp | Bin .../assets}/art/stickers/study/notebook.webp | Bin .../assets}/art/stickers/study/open-book.webp | Bin .../assets}/art/stickers/study/pencil.webp | Bin .../art/stickers/study/reading-glasses.webp | Bin .../art/stickers/study/star-cluster.webp | Bin {assets => extension/assets}/logo-dark.svg | 0 {assets => extension/assets}/logo.svg | 0 .../assets}/shaders/aurora.glsl | 0 .../assets}/shaders/liquid.glsl | 0 .../assets}/shaders/starfield.glsl | 0 canvas.css => extension/canvas.css | 0 .../content-canvas.js | 0 .../content-ultra.js | 0 content.js => extension/content.js | 0 defaults.js => extension/defaults.js | 0 extras.css => extension/extras.css | 0 {features => extension/features}/allow.css | 0 {features => extension/features}/allow.html | 0 {features => extension/features}/allow.js | 0 .../features}/announce-read.js | 0 {features => extension/features}/announce.js | 0 {features => extension/features}/apps.css | 0 {features => extension/features}/bbpreview.js | 0 {features => extension/features}/calexport.js | 0 {features => extension/features}/cards.js | 0 {features => extension/features}/cardtasks.js | 0 {features => extension/features}/classes.js | 0 {features => extension/features}/consent.js | 0 .../features}/coursecards.js | 0 .../features}/coursemenu.js | 0 .../features}/coursetabs.css | 0 .../features}/coursetabs.js | 0 {features => extension/features}/dashboard.js | 0 .../features}/discussion-nav.js | 0 .../features}/docreview.css | 0 {features => extension/features}/docreview.js | 0 {features => extension/features}/docs.js | 0 {features => extension/features}/docviewer.js | 0 {features => extension/features}/download.js | 0 {features => extension/features}/drafts.js | 0 .../features}/editor-menu.js | 0 {features => extension/features}/folders.js | 0 {features => extension/features}/gcal.js | 0 {features => extension/features}/gpaterms.js | 0 {features => extension/features}/grades.js | 0 .../features}/gradescope.js | 0 {features => extension/features}/import.js | 0 {features => extension/features}/insights.js | 0 {features => extension/features}/keys.js | 0 {features => extension/features}/light.js | 0 {features => extension/features}/lookparts.js | 0 {features => extension/features}/lookx.css | 0 {features => extension/features}/lookx.js | 0 {features => extension/features}/mods.js | 0 {features => extension/features}/modui.js | 0 .../features}/modwidgets.js | 0 {features => extension/features}/moodle.js | 0 {features => extension/features}/motionx.js | 0 {features => extension/features}/nav.js | 0 .../features}/navbar-canvas.js | 0 .../features}/navbar-moodle.js | 0 .../features}/navbar-widgets.js | 0 {features => extension/features}/navbar.css | 0 {features => extension/features}/navbar.js | 0 .../features}/navlayout.css | 0 .../features}/notes-home.js | 0 {features => extension/features}/notes.js | 0 {features => extension/features}/palette.js | 0 {features => extension/features}/pet.js | 0 {features => extension/features}/petworld.js | 0 {features => extension/features}/place.js | 0 {features => extension/features}/popup.js | 0 {features => extension/features}/previews.js | 0 {features => extension/features}/reminders.js | 0 {features => extension/features}/routes.js | 0 {features => extension/features}/rubric.js | 0 {features => extension/features}/rules.css | 0 {features => extension/features}/rules.js | 0 {features => extension/features}/schedule.js | 0 {features => extension/features}/search.js | 0 .../features}/settingspage.js | 0 {features => extension/features}/sharing.js | 0 {features => extension/features}/sidebar.css | 0 {features => extension/features}/sidebar.js | 0 .../features}/sticky-fold.js | 0 {features => extension/features}/stream.js | 0 {features => extension/features}/surfaces.css | 0 {features => extension/features}/syllabus.js | 0 {features => extension/features}/todo.js | 0 {features => extension/features}/welcome.css | 0 {features => extension/features}/welcome.html | 0 {features => extension/features}/welcome.js | 0 {features => extension/features}/widgets.js | 0 .../features}/writer-quote.js | 0 .../features}/writer-submit.js | 0 {features => extension/features}/writer.js | 0 .../fonts}/OFL-AtkinsonHyperlegible.txt | 0 {fonts => extension/fonts}/OFL-Inter.txt | 0 {fonts => extension/fonts}/atkinson-400.woff2 | Bin {fonts => extension/fonts}/atkinson-700.woff2 | Bin {fonts => extension/fonts}/inter.woff2 | Bin {i18n => extension/i18n}/de.js | 0 {i18n => extension/i18n}/es.js | 0 {i18n => extension/i18n}/fr.js | 0 {i18n => extension/i18n}/nl.js | 0 {i18n => extension/i18n}/pt-BR.js | 0 {icons => extension/icons}/icon.svg | 0 {icons => extension/icons}/icon128.png | Bin {icons => extension/icons}/icon16.png | Bin {icons => extension/icons}/icon32.png | Bin {icons => extension/icons}/icon48.png | Bin {lib => extension/lib}/agent-frames.js | 0 {lib => extension/lib}/agent-link.js | 0 {lib => extension/lib}/ai/broker.js | 0 {lib => extension/lib}/ai/endpoint.js | 0 {lib => extension/lib}/ai/failure.js | 0 {lib => extension/lib}/ai/loop.js | 0 {lib => extension/lib}/ai/models.js | 0 {lib => extension/lib}/ai/providers.js | 0 {lib => extension/lib}/ai/providers/apple.js | 0 {lib => extension/lib}/ai/providers/device.js | 0 {lib => extension/lib}/ai/providers/mock.js | 0 {lib => extension/lib}/ai/providers/openai.js | 0 {lib => extension/lib}/ai/request.js | 0 {lib => extension/lib}/ai/schema.js | 0 {lib => extension/lib}/ai/settings.js | 0 {lib => extension/lib}/ai/stream.js | 0 {lib => extension/lib}/ai/task.js | 0 .../lib}/ai/tasks/syllabus-read.js | 0 .../lib}/ai/tasks/writer-cite.js | 0 .../lib}/ai/tasks/writer-complete.js | 0 .../lib}/ai/tasks/writer-context.js | 0 .../lib}/ai/tasks/writer-decide.js | 0 .../lib}/ai/tasks/writer-feedback.js | 0 .../lib}/ai/tasks/writer-image.js | 0 .../lib}/ai/tasks/writer-outline.js | 0 .../lib}/ai/tasks/writer-requirements.js | 0 .../lib}/ai/tasks/writer-review.js | 0 {lib => extension/lib}/ai/text.js | 0 {lib => extension/lib}/ai/tools.js | 0 {lib => extension/lib}/ai/visual-cache.js | 0 {lib => extension/lib}/announce.js | 0 {lib => extension/lib}/background.js | 0 {lib => extension/lib}/backup.js | 0 {lib => extension/lib}/bb-preview.js | 0 {lib => extension/lib}/blocklist.js | 0 {lib => extension/lib}/calexport.js | 0 {lib => extension/lib}/cards.js | 0 {lib => extension/lib}/classes.js | 0 {lib => extension/lib}/color.js | 0 {lib => extension/lib}/coursetabs.js | 0 {lib => extension/lib}/detect.js | 0 {lib => extension/lib}/doc-files.js | 0 {lib => extension/lib}/doc-home.js | 0 {lib => extension/lib}/doc-kinds.js | 0 {lib => extension/lib}/doctext.js | 0 {lib => extension/lib}/download.js | 0 {lib => extension/lib}/drafts.js | 0 {lib => extension/lib}/editor-agents.js | 0 {lib => extension/lib}/editor-ai.js | 0 {lib => extension/lib}/editor-info.js | 0 {lib => extension/lib}/editor-mods.js | 0 {lib => extension/lib}/editor-navbar.js | 0 {lib => extension/lib}/editor-pointer.js | 0 {lib => extension/lib}/editor-preview.js | 0 {lib => extension/lib}/editor-widget.js | 0 {lib => extension/lib}/editor-writer.js | 0 {lib => extension/lib}/editor-x.js | 0 {lib => extension/lib}/editor-xbg.js | 0 {lib => extension/lib}/editor-xmotion.js | 0 {lib => extension/lib}/editor-xparts.js | 0 {lib => extension/lib}/editor.js | 0 {lib => extension/lib}/folders.js | 0 {lib => extension/lib}/fonts.js | 0 .../lib}/fonts/LICENSE-arimo.txt | 0 .../lib}/fonts/LICENSE-carlito.txt | 0 .../lib}/fonts/LICENSE-cousine.txt | 0 .../lib}/fonts/LICENSE-tinos.txt | 0 .../lib}/fonts/arimo-400-italic.woff2 | Bin .../lib}/fonts/arimo-400-normal.woff2 | Bin .../lib}/fonts/arimo-700-italic.woff2 | Bin .../lib}/fonts/arimo-700-normal.woff2 | Bin .../lib}/fonts/carlito-400-italic.woff2 | Bin .../lib}/fonts/carlito-400-normal.woff2 | Bin .../lib}/fonts/carlito-700-italic.woff2 | Bin .../lib}/fonts/carlito-700-normal.woff2 | Bin .../lib}/fonts/cousine-400-italic.woff2 | Bin .../lib}/fonts/cousine-400-normal.woff2 | Bin .../lib}/fonts/cousine-700-italic.woff2 | Bin .../lib}/fonts/cousine-700-normal.woff2 | Bin .../lib}/fonts/tinos-400-italic.woff2 | Bin .../lib}/fonts/tinos-400-normal.woff2 | Bin .../lib}/fonts/tinos-700-italic.woff2 | Bin .../lib}/fonts/tinos-700-normal.woff2 | Bin {lib => extension/lib}/gallery.js | 0 {lib => extension/lib}/gcal.js | 0 {lib => extension/lib}/glsl.js | 0 {lib => extension/lib}/gpa.js | 0 {lib => extension/lib}/gradecalc.js | 0 {lib => extension/lib}/grades.js | 0 {lib => extension/lib}/gradescope.js | 0 .../lib}/harper/BinaryModule-S5UABkWQ.js | 0 {lib => extension/lib}/harper/LICENSE | 0 .../lib}/harper/harper_wasm_slim_bg.wasm | Bin {lib => extension/lib}/harper/index.js | 0 {lib => extension/lib}/harper/shared.js | 0 {lib => extension/lib}/harper/slimBinary.js | 0 {lib => extension/lib}/history.js | 0 {lib => extension/lib}/i18n.js | 0 {lib => extension/lib}/ics.js | 0 {lib => extension/lib}/ink.js | 0 {lib => extension/lib}/insights.js | 0 {lib => extension/lib}/lmsq.js | 0 {lib => extension/lib}/lookparts.js | 0 {lib => extension/lib}/looks.js | 0 {lib => extension/lib}/lookset.js | 0 {lib => extension/lib}/lookx.js | 0 {lib => extension/lib}/lottie/LICENSE | 0 .../lib}/lottie/lottie_light_canvas.js | 0 {lib => extension/lib}/mcp/approve-page.js | 0 {lib => extension/lib}/mcp/approve.css | 0 {lib => extension/lib}/mcp/approve.html | 0 {lib => extension/lib}/mcp/approve.js | 0 {lib => extension/lib}/mcp/calls.js | 0 {lib => extension/lib}/mcp/grants.js | 0 {lib => extension/lib}/mcp/modroute.js | 0 {lib => extension/lib}/mcp/registry.js | 0 {lib => extension/lib}/mcp/tab.js | 0 {lib => extension/lib}/mcp/tabs.js | 0 {lib => extension/lib}/mcp/tools-assign.js | 0 {lib => extension/lib}/mcp/tools-grades.js | 0 {lib => extension/lib}/mcp/tools-study.js | 0 {lib => extension/lib}/mcp/wire.js | 0 {lib => extension/lib}/mcp/writer.js | 0 {lib => extension/lib}/media.js | 0 {lib => extension/lib}/modapi.js | 0 {lib => extension/lib}/modbroker.js | 0 {lib => extension/lib}/modbundle.js | 0 {lib => extension/lib}/modchannel.js | 0 {lib => extension/lib}/modtools.js | 0 {lib => extension/lib}/modwidget.js | 0 {lib => extension/lib}/moodle.js | 0 {lib => extension/lib}/motion.js | 0 {lib => extension/lib}/motionx.js | 0 {lib => extension/lib}/nav-items.js | 0 {lib => extension/lib}/nav-store.js | 0 {lib => extension/lib}/nav.js | 0 {lib => extension/lib}/navbar.js | 0 {lib => extension/lib}/navlayout.js | 0 {lib => extension/lib}/news.js | 0 {lib => extension/lib}/notes.js | 0 {lib => extension/lib}/oauth.js | 0 {lib => extension/lib}/ocr.js | 0 {lib => extension/lib}/page.js | 0 {lib => extension/lib}/parts.js | 0 {lib => extension/lib}/pdf-doc.js | 0 {lib => extension/lib}/pdf-l10n.js | 0 {lib => extension/lib}/pdfjs/LICENSE | 0 {lib => extension/lib}/pdfjs/LICENSE_BROTLI | 0 {lib => extension/lib}/pdfjs/LICENSE_CORE_JS | 0 {lib => extension/lib}/pdfjs/LICENSE_JBIG2 | 0 {lib => extension/lib}/pdfjs/LICENSE_MPL | 0 {lib => extension/lib}/pdfjs/LICENSE_OPENJPEG | 0 .../lib}/pdfjs/LICENSE_PDFJS_JBIG2 | 0 .../lib}/pdfjs/LICENSE_PDFJS_OPENJPEG | 0 .../lib}/pdfjs/brotli-dictionary.bin | 0 .../lib}/pdfjs/images/altText_add.svg | 0 .../lib}/pdfjs/images/altText_disclaimer.svg | 0 .../lib}/pdfjs/images/altText_done.svg | 0 .../lib}/pdfjs/images/altText_spinner.svg | 0 .../lib}/pdfjs/images/altText_warning.svg | 0 .../lib}/pdfjs/images/checkmark.svg | 0 .../lib}/pdfjs/images/comment-closeButton.svg | 0 .../pdfjs/images/comment-popup-editButton.svg | 0 .../images/cursor-editorFreeHighlight.svg | 0 .../pdfjs/images/cursor-editorFreeText.svg | 0 .../lib}/pdfjs/images/cursor-editorInk.svg | 0 .../images/cursor-editorTextHighlight.svg | 0 .../pdfjs/images/editor-toolbar-delete.svg | 0 .../lib}/pdfjs/images/editor-toolbar-edit.svg | 0 .../lib}/pdfjs/images/loading-icon.gif | Bin .../pdfjs/images/messageBar_closingButton.svg | 0 .../lib}/pdfjs/images/messageBar_info.svg | 0 .../lib}/pdfjs/images/messageBar_warning.svg | 0 .../lib}/pdfjs/images/pages_closeButton.svg | 0 .../lib}/pdfjs/images/pages_selected.svg | 0 .../lib}/pdfjs/images/pages_viewArrow.svg | 0 .../lib}/pdfjs/images/pages_viewButton.svg | 0 .../toolbarButton-currentOutlineItem.svg | 0 .../images/toolbarButton-editorHighlight.svg | 0 .../pdfjs/images/toolbarButton-menuArrow.svg | 0 .../pdfjs/images/toolbarButton-pageDown.svg | 0 .../images/toolbarButton-viewAttachments.svg | 0 .../pdfjs/images/toolbarButton-viewLayers.svg | 0 .../images/toolbarButton-viewOutline.svg | 0 .../pdfjs/images/toolbarButton-zoomIn.svg | 0 .../lib}/pdfjs/images/treeitem-collapsed.svg | 0 .../lib}/pdfjs/images/treeitem-expanded.svg | 0 {lib => extension/lib}/pdfjs/jbig2.wasm | Bin {lib => extension/lib}/pdfjs/openjpeg.wasm | Bin {lib => extension/lib}/pdfjs/pdf.mjs | 0 {lib => extension/lib}/pdfjs/pdf.worker.mjs | 0 {lib => extension/lib}/pdfjs/pdf_viewer.css | 0 {lib => extension/lib}/pdfjs/pdf_viewer.mjs | 0 {lib => extension/lib}/pet.js | 0 {lib => extension/lib}/petbrain.js | 0 {lib => extension/lib}/petlibrary.js | 0 {lib => extension/lib}/petphysics.js | 0 {lib => extension/lib}/petplayer.js | 0 {lib => extension/lib}/petsprites.js | 0 {lib => extension/lib}/pm.LICENSE | 0 {lib => extension/lib}/pm.css | 0 {lib => extension/lib}/pm.js | 0 {lib => extension/lib}/popup.js | 0 {lib => extension/lib}/pptx-geom.js | 0 {lib => extension/lib}/pptx.js | 0 {lib => extension/lib}/relay.js | 0 {lib => extension/lib}/remind.js | 0 {lib => extension/lib}/rubric.js | 0 {lib => extension/lib}/scene-host.js | 0 {lib => extension/lib}/scene-media.js | 0 {lib => extension/lib}/scene.js | 0 {lib => extension/lib}/search.js | 0 {lib => extension/lib}/seek.js | 0 {lib => extension/lib}/sessions-ui.js | 0 {lib => extension/lib}/sessions.js | 0 {lib => extension/lib}/share.js | 0 {lib => extension/lib}/sharing.js | 0 {lib => extension/lib}/sheet-calc.js | 0 {lib => extension/lib}/sheet-chart-xml.js | 0 {lib => extension/lib}/sheet-chart.js | 0 {lib => extension/lib}/sheet-fn-database.js | 0 {lib => extension/lib}/sheet-fn-date.js | 0 {lib => extension/lib}/sheet-fn-financial.js | 0 {lib => extension/lib}/sheet-fn-financial2.js | 0 {lib => extension/lib}/sheet-fn-info.js | 0 {lib => extension/lib}/sheet-fn-logical.js | 0 {lib => extension/lib}/sheet-fn-logical2.js | 0 {lib => extension/lib}/sheet-fn-lookup.js | 0 {lib => extension/lib}/sheet-fn-lookup2.js | 0 {lib => extension/lib}/sheet-fn-math.js | 0 {lib => extension/lib}/sheet-fn-math2.js | 0 {lib => extension/lib}/sheet-fn-math3.js | 0 {lib => extension/lib}/sheet-fn-stats.js | 0 {lib => extension/lib}/sheet-fn-stats2.js | 0 {lib => extension/lib}/sheet-fn-text.js | 0 {lib => extension/lib}/sheet-fn-text2.js | 0 {lib => extension/lib}/sheet-formula.js | 0 {lib => extension/lib}/sheet-model.js | 0 {lib => extension/lib}/sheet-numfmt.js | 0 {lib => extension/lib}/sheet-xlsx.js | 0 {lib => extension/lib}/sidebar.js | 0 {lib => extension/lib}/site.js | 0 {lib => extension/lib}/slides-render.js | 0 {lib => extension/lib}/stream.js | 0 {lib => extension/lib}/surfaces.js | 0 {lib => extension/lib}/syllabus-ai.js | 0 {lib => extension/lib}/syllabus.js | 0 .../lib}/tesseract/deu.traineddata.gz | Bin .../lib}/tesseract/eng.traineddata.gz | Bin .../lib}/tesseract/fra.traineddata.gz | Bin .../lib}/tesseract/nld.traineddata.gz | Bin .../lib}/tesseract/por.traineddata.gz | Bin .../lib}/tesseract/spa.traineddata.gz | Bin .../lib}/tesseract/tessdata.LICENSE | 0 .../tesseract/tesseract-core-simd-lstm.js | 0 .../tesseract/tesseract-core-simd-lstm.wasm | Bin .../lib}/tesseract/tesseract-core.LICENSE | 0 {lib => extension/lib}/three.LICENSE | 0 {lib => extension/lib}/three.js | 0 {lib => extension/lib}/todo.js | 0 {lib => extension/lib}/ultra.js | 0 {lib => extension/lib}/widgets.js | 0 {lib => extension/lib}/writer-bibliography.js | 0 {lib => extension/lib}/writer-cite.js | 0 {lib => extension/lib}/writer-color.js | 0 .../lib}/writer-context-assignment.js | 0 .../lib}/writer-context-discussion.js | 0 {lib => extension/lib}/writer-docx.js | 0 {lib => extension/lib}/writer-editor.js | 0 {lib => extension/lib}/writer-export.js | 0 {lib => extension/lib}/writer-format.js | 0 {lib => extension/lib}/writer-issues.js | 0 {lib => extension/lib}/writer-lookup.js | 0 {lib => extension/lib}/writer-main.js | 0 {lib => extension/lib}/writer-objects-docx.js | 0 {lib => extension/lib}/writer-objects.js | 0 {lib => extension/lib}/writer-outline.js | 0 {lib => extension/lib}/writer-paginate.js | 0 {lib => extension/lib}/writer-papers.js | 0 {lib => extension/lib}/writer-proof.js | 0 {lib => extension/lib}/writer-schema.js | 0 {lib => extension/lib}/writer-settings.js | 0 {lib => extension/lib}/writer-sources.js | 0 {lib => extension/lib}/writer-styles.js | 0 {lib => extension/lib}/writer-submit.js | 0 {lib => extension/lib}/writer-target.js | 0 {lib => extension/lib}/writer-thumb.js | 0 {lib => extension/lib}/writer-tools.js | 0 {lib => extension/lib}/writer-ultra.js | 0 {lib => extension/lib}/zip.js | 0 manifest.json => extension/manifest.json | 0 {mods => extension/mods}/boot.js | 0 {mods => extension/mods}/host.html | 0 {mods => extension/mods}/host.js | 0 moodle.css => extension/moodle.css | 0 motion.css => extension/motion.css | 0 options.html => extension/options.html | 0 options.js => extension/options.js | 0 .../papers-motion.js | 0 papers.css => extension/papers.css | 0 papers.html => extension/papers.html | 0 papers.js => extension/papers.js | 0 pdf-kind.css => extension/pdf-kind.css | 0 pdf-kind.js => extension/pdf-kind.js | 0 popup.html => extension/popup.html | 0 popup.js => extension/popup.js | 0 preview.html => extension/preview.html | 0 reader.html => extension/reader.html | 0 reader.js => extension/reader.js | 0 {sharing => extension/sharing}/quick.css | 0 {sharing => extension/sharing}/quick.html | 0 {sharing => extension/sharing}/quick.js | 0 sheet-data.js => extension/sheet-data.js | 0 sheet-grid.js => extension/sheet-grid.js | 0 sheet-kind.js => extension/sheet-kind.js | 0 sheet.css => extension/sheet.css | 0 slides-kind.css => extension/slides-kind.css | 0 slides-kind.js => extension/slides-kind.js | 0 theme.css => extension/theme.css | 0 ui.css => extension/ui.css | 0 ui.js => extension/ui.js | 0 writer-ai.css => extension/writer-ai.css | 0 writer-ai.js => extension/writer-ai.js | 0 writer-cite.css => extension/writer-cite.css | 0 writer-cite.js => extension/writer-cite.js | 0 .../writer-compose.css | 0 .../writer-compose.js | 0 .../writer-counts.js | 0 writer-edit.css => extension/writer-edit.css | 0 writer-find.js => extension/writer-find.js | 0 .../writer-import.css | 0 .../writer-import.js | 0 writer-kinds.js => extension/writer-kinds.js | 0 writer-mcp.js => extension/writer-mcp.js | 0 .../writer-objects.css | 0 .../writer-objects.js | 0 .../writer-paper.css | 0 writer-paper.js => extension/writer-paper.js | 0 writer-paste.js => extension/writer-paste.js | 0 writer-print.js => extension/writer-print.js | 0 .../writer-proof.css | 0 writer-proof.js => extension/writer-proof.js | 0 .../writer-ruler.css | 0 writer-ruler.js => extension/writer-ruler.js | 0 .../writer-submit.css | 0 .../writer-submit.js | 0 writer.css => extension/writer.css | 0 writer.html => extension/writer.html | 0 writer.js => extension/writer.js | 0 notes/codex-review-ultra-perf.md | 61 ------------------ {assets => store/art}/halloween/bat.svg | 0 {assets => store/art}/halloween/ghost.svg | 0 .../art}/halloween/jack-o-lantern.svg | 0 {assets => store/art}/halloween/pumpkin.svg | 0 {assets => store/art}/halloween/skeleton.svg | 0 {assets/store => store/art}/marquee.png | Bin .../art}/screenshots/1-blackboard-frost.jpg | Bin .../art}/screenshots/2-canvas-modules.jpg | Bin .../art}/screenshots/3-canvas-dashboard.jpg | Bin .../screenshots/4-halloween-pumpkin-patch.jpg | Bin .../art}/screenshots/5-backgrounds-editor.jpg | Bin {assets/store => store/art}/small-tile.png | Bin test/{ => agents}/agent-frames.test.js | 0 test/{ => agents}/agent-link-check.test.js | 0 test/{ => agents}/agent-link.test.js | 0 test/{ => agents}/agents-docs.test.js | 0 test/{ => agents}/mcp-agent.test.js | 0 test/{ => agents}/mcp-approve.test.js | 0 test/{ => agents}/mcp-assign.test.js | 0 test/{ => agents}/mcp-calls.test.js | 0 .../mcp-conformance-check.test.js | 0 test/{ => agents}/mcp-grants.test.js | 0 test/{ => agents}/mcp-host.test.js | 0 test/{ => agents}/mcp-install.test.js | 0 test/{ => agents}/mcp-mcpb.test.js | 0 test/{ => agents}/mcp-mods.test.js | 0 test/{ => agents}/mcp-native.test.js | 0 test/{ => agents}/mcp-registry.test.js | 0 test/{ => agents}/mcp-release-fixes.test.js | 0 test/{ => agents}/mcp-rendezvous.test.js | 0 test/{ => agents}/mcp-tabs.test.js | 0 test/{ => agents}/mcp-todo-write.test.js | 0 test/{ => agents}/mcp-tools-grades.test.js | 0 test/{ => agents}/mcp-tools-study.test.js | 0 test/{ => agents}/mcp-windows-check.test.js | 0 test/{ => agents}/mcp-wire.test.js | 0 test/{ => agents}/mcp-worker.test.js | 0 test/{ => agents}/mcp-writer.test.js | 0 test/{ => ai}/ai-apple.test.js | 0 test/{ => ai}/ai-broker.test.js | 0 test/{ => ai}/ai-device.test.js | 0 test/{ => ai}/ai-failure.test.js | 0 test/{ => ai}/ai-loop.test.js | 0 test/{ => ai}/ai-models.test.js | 0 test/{ => ai}/ai-openai.test.js | 0 test/{ => ai}/ai-request.test.js | 0 test/{ => ai}/ai-settings-store.test.js | 0 test/{ => ai}/ai-settings.test.js | 0 test/{ => ai}/ai-stream.test.js | 0 test/{ => ai}/ai-syllabus.test.js | 0 test/{ => ai}/ai-task.test.js | 0 test/{ => ai}/ai-text.test.js | 0 test/{ => ai}/ai-token-budget.test.js | 0 test/{ => ai}/ai-tools-mcp.test.js | 0 test/{ => ai}/ai-visual-cache.test.js | 0 test/{ => ai}/ai-visual-scope.test.js | 0 test/{ => ai}/ai-worker.test.js | 0 test/{ => ai}/ai-writer-complete.test.js | 0 test/{ => ai}/ai-writer-frame.test.js | 0 test/{ => ai}/ai-writer-regressions.test.js | 0 test/{ => ai}/ai-writer-tasks.test.js | 0 test/{ => ai}/ai-writer-tools.test.js | 0 test/{ => core}/color.test.js | 0 test/{ => core}/consent.test.js | 0 test/{ => core}/docbutton.test.js | 0 test/{ => core}/download-allow.test.js | 0 test/{ => core}/download-check-waits.test.js | 0 test/{ => core}/download.test.js | 0 test/{ => core}/fresh-defaults.test.js | 0 test/{ => core}/page.test.js | 0 test/{ => core}/site.test.js | 0 test/{ => core}/starter-flags.test.js | 0 test/{ => core}/tooltips.test.js | 0 test/{ => core}/ultra-panel.test.js | 0 test/{ => core}/widgets-checks.test.js | 0 test/{ => core}/widgets-tiers.test.js | 0 test/{ => core}/widgets-ui.test.js | 0 test/{ => core}/widgets.test.js | 0 test/{ => core}/zip.test.js | 0 test/{ => courses}/allow-tab.test.js | 0 test/{ => courses}/allow.test.js | 0 test/{ => courses}/announce-sites.test.js | 0 test/{ => courses}/announce-ui.test.js | 0 test/{ => courses}/announce.test.js | 0 test/{ => courses}/bb-preview.test.js | 0 test/{ => courses}/board-card-checks.test.js | 0 test/{ => courses}/board-checks.test.js | 0 test/{ => courses}/boards-check.test.js | 0 test/{ => courses}/canvas-boards.test.js | 0 .../canvas-dash-audit-i18n-css.test.js | 0 .../canvas-dash-audit-ui.test.js | 0 test/{ => courses}/canvas-dash-audit.test.js | 0 test/{ => courses}/canvas-fixtures.test.js | 0 .../canvas-load-audit-contracts.test.js | 0 .../canvas-load-audit-runtime.test.js | 0 test/{ => courses}/canvas-uiux-audit.test.js | 0 test/{ => courses}/cards-ui.test.js | 0 test/{ => courses}/cards.test.js | 0 test/{ => courses}/cardtasks-ui.test.js | 0 test/{ => courses}/coursecards.test.js | 0 test/{ => courses}/docreview.test.js | 0 test/{ => courses}/docs-dark.test.js | 0 test/{ => courses}/docviewer-ui.test.js | 0 test/{ => courses}/docviewer.test.js | 0 test/{ => courses}/folders-canvas.test.js | 0 test/{ => courses}/folders-ui.test.js | 0 test/{ => courses}/folders.test.js | 0 test/{ => courses}/moodle-root.test.js | 0 test/{ => courses}/moodle.test.js | 0 test/{ => courses}/rubric-data.test.js | 0 test/{ => courses}/rubric-ultra-state.test.js | 0 test/{ => courses}/stream.test.js | 0 test/{ => courses}/ultra-boards.test.js | 0 test/{ => courses}/ultra-card-storage.test.js | 0 test/{ => courses}/ultra-fixtures.test.js | 0 test/{ => courses}/ultra-mock.test.js | 0 test/{ => courses}/ultra-perf.test.js | 0 .../ultra-preview-ticket.test.js | 0 test/{ => courses}/ultra-test-attempt.test.js | 0 test/{ => courses}/ultra.test.js | 0 test/{ => documents}/doc-files.test.js | 0 test/{ => documents}/doc-kinds.test.js | 0 test/{ => documents}/doctext.test.js | 0 .../papers-motion-lifecycle.test.js | 0 test/{ => documents}/papers-motion.test.js | 0 test/{ => documents}/papers-page.test.js | 0 test/{ => documents}/papers-sources.test.js | 0 test/{ => documents}/papers-theme.test.js | 0 test/{ => documents}/pdf-doc.test.js | 0 test/{ => documents}/pdf-kind.test.js | 0 test/{ => documents}/pdf-l10n.test.js | 0 test/{ => documents}/pptx-geom.test.js | 0 test/{ => documents}/pptx.test.js | 0 test/{ => documents}/sheet-calc.test.js | 0 .../sheet-chart-insert.test.js | 0 test/{ => documents}/sheet-chart.test.js | 0 test/{ => documents}/sheet-excel.test.js | 0 .../{ => documents}/sheet-fn-database.test.js | 0 test/{ => documents}/sheet-fn-date.test.js | 0 .../sheet-fn-financial.test.js | 0 .../sheet-fn-financial2.test.js | 0 test/{ => documents}/sheet-fn-info.test.js | 0 .../{ => documents}/sheet-fn-logical2.test.js | 0 test/{ => documents}/sheet-fn-lookup.test.js | 0 test/{ => documents}/sheet-fn-lookup2.test.js | 0 test/{ => documents}/sheet-fn-math2.test.js | 0 test/{ => documents}/sheet-fn-math3.test.js | 0 test/{ => documents}/sheet-fn-stats.test.js | 0 test/{ => documents}/sheet-fn-stats2.test.js | 0 test/{ => documents}/sheet-fn-text2.test.js | 0 test/{ => documents}/sheet-formula.test.js | 0 test/{ => documents}/sheet-golden.test.js | 0 .../sheet-grid-lifecycle.test.js | 0 test/{ => documents}/sheet-grid.test.js | 0 test/{ => documents}/sheet-kind.test.js | 0 test/{ => documents}/sheet-model.test.js | 0 test/{ => documents}/sheet-numfmt.test.js | 0 test/{ => documents}/sheet-xlsx.test.js | 0 test/{ => documents}/slides-fidelity.test.js | 0 test/{ => documents}/slides-kind.test.js | 0 test/{ => documents}/slides-render.test.js | 0 .../agent-link-firefox-check.test.js | 0 test/{ => firefox}/firefox-html-sinks.test.js | 0 test/{ => firefox}/firefox-waits.test.js | 0 test/{ => firefox}/firefox.test.js | 0 test/{ => looks}/course-looks-sync.test.js | 0 test/{ => looks}/courselook.test.js | 0 test/{ => looks}/ctab-one-background.test.js | 0 test/{ => looks}/fonts.test.js | 0 .../{ => looks}/glass-remaining-check.test.js | 0 test/{ => looks}/glsl.test.js | 0 test/{ => looks}/history.test.js | 0 test/{ => looks}/ink.test.js | 0 test/{ => looks}/look-disclosure.test.js | 0 test/{ => looks}/look-editor.test.js | 0 test/{ => looks}/look-media-share.test.js | 0 test/{ => looks}/look-offer-review.test.js | 0 test/{ => looks}/lookparts-page.test.js | 0 test/{ => looks}/lookparts.test.js | 0 test/{ => looks}/looks-catalog.test.js | 0 test/{ => looks}/lookset.test.js | 0 test/{ => looks}/lookx-ui.test.js | 0 test/{ => looks}/lookx.test.js | 0 .../{ => looks}/media-avif-validation.test.js | 0 test/{ => looks}/media-broker.test.js | 0 test/{ => looks}/media-check.test.js | 0 test/{ => looks}/media-cost-check.test.js | 0 test/{ => looks}/media-gallery.test.js | 0 test/{ => looks}/media-host.test.js | 0 test/{ => looks}/media-lottie-mattes.test.js | 0 .../media-lottie-validation.test.js | 0 test/{ => looks}/media-metadata.test.js | 0 test/{ => looks}/media-parts.test.js | 0 test/{ => looks}/media-remote.test.js | 0 test/{ => looks}/media-store.test.js | 0 .../media-video-validation.test.js | 0 test/{ => looks}/media.test.js | 0 test/{ => looks}/moments.test.js | 0 test/{ => looks}/motion-check.test.js | 0 test/{ => looks}/motion-default.test.js | 0 test/{ => looks}/motion-lib.test.js | 0 test/{ => looks}/motionx-page.test.js | 0 test/{ => looks}/motionx.test.js | 0 test/{ => looks}/parts.test.js | 0 test/{ => looks}/pointer-check.test.js | 0 test/{ => looks}/scene-lifecycle.test.js | 0 test/{ => looks}/scene-media.test.js | 0 test/{ => looks}/scene-modes.test.js | 0 test/{ => looks}/scene-placement.test.js | 0 test/{ => looks}/scene-sync.test.js | 0 test/{ => looks}/surfaces.test.js | 0 test/{ => looks}/ultra-appearance.test.js | 0 test/{ => looks}/ultra-header-surface.test.js | 0 test/{ => looks}/ultra-states.test.js | 0 test/{ => mods}/blocklist.test.js | 0 test/{ => mods}/combined-check.test.js | 0 test/{ => mods}/mod-account.test.js | 0 test/{ => mods}/mod-nav.test.js | 0 test/{ => mods}/mod-privacy.test.js | 0 test/{ => mods}/modapi-documents.test.js | 0 test/{ => mods}/modapi.test.js | 0 test/{ => mods}/modbroker.test.js | 0 test/{ => mods}/modbundle.test.js | 0 test/{ => mods}/modchannel-worker.test.js | 0 test/{ => mods}/modchannel.test.js | 0 test/{ => mods}/modexamples.test.js | 0 test/{ => mods}/modgallery.test.js | 0 test/{ => mods}/modpacks.test.js | 0 test/{ => mods}/mods-runtime.test.js | 0 test/{ => mods}/modtools.test.js | 0 test/{ => mods}/modtypes.test.js | 0 test/{ => mods}/modui.test.js | 0 test/{ => mods}/modwidget.test.js | 0 test/{ => mods}/modwidgets-runtime.test.js | 0 .../canvas-coursemenu.test.js | 0 .../content-navigation.test.js | 0 .../context-lifecycle.test.js | 0 test/{ => navigation}/coursemenu.test.js | 0 .../coursetabs-canvas-audit-frames.test.js | 0 .../coursetabs-canvas-audit-routing.test.js | 0 .../coursetabs-canvas.test.js | 0 .../coursetabs-course-detail.test.js | 0 .../coursetabs-frames.test.js | 0 test/{ => navigation}/coursetabs-keep.test.js | 0 .../{ => navigation}/coursetabs-label.test.js | 0 .../coursetabs-native.test.js | 0 .../{ => navigation}/coursetabs-pages.test.js | 0 .../coursetabs-provenance.test.js | 0 .../coursetabs-repaint.test.js | 0 .../coursetabs-routed.test.js | 0 .../coursetabs-sidebar.test.js | 0 .../coursetabs-takeover.test.js | 0 .../{ => navigation}/coursetabs-turns.test.js | 0 .../coursetabs-worker.test.js | 0 test/{ => navigation}/coursetabs.test.js | 0 .../{ => navigation}/ctab-panel-close.test.js | 0 .../ctab-stall-switch.test.js | 0 .../{ => navigation}/ctab-strip-glass.test.js | 0 test/{ => navigation}/load-recovery.test.js | 0 test/{ => navigation}/nav-a11y.test.js | 0 .../nav-lifecycle-review.test.js | 0 test/{ => navigation}/nav-logo-review.test.js | 0 test/{ => navigation}/nav-mod-rail.test.js | 0 test/{ => navigation}/nav-overlay.test.js | 0 test/{ => navigation}/nav-paint.test.js | 0 test/{ => navigation}/nav.test.js | 0 .../navbar-canvas-audit-frames.test.js | 0 .../navbar-canvas-audit.test.js | 0 test/{ => navigation}/navbar-canvas.test.js | 0 .../navbar-moodle-controls.test.js | 0 test/{ => navigation}/navbar-moodle.test.js | 0 .../{ => navigation}/navbar-nav-perms.test.js | 0 test/{ => navigation}/navbar-panels.test.js | 0 .../navbar-polish-review.test.js | 0 test/{ => navigation}/navbar-widgets.test.js | 0 test/{ => navigation}/navbar.test.js | 0 .../{ => navigation}/navlayout-review.test.js | 0 test/{ => navigation}/navlayout.test.js | 0 test/{ => navigation}/place.test.js | 0 test/{ => navigation}/search.test.js | 0 test/{ => navigation}/seek.test.js | 0 test/{ => navigation}/sidebar-absorb.test.js | 0 .../sidebar-assessment-native.test.js | 0 test/{ => navigation}/sidebar-broker.test.js | 0 .../sidebar-native-root.test.js | 0 test/{ => navigation}/sidebar-rubric.test.js | 0 .../sidebar-test-attempt.test.js | 0 test/{ => navigation}/sidebar-ui.test.js | 0 test/{ => navigation}/sidebar-ultra.test.js | 0 test/{ => navigation}/sidebar.test.js | 0 test/{ => navigation}/sticky-fold.test.js | 0 test/{ => navigation}/tabs-auto.test.js | 0 .../ultra-course-room.test.js | 0 .../{ => navigation}/ultra-nav-review.test.js | 0 test/{ => navigation}/ultra-nav.test.js | 0 test/{ => navigation}/ultra-off.test.js | 0 test/{ => navigation}/ultra-search.test.js | 0 test/{ => navigation}/ultra-todo-line.test.js | 0 test/{ => pet}/pet-clock.test.js | 0 test/{ => pet}/pet-review.test.js | 0 test/{ => pet}/pet-ui.test.js | 0 test/{ => pet}/pet-yield.test.js | 0 test/{ => pet}/pet.test.js | 0 test/{ => pet}/petlibrary.test.js | 0 test/{ => pet}/petplayer.test.js | 0 test/{ => pet}/petsprites.test.js | 0 test/{ => pet}/petworld.test.js | 0 test/{ => planner}/calendar.test.js | 0 test/{ => planner}/calexport.test.js | 0 test/{ => planner}/classes-review.test.js | 0 test/{ => planner}/classes-ui.test.js | 0 test/{ => planner}/classes.test.js | 0 test/{ => planner}/gcal.test.js | 0 test/{ => planner}/gpa.test.js | 0 test/{ => planner}/gradecalc-parity.test.js | 0 test/{ => planner}/gradecalc.test.js | 0 test/{ => planner}/grades-ui.test.js | 0 test/{ => planner}/grades.test.js | 0 test/{ => planner}/gradescope.test.js | 0 test/{ => planner}/ics.test.js | 0 test/{ => planner}/import.test.js | 0 test/{ => planner}/insights-ui.test.js | 0 test/{ => planner}/insights.test.js | 0 test/{ => planner}/notes-ui.test.js | 0 test/{ => planner}/notes.test.js | 0 test/{ => planner}/remind.test.js | 0 test/{ => planner}/reminders-ui.test.js | 0 test/{ => planner}/schedule.test.js | 0 test/{ => planner}/syllabus-ai-ui.test.js | 0 test/{ => planner}/syllabus-sync-ui.test.js | 0 test/{ => planner}/syllabus-ui.test.js | 0 test/{ => planner}/syllabus.test.js | 0 test/{ => planner}/todo-ui.test.js | 0 test/{ => planner}/todo.test.js | 0 .../ultra-assessment-results.test.js | 0 test/{ => planner}/ultra-due-chip.test.js | 0 test/{ => planner}/ultra-plan-ui.test.js | 0 test/{ => planner}/ultra-plan.test.js | 0 test/{ => planner}/ultra-submission.test.js | 0 test/{ => platform}/animation-imports.test.js | 0 .../background-pages-check.test.js | 0 test/{ => platform}/background-update.test.js | 0 test/{ => platform}/editor-hosts.test.js | 0 test/{ => platform}/feature-hosts.test.js | 0 test/{ => platform}/fetch-hosts.test.js | 0 test/{ => platform}/frame-tabs.test.js | 0 test/{ => platform}/frame-trust.test.js | 0 test/{ => platform}/head.test.js | 0 test/{ => platform}/i18n.test.js | 0 test/{ => platform}/lms-requests.test.js | 0 test/{ => platform}/loader.test.js | 0 test/{ => platform}/manifest.test.js | 0 test/{ => platform}/oauth.test.js | 0 test/{ => platform}/perf-budget.test.js | 0 test/{ => platform}/privacy-claims.test.js | 0 test/{ => platform}/register-update.test.js | 0 .../runtime-audit-fixes.test.js | 0 test/{ => platform}/site-part.test.js | 0 test/{ => platform}/vendor-pdfjs.test.js | 0 test/{ => platform}/vendor-three.test.js | 0 test/{ => platform}/worker-i18n.test.js | 0 .../{ => release}/audit-documentation.test.js | 0 .../audit-release-readiness.test.js | 0 .../audit-ultra-completeness.test.js | 0 test/{ => release}/audit.test.js | 0 test/{ => release}/blocklist-sign.test.js | 0 test/{ => release}/browser-checks.test.js | 0 test/{ => release}/check-env.test.js | 0 test/{ => release}/check-readiness.test.js | 0 test/{ => release}/ci-browser.test.js | 0 test/{ => release}/narrow-check-seeds.test.js | 0 test/{ => release}/package.test.js | 0 test/{ => release}/release-copy.test.js | 0 test/{ => release}/release-env.test.js | 0 test/{ => release}/release-listings.test.js | 0 test/{ => release}/release-mcpb.test.js | 0 test/{ => release}/release-redact.test.js | 0 test/{ => release}/release-sources.test.js | 0 test/{ => release}/release-store-api.test.js | 0 test/{ => release}/release-verify.test.js | 0 test/{ => release}/stand-in-dom.test.js | 0 test/{ => release}/store-listing.test.js | 0 test/{ => release}/workflows.test.js | 0 test/{ => safari}/agent-bridge-native.test.js | 0 test/{ => safari}/agent-host-native.test.js | 0 test/{ => safari}/ai-apple-native.test.js | 0 test/{ => safari}/make-dmg.test.js | 0 test/{ => safari}/safari-keep-data.test.js | 0 test/{ => safari}/safari-resources.test.js | 0 .../safari-source-resources.test.js | 0 .../{ => safari}/safari-update-native.test.js | 0 test/{ => settings}/backup-check.test.js | 0 test/{ => settings}/backup-files.test.js | 0 test/{ => settings}/backup-media.test.js | 0 test/{ => settings}/backup-picker.test.js | 0 test/{ => settings}/backup-worker.test.js | 0 test/{ => settings}/backup.test.js | 0 .../docs-settings-paths.test.js | 0 test/{ => settings}/editor-agents.test.js | 0 test/{ => settings}/editor-info.test.js | 0 .../editor-media-builder.test.js | 0 test/{ => settings}/editor-media-size.test.js | 0 test/{ => settings}/editor-mods.test.js | 0 test/{ => settings}/editor-pointer.test.js | 0 test/{ => settings}/editor-widget.test.js | 0 test/{ => settings}/editor-x.test.js | 0 .../{ => settings}/editor-xparts-save.test.js | 0 test/{ => settings}/editor-xparts-ui.test.js | 0 test/{ => settings}/editor-xparts.test.js | 0 test/{ => settings}/editor.test.js | 0 .../embedded-settings-overlays.test.js | 0 test/{ => settings}/news.test.js | 0 test/{ => settings}/popup-motion.test.js | 0 test/{ => settings}/popup-site.test.js | 0 test/{ => settings}/rubric-settings.test.js | 0 test/{ => settings}/settings-ai-check.test.js | 0 test/{ => settings}/settings-copy.test.js | 0 test/{ => settings}/settings-page.test.js | 0 test/{ => settings}/settings-sites.test.js | 0 test/{ => settings}/settings-writer.test.js | 0 test/{ => settings}/settings.test.js | 0 test/{ => settings}/welcome.test.js | 0 test/{ => sharing}/quick-broker.test.js | 0 test/{ => sharing}/quick-ui.test.js | 0 test/{ => sharing}/relay-gesture.test.js | 0 test/{ => sharing}/relay-quota-review.test.js | 0 test/{ => sharing}/relay-quota.test.js | 0 test/{ => sharing}/relay-resource.test.js | 0 test/{ => sharing}/relay-ui.test.js | 0 test/{ => sharing}/relay.test.js | 0 test/{ => sharing}/sessions-create.test.js | 0 test/{ => sharing}/sessions-files.test.js | 0 test/{ => sharing}/sessions-media.test.js | 0 test/{ => sharing}/sessions-sync.test.js | 0 .../sessions-ui-lifecycle.test.js | 0 test/{ => sharing}/sessions.test.js | 0 test/{ => sharing}/share-keys.test.js | 0 test/{ => sharing}/share-protocol.test.js | 0 test/{ => sharing}/sharing.test.js | 0 test/{ => writer}/drafts-browser.test.js | 0 test/{ => writer}/drafts-ui.test.js | 0 test/{ => writer}/drafts.test.js | 0 test/{ => writer}/editor-menu.test.js | 0 test/{ => writer}/ondemand.test.js | 0 test/{ => writer}/practice-ai.test.js | 0 test/{ => writer}/writer-attach-open.test.js | 0 test/{ => writer}/writer-attach.test.js | 0 .../writer-audit-regressions.test.js | 0 test/{ => writer}/writer-bibliography.test.js | 0 test/{ => writer}/writer-cite-check.test.js | 0 test/{ => writer}/writer-cite-frame.test.js | 0 test/{ => writer}/writer-cite-lookup.test.js | 0 test/{ => writer}/writer-cite-worker.test.js | 0 test/{ => writer}/writer-cite.test.js | 0 test/{ => writer}/writer-content.test.js | 0 .../writer-context-compose.test.js | 0 .../writer-context-lifecycle.test.js | 0 test/{ => writer}/writer-context.test.js | 0 test/{ => writer}/writer-counts.test.js | 0 .../writer-discussion-sources.test.js | 0 test/{ => writer}/writer-docx-import.test.js | 0 test/{ => writer}/writer-docx.test.js | 0 test/{ => writer}/writer-editor.test.js | 0 test/{ => writer}/writer-export.test.js | 0 .../{ => writer}/writer-feedback-flow.test.js | 0 .../writer-feedback-grade.test.js | 0 .../writer-feedback-images.test.js | 0 .../writer-feedback-local.test.js | 0 test/{ => writer}/writer-final-cite.test.js | 0 test/{ => writer}/writer-final-docx.test.js | 0 .../writer-final-paginate.test.js | 0 test/{ => writer}/writer-final-paste.test.js | 0 test/{ => writer}/writer-final-proof.test.js | 0 test/{ => writer}/writer-final-submit.test.js | 0 test/{ => writer}/writer-find.test.js | 0 .../writer-format-commands.test.js | 0 test/{ => writer}/writer-format-setup.test.js | 0 test/{ => writer}/writer-frame-count.test.js | 0 test/{ => writer}/writer-frame.test.js | 0 .../writer-from-documents-sheet.test.js | 0 .../writer-hostile-roundtrip.test.js | 0 test/{ => writer}/writer-huge-budget.test.js | 0 test/{ => writer}/writer-image.test.js | 0 .../writer-import-paper-review.test.js | 0 test/{ => writer}/writer-import.test.js | 0 test/{ => writer}/writer-kinds-submit.test.js | 0 test/{ => writer}/writer-kinds.test.js | 0 test/{ => writer}/writer-local-fonts.test.js | 0 test/{ => writer}/writer-objects-docx.test.js | 0 test/{ => writer}/writer-objects.test.js | 0 test/{ => writer}/writer-outline-ai.test.js | 0 .../writer-paginate-scheduler.test.js | 0 .../writer-paginate-slices.test.js | 0 .../{ => writer}/writer-paper-compact.test.js | 0 .../{ => writer}/writer-paper-content.test.js | 0 .../{ => writer}/writer-paper-context.test.js | 0 test/{ => writer}/writer-paper-format.test.js | 0 test/{ => writer}/writer-paper-key.test.js | 0 .../writer-paper-paginate.test.js | 0 test/{ => writer}/writer-paper-papers.test.js | 0 test/{ => writer}/writer-paper-styles.test.js | 0 .../writer-paper-view-save.test.js | 0 test/{ => writer}/writer-papers-files.test.js | 0 test/{ => writer}/writer-papers-proxy.test.js | 0 .../writer-papers-versions.test.js | 0 test/{ => writer}/writer-paste.test.js | 0 test/{ => writer}/writer-pm.test.js | 0 test/{ => writer}/writer-practice.test.js | 0 test/{ => writer}/writer-proof-frame.test.js | 0 test/{ => writer}/writer-proof-harper.test.js | 0 test/{ => writer}/writer-proof-worker.test.js | 0 test/{ => writer}/writer-proof.test.js | 0 .../writer-quote-lifecycle.test.js | 0 .../writer-round1-document.test.js | 0 test/{ => writer}/writer-round1-docx.test.js | 0 test/{ => writer}/writer-round4-notes.test.js | 0 test/{ => writer}/writer-round4-ui.test.js | 0 test/{ => writer}/writer-schema.test.js | 0 .../writer-storage-failures.test.js | 0 test/{ => writer}/writer-submit-check.test.js | 0 .../{ => writer}/writer-submit-review.test.js | 0 .../writer-submit-sheet-review.test.js | 0 test/{ => writer}/writer-submit-tap.test.js | 0 test/{ => writer}/writer-submit-wait.test.js | 0 test/{ => writer}/writer-submit.test.js | 0 test/{ => writer}/writer-target.test.js | 0 test/{ => writer}/writer-thumb.test.js | 0 test/{ => writer}/writer-ultra.test.js | 0 {scripts => tools/build}/appstore-ios.sh | 0 {scripts => tools/build}/appstore-safari.sh | 0 {scripts => tools/build}/build-safari.sh | 0 {scripts => tools/build}/make-dmg.sh | 0 {scripts => tools/build}/package-firefox.sh | 0 {scripts => tools/build}/package-safari.sh | 0 package.sh => tools/build/package.sh | 0 {scripts => tools/build}/safari-build-ids.py | 0 {scripts => tools/build}/safari-manifest.sh | 0 {scripts => tools/build}/safari-resources.js | 0 {scripts => tools/build}/stage-safari-dmg.py | 0 .../build}/sync-safari-resources.sh | 0 {scripts => tools/dev}/audit.js | 0 {scripts => tools/dev}/canvas-shots.js | 0 {scripts => tools/dev}/check-env.js | 0 {scripts => tools/dev}/ci-browser-checks.js | 0 {scripts => tools/dev}/ci-chromium.js | 0 {scripts => tools/dev}/dev-gcal.sh | 0 {scripts => tools/dev}/docs-mirror.js | 0 {scripts => tools/dev}/firefox-android.sh | 0 {scripts => tools/dev}/practice-ai.js | 0 {scripts => tools/dev}/redact.js | 0 {scripts => tools/dev}/wait-for-browser.js | 0 {scripts => tools/dev}/writer-practice.js | 0 .../fidelity}/import-fixtures.js | 0 {scripts => tools/fidelity}/pdf-fidelity.js | 0 {scripts => tools/fidelity}/pdf-fixtures.js | 0 .../fidelity}/sheet-excel-fidelity.js | 0 .../fidelity}/sheet-excel-golden.js | 0 {scripts => tools/fidelity}/sheet-excel.js | 0 .../fidelity}/sheet-fidelity-books.js | 0 .../fidelity}/sheet-numbers-fidelity.js | 0 .../fidelity}/slides-fidelity.js | 0 .../fidelity}/writer-import-fidelity.js | 0 .../fidelity}/writer-word-fidelity.js | 0 {scripts => tools/release}/blocklist-sign.mjs | 0 {scripts => tools/release}/clean-source.sh | 0 {scripts => tools/release}/public-release.mjs | 0 {scripts => tools/release}/publish-chrome.sh | 0 {scripts => tools/release}/release.sh | 0 {scripts => tools/release}/safari-appcast.sh | 0 {scripts => tools/release}/store-api.mjs | 0 .../release}/store-firefox-source.py | 0 {scripts => tools/release}/store-listings.mjs | 0 .../release}/store-release.env.example | 0 {scripts => tools/release}/store-release.mjs | 0 .../release}/store-screenshots.js | 0 {scripts => tools/vendor}/npm-integrity.js | 0 {scripts => tools/vendor}/vendor-harper.sh | 0 {scripts => tools/vendor}/vendor-lottie.sh | 0 .../vendor}/vendor-paper-fonts.sh | 0 .../vendor}/vendor-pdfjs-brotli.js | 0 .../vendor}/vendor-pdfjs-limits.js | 0 {scripts => tools/vendor}/vendor-pdfjs.sh | 0 .../vendor}/vendor-prosemirror.sh | 0 {scripts => tools/vendor}/vendor-tesseract.sh | 0 {scripts => tools/vendor}/vendor-three.sh | 0 1341 files changed, 61 deletions(-) rename {ci => .github/pins}/chromium.pins (100%) rename {ci => .github/pins}/ffmpeg.pins (100%) rename {ci => .github/pins}/playwright.pins (100%) rename {scripts => checks/agents}/agent-link-check.js (100%) rename {scripts => checks/agents}/mcp-approve-check.js (100%) rename {scripts => checks/agents}/mcp-conformance-check.js (100%) rename {scripts => checks/agents}/mcp-refresh-check.js (100%) rename {scripts => checks/agents}/mcp-windows-check.js (100%) rename {scripts => checks/agents}/mcp-writer-check.js (100%) rename {scripts => checks/core}/download-check.js (100%) rename {scripts => checks/core}/widgets-builder-check.js (100%) rename {scripts => checks/core}/widgets-sizes-check.js (100%) rename {scripts => checks/courses}/allow-check.js (100%) rename {scripts => checks/courses}/announce-check-sites.js (100%) rename {scripts => checks/courses}/announce-check.js (100%) rename {scripts => checks/courses}/announce-read-check.js (100%) rename {scripts => checks/courses}/canvas-boards-check.js (100%) rename {scripts => checks/courses}/docreview-check.js (100%) rename {scripts => checks/courses}/docviewer-check.js (100%) rename {scripts => checks/courses}/rubric-check.js (100%) rename {scripts => checks/courses}/stream-check.js (100%) rename {scripts => checks/courses}/stream-runtime-check.js (100%) rename {scripts => checks/courses}/ultra-assignment-attempt-check.js (100%) rename {scripts => checks/courses}/ultra-boards-check.js (100%) rename {scripts => checks/courses}/ultra-cards-check.js (100%) rename {scripts => checks/courses}/ultra-folders-check.js (100%) rename {scripts => checks/courses}/ultra-live-frames-check.js (100%) rename {scripts => checks/courses}/ultra-perf-check.js (100%) rename {scripts => checks/courses}/ultra-preview-check.js (100%) rename {scripts => checks/courses}/ultra-test-attempt-check.js (100%) rename {scripts => checks/documents}/documents-ai-check.js (100%) rename {scripts => checks/documents}/documents-check.js (100%) rename {scripts => checks/documents}/documents-entry-check.js (100%) rename {scripts => checks/documents}/documents-page-check.js (100%) rename {scripts => checks/documents}/pdf-check.js (100%) rename {scripts => checks/documents}/sheets-check.js (100%) rename {scripts => checks/documents}/slides-check.js (100%) rename {scripts => checks/firefox}/agent-link-firefox-check.mjs (100%) rename {scripts => checks/firefox}/firefox-check.mjs (100%) rename {scripts => checks/looks}/glass-remaining-check.js (100%) rename {scripts => checks/looks}/lookparts-check.js (100%) rename {scripts => checks/looks}/looks-catalog-check.js (100%) rename {scripts => checks/looks}/media-check.js (100%) rename {scripts => checks/looks}/media-cost-check.js (100%) rename {scripts => checks/looks}/motion-check.js (100%) rename {scripts => checks/looks}/motion-default-check.js (100%) rename {scripts => checks/looks}/motionx-check.js (100%) rename {scripts => checks/looks}/pointer-check.js (100%) rename {scripts => checks/looks}/rules-check.js (100%) rename {scripts => checks/looks}/scene-check.html (100%) rename {scripts => checks/looks}/scene-check.mjs (100%) rename {scripts => checks/looks}/scene-page-check.js (100%) rename {scripts => checks/looks}/surfaces-check.js (100%) rename {scripts => checks/mocks}/frame-tap.js (100%) rename {scripts => checks/mocks}/gcal-mock.js (100%) rename {scripts => checks/mocks}/ultra-attempt-harness.js (100%) rename {scripts => checks/mocks}/ultra-mock.js (100%) rename {scripts => checks/mocks}/ultra-offcanvas-fixture.js (100%) rename {scripts => checks/mods}/blocklist-check.js (100%) rename {scripts => checks/mods}/modbundle-check.js (100%) rename {scripts => checks/mods}/mods-api-check.js (100%) rename {scripts => checks/mods}/mods-channel-check.js (100%) rename {scripts => checks/mods}/mods-combined-check.js (100%) rename {scripts => checks/mods}/mods-parts-check.js (100%) rename {scripts => checks/mods}/mods-sandbox-check.js (100%) rename {scripts => checks/mods}/mods-section-check.js (100%) rename {scripts => checks/mods}/mods-tools-check.js (100%) rename {scripts => checks/navigation}/coursetabs-sketch-check.js (100%) rename {scripts => checks/navigation}/ctab-background-check.js (100%) rename {scripts => checks/navigation}/ctab-palette-check.js (100%) rename {scripts => checks/navigation}/ctab-panel-close-check.js (100%) rename {scripts => checks/navigation}/ctab-strip-glass-check.js (100%) rename {scripts => checks/navigation}/nav-layout-check.js (100%) rename {scripts => checks/navigation}/navbar-canvas-check.js (100%) rename {scripts => checks/navigation}/navbar-check.js (100%) rename {scripts => checks/navigation}/navbar-moodle-check.js (100%) rename {scripts => checks/navigation}/sidebar-assessment-check.js (100%) rename {scripts => checks/navigation}/sidebar-canvas-check.js (100%) rename {scripts => checks/navigation}/sidebar-participants-check.js (100%) rename {scripts => checks/navigation}/sidebar-tabs-check.js (100%) rename {scripts => checks/navigation}/sidebar-test-attempt-check.js (100%) rename {scripts => checks/navigation}/sidebar-ultra-check.js (100%) rename {scripts => checks/navigation}/sidebar-ultra-tabs-check.js (100%) rename {scripts => checks/navigation}/ultra-contents-drawer-check.js (100%) rename {scripts => checks/navigation}/ultra-course-bar-check.js (100%) rename {scripts => checks/navigation}/ultra-offcanvas-check.js (100%) rename {scripts => checks/navigation}/ultra-panel-background-check.js (100%) rename {scripts => checks/navigation}/ultra-panels-check.js (100%) rename {scripts => checks/navigation}/ultra-tabs-check.js (100%) rename {scripts => checks/planner}/gradescope-switch-check.js (100%) rename {scripts => checks/planner}/import-check.js (100%) rename {scripts => checks/planner}/import-truth.js (100%) rename {scripts => checks/planner}/whatif-ultra-check.js (100%) rename {scripts => checks/platform}/background-pages-check.js (100%) rename {scripts => checks/platform}/background-shell-check.js (100%) rename {scripts => checks/platform}/lit-check.js (100%) rename {scripts => checks/platform}/register-update-check.js (100%) rename {scripts => checks/platform}/registered-hosts-check.js (100%) rename {scripts => checks/platform}/transition-guard-check.js (100%) rename {scripts => checks/safari}/content-navigation-safari-check.py (100%) rename {scripts => checks/settings}/backup-check.js (100%) rename {scripts => checks/settings}/editor-x-check.js (100%) rename {scripts => checks/settings}/embedded-settings-check.js (100%) rename {scripts => checks/settings}/popup-check.js (100%) rename {scripts => checks/settings}/popup-motion-check.js (100%) rename {scripts => checks/settings}/settings-agents-check.js (100%) rename {scripts => checks/settings}/settings-ai-check.js (100%) rename {scripts => checks/settings}/settings-ia-check.js (100%) rename {scripts => checks/settings}/settings-wiring-check.js (100%) rename {scripts => checks/settings}/settings-writer-check.js (100%) rename {scripts => checks/settings}/sites-check.js (100%) rename {scripts => checks/settings}/welcome-check.js (100%) rename {scripts => checks/settings}/xparts-editor-check.js (100%) rename {scripts => checks/sharing}/share-check.js (100%) rename {scripts => checks/writer}/writer-adversarial-check.js (100%) rename {scripts => checks/writer}/writer-ai-check.js (100%) rename {scripts => checks/writer}/writer-check.js (100%) rename {scripts => checks/writer}/writer-cite-check.js (100%) rename {scripts => checks/writer}/writer-completion-check.js (100%) rename {scripts => checks/writer}/writer-compose-check.js (100%) rename {scripts => checks/writer}/writer-core-check.js (100%) rename {scripts => checks/writer}/writer-dark-check.js (100%) rename {scripts => checks/writer}/writer-edit-check.js (100%) rename {scripts => checks/writer}/writer-format-check.js (100%) rename {scripts => checks/writer}/writer-huge-check.js (100%) rename {scripts => checks/writer}/writer-keyboard-check.js (100%) rename {scripts => checks/writer}/writer-kinds-check.js (100%) rename {scripts => checks/writer}/writer-look-check.js (100%) rename {scripts => checks/writer}/writer-objects-check.js (100%) rename {scripts => checks/writer}/writer-paper-check.js (100%) rename {scripts => checks/writer}/writer-paper-regression-check.js (100%) rename {scripts => checks/writer}/writer-proof-check.js (100%) rename {scripts => checks/writer}/writer-sources-check.js (100%) rename {scripts => checks/writer}/writer-submit-check.js (100%) rename {_locales => extension/_locales}/de/messages.json (100%) rename {_locales => extension/_locales}/en/messages.json (100%) rename {_locales => extension/_locales}/es/messages.json (100%) rename {_locales => extension/_locales}/fr/messages.json (100%) rename {_locales => extension/_locales}/nl/messages.json (100%) rename {_locales => extension/_locales}/pt_BR/messages.json (100%) rename {assets => extension/assets}/art/README.md (100%) rename {assets => extension/assets}/art/contact-sheet.png (100%) rename {assets => extension/assets}/art/cursors/aqua/default.png (100%) rename {assets => extension/assets}/art/cursors/aqua/pointer.png (100%) rename {assets => extension/assets}/art/cursors/aurora/default.png (100%) rename {assets => extension/assets}/art/cursors/aurora/default.svg (100%) rename {assets => extension/assets}/art/cursors/aurora/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/aurora/pointer.png (100%) rename {assets => extension/assets}/art/cursors/aurora/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/aurora/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/clear/default.png (100%) rename {assets => extension/assets}/art/cursors/clear/default.svg (100%) rename {assets => extension/assets}/art/cursors/clear/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/clear/pointer.png (100%) rename {assets => extension/assets}/art/cursors/clear/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/clear/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/frostdev/default.png (100%) rename {assets => extension/assets}/art/cursors/frostdev/default.svg (100%) rename {assets => extension/assets}/art/cursors/frostdev/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/frostdev/pointer.png (100%) rename {assets => extension/assets}/art/cursors/frostdev/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/frostdev/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/ghost/default.png (100%) rename {assets => extension/assets}/art/cursors/ghost/default.svg (100%) rename {assets => extension/assets}/art/cursors/ghost/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/ghost/pointer.png (100%) rename {assets => extension/assets}/art/cursors/ghost/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/ghost/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/lagoon/default.png (100%) rename {assets => extension/assets}/art/cursors/lagoon/default.svg (100%) rename {assets => extension/assets}/art/cursors/lagoon/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/lagoon/pointer.png (100%) rename {assets => extension/assets}/art/cursors/lagoon/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/lagoon/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/lava/default.png (100%) rename {assets => extension/assets}/art/cursors/lava/default.svg (100%) rename {assets => extension/assets}/art/cursors/lava/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/lava/pointer.png (100%) rename {assets => extension/assets}/art/cursors/lava/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/lava/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/lilac/default.png (100%) rename {assets => extension/assets}/art/cursors/lilac/pointer.png (100%) rename {assets => extension/assets}/art/cursors/neon/default.png (100%) rename {assets => extension/assets}/art/cursors/neon/default.svg (100%) rename {assets => extension/assets}/art/cursors/neon/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/neon/pointer.png (100%) rename {assets => extension/assets}/art/cursors/neon/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/neon/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/pearl/default.png (100%) rename {assets => extension/assets}/art/cursors/pearl/pointer.png (100%) rename {assets => extension/assets}/art/cursors/pumpkin/default.png (100%) rename {assets => extension/assets}/art/cursors/pumpkin/default.svg (100%) rename {assets => extension/assets}/art/cursors/pumpkin/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/pumpkin/pointer.png (100%) rename {assets => extension/assets}/art/cursors/pumpkin/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/pumpkin/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/quill/default.png (100%) rename {assets => extension/assets}/art/cursors/quill/default.svg (100%) rename {assets => extension/assets}/art/cursors/quill/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/quill/pointer.png (100%) rename {assets => extension/assets}/art/cursors/quill/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/quill/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/rose/default.png (100%) rename {assets => extension/assets}/art/cursors/rose/default.svg (100%) rename {assets => extension/assets}/art/cursors/rose/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/rose/pointer.png (100%) rename {assets => extension/assets}/art/cursors/rose/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/rose/pointer@2x.png (100%) rename {assets => extension/assets}/art/cursors/terminal/default.png (100%) rename {assets => extension/assets}/art/cursors/terminal/default.svg (100%) rename {assets => extension/assets}/art/cursors/terminal/default@2x.png (100%) rename {assets => extension/assets}/art/cursors/terminal/pointer.png (100%) rename {assets => extension/assets}/art/cursors/terminal/pointer.svg (100%) rename {assets => extension/assets}/art/cursors/terminal/pointer@2x.png (100%) rename {assets => extension/assets}/art/pet-reactions.md (100%) rename {assets => extension/assets}/art/pets/bunny/reactions.webp (100%) rename {assets => extension/assets}/art/pets/bunny/sprites.webp (100%) rename {assets => extension/assets}/art/pets/cat/reactions.webp (100%) rename {assets => extension/assets}/art/pets/cat/sprites.webp (100%) rename {assets => extension/assets}/art/pets/ember/pet.json (100%) rename {assets => extension/assets}/art/pets/ember/reactions.webp (100%) rename {assets => extension/assets}/art/pets/ember/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/glaze/pet.json (100%) rename {assets => extension/assets}/art/pets/glaze/reactions.webp (100%) rename {assets => extension/assets}/art/pets/glaze/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/luna/pet.json (100%) rename {assets => extension/assets}/art/pets/luna/reactions.webp (100%) rename {assets => extension/assets}/art/pets/luna/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/mochi/pet.json (100%) rename {assets => extension/assets}/art/pets/mochi/reactions.webp (100%) rename {assets => extension/assets}/art/pets/mochi/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/pebble/pet.json (100%) rename {assets => extension/assets}/art/pets/pebble/reactions.webp (100%) rename {assets => extension/assets}/art/pets/pebble/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/rime/pet.json (100%) rename {assets => extension/assets}/art/pets/rime/reactions.webp (100%) rename {assets => extension/assets}/art/pets/rime/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/sprout/pet.json (100%) rename {assets => extension/assets}/art/pets/sprout/reactions.webp (100%) rename {assets => extension/assets}/art/pets/sprout/spritesheet.webp (100%) rename {assets => extension/assets}/art/pets/study-buddy/reactions.webp (100%) rename {assets => extension/assets}/art/pets/study-buddy/sprites.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/acorn.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/candle.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/cinnamon-bun.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/cozy-sock.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/folded-blanket.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/knitted-mittens.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/maple-leaf.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/mushroom.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/oak-leaf.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/pumpkin.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/scarf.webp (100%) rename {assets => extension/assets}/art/stickers/cozy-autumn/steaming-cocoa-mug.webp (100%) rename {assets => extension/assets}/art/stickers/critters/bumblebee.webp (100%) rename {assets => extension/assets}/art/stickers/critters/gentle-otter.webp (100%) rename {assets => extension/assets}/art/stickers/critters/lilac-hedgehog.webp (100%) rename {assets => extension/assets}/art/stickers/critters/little-mouse.webp (100%) rename {assets => extension/assets}/art/stickers/critters/mint-frog.webp (100%) rename {assets => extension/assets}/art/stickers/critters/penguin-chick.webp (100%) rename {assets => extension/assets}/art/stickers/critters/red-panda.webp (100%) rename {assets => extension/assets}/art/stickers/critters/round-owl.webp (100%) rename {assets => extension/assets}/art/stickers/critters/seal-pup.webp (100%) rename {assets => extension/assets}/art/stickers/critters/sitting-bunny.webp (100%) rename {assets => extension/assets}/art/stickers/critters/small-fox.webp (100%) rename {assets => extension/assets}/art/stickers/critters/study-buddy-bandana.webp (100%) rename {assets => extension/assets}/art/stickers/critters/study-buddy-happy.webp (100%) rename {assets => extension/assets}/art/stickers/critters/study-buddy-sleepy.webp (100%) rename {assets => extension/assets}/art/stickers/critters/study-buddy.webp (100%) rename {assets => extension/assets}/art/stickers/critters/teal-turtle.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/kelp.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/pet-bed.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/sandcastle.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/shelf.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/toy-ball.webp (100%) rename {assets => extension/assets}/art/stickers/habitat/yarn-ball.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/comet.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/constellation.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/crescent-moon.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/earthlike-planet.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/fluffy-cloud.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/full-moon.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/rainbow.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/ringed-planet.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/rocket.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/shooting-star.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/sun.webp (100%) rename {assets => extension/assets}/art/stickers/space-sky/telescope.webp (100%) rename {assets => extension/assets}/art/stickers/study/blank-sticky-note.webp (100%) rename {assets => extension/assets}/art/stickers/study/checkmark.webp (100%) rename {assets => extension/assets}/art/stickers/study/closed-books.webp (100%) rename {assets => extension/assets}/art/stickers/study/coffee.webp (100%) rename {assets => extension/assets}/art/stickers/study/desk-lamp.webp (100%) rename {assets => extension/assets}/art/stickers/study/graduation-cap.webp (100%) rename {assets => extension/assets}/art/stickers/study/hourglass.webp (100%) rename {assets => extension/assets}/art/stickers/study/notebook.webp (100%) rename {assets => extension/assets}/art/stickers/study/open-book.webp (100%) rename {assets => extension/assets}/art/stickers/study/pencil.webp (100%) rename {assets => extension/assets}/art/stickers/study/reading-glasses.webp (100%) rename {assets => extension/assets}/art/stickers/study/star-cluster.webp (100%) rename {assets => extension/assets}/logo-dark.svg (100%) rename {assets => extension/assets}/logo.svg (100%) rename {assets => extension/assets}/shaders/aurora.glsl (100%) rename {assets => extension/assets}/shaders/liquid.glsl (100%) rename {assets => extension/assets}/shaders/starfield.glsl (100%) rename canvas.css => extension/canvas.css (100%) rename content-canvas.js => extension/content-canvas.js (100%) rename content-ultra.js => extension/content-ultra.js (100%) rename content.js => extension/content.js (100%) rename defaults.js => extension/defaults.js (100%) rename extras.css => extension/extras.css (100%) rename {features => extension/features}/allow.css (100%) rename {features => extension/features}/allow.html (100%) rename {features => extension/features}/allow.js (100%) rename {features => extension/features}/announce-read.js (100%) rename {features => extension/features}/announce.js (100%) rename {features => extension/features}/apps.css (100%) rename {features => extension/features}/bbpreview.js (100%) rename {features => extension/features}/calexport.js (100%) rename {features => extension/features}/cards.js (100%) rename {features => extension/features}/cardtasks.js (100%) rename {features => extension/features}/classes.js (100%) rename {features => extension/features}/consent.js (100%) rename {features => extension/features}/coursecards.js (100%) rename {features => extension/features}/coursemenu.js (100%) rename {features => extension/features}/coursetabs.css (100%) rename {features => extension/features}/coursetabs.js (100%) rename {features => extension/features}/dashboard.js (100%) rename {features => extension/features}/discussion-nav.js (100%) rename {features => extension/features}/docreview.css (100%) rename {features => extension/features}/docreview.js (100%) rename {features => extension/features}/docs.js (100%) rename {features => extension/features}/docviewer.js (100%) rename {features => extension/features}/download.js (100%) rename {features => extension/features}/drafts.js (100%) rename {features => extension/features}/editor-menu.js (100%) rename {features => extension/features}/folders.js (100%) rename {features => extension/features}/gcal.js (100%) rename {features => extension/features}/gpaterms.js (100%) rename {features => extension/features}/grades.js (100%) rename {features => extension/features}/gradescope.js (100%) rename {features => extension/features}/import.js (100%) rename {features => extension/features}/insights.js (100%) rename {features => extension/features}/keys.js (100%) rename {features => extension/features}/light.js (100%) rename {features => extension/features}/lookparts.js (100%) rename {features => extension/features}/lookx.css (100%) rename {features => extension/features}/lookx.js (100%) rename {features => extension/features}/mods.js (100%) rename {features => extension/features}/modui.js (100%) rename {features => extension/features}/modwidgets.js (100%) rename {features => extension/features}/moodle.js (100%) rename {features => extension/features}/motionx.js (100%) rename {features => extension/features}/nav.js (100%) rename {features => extension/features}/navbar-canvas.js (100%) rename {features => extension/features}/navbar-moodle.js (100%) rename {features => extension/features}/navbar-widgets.js (100%) rename {features => extension/features}/navbar.css (100%) rename {features => extension/features}/navbar.js (100%) rename {features => extension/features}/navlayout.css (100%) rename {features => extension/features}/notes-home.js (100%) rename {features => extension/features}/notes.js (100%) rename {features => extension/features}/palette.js (100%) rename {features => extension/features}/pet.js (100%) rename {features => extension/features}/petworld.js (100%) rename {features => extension/features}/place.js (100%) rename {features => extension/features}/popup.js (100%) rename {features => extension/features}/previews.js (100%) rename {features => extension/features}/reminders.js (100%) rename {features => extension/features}/routes.js (100%) rename {features => extension/features}/rubric.js (100%) rename {features => extension/features}/rules.css (100%) rename {features => extension/features}/rules.js (100%) rename {features => extension/features}/schedule.js (100%) rename {features => extension/features}/search.js (100%) rename {features => extension/features}/settingspage.js (100%) rename {features => extension/features}/sharing.js (100%) rename {features => extension/features}/sidebar.css (100%) rename {features => extension/features}/sidebar.js (100%) rename {features => extension/features}/sticky-fold.js (100%) rename {features => extension/features}/stream.js (100%) rename {features => extension/features}/surfaces.css (100%) rename {features => extension/features}/syllabus.js (100%) rename {features => extension/features}/todo.js (100%) rename {features => extension/features}/welcome.css (100%) rename {features => extension/features}/welcome.html (100%) rename {features => extension/features}/welcome.js (100%) rename {features => extension/features}/widgets.js (100%) rename {features => extension/features}/writer-quote.js (100%) rename {features => extension/features}/writer-submit.js (100%) rename {features => extension/features}/writer.js (100%) rename {fonts => extension/fonts}/OFL-AtkinsonHyperlegible.txt (100%) rename {fonts => extension/fonts}/OFL-Inter.txt (100%) rename {fonts => extension/fonts}/atkinson-400.woff2 (100%) rename {fonts => extension/fonts}/atkinson-700.woff2 (100%) rename {fonts => extension/fonts}/inter.woff2 (100%) rename {i18n => extension/i18n}/de.js (100%) rename {i18n => extension/i18n}/es.js (100%) rename {i18n => extension/i18n}/fr.js (100%) rename {i18n => extension/i18n}/nl.js (100%) rename {i18n => extension/i18n}/pt-BR.js (100%) rename {icons => extension/icons}/icon.svg (100%) rename {icons => extension/icons}/icon128.png (100%) rename {icons => extension/icons}/icon16.png (100%) rename {icons => extension/icons}/icon32.png (100%) rename {icons => extension/icons}/icon48.png (100%) rename {lib => extension/lib}/agent-frames.js (100%) rename {lib => extension/lib}/agent-link.js (100%) rename {lib => extension/lib}/ai/broker.js (100%) rename {lib => extension/lib}/ai/endpoint.js (100%) rename {lib => extension/lib}/ai/failure.js (100%) rename {lib => extension/lib}/ai/loop.js (100%) rename {lib => extension/lib}/ai/models.js (100%) rename {lib => extension/lib}/ai/providers.js (100%) rename {lib => extension/lib}/ai/providers/apple.js (100%) rename {lib => extension/lib}/ai/providers/device.js (100%) rename {lib => extension/lib}/ai/providers/mock.js (100%) rename {lib => extension/lib}/ai/providers/openai.js (100%) rename {lib => extension/lib}/ai/request.js (100%) rename {lib => extension/lib}/ai/schema.js (100%) rename {lib => extension/lib}/ai/settings.js (100%) rename {lib => extension/lib}/ai/stream.js (100%) rename {lib => extension/lib}/ai/task.js (100%) rename {lib => extension/lib}/ai/tasks/syllabus-read.js (100%) rename {lib => extension/lib}/ai/tasks/writer-cite.js (100%) rename {lib => extension/lib}/ai/tasks/writer-complete.js (100%) rename {lib => extension/lib}/ai/tasks/writer-context.js (100%) rename {lib => extension/lib}/ai/tasks/writer-decide.js (100%) rename {lib => extension/lib}/ai/tasks/writer-feedback.js (100%) rename {lib => extension/lib}/ai/tasks/writer-image.js (100%) rename {lib => extension/lib}/ai/tasks/writer-outline.js (100%) rename {lib => extension/lib}/ai/tasks/writer-requirements.js (100%) rename {lib => extension/lib}/ai/tasks/writer-review.js (100%) rename {lib => extension/lib}/ai/text.js (100%) rename {lib => extension/lib}/ai/tools.js (100%) rename {lib => extension/lib}/ai/visual-cache.js (100%) rename {lib => extension/lib}/announce.js (100%) rename {lib => extension/lib}/background.js (100%) rename {lib => extension/lib}/backup.js (100%) rename {lib => extension/lib}/bb-preview.js (100%) rename {lib => extension/lib}/blocklist.js (100%) rename {lib => extension/lib}/calexport.js (100%) rename {lib => extension/lib}/cards.js (100%) rename {lib => extension/lib}/classes.js (100%) rename {lib => extension/lib}/color.js (100%) rename {lib => extension/lib}/coursetabs.js (100%) rename {lib => extension/lib}/detect.js (100%) rename {lib => extension/lib}/doc-files.js (100%) rename {lib => extension/lib}/doc-home.js (100%) rename {lib => extension/lib}/doc-kinds.js (100%) rename {lib => extension/lib}/doctext.js (100%) rename {lib => extension/lib}/download.js (100%) rename {lib => extension/lib}/drafts.js (100%) rename {lib => extension/lib}/editor-agents.js (100%) rename {lib => extension/lib}/editor-ai.js (100%) rename {lib => extension/lib}/editor-info.js (100%) rename {lib => extension/lib}/editor-mods.js (100%) rename {lib => extension/lib}/editor-navbar.js (100%) rename {lib => extension/lib}/editor-pointer.js (100%) rename {lib => extension/lib}/editor-preview.js (100%) rename {lib => extension/lib}/editor-widget.js (100%) rename {lib => extension/lib}/editor-writer.js (100%) rename {lib => extension/lib}/editor-x.js (100%) rename {lib => extension/lib}/editor-xbg.js (100%) rename {lib => extension/lib}/editor-xmotion.js (100%) rename {lib => extension/lib}/editor-xparts.js (100%) rename {lib => extension/lib}/editor.js (100%) rename {lib => extension/lib}/folders.js (100%) rename {lib => extension/lib}/fonts.js (100%) rename {lib => extension/lib}/fonts/LICENSE-arimo.txt (100%) rename {lib => extension/lib}/fonts/LICENSE-carlito.txt (100%) rename {lib => extension/lib}/fonts/LICENSE-cousine.txt (100%) rename {lib => extension/lib}/fonts/LICENSE-tinos.txt (100%) rename {lib => extension/lib}/fonts/arimo-400-italic.woff2 (100%) rename {lib => extension/lib}/fonts/arimo-400-normal.woff2 (100%) rename {lib => extension/lib}/fonts/arimo-700-italic.woff2 (100%) rename {lib => extension/lib}/fonts/arimo-700-normal.woff2 (100%) rename {lib => extension/lib}/fonts/carlito-400-italic.woff2 (100%) rename {lib => extension/lib}/fonts/carlito-400-normal.woff2 (100%) rename {lib => extension/lib}/fonts/carlito-700-italic.woff2 (100%) rename {lib => extension/lib}/fonts/carlito-700-normal.woff2 (100%) rename {lib => extension/lib}/fonts/cousine-400-italic.woff2 (100%) rename {lib => extension/lib}/fonts/cousine-400-normal.woff2 (100%) rename {lib => extension/lib}/fonts/cousine-700-italic.woff2 (100%) rename {lib => extension/lib}/fonts/cousine-700-normal.woff2 (100%) rename {lib => extension/lib}/fonts/tinos-400-italic.woff2 (100%) rename {lib => extension/lib}/fonts/tinos-400-normal.woff2 (100%) rename {lib => extension/lib}/fonts/tinos-700-italic.woff2 (100%) rename {lib => extension/lib}/fonts/tinos-700-normal.woff2 (100%) rename {lib => extension/lib}/gallery.js (100%) rename {lib => extension/lib}/gcal.js (100%) rename {lib => extension/lib}/glsl.js (100%) rename {lib => extension/lib}/gpa.js (100%) rename {lib => extension/lib}/gradecalc.js (100%) rename {lib => extension/lib}/grades.js (100%) rename {lib => extension/lib}/gradescope.js (100%) rename {lib => extension/lib}/harper/BinaryModule-S5UABkWQ.js (100%) rename {lib => extension/lib}/harper/LICENSE (100%) rename {lib => extension/lib}/harper/harper_wasm_slim_bg.wasm (100%) rename {lib => extension/lib}/harper/index.js (100%) rename {lib => extension/lib}/harper/shared.js (100%) rename {lib => extension/lib}/harper/slimBinary.js (100%) rename {lib => extension/lib}/history.js (100%) rename {lib => extension/lib}/i18n.js (100%) rename {lib => extension/lib}/ics.js (100%) rename {lib => extension/lib}/ink.js (100%) rename {lib => extension/lib}/insights.js (100%) rename {lib => extension/lib}/lmsq.js (100%) rename {lib => extension/lib}/lookparts.js (100%) rename {lib => extension/lib}/looks.js (100%) rename {lib => extension/lib}/lookset.js (100%) rename {lib => extension/lib}/lookx.js (100%) rename {lib => extension/lib}/lottie/LICENSE (100%) rename {lib => extension/lib}/lottie/lottie_light_canvas.js (100%) rename {lib => extension/lib}/mcp/approve-page.js (100%) rename {lib => extension/lib}/mcp/approve.css (100%) rename {lib => extension/lib}/mcp/approve.html (100%) rename {lib => extension/lib}/mcp/approve.js (100%) rename {lib => extension/lib}/mcp/calls.js (100%) rename {lib => extension/lib}/mcp/grants.js (100%) rename {lib => extension/lib}/mcp/modroute.js (100%) rename {lib => extension/lib}/mcp/registry.js (100%) rename {lib => extension/lib}/mcp/tab.js (100%) rename {lib => extension/lib}/mcp/tabs.js (100%) rename {lib => extension/lib}/mcp/tools-assign.js (100%) rename {lib => extension/lib}/mcp/tools-grades.js (100%) rename {lib => extension/lib}/mcp/tools-study.js (100%) rename {lib => extension/lib}/mcp/wire.js (100%) rename {lib => extension/lib}/mcp/writer.js (100%) rename {lib => extension/lib}/media.js (100%) rename {lib => extension/lib}/modapi.js (100%) rename {lib => extension/lib}/modbroker.js (100%) rename {lib => extension/lib}/modbundle.js (100%) rename {lib => extension/lib}/modchannel.js (100%) rename {lib => extension/lib}/modtools.js (100%) rename {lib => extension/lib}/modwidget.js (100%) rename {lib => extension/lib}/moodle.js (100%) rename {lib => extension/lib}/motion.js (100%) rename {lib => extension/lib}/motionx.js (100%) rename {lib => extension/lib}/nav-items.js (100%) rename {lib => extension/lib}/nav-store.js (100%) rename {lib => extension/lib}/nav.js (100%) rename {lib => extension/lib}/navbar.js (100%) rename {lib => extension/lib}/navlayout.js (100%) rename {lib => extension/lib}/news.js (100%) rename {lib => extension/lib}/notes.js (100%) rename {lib => extension/lib}/oauth.js (100%) rename {lib => extension/lib}/ocr.js (100%) rename {lib => extension/lib}/page.js (100%) rename {lib => extension/lib}/parts.js (100%) rename {lib => extension/lib}/pdf-doc.js (100%) rename {lib => extension/lib}/pdf-l10n.js (100%) rename {lib => extension/lib}/pdfjs/LICENSE (100%) rename {lib => extension/lib}/pdfjs/LICENSE_BROTLI (100%) rename {lib => extension/lib}/pdfjs/LICENSE_CORE_JS (100%) rename {lib => extension/lib}/pdfjs/LICENSE_JBIG2 (100%) rename {lib => extension/lib}/pdfjs/LICENSE_MPL (100%) rename {lib => extension/lib}/pdfjs/LICENSE_OPENJPEG (100%) rename {lib => extension/lib}/pdfjs/LICENSE_PDFJS_JBIG2 (100%) rename {lib => extension/lib}/pdfjs/LICENSE_PDFJS_OPENJPEG (100%) rename {lib => extension/lib}/pdfjs/brotli-dictionary.bin (100%) rename {lib => extension/lib}/pdfjs/images/altText_add.svg (100%) rename {lib => extension/lib}/pdfjs/images/altText_disclaimer.svg (100%) rename {lib => extension/lib}/pdfjs/images/altText_done.svg (100%) rename {lib => extension/lib}/pdfjs/images/altText_spinner.svg (100%) rename {lib => extension/lib}/pdfjs/images/altText_warning.svg (100%) rename {lib => extension/lib}/pdfjs/images/checkmark.svg (100%) rename {lib => extension/lib}/pdfjs/images/comment-closeButton.svg (100%) rename {lib => extension/lib}/pdfjs/images/comment-popup-editButton.svg (100%) rename {lib => extension/lib}/pdfjs/images/cursor-editorFreeHighlight.svg (100%) rename {lib => extension/lib}/pdfjs/images/cursor-editorFreeText.svg (100%) rename {lib => extension/lib}/pdfjs/images/cursor-editorInk.svg (100%) rename {lib => extension/lib}/pdfjs/images/cursor-editorTextHighlight.svg (100%) rename {lib => extension/lib}/pdfjs/images/editor-toolbar-delete.svg (100%) rename {lib => extension/lib}/pdfjs/images/editor-toolbar-edit.svg (100%) rename {lib => extension/lib}/pdfjs/images/loading-icon.gif (100%) rename {lib => extension/lib}/pdfjs/images/messageBar_closingButton.svg (100%) rename {lib => extension/lib}/pdfjs/images/messageBar_info.svg (100%) rename {lib => extension/lib}/pdfjs/images/messageBar_warning.svg (100%) rename {lib => extension/lib}/pdfjs/images/pages_closeButton.svg (100%) rename {lib => extension/lib}/pdfjs/images/pages_selected.svg (100%) rename {lib => extension/lib}/pdfjs/images/pages_viewArrow.svg (100%) rename {lib => extension/lib}/pdfjs/images/pages_viewButton.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-currentOutlineItem.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-editorHighlight.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-menuArrow.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-pageDown.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-viewAttachments.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-viewLayers.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-viewOutline.svg (100%) rename {lib => extension/lib}/pdfjs/images/toolbarButton-zoomIn.svg (100%) rename {lib => extension/lib}/pdfjs/images/treeitem-collapsed.svg (100%) rename {lib => extension/lib}/pdfjs/images/treeitem-expanded.svg (100%) rename {lib => extension/lib}/pdfjs/jbig2.wasm (100%) rename {lib => extension/lib}/pdfjs/openjpeg.wasm (100%) rename {lib => extension/lib}/pdfjs/pdf.mjs (100%) rename {lib => extension/lib}/pdfjs/pdf.worker.mjs (100%) rename {lib => extension/lib}/pdfjs/pdf_viewer.css (100%) rename {lib => extension/lib}/pdfjs/pdf_viewer.mjs (100%) rename {lib => extension/lib}/pet.js (100%) rename {lib => extension/lib}/petbrain.js (100%) rename {lib => extension/lib}/petlibrary.js (100%) rename {lib => extension/lib}/petphysics.js (100%) rename {lib => extension/lib}/petplayer.js (100%) rename {lib => extension/lib}/petsprites.js (100%) rename {lib => extension/lib}/pm.LICENSE (100%) rename {lib => extension/lib}/pm.css (100%) rename {lib => extension/lib}/pm.js (100%) rename {lib => extension/lib}/popup.js (100%) rename {lib => extension/lib}/pptx-geom.js (100%) rename {lib => extension/lib}/pptx.js (100%) rename {lib => extension/lib}/relay.js (100%) rename {lib => extension/lib}/remind.js (100%) rename {lib => extension/lib}/rubric.js (100%) rename {lib => extension/lib}/scene-host.js (100%) rename {lib => extension/lib}/scene-media.js (100%) rename {lib => extension/lib}/scene.js (100%) rename {lib => extension/lib}/search.js (100%) rename {lib => extension/lib}/seek.js (100%) rename {lib => extension/lib}/sessions-ui.js (100%) rename {lib => extension/lib}/sessions.js (100%) rename {lib => extension/lib}/share.js (100%) rename {lib => extension/lib}/sharing.js (100%) rename {lib => extension/lib}/sheet-calc.js (100%) rename {lib => extension/lib}/sheet-chart-xml.js (100%) rename {lib => extension/lib}/sheet-chart.js (100%) rename {lib => extension/lib}/sheet-fn-database.js (100%) rename {lib => extension/lib}/sheet-fn-date.js (100%) rename {lib => extension/lib}/sheet-fn-financial.js (100%) rename {lib => extension/lib}/sheet-fn-financial2.js (100%) rename {lib => extension/lib}/sheet-fn-info.js (100%) rename {lib => extension/lib}/sheet-fn-logical.js (100%) rename {lib => extension/lib}/sheet-fn-logical2.js (100%) rename {lib => extension/lib}/sheet-fn-lookup.js (100%) rename {lib => extension/lib}/sheet-fn-lookup2.js (100%) rename {lib => extension/lib}/sheet-fn-math.js (100%) rename {lib => extension/lib}/sheet-fn-math2.js (100%) rename {lib => extension/lib}/sheet-fn-math3.js (100%) rename {lib => extension/lib}/sheet-fn-stats.js (100%) rename {lib => extension/lib}/sheet-fn-stats2.js (100%) rename {lib => extension/lib}/sheet-fn-text.js (100%) rename {lib => extension/lib}/sheet-fn-text2.js (100%) rename {lib => extension/lib}/sheet-formula.js (100%) rename {lib => extension/lib}/sheet-model.js (100%) rename {lib => extension/lib}/sheet-numfmt.js (100%) rename {lib => extension/lib}/sheet-xlsx.js (100%) rename {lib => extension/lib}/sidebar.js (100%) rename {lib => extension/lib}/site.js (100%) rename {lib => extension/lib}/slides-render.js (100%) rename {lib => extension/lib}/stream.js (100%) rename {lib => extension/lib}/surfaces.js (100%) rename {lib => extension/lib}/syllabus-ai.js (100%) rename {lib => extension/lib}/syllabus.js (100%) rename {lib => extension/lib}/tesseract/deu.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/eng.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/fra.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/nld.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/por.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/spa.traineddata.gz (100%) rename {lib => extension/lib}/tesseract/tessdata.LICENSE (100%) rename {lib => extension/lib}/tesseract/tesseract-core-simd-lstm.js (100%) rename {lib => extension/lib}/tesseract/tesseract-core-simd-lstm.wasm (100%) rename {lib => extension/lib}/tesseract/tesseract-core.LICENSE (100%) rename {lib => extension/lib}/three.LICENSE (100%) rename {lib => extension/lib}/three.js (100%) rename {lib => extension/lib}/todo.js (100%) rename {lib => extension/lib}/ultra.js (100%) rename {lib => extension/lib}/widgets.js (100%) rename {lib => extension/lib}/writer-bibliography.js (100%) rename {lib => extension/lib}/writer-cite.js (100%) rename {lib => extension/lib}/writer-color.js (100%) rename {lib => extension/lib}/writer-context-assignment.js (100%) rename {lib => extension/lib}/writer-context-discussion.js (100%) rename {lib => extension/lib}/writer-docx.js (100%) rename {lib => extension/lib}/writer-editor.js (100%) rename {lib => extension/lib}/writer-export.js (100%) rename {lib => extension/lib}/writer-format.js (100%) rename {lib => extension/lib}/writer-issues.js (100%) rename {lib => extension/lib}/writer-lookup.js (100%) rename {lib => extension/lib}/writer-main.js (100%) rename {lib => extension/lib}/writer-objects-docx.js (100%) rename {lib => extension/lib}/writer-objects.js (100%) rename {lib => extension/lib}/writer-outline.js (100%) rename {lib => extension/lib}/writer-paginate.js (100%) rename {lib => extension/lib}/writer-papers.js (100%) rename {lib => extension/lib}/writer-proof.js (100%) rename {lib => extension/lib}/writer-schema.js (100%) rename {lib => extension/lib}/writer-settings.js (100%) rename {lib => extension/lib}/writer-sources.js (100%) rename {lib => extension/lib}/writer-styles.js (100%) rename {lib => extension/lib}/writer-submit.js (100%) rename {lib => extension/lib}/writer-target.js (100%) rename {lib => extension/lib}/writer-thumb.js (100%) rename {lib => extension/lib}/writer-tools.js (100%) rename {lib => extension/lib}/writer-ultra.js (100%) rename {lib => extension/lib}/zip.js (100%) rename manifest.json => extension/manifest.json (100%) rename {mods => extension/mods}/boot.js (100%) rename {mods => extension/mods}/host.html (100%) rename {mods => extension/mods}/host.js (100%) rename moodle.css => extension/moodle.css (100%) rename motion.css => extension/motion.css (100%) rename options.html => extension/options.html (100%) rename options.js => extension/options.js (100%) rename papers-motion.js => extension/papers-motion.js (100%) rename papers.css => extension/papers.css (100%) rename papers.html => extension/papers.html (100%) rename papers.js => extension/papers.js (100%) rename pdf-kind.css => extension/pdf-kind.css (100%) rename pdf-kind.js => extension/pdf-kind.js (100%) rename popup.html => extension/popup.html (100%) rename popup.js => extension/popup.js (100%) rename preview.html => extension/preview.html (100%) rename reader.html => extension/reader.html (100%) rename reader.js => extension/reader.js (100%) rename {sharing => extension/sharing}/quick.css (100%) rename {sharing => extension/sharing}/quick.html (100%) rename {sharing => extension/sharing}/quick.js (100%) rename sheet-data.js => extension/sheet-data.js (100%) rename sheet-grid.js => extension/sheet-grid.js (100%) rename sheet-kind.js => extension/sheet-kind.js (100%) rename sheet.css => extension/sheet.css (100%) rename slides-kind.css => extension/slides-kind.css (100%) rename slides-kind.js => extension/slides-kind.js (100%) rename theme.css => extension/theme.css (100%) rename ui.css => extension/ui.css (100%) rename ui.js => extension/ui.js (100%) rename writer-ai.css => extension/writer-ai.css (100%) rename writer-ai.js => extension/writer-ai.js (100%) rename writer-cite.css => extension/writer-cite.css (100%) rename writer-cite.js => extension/writer-cite.js (100%) rename writer-compose.css => extension/writer-compose.css (100%) rename writer-compose.js => extension/writer-compose.js (100%) rename writer-counts.js => extension/writer-counts.js (100%) rename writer-edit.css => extension/writer-edit.css (100%) rename writer-find.js => extension/writer-find.js (100%) rename writer-import.css => extension/writer-import.css (100%) rename writer-import.js => extension/writer-import.js (100%) rename writer-kinds.js => extension/writer-kinds.js (100%) rename writer-mcp.js => extension/writer-mcp.js (100%) rename writer-objects.css => extension/writer-objects.css (100%) rename writer-objects.js => extension/writer-objects.js (100%) rename writer-paper.css => extension/writer-paper.css (100%) rename writer-paper.js => extension/writer-paper.js (100%) rename writer-paste.js => extension/writer-paste.js (100%) rename writer-print.js => extension/writer-print.js (100%) rename writer-proof.css => extension/writer-proof.css (100%) rename writer-proof.js => extension/writer-proof.js (100%) rename writer-ruler.css => extension/writer-ruler.css (100%) rename writer-ruler.js => extension/writer-ruler.js (100%) rename writer-submit.css => extension/writer-submit.css (100%) rename writer-submit.js => extension/writer-submit.js (100%) rename writer.css => extension/writer.css (100%) rename writer.html => extension/writer.html (100%) rename writer.js => extension/writer.js (100%) delete mode 100644 notes/codex-review-ultra-perf.md rename {assets => store/art}/halloween/bat.svg (100%) rename {assets => store/art}/halloween/ghost.svg (100%) rename {assets => store/art}/halloween/jack-o-lantern.svg (100%) rename {assets => store/art}/halloween/pumpkin.svg (100%) rename {assets => store/art}/halloween/skeleton.svg (100%) rename {assets/store => store/art}/marquee.png (100%) rename {assets/store => store/art}/screenshots/1-blackboard-frost.jpg (100%) rename {assets/store => store/art}/screenshots/2-canvas-modules.jpg (100%) rename {assets/store => store/art}/screenshots/3-canvas-dashboard.jpg (100%) rename {assets/store => store/art}/screenshots/4-halloween-pumpkin-patch.jpg (100%) rename {assets/store => store/art}/screenshots/5-backgrounds-editor.jpg (100%) rename {assets/store => store/art}/small-tile.png (100%) rename test/{ => agents}/agent-frames.test.js (100%) rename test/{ => agents}/agent-link-check.test.js (100%) rename test/{ => agents}/agent-link.test.js (100%) rename test/{ => agents}/agents-docs.test.js (100%) rename test/{ => agents}/mcp-agent.test.js (100%) rename test/{ => agents}/mcp-approve.test.js (100%) rename test/{ => agents}/mcp-assign.test.js (100%) rename test/{ => agents}/mcp-calls.test.js (100%) rename test/{ => agents}/mcp-conformance-check.test.js (100%) rename test/{ => agents}/mcp-grants.test.js (100%) rename test/{ => agents}/mcp-host.test.js (100%) rename test/{ => agents}/mcp-install.test.js (100%) rename test/{ => agents}/mcp-mcpb.test.js (100%) rename test/{ => agents}/mcp-mods.test.js (100%) rename test/{ => agents}/mcp-native.test.js (100%) rename test/{ => agents}/mcp-registry.test.js (100%) rename test/{ => agents}/mcp-release-fixes.test.js (100%) rename test/{ => agents}/mcp-rendezvous.test.js (100%) rename test/{ => agents}/mcp-tabs.test.js (100%) rename test/{ => agents}/mcp-todo-write.test.js (100%) rename test/{ => agents}/mcp-tools-grades.test.js (100%) rename test/{ => agents}/mcp-tools-study.test.js (100%) rename test/{ => agents}/mcp-windows-check.test.js (100%) rename test/{ => agents}/mcp-wire.test.js (100%) rename test/{ => agents}/mcp-worker.test.js (100%) rename test/{ => agents}/mcp-writer.test.js (100%) rename test/{ => ai}/ai-apple.test.js (100%) rename test/{ => ai}/ai-broker.test.js (100%) rename test/{ => ai}/ai-device.test.js (100%) rename test/{ => ai}/ai-failure.test.js (100%) rename test/{ => ai}/ai-loop.test.js (100%) rename test/{ => ai}/ai-models.test.js (100%) rename test/{ => ai}/ai-openai.test.js (100%) rename test/{ => ai}/ai-request.test.js (100%) rename test/{ => ai}/ai-settings-store.test.js (100%) rename test/{ => ai}/ai-settings.test.js (100%) rename test/{ => ai}/ai-stream.test.js (100%) rename test/{ => ai}/ai-syllabus.test.js (100%) rename test/{ => ai}/ai-task.test.js (100%) rename test/{ => ai}/ai-text.test.js (100%) rename test/{ => ai}/ai-token-budget.test.js (100%) rename test/{ => ai}/ai-tools-mcp.test.js (100%) rename test/{ => ai}/ai-visual-cache.test.js (100%) rename test/{ => ai}/ai-visual-scope.test.js (100%) rename test/{ => ai}/ai-worker.test.js (100%) rename test/{ => ai}/ai-writer-complete.test.js (100%) rename test/{ => ai}/ai-writer-frame.test.js (100%) rename test/{ => ai}/ai-writer-regressions.test.js (100%) rename test/{ => ai}/ai-writer-tasks.test.js (100%) rename test/{ => ai}/ai-writer-tools.test.js (100%) rename test/{ => core}/color.test.js (100%) rename test/{ => core}/consent.test.js (100%) rename test/{ => core}/docbutton.test.js (100%) rename test/{ => core}/download-allow.test.js (100%) rename test/{ => core}/download-check-waits.test.js (100%) rename test/{ => core}/download.test.js (100%) rename test/{ => core}/fresh-defaults.test.js (100%) rename test/{ => core}/page.test.js (100%) rename test/{ => core}/site.test.js (100%) rename test/{ => core}/starter-flags.test.js (100%) rename test/{ => core}/tooltips.test.js (100%) rename test/{ => core}/ultra-panel.test.js (100%) rename test/{ => core}/widgets-checks.test.js (100%) rename test/{ => core}/widgets-tiers.test.js (100%) rename test/{ => core}/widgets-ui.test.js (100%) rename test/{ => core}/widgets.test.js (100%) rename test/{ => core}/zip.test.js (100%) rename test/{ => courses}/allow-tab.test.js (100%) rename test/{ => courses}/allow.test.js (100%) rename test/{ => courses}/announce-sites.test.js (100%) rename test/{ => courses}/announce-ui.test.js (100%) rename test/{ => courses}/announce.test.js (100%) rename test/{ => courses}/bb-preview.test.js (100%) rename test/{ => courses}/board-card-checks.test.js (100%) rename test/{ => courses}/board-checks.test.js (100%) rename test/{ => courses}/boards-check.test.js (100%) rename test/{ => courses}/canvas-boards.test.js (100%) rename test/{ => courses}/canvas-dash-audit-i18n-css.test.js (100%) rename test/{ => courses}/canvas-dash-audit-ui.test.js (100%) rename test/{ => courses}/canvas-dash-audit.test.js (100%) rename test/{ => courses}/canvas-fixtures.test.js (100%) rename test/{ => courses}/canvas-load-audit-contracts.test.js (100%) rename test/{ => courses}/canvas-load-audit-runtime.test.js (100%) rename test/{ => courses}/canvas-uiux-audit.test.js (100%) rename test/{ => courses}/cards-ui.test.js (100%) rename test/{ => courses}/cards.test.js (100%) rename test/{ => courses}/cardtasks-ui.test.js (100%) rename test/{ => courses}/coursecards.test.js (100%) rename test/{ => courses}/docreview.test.js (100%) rename test/{ => courses}/docs-dark.test.js (100%) rename test/{ => courses}/docviewer-ui.test.js (100%) rename test/{ => courses}/docviewer.test.js (100%) rename test/{ => courses}/folders-canvas.test.js (100%) rename test/{ => courses}/folders-ui.test.js (100%) rename test/{ => courses}/folders.test.js (100%) rename test/{ => courses}/moodle-root.test.js (100%) rename test/{ => courses}/moodle.test.js (100%) rename test/{ => courses}/rubric-data.test.js (100%) rename test/{ => courses}/rubric-ultra-state.test.js (100%) rename test/{ => courses}/stream.test.js (100%) rename test/{ => courses}/ultra-boards.test.js (100%) rename test/{ => courses}/ultra-card-storage.test.js (100%) rename test/{ => courses}/ultra-fixtures.test.js (100%) rename test/{ => courses}/ultra-mock.test.js (100%) rename test/{ => courses}/ultra-perf.test.js (100%) rename test/{ => courses}/ultra-preview-ticket.test.js (100%) rename test/{ => courses}/ultra-test-attempt.test.js (100%) rename test/{ => courses}/ultra.test.js (100%) rename test/{ => documents}/doc-files.test.js (100%) rename test/{ => documents}/doc-kinds.test.js (100%) rename test/{ => documents}/doctext.test.js (100%) rename test/{ => documents}/papers-motion-lifecycle.test.js (100%) rename test/{ => documents}/papers-motion.test.js (100%) rename test/{ => documents}/papers-page.test.js (100%) rename test/{ => documents}/papers-sources.test.js (100%) rename test/{ => documents}/papers-theme.test.js (100%) rename test/{ => documents}/pdf-doc.test.js (100%) rename test/{ => documents}/pdf-kind.test.js (100%) rename test/{ => documents}/pdf-l10n.test.js (100%) rename test/{ => documents}/pptx-geom.test.js (100%) rename test/{ => documents}/pptx.test.js (100%) rename test/{ => documents}/sheet-calc.test.js (100%) rename test/{ => documents}/sheet-chart-insert.test.js (100%) rename test/{ => documents}/sheet-chart.test.js (100%) rename test/{ => documents}/sheet-excel.test.js (100%) rename test/{ => documents}/sheet-fn-database.test.js (100%) rename test/{ => documents}/sheet-fn-date.test.js (100%) rename test/{ => documents}/sheet-fn-financial.test.js (100%) rename test/{ => documents}/sheet-fn-financial2.test.js (100%) rename test/{ => documents}/sheet-fn-info.test.js (100%) rename test/{ => documents}/sheet-fn-logical2.test.js (100%) rename test/{ => documents}/sheet-fn-lookup.test.js (100%) rename test/{ => documents}/sheet-fn-lookup2.test.js (100%) rename test/{ => documents}/sheet-fn-math2.test.js (100%) rename test/{ => documents}/sheet-fn-math3.test.js (100%) rename test/{ => documents}/sheet-fn-stats.test.js (100%) rename test/{ => documents}/sheet-fn-stats2.test.js (100%) rename test/{ => documents}/sheet-fn-text2.test.js (100%) rename test/{ => documents}/sheet-formula.test.js (100%) rename test/{ => documents}/sheet-golden.test.js (100%) rename test/{ => documents}/sheet-grid-lifecycle.test.js (100%) rename test/{ => documents}/sheet-grid.test.js (100%) rename test/{ => documents}/sheet-kind.test.js (100%) rename test/{ => documents}/sheet-model.test.js (100%) rename test/{ => documents}/sheet-numfmt.test.js (100%) rename test/{ => documents}/sheet-xlsx.test.js (100%) rename test/{ => documents}/slides-fidelity.test.js (100%) rename test/{ => documents}/slides-kind.test.js (100%) rename test/{ => documents}/slides-render.test.js (100%) rename test/{ => firefox}/agent-link-firefox-check.test.js (100%) rename test/{ => firefox}/firefox-html-sinks.test.js (100%) rename test/{ => firefox}/firefox-waits.test.js (100%) rename test/{ => firefox}/firefox.test.js (100%) rename test/{ => looks}/course-looks-sync.test.js (100%) rename test/{ => looks}/courselook.test.js (100%) rename test/{ => looks}/ctab-one-background.test.js (100%) rename test/{ => looks}/fonts.test.js (100%) rename test/{ => looks}/glass-remaining-check.test.js (100%) rename test/{ => looks}/glsl.test.js (100%) rename test/{ => looks}/history.test.js (100%) rename test/{ => looks}/ink.test.js (100%) rename test/{ => looks}/look-disclosure.test.js (100%) rename test/{ => looks}/look-editor.test.js (100%) rename test/{ => looks}/look-media-share.test.js (100%) rename test/{ => looks}/look-offer-review.test.js (100%) rename test/{ => looks}/lookparts-page.test.js (100%) rename test/{ => looks}/lookparts.test.js (100%) rename test/{ => looks}/looks-catalog.test.js (100%) rename test/{ => looks}/lookset.test.js (100%) rename test/{ => looks}/lookx-ui.test.js (100%) rename test/{ => looks}/lookx.test.js (100%) rename test/{ => looks}/media-avif-validation.test.js (100%) rename test/{ => looks}/media-broker.test.js (100%) rename test/{ => looks}/media-check.test.js (100%) rename test/{ => looks}/media-cost-check.test.js (100%) rename test/{ => looks}/media-gallery.test.js (100%) rename test/{ => looks}/media-host.test.js (100%) rename test/{ => looks}/media-lottie-mattes.test.js (100%) rename test/{ => looks}/media-lottie-validation.test.js (100%) rename test/{ => looks}/media-metadata.test.js (100%) rename test/{ => looks}/media-parts.test.js (100%) rename test/{ => looks}/media-remote.test.js (100%) rename test/{ => looks}/media-store.test.js (100%) rename test/{ => looks}/media-video-validation.test.js (100%) rename test/{ => looks}/media.test.js (100%) rename test/{ => looks}/moments.test.js (100%) rename test/{ => looks}/motion-check.test.js (100%) rename test/{ => looks}/motion-default.test.js (100%) rename test/{ => looks}/motion-lib.test.js (100%) rename test/{ => looks}/motionx-page.test.js (100%) rename test/{ => looks}/motionx.test.js (100%) rename test/{ => looks}/parts.test.js (100%) rename test/{ => looks}/pointer-check.test.js (100%) rename test/{ => looks}/scene-lifecycle.test.js (100%) rename test/{ => looks}/scene-media.test.js (100%) rename test/{ => looks}/scene-modes.test.js (100%) rename test/{ => looks}/scene-placement.test.js (100%) rename test/{ => looks}/scene-sync.test.js (100%) rename test/{ => looks}/surfaces.test.js (100%) rename test/{ => looks}/ultra-appearance.test.js (100%) rename test/{ => looks}/ultra-header-surface.test.js (100%) rename test/{ => looks}/ultra-states.test.js (100%) rename test/{ => mods}/blocklist.test.js (100%) rename test/{ => mods}/combined-check.test.js (100%) rename test/{ => mods}/mod-account.test.js (100%) rename test/{ => mods}/mod-nav.test.js (100%) rename test/{ => mods}/mod-privacy.test.js (100%) rename test/{ => mods}/modapi-documents.test.js (100%) rename test/{ => mods}/modapi.test.js (100%) rename test/{ => mods}/modbroker.test.js (100%) rename test/{ => mods}/modbundle.test.js (100%) rename test/{ => mods}/modchannel-worker.test.js (100%) rename test/{ => mods}/modchannel.test.js (100%) rename test/{ => mods}/modexamples.test.js (100%) rename test/{ => mods}/modgallery.test.js (100%) rename test/{ => mods}/modpacks.test.js (100%) rename test/{ => mods}/mods-runtime.test.js (100%) rename test/{ => mods}/modtools.test.js (100%) rename test/{ => mods}/modtypes.test.js (100%) rename test/{ => mods}/modui.test.js (100%) rename test/{ => mods}/modwidget.test.js (100%) rename test/{ => mods}/modwidgets-runtime.test.js (100%) rename test/{ => navigation}/canvas-coursemenu.test.js (100%) rename test/{ => navigation}/content-navigation.test.js (100%) rename test/{ => navigation}/context-lifecycle.test.js (100%) rename test/{ => navigation}/coursemenu.test.js (100%) rename test/{ => navigation}/coursetabs-canvas-audit-frames.test.js (100%) rename test/{ => navigation}/coursetabs-canvas-audit-routing.test.js (100%) rename test/{ => navigation}/coursetabs-canvas.test.js (100%) rename test/{ => navigation}/coursetabs-course-detail.test.js (100%) rename test/{ => navigation}/coursetabs-frames.test.js (100%) rename test/{ => navigation}/coursetabs-keep.test.js (100%) rename test/{ => navigation}/coursetabs-label.test.js (100%) rename test/{ => navigation}/coursetabs-native.test.js (100%) rename test/{ => navigation}/coursetabs-pages.test.js (100%) rename test/{ => navigation}/coursetabs-provenance.test.js (100%) rename test/{ => navigation}/coursetabs-repaint.test.js (100%) rename test/{ => navigation}/coursetabs-routed.test.js (100%) rename test/{ => navigation}/coursetabs-sidebar.test.js (100%) rename test/{ => navigation}/coursetabs-takeover.test.js (100%) rename test/{ => navigation}/coursetabs-turns.test.js (100%) rename test/{ => navigation}/coursetabs-worker.test.js (100%) rename test/{ => navigation}/coursetabs.test.js (100%) rename test/{ => navigation}/ctab-panel-close.test.js (100%) rename test/{ => navigation}/ctab-stall-switch.test.js (100%) rename test/{ => navigation}/ctab-strip-glass.test.js (100%) rename test/{ => navigation}/load-recovery.test.js (100%) rename test/{ => navigation}/nav-a11y.test.js (100%) rename test/{ => navigation}/nav-lifecycle-review.test.js (100%) rename test/{ => navigation}/nav-logo-review.test.js (100%) rename test/{ => navigation}/nav-mod-rail.test.js (100%) rename test/{ => navigation}/nav-overlay.test.js (100%) rename test/{ => navigation}/nav-paint.test.js (100%) rename test/{ => navigation}/nav.test.js (100%) rename test/{ => navigation}/navbar-canvas-audit-frames.test.js (100%) rename test/{ => navigation}/navbar-canvas-audit.test.js (100%) rename test/{ => navigation}/navbar-canvas.test.js (100%) rename test/{ => navigation}/navbar-moodle-controls.test.js (100%) rename test/{ => navigation}/navbar-moodle.test.js (100%) rename test/{ => navigation}/navbar-nav-perms.test.js (100%) rename test/{ => navigation}/navbar-panels.test.js (100%) rename test/{ => navigation}/navbar-polish-review.test.js (100%) rename test/{ => navigation}/navbar-widgets.test.js (100%) rename test/{ => navigation}/navbar.test.js (100%) rename test/{ => navigation}/navlayout-review.test.js (100%) rename test/{ => navigation}/navlayout.test.js (100%) rename test/{ => navigation}/place.test.js (100%) rename test/{ => navigation}/search.test.js (100%) rename test/{ => navigation}/seek.test.js (100%) rename test/{ => navigation}/sidebar-absorb.test.js (100%) rename test/{ => navigation}/sidebar-assessment-native.test.js (100%) rename test/{ => navigation}/sidebar-broker.test.js (100%) rename test/{ => navigation}/sidebar-native-root.test.js (100%) rename test/{ => navigation}/sidebar-rubric.test.js (100%) rename test/{ => navigation}/sidebar-test-attempt.test.js (100%) rename test/{ => navigation}/sidebar-ui.test.js (100%) rename test/{ => navigation}/sidebar-ultra.test.js (100%) rename test/{ => navigation}/sidebar.test.js (100%) rename test/{ => navigation}/sticky-fold.test.js (100%) rename test/{ => navigation}/tabs-auto.test.js (100%) rename test/{ => navigation}/ultra-course-room.test.js (100%) rename test/{ => navigation}/ultra-nav-review.test.js (100%) rename test/{ => navigation}/ultra-nav.test.js (100%) rename test/{ => navigation}/ultra-off.test.js (100%) rename test/{ => navigation}/ultra-search.test.js (100%) rename test/{ => navigation}/ultra-todo-line.test.js (100%) rename test/{ => pet}/pet-clock.test.js (100%) rename test/{ => pet}/pet-review.test.js (100%) rename test/{ => pet}/pet-ui.test.js (100%) rename test/{ => pet}/pet-yield.test.js (100%) rename test/{ => pet}/pet.test.js (100%) rename test/{ => pet}/petlibrary.test.js (100%) rename test/{ => pet}/petplayer.test.js (100%) rename test/{ => pet}/petsprites.test.js (100%) rename test/{ => pet}/petworld.test.js (100%) rename test/{ => planner}/calendar.test.js (100%) rename test/{ => planner}/calexport.test.js (100%) rename test/{ => planner}/classes-review.test.js (100%) rename test/{ => planner}/classes-ui.test.js (100%) rename test/{ => planner}/classes.test.js (100%) rename test/{ => planner}/gcal.test.js (100%) rename test/{ => planner}/gpa.test.js (100%) rename test/{ => planner}/gradecalc-parity.test.js (100%) rename test/{ => planner}/gradecalc.test.js (100%) rename test/{ => planner}/grades-ui.test.js (100%) rename test/{ => planner}/grades.test.js (100%) rename test/{ => planner}/gradescope.test.js (100%) rename test/{ => planner}/ics.test.js (100%) rename test/{ => planner}/import.test.js (100%) rename test/{ => planner}/insights-ui.test.js (100%) rename test/{ => planner}/insights.test.js (100%) rename test/{ => planner}/notes-ui.test.js (100%) rename test/{ => planner}/notes.test.js (100%) rename test/{ => planner}/remind.test.js (100%) rename test/{ => planner}/reminders-ui.test.js (100%) rename test/{ => planner}/schedule.test.js (100%) rename test/{ => planner}/syllabus-ai-ui.test.js (100%) rename test/{ => planner}/syllabus-sync-ui.test.js (100%) rename test/{ => planner}/syllabus-ui.test.js (100%) rename test/{ => planner}/syllabus.test.js (100%) rename test/{ => planner}/todo-ui.test.js (100%) rename test/{ => planner}/todo.test.js (100%) rename test/{ => planner}/ultra-assessment-results.test.js (100%) rename test/{ => planner}/ultra-due-chip.test.js (100%) rename test/{ => planner}/ultra-plan-ui.test.js (100%) rename test/{ => planner}/ultra-plan.test.js (100%) rename test/{ => planner}/ultra-submission.test.js (100%) rename test/{ => platform}/animation-imports.test.js (100%) rename test/{ => platform}/background-pages-check.test.js (100%) rename test/{ => platform}/background-update.test.js (100%) rename test/{ => platform}/editor-hosts.test.js (100%) rename test/{ => platform}/feature-hosts.test.js (100%) rename test/{ => platform}/fetch-hosts.test.js (100%) rename test/{ => platform}/frame-tabs.test.js (100%) rename test/{ => platform}/frame-trust.test.js (100%) rename test/{ => platform}/head.test.js (100%) rename test/{ => platform}/i18n.test.js (100%) rename test/{ => platform}/lms-requests.test.js (100%) rename test/{ => platform}/loader.test.js (100%) rename test/{ => platform}/manifest.test.js (100%) rename test/{ => platform}/oauth.test.js (100%) rename test/{ => platform}/perf-budget.test.js (100%) rename test/{ => platform}/privacy-claims.test.js (100%) rename test/{ => platform}/register-update.test.js (100%) rename test/{ => platform}/runtime-audit-fixes.test.js (100%) rename test/{ => platform}/site-part.test.js (100%) rename test/{ => platform}/vendor-pdfjs.test.js (100%) rename test/{ => platform}/vendor-three.test.js (100%) rename test/{ => platform}/worker-i18n.test.js (100%) rename test/{ => release}/audit-documentation.test.js (100%) rename test/{ => release}/audit-release-readiness.test.js (100%) rename test/{ => release}/audit-ultra-completeness.test.js (100%) rename test/{ => release}/audit.test.js (100%) rename test/{ => release}/blocklist-sign.test.js (100%) rename test/{ => release}/browser-checks.test.js (100%) rename test/{ => release}/check-env.test.js (100%) rename test/{ => release}/check-readiness.test.js (100%) rename test/{ => release}/ci-browser.test.js (100%) rename test/{ => release}/narrow-check-seeds.test.js (100%) rename test/{ => release}/package.test.js (100%) rename test/{ => release}/release-copy.test.js (100%) rename test/{ => release}/release-env.test.js (100%) rename test/{ => release}/release-listings.test.js (100%) rename test/{ => release}/release-mcpb.test.js (100%) rename test/{ => release}/release-redact.test.js (100%) rename test/{ => release}/release-sources.test.js (100%) rename test/{ => release}/release-store-api.test.js (100%) rename test/{ => release}/release-verify.test.js (100%) rename test/{ => release}/stand-in-dom.test.js (100%) rename test/{ => release}/store-listing.test.js (100%) rename test/{ => release}/workflows.test.js (100%) rename test/{ => safari}/agent-bridge-native.test.js (100%) rename test/{ => safari}/agent-host-native.test.js (100%) rename test/{ => safari}/ai-apple-native.test.js (100%) rename test/{ => safari}/make-dmg.test.js (100%) rename test/{ => safari}/safari-keep-data.test.js (100%) rename test/{ => safari}/safari-resources.test.js (100%) rename test/{ => safari}/safari-source-resources.test.js (100%) rename test/{ => safari}/safari-update-native.test.js (100%) rename test/{ => settings}/backup-check.test.js (100%) rename test/{ => settings}/backup-files.test.js (100%) rename test/{ => settings}/backup-media.test.js (100%) rename test/{ => settings}/backup-picker.test.js (100%) rename test/{ => settings}/backup-worker.test.js (100%) rename test/{ => settings}/backup.test.js (100%) rename test/{ => settings}/docs-settings-paths.test.js (100%) rename test/{ => settings}/editor-agents.test.js (100%) rename test/{ => settings}/editor-info.test.js (100%) rename test/{ => settings}/editor-media-builder.test.js (100%) rename test/{ => settings}/editor-media-size.test.js (100%) rename test/{ => settings}/editor-mods.test.js (100%) rename test/{ => settings}/editor-pointer.test.js (100%) rename test/{ => settings}/editor-widget.test.js (100%) rename test/{ => settings}/editor-x.test.js (100%) rename test/{ => settings}/editor-xparts-save.test.js (100%) rename test/{ => settings}/editor-xparts-ui.test.js (100%) rename test/{ => settings}/editor-xparts.test.js (100%) rename test/{ => settings}/editor.test.js (100%) rename test/{ => settings}/embedded-settings-overlays.test.js (100%) rename test/{ => settings}/news.test.js (100%) rename test/{ => settings}/popup-motion.test.js (100%) rename test/{ => settings}/popup-site.test.js (100%) rename test/{ => settings}/rubric-settings.test.js (100%) rename test/{ => settings}/settings-ai-check.test.js (100%) rename test/{ => settings}/settings-copy.test.js (100%) rename test/{ => settings}/settings-page.test.js (100%) rename test/{ => settings}/settings-sites.test.js (100%) rename test/{ => settings}/settings-writer.test.js (100%) rename test/{ => settings}/settings.test.js (100%) rename test/{ => settings}/welcome.test.js (100%) rename test/{ => sharing}/quick-broker.test.js (100%) rename test/{ => sharing}/quick-ui.test.js (100%) rename test/{ => sharing}/relay-gesture.test.js (100%) rename test/{ => sharing}/relay-quota-review.test.js (100%) rename test/{ => sharing}/relay-quota.test.js (100%) rename test/{ => sharing}/relay-resource.test.js (100%) rename test/{ => sharing}/relay-ui.test.js (100%) rename test/{ => sharing}/relay.test.js (100%) rename test/{ => sharing}/sessions-create.test.js (100%) rename test/{ => sharing}/sessions-files.test.js (100%) rename test/{ => sharing}/sessions-media.test.js (100%) rename test/{ => sharing}/sessions-sync.test.js (100%) rename test/{ => sharing}/sessions-ui-lifecycle.test.js (100%) rename test/{ => sharing}/sessions.test.js (100%) rename test/{ => sharing}/share-keys.test.js (100%) rename test/{ => sharing}/share-protocol.test.js (100%) rename test/{ => sharing}/sharing.test.js (100%) rename test/{ => writer}/drafts-browser.test.js (100%) rename test/{ => writer}/drafts-ui.test.js (100%) rename test/{ => writer}/drafts.test.js (100%) rename test/{ => writer}/editor-menu.test.js (100%) rename test/{ => writer}/ondemand.test.js (100%) rename test/{ => writer}/practice-ai.test.js (100%) rename test/{ => writer}/writer-attach-open.test.js (100%) rename test/{ => writer}/writer-attach.test.js (100%) rename test/{ => writer}/writer-audit-regressions.test.js (100%) rename test/{ => writer}/writer-bibliography.test.js (100%) rename test/{ => writer}/writer-cite-check.test.js (100%) rename test/{ => writer}/writer-cite-frame.test.js (100%) rename test/{ => writer}/writer-cite-lookup.test.js (100%) rename test/{ => writer}/writer-cite-worker.test.js (100%) rename test/{ => writer}/writer-cite.test.js (100%) rename test/{ => writer}/writer-content.test.js (100%) rename test/{ => writer}/writer-context-compose.test.js (100%) rename test/{ => writer}/writer-context-lifecycle.test.js (100%) rename test/{ => writer}/writer-context.test.js (100%) rename test/{ => writer}/writer-counts.test.js (100%) rename test/{ => writer}/writer-discussion-sources.test.js (100%) rename test/{ => writer}/writer-docx-import.test.js (100%) rename test/{ => writer}/writer-docx.test.js (100%) rename test/{ => writer}/writer-editor.test.js (100%) rename test/{ => writer}/writer-export.test.js (100%) rename test/{ => writer}/writer-feedback-flow.test.js (100%) rename test/{ => writer}/writer-feedback-grade.test.js (100%) rename test/{ => writer}/writer-feedback-images.test.js (100%) rename test/{ => writer}/writer-feedback-local.test.js (100%) rename test/{ => writer}/writer-final-cite.test.js (100%) rename test/{ => writer}/writer-final-docx.test.js (100%) rename test/{ => writer}/writer-final-paginate.test.js (100%) rename test/{ => writer}/writer-final-paste.test.js (100%) rename test/{ => writer}/writer-final-proof.test.js (100%) rename test/{ => writer}/writer-final-submit.test.js (100%) rename test/{ => writer}/writer-find.test.js (100%) rename test/{ => writer}/writer-format-commands.test.js (100%) rename test/{ => writer}/writer-format-setup.test.js (100%) rename test/{ => writer}/writer-frame-count.test.js (100%) rename test/{ => writer}/writer-frame.test.js (100%) rename test/{ => writer}/writer-from-documents-sheet.test.js (100%) rename test/{ => writer}/writer-hostile-roundtrip.test.js (100%) rename test/{ => writer}/writer-huge-budget.test.js (100%) rename test/{ => writer}/writer-image.test.js (100%) rename test/{ => writer}/writer-import-paper-review.test.js (100%) rename test/{ => writer}/writer-import.test.js (100%) rename test/{ => writer}/writer-kinds-submit.test.js (100%) rename test/{ => writer}/writer-kinds.test.js (100%) rename test/{ => writer}/writer-local-fonts.test.js (100%) rename test/{ => writer}/writer-objects-docx.test.js (100%) rename test/{ => writer}/writer-objects.test.js (100%) rename test/{ => writer}/writer-outline-ai.test.js (100%) rename test/{ => writer}/writer-paginate-scheduler.test.js (100%) rename test/{ => writer}/writer-paginate-slices.test.js (100%) rename test/{ => writer}/writer-paper-compact.test.js (100%) rename test/{ => writer}/writer-paper-content.test.js (100%) rename test/{ => writer}/writer-paper-context.test.js (100%) rename test/{ => writer}/writer-paper-format.test.js (100%) rename test/{ => writer}/writer-paper-key.test.js (100%) rename test/{ => writer}/writer-paper-paginate.test.js (100%) rename test/{ => writer}/writer-paper-papers.test.js (100%) rename test/{ => writer}/writer-paper-styles.test.js (100%) rename test/{ => writer}/writer-paper-view-save.test.js (100%) rename test/{ => writer}/writer-papers-files.test.js (100%) rename test/{ => writer}/writer-papers-proxy.test.js (100%) rename test/{ => writer}/writer-papers-versions.test.js (100%) rename test/{ => writer}/writer-paste.test.js (100%) rename test/{ => writer}/writer-pm.test.js (100%) rename test/{ => writer}/writer-practice.test.js (100%) rename test/{ => writer}/writer-proof-frame.test.js (100%) rename test/{ => writer}/writer-proof-harper.test.js (100%) rename test/{ => writer}/writer-proof-worker.test.js (100%) rename test/{ => writer}/writer-proof.test.js (100%) rename test/{ => writer}/writer-quote-lifecycle.test.js (100%) rename test/{ => writer}/writer-round1-document.test.js (100%) rename test/{ => writer}/writer-round1-docx.test.js (100%) rename test/{ => writer}/writer-round4-notes.test.js (100%) rename test/{ => writer}/writer-round4-ui.test.js (100%) rename test/{ => writer}/writer-schema.test.js (100%) rename test/{ => writer}/writer-storage-failures.test.js (100%) rename test/{ => writer}/writer-submit-check.test.js (100%) rename test/{ => writer}/writer-submit-review.test.js (100%) rename test/{ => writer}/writer-submit-sheet-review.test.js (100%) rename test/{ => writer}/writer-submit-tap.test.js (100%) rename test/{ => writer}/writer-submit-wait.test.js (100%) rename test/{ => writer}/writer-submit.test.js (100%) rename test/{ => writer}/writer-target.test.js (100%) rename test/{ => writer}/writer-thumb.test.js (100%) rename test/{ => writer}/writer-ultra.test.js (100%) rename {scripts => tools/build}/appstore-ios.sh (100%) rename {scripts => tools/build}/appstore-safari.sh (100%) rename {scripts => tools/build}/build-safari.sh (100%) rename {scripts => tools/build}/make-dmg.sh (100%) rename {scripts => tools/build}/package-firefox.sh (100%) rename {scripts => tools/build}/package-safari.sh (100%) rename package.sh => tools/build/package.sh (100%) rename {scripts => tools/build}/safari-build-ids.py (100%) rename {scripts => tools/build}/safari-manifest.sh (100%) rename {scripts => tools/build}/safari-resources.js (100%) rename {scripts => tools/build}/stage-safari-dmg.py (100%) rename {scripts => tools/build}/sync-safari-resources.sh (100%) rename {scripts => tools/dev}/audit.js (100%) rename {scripts => tools/dev}/canvas-shots.js (100%) rename {scripts => tools/dev}/check-env.js (100%) rename {scripts => tools/dev}/ci-browser-checks.js (100%) rename {scripts => tools/dev}/ci-chromium.js (100%) rename {scripts => tools/dev}/dev-gcal.sh (100%) rename {scripts => tools/dev}/docs-mirror.js (100%) rename {scripts => tools/dev}/firefox-android.sh (100%) rename {scripts => tools/dev}/practice-ai.js (100%) rename {scripts => tools/dev}/redact.js (100%) rename {scripts => tools/dev}/wait-for-browser.js (100%) rename {scripts => tools/dev}/writer-practice.js (100%) rename {scripts => tools/fidelity}/import-fixtures.js (100%) rename {scripts => tools/fidelity}/pdf-fidelity.js (100%) rename {scripts => tools/fidelity}/pdf-fixtures.js (100%) rename {scripts => tools/fidelity}/sheet-excel-fidelity.js (100%) rename {scripts => tools/fidelity}/sheet-excel-golden.js (100%) rename {scripts => tools/fidelity}/sheet-excel.js (100%) rename {scripts => tools/fidelity}/sheet-fidelity-books.js (100%) rename {scripts => tools/fidelity}/sheet-numbers-fidelity.js (100%) rename {scripts => tools/fidelity}/slides-fidelity.js (100%) rename {scripts => tools/fidelity}/writer-import-fidelity.js (100%) rename {scripts => tools/fidelity}/writer-word-fidelity.js (100%) rename {scripts => tools/release}/blocklist-sign.mjs (100%) rename {scripts => tools/release}/clean-source.sh (100%) rename {scripts => tools/release}/public-release.mjs (100%) rename {scripts => tools/release}/publish-chrome.sh (100%) rename {scripts => tools/release}/release.sh (100%) rename {scripts => tools/release}/safari-appcast.sh (100%) rename {scripts => tools/release}/store-api.mjs (100%) rename {scripts => tools/release}/store-firefox-source.py (100%) rename {scripts => tools/release}/store-listings.mjs (100%) rename {scripts => tools/release}/store-release.env.example (100%) rename {scripts => tools/release}/store-release.mjs (100%) rename {scripts => tools/release}/store-screenshots.js (100%) rename {scripts => tools/vendor}/npm-integrity.js (100%) rename {scripts => tools/vendor}/vendor-harper.sh (100%) rename {scripts => tools/vendor}/vendor-lottie.sh (100%) rename {scripts => tools/vendor}/vendor-paper-fonts.sh (100%) rename {scripts => tools/vendor}/vendor-pdfjs-brotli.js (100%) rename {scripts => tools/vendor}/vendor-pdfjs-limits.js (100%) rename {scripts => tools/vendor}/vendor-pdfjs.sh (100%) rename {scripts => tools/vendor}/vendor-prosemirror.sh (100%) rename {scripts => tools/vendor}/vendor-tesseract.sh (100%) rename {scripts => tools/vendor}/vendor-three.sh (100%) diff --git a/ci/chromium.pins b/.github/pins/chromium.pins similarity index 100% rename from ci/chromium.pins rename to .github/pins/chromium.pins diff --git a/ci/ffmpeg.pins b/.github/pins/ffmpeg.pins similarity index 100% rename from ci/ffmpeg.pins rename to .github/pins/ffmpeg.pins diff --git a/ci/playwright.pins b/.github/pins/playwright.pins similarity index 100% rename from ci/playwright.pins rename to .github/pins/playwright.pins diff --git a/scripts/agent-link-check.js b/checks/agents/agent-link-check.js similarity index 100% rename from scripts/agent-link-check.js rename to checks/agents/agent-link-check.js diff --git a/scripts/mcp-approve-check.js b/checks/agents/mcp-approve-check.js similarity index 100% rename from scripts/mcp-approve-check.js rename to checks/agents/mcp-approve-check.js diff --git a/scripts/mcp-conformance-check.js b/checks/agents/mcp-conformance-check.js similarity index 100% rename from scripts/mcp-conformance-check.js rename to checks/agents/mcp-conformance-check.js diff --git a/scripts/mcp-refresh-check.js b/checks/agents/mcp-refresh-check.js similarity index 100% rename from scripts/mcp-refresh-check.js rename to checks/agents/mcp-refresh-check.js diff --git a/scripts/mcp-windows-check.js b/checks/agents/mcp-windows-check.js similarity index 100% rename from scripts/mcp-windows-check.js rename to checks/agents/mcp-windows-check.js diff --git a/scripts/mcp-writer-check.js b/checks/agents/mcp-writer-check.js similarity index 100% rename from scripts/mcp-writer-check.js rename to checks/agents/mcp-writer-check.js diff --git a/scripts/download-check.js b/checks/core/download-check.js similarity index 100% rename from scripts/download-check.js rename to checks/core/download-check.js diff --git a/scripts/widgets-builder-check.js b/checks/core/widgets-builder-check.js similarity index 100% rename from scripts/widgets-builder-check.js rename to checks/core/widgets-builder-check.js diff --git a/scripts/widgets-sizes-check.js b/checks/core/widgets-sizes-check.js similarity index 100% rename from scripts/widgets-sizes-check.js rename to checks/core/widgets-sizes-check.js diff --git a/scripts/allow-check.js b/checks/courses/allow-check.js similarity index 100% rename from scripts/allow-check.js rename to checks/courses/allow-check.js diff --git a/scripts/announce-check-sites.js b/checks/courses/announce-check-sites.js similarity index 100% rename from scripts/announce-check-sites.js rename to checks/courses/announce-check-sites.js diff --git a/scripts/announce-check.js b/checks/courses/announce-check.js similarity index 100% rename from scripts/announce-check.js rename to checks/courses/announce-check.js diff --git a/scripts/announce-read-check.js b/checks/courses/announce-read-check.js similarity index 100% rename from scripts/announce-read-check.js rename to checks/courses/announce-read-check.js diff --git a/scripts/canvas-boards-check.js b/checks/courses/canvas-boards-check.js similarity index 100% rename from scripts/canvas-boards-check.js rename to checks/courses/canvas-boards-check.js diff --git a/scripts/docreview-check.js b/checks/courses/docreview-check.js similarity index 100% rename from scripts/docreview-check.js rename to checks/courses/docreview-check.js diff --git a/scripts/docviewer-check.js b/checks/courses/docviewer-check.js similarity index 100% rename from scripts/docviewer-check.js rename to checks/courses/docviewer-check.js diff --git a/scripts/rubric-check.js b/checks/courses/rubric-check.js similarity index 100% rename from scripts/rubric-check.js rename to checks/courses/rubric-check.js diff --git a/scripts/stream-check.js b/checks/courses/stream-check.js similarity index 100% rename from scripts/stream-check.js rename to checks/courses/stream-check.js diff --git a/scripts/stream-runtime-check.js b/checks/courses/stream-runtime-check.js similarity index 100% rename from scripts/stream-runtime-check.js rename to checks/courses/stream-runtime-check.js diff --git a/scripts/ultra-assignment-attempt-check.js b/checks/courses/ultra-assignment-attempt-check.js similarity index 100% rename from scripts/ultra-assignment-attempt-check.js rename to checks/courses/ultra-assignment-attempt-check.js diff --git a/scripts/ultra-boards-check.js b/checks/courses/ultra-boards-check.js similarity index 100% rename from scripts/ultra-boards-check.js rename to checks/courses/ultra-boards-check.js diff --git a/scripts/ultra-cards-check.js b/checks/courses/ultra-cards-check.js similarity index 100% rename from scripts/ultra-cards-check.js rename to checks/courses/ultra-cards-check.js diff --git a/scripts/ultra-folders-check.js b/checks/courses/ultra-folders-check.js similarity index 100% rename from scripts/ultra-folders-check.js rename to checks/courses/ultra-folders-check.js diff --git a/scripts/ultra-live-frames-check.js b/checks/courses/ultra-live-frames-check.js similarity index 100% rename from scripts/ultra-live-frames-check.js rename to checks/courses/ultra-live-frames-check.js diff --git a/scripts/ultra-perf-check.js b/checks/courses/ultra-perf-check.js similarity index 100% rename from scripts/ultra-perf-check.js rename to checks/courses/ultra-perf-check.js diff --git a/scripts/ultra-preview-check.js b/checks/courses/ultra-preview-check.js similarity index 100% rename from scripts/ultra-preview-check.js rename to checks/courses/ultra-preview-check.js diff --git a/scripts/ultra-test-attempt-check.js b/checks/courses/ultra-test-attempt-check.js similarity index 100% rename from scripts/ultra-test-attempt-check.js rename to checks/courses/ultra-test-attempt-check.js diff --git a/scripts/documents-ai-check.js b/checks/documents/documents-ai-check.js similarity index 100% rename from scripts/documents-ai-check.js rename to checks/documents/documents-ai-check.js diff --git a/scripts/documents-check.js b/checks/documents/documents-check.js similarity index 100% rename from scripts/documents-check.js rename to checks/documents/documents-check.js diff --git a/scripts/documents-entry-check.js b/checks/documents/documents-entry-check.js similarity index 100% rename from scripts/documents-entry-check.js rename to checks/documents/documents-entry-check.js diff --git a/scripts/documents-page-check.js b/checks/documents/documents-page-check.js similarity index 100% rename from scripts/documents-page-check.js rename to checks/documents/documents-page-check.js diff --git a/scripts/pdf-check.js b/checks/documents/pdf-check.js similarity index 100% rename from scripts/pdf-check.js rename to checks/documents/pdf-check.js diff --git a/scripts/sheets-check.js b/checks/documents/sheets-check.js similarity index 100% rename from scripts/sheets-check.js rename to checks/documents/sheets-check.js diff --git a/scripts/slides-check.js b/checks/documents/slides-check.js similarity index 100% rename from scripts/slides-check.js rename to checks/documents/slides-check.js diff --git a/scripts/agent-link-firefox-check.mjs b/checks/firefox/agent-link-firefox-check.mjs similarity index 100% rename from scripts/agent-link-firefox-check.mjs rename to checks/firefox/agent-link-firefox-check.mjs diff --git a/scripts/firefox-check.mjs b/checks/firefox/firefox-check.mjs similarity index 100% rename from scripts/firefox-check.mjs rename to checks/firefox/firefox-check.mjs diff --git a/scripts/glass-remaining-check.js b/checks/looks/glass-remaining-check.js similarity index 100% rename from scripts/glass-remaining-check.js rename to checks/looks/glass-remaining-check.js diff --git a/scripts/lookparts-check.js b/checks/looks/lookparts-check.js similarity index 100% rename from scripts/lookparts-check.js rename to checks/looks/lookparts-check.js diff --git a/scripts/looks-catalog-check.js b/checks/looks/looks-catalog-check.js similarity index 100% rename from scripts/looks-catalog-check.js rename to checks/looks/looks-catalog-check.js diff --git a/scripts/media-check.js b/checks/looks/media-check.js similarity index 100% rename from scripts/media-check.js rename to checks/looks/media-check.js diff --git a/scripts/media-cost-check.js b/checks/looks/media-cost-check.js similarity index 100% rename from scripts/media-cost-check.js rename to checks/looks/media-cost-check.js diff --git a/scripts/motion-check.js b/checks/looks/motion-check.js similarity index 100% rename from scripts/motion-check.js rename to checks/looks/motion-check.js diff --git a/scripts/motion-default-check.js b/checks/looks/motion-default-check.js similarity index 100% rename from scripts/motion-default-check.js rename to checks/looks/motion-default-check.js diff --git a/scripts/motionx-check.js b/checks/looks/motionx-check.js similarity index 100% rename from scripts/motionx-check.js rename to checks/looks/motionx-check.js diff --git a/scripts/pointer-check.js b/checks/looks/pointer-check.js similarity index 100% rename from scripts/pointer-check.js rename to checks/looks/pointer-check.js diff --git a/scripts/rules-check.js b/checks/looks/rules-check.js similarity index 100% rename from scripts/rules-check.js rename to checks/looks/rules-check.js diff --git a/scripts/scene-check.html b/checks/looks/scene-check.html similarity index 100% rename from scripts/scene-check.html rename to checks/looks/scene-check.html diff --git a/scripts/scene-check.mjs b/checks/looks/scene-check.mjs similarity index 100% rename from scripts/scene-check.mjs rename to checks/looks/scene-check.mjs diff --git a/scripts/scene-page-check.js b/checks/looks/scene-page-check.js similarity index 100% rename from scripts/scene-page-check.js rename to checks/looks/scene-page-check.js diff --git a/scripts/surfaces-check.js b/checks/looks/surfaces-check.js similarity index 100% rename from scripts/surfaces-check.js rename to checks/looks/surfaces-check.js diff --git a/scripts/frame-tap.js b/checks/mocks/frame-tap.js similarity index 100% rename from scripts/frame-tap.js rename to checks/mocks/frame-tap.js diff --git a/scripts/gcal-mock.js b/checks/mocks/gcal-mock.js similarity index 100% rename from scripts/gcal-mock.js rename to checks/mocks/gcal-mock.js diff --git a/scripts/ultra-attempt-harness.js b/checks/mocks/ultra-attempt-harness.js similarity index 100% rename from scripts/ultra-attempt-harness.js rename to checks/mocks/ultra-attempt-harness.js diff --git a/scripts/ultra-mock.js b/checks/mocks/ultra-mock.js similarity index 100% rename from scripts/ultra-mock.js rename to checks/mocks/ultra-mock.js diff --git a/scripts/ultra-offcanvas-fixture.js b/checks/mocks/ultra-offcanvas-fixture.js similarity index 100% rename from scripts/ultra-offcanvas-fixture.js rename to checks/mocks/ultra-offcanvas-fixture.js diff --git a/scripts/blocklist-check.js b/checks/mods/blocklist-check.js similarity index 100% rename from scripts/blocklist-check.js rename to checks/mods/blocklist-check.js diff --git a/scripts/modbundle-check.js b/checks/mods/modbundle-check.js similarity index 100% rename from scripts/modbundle-check.js rename to checks/mods/modbundle-check.js diff --git a/scripts/mods-api-check.js b/checks/mods/mods-api-check.js similarity index 100% rename from scripts/mods-api-check.js rename to checks/mods/mods-api-check.js diff --git a/scripts/mods-channel-check.js b/checks/mods/mods-channel-check.js similarity index 100% rename from scripts/mods-channel-check.js rename to checks/mods/mods-channel-check.js diff --git a/scripts/mods-combined-check.js b/checks/mods/mods-combined-check.js similarity index 100% rename from scripts/mods-combined-check.js rename to checks/mods/mods-combined-check.js diff --git a/scripts/mods-parts-check.js b/checks/mods/mods-parts-check.js similarity index 100% rename from scripts/mods-parts-check.js rename to checks/mods/mods-parts-check.js diff --git a/scripts/mods-sandbox-check.js b/checks/mods/mods-sandbox-check.js similarity index 100% rename from scripts/mods-sandbox-check.js rename to checks/mods/mods-sandbox-check.js diff --git a/scripts/mods-section-check.js b/checks/mods/mods-section-check.js similarity index 100% rename from scripts/mods-section-check.js rename to checks/mods/mods-section-check.js diff --git a/scripts/mods-tools-check.js b/checks/mods/mods-tools-check.js similarity index 100% rename from scripts/mods-tools-check.js rename to checks/mods/mods-tools-check.js diff --git a/scripts/coursetabs-sketch-check.js b/checks/navigation/coursetabs-sketch-check.js similarity index 100% rename from scripts/coursetabs-sketch-check.js rename to checks/navigation/coursetabs-sketch-check.js diff --git a/scripts/ctab-background-check.js b/checks/navigation/ctab-background-check.js similarity index 100% rename from scripts/ctab-background-check.js rename to checks/navigation/ctab-background-check.js diff --git a/scripts/ctab-palette-check.js b/checks/navigation/ctab-palette-check.js similarity index 100% rename from scripts/ctab-palette-check.js rename to checks/navigation/ctab-palette-check.js diff --git a/scripts/ctab-panel-close-check.js b/checks/navigation/ctab-panel-close-check.js similarity index 100% rename from scripts/ctab-panel-close-check.js rename to checks/navigation/ctab-panel-close-check.js diff --git a/scripts/ctab-strip-glass-check.js b/checks/navigation/ctab-strip-glass-check.js similarity index 100% rename from scripts/ctab-strip-glass-check.js rename to checks/navigation/ctab-strip-glass-check.js diff --git a/scripts/nav-layout-check.js b/checks/navigation/nav-layout-check.js similarity index 100% rename from scripts/nav-layout-check.js rename to checks/navigation/nav-layout-check.js diff --git a/scripts/navbar-canvas-check.js b/checks/navigation/navbar-canvas-check.js similarity index 100% rename from scripts/navbar-canvas-check.js rename to checks/navigation/navbar-canvas-check.js diff --git a/scripts/navbar-check.js b/checks/navigation/navbar-check.js similarity index 100% rename from scripts/navbar-check.js rename to checks/navigation/navbar-check.js diff --git a/scripts/navbar-moodle-check.js b/checks/navigation/navbar-moodle-check.js similarity index 100% rename from scripts/navbar-moodle-check.js rename to checks/navigation/navbar-moodle-check.js diff --git a/scripts/sidebar-assessment-check.js b/checks/navigation/sidebar-assessment-check.js similarity index 100% rename from scripts/sidebar-assessment-check.js rename to checks/navigation/sidebar-assessment-check.js diff --git a/scripts/sidebar-canvas-check.js b/checks/navigation/sidebar-canvas-check.js similarity index 100% rename from scripts/sidebar-canvas-check.js rename to checks/navigation/sidebar-canvas-check.js diff --git a/scripts/sidebar-participants-check.js b/checks/navigation/sidebar-participants-check.js similarity index 100% rename from scripts/sidebar-participants-check.js rename to checks/navigation/sidebar-participants-check.js diff --git a/scripts/sidebar-tabs-check.js b/checks/navigation/sidebar-tabs-check.js similarity index 100% rename from scripts/sidebar-tabs-check.js rename to checks/navigation/sidebar-tabs-check.js diff --git a/scripts/sidebar-test-attempt-check.js b/checks/navigation/sidebar-test-attempt-check.js similarity index 100% rename from scripts/sidebar-test-attempt-check.js rename to checks/navigation/sidebar-test-attempt-check.js diff --git a/scripts/sidebar-ultra-check.js b/checks/navigation/sidebar-ultra-check.js similarity index 100% rename from scripts/sidebar-ultra-check.js rename to checks/navigation/sidebar-ultra-check.js diff --git a/scripts/sidebar-ultra-tabs-check.js b/checks/navigation/sidebar-ultra-tabs-check.js similarity index 100% rename from scripts/sidebar-ultra-tabs-check.js rename to checks/navigation/sidebar-ultra-tabs-check.js diff --git a/scripts/ultra-contents-drawer-check.js b/checks/navigation/ultra-contents-drawer-check.js similarity index 100% rename from scripts/ultra-contents-drawer-check.js rename to checks/navigation/ultra-contents-drawer-check.js diff --git a/scripts/ultra-course-bar-check.js b/checks/navigation/ultra-course-bar-check.js similarity index 100% rename from scripts/ultra-course-bar-check.js rename to checks/navigation/ultra-course-bar-check.js diff --git a/scripts/ultra-offcanvas-check.js b/checks/navigation/ultra-offcanvas-check.js similarity index 100% rename from scripts/ultra-offcanvas-check.js rename to checks/navigation/ultra-offcanvas-check.js diff --git a/scripts/ultra-panel-background-check.js b/checks/navigation/ultra-panel-background-check.js similarity index 100% rename from scripts/ultra-panel-background-check.js rename to checks/navigation/ultra-panel-background-check.js diff --git a/scripts/ultra-panels-check.js b/checks/navigation/ultra-panels-check.js similarity index 100% rename from scripts/ultra-panels-check.js rename to checks/navigation/ultra-panels-check.js diff --git a/scripts/ultra-tabs-check.js b/checks/navigation/ultra-tabs-check.js similarity index 100% rename from scripts/ultra-tabs-check.js rename to checks/navigation/ultra-tabs-check.js diff --git a/scripts/gradescope-switch-check.js b/checks/planner/gradescope-switch-check.js similarity index 100% rename from scripts/gradescope-switch-check.js rename to checks/planner/gradescope-switch-check.js diff --git a/scripts/import-check.js b/checks/planner/import-check.js similarity index 100% rename from scripts/import-check.js rename to checks/planner/import-check.js diff --git a/scripts/import-truth.js b/checks/planner/import-truth.js similarity index 100% rename from scripts/import-truth.js rename to checks/planner/import-truth.js diff --git a/scripts/whatif-ultra-check.js b/checks/planner/whatif-ultra-check.js similarity index 100% rename from scripts/whatif-ultra-check.js rename to checks/planner/whatif-ultra-check.js diff --git a/scripts/background-pages-check.js b/checks/platform/background-pages-check.js similarity index 100% rename from scripts/background-pages-check.js rename to checks/platform/background-pages-check.js diff --git a/scripts/background-shell-check.js b/checks/platform/background-shell-check.js similarity index 100% rename from scripts/background-shell-check.js rename to checks/platform/background-shell-check.js diff --git a/scripts/lit-check.js b/checks/platform/lit-check.js similarity index 100% rename from scripts/lit-check.js rename to checks/platform/lit-check.js diff --git a/scripts/register-update-check.js b/checks/platform/register-update-check.js similarity index 100% rename from scripts/register-update-check.js rename to checks/platform/register-update-check.js diff --git a/scripts/registered-hosts-check.js b/checks/platform/registered-hosts-check.js similarity index 100% rename from scripts/registered-hosts-check.js rename to checks/platform/registered-hosts-check.js diff --git a/scripts/transition-guard-check.js b/checks/platform/transition-guard-check.js similarity index 100% rename from scripts/transition-guard-check.js rename to checks/platform/transition-guard-check.js diff --git a/scripts/content-navigation-safari-check.py b/checks/safari/content-navigation-safari-check.py similarity index 100% rename from scripts/content-navigation-safari-check.py rename to checks/safari/content-navigation-safari-check.py diff --git a/scripts/backup-check.js b/checks/settings/backup-check.js similarity index 100% rename from scripts/backup-check.js rename to checks/settings/backup-check.js diff --git a/scripts/editor-x-check.js b/checks/settings/editor-x-check.js similarity index 100% rename from scripts/editor-x-check.js rename to checks/settings/editor-x-check.js diff --git a/scripts/embedded-settings-check.js b/checks/settings/embedded-settings-check.js similarity index 100% rename from scripts/embedded-settings-check.js rename to checks/settings/embedded-settings-check.js diff --git a/scripts/popup-check.js b/checks/settings/popup-check.js similarity index 100% rename from scripts/popup-check.js rename to checks/settings/popup-check.js diff --git a/scripts/popup-motion-check.js b/checks/settings/popup-motion-check.js similarity index 100% rename from scripts/popup-motion-check.js rename to checks/settings/popup-motion-check.js diff --git a/scripts/settings-agents-check.js b/checks/settings/settings-agents-check.js similarity index 100% rename from scripts/settings-agents-check.js rename to checks/settings/settings-agents-check.js diff --git a/scripts/settings-ai-check.js b/checks/settings/settings-ai-check.js similarity index 100% rename from scripts/settings-ai-check.js rename to checks/settings/settings-ai-check.js diff --git a/scripts/settings-ia-check.js b/checks/settings/settings-ia-check.js similarity index 100% rename from scripts/settings-ia-check.js rename to checks/settings/settings-ia-check.js diff --git a/scripts/settings-wiring-check.js b/checks/settings/settings-wiring-check.js similarity index 100% rename from scripts/settings-wiring-check.js rename to checks/settings/settings-wiring-check.js diff --git a/scripts/settings-writer-check.js b/checks/settings/settings-writer-check.js similarity index 100% rename from scripts/settings-writer-check.js rename to checks/settings/settings-writer-check.js diff --git a/scripts/sites-check.js b/checks/settings/sites-check.js similarity index 100% rename from scripts/sites-check.js rename to checks/settings/sites-check.js diff --git a/scripts/welcome-check.js b/checks/settings/welcome-check.js similarity index 100% rename from scripts/welcome-check.js rename to checks/settings/welcome-check.js diff --git a/scripts/xparts-editor-check.js b/checks/settings/xparts-editor-check.js similarity index 100% rename from scripts/xparts-editor-check.js rename to checks/settings/xparts-editor-check.js diff --git a/scripts/share-check.js b/checks/sharing/share-check.js similarity index 100% rename from scripts/share-check.js rename to checks/sharing/share-check.js diff --git a/scripts/writer-adversarial-check.js b/checks/writer/writer-adversarial-check.js similarity index 100% rename from scripts/writer-adversarial-check.js rename to checks/writer/writer-adversarial-check.js diff --git a/scripts/writer-ai-check.js b/checks/writer/writer-ai-check.js similarity index 100% rename from scripts/writer-ai-check.js rename to checks/writer/writer-ai-check.js diff --git a/scripts/writer-check.js b/checks/writer/writer-check.js similarity index 100% rename from scripts/writer-check.js rename to checks/writer/writer-check.js diff --git a/scripts/writer-cite-check.js b/checks/writer/writer-cite-check.js similarity index 100% rename from scripts/writer-cite-check.js rename to checks/writer/writer-cite-check.js diff --git a/scripts/writer-completion-check.js b/checks/writer/writer-completion-check.js similarity index 100% rename from scripts/writer-completion-check.js rename to checks/writer/writer-completion-check.js diff --git a/scripts/writer-compose-check.js b/checks/writer/writer-compose-check.js similarity index 100% rename from scripts/writer-compose-check.js rename to checks/writer/writer-compose-check.js diff --git a/scripts/writer-core-check.js b/checks/writer/writer-core-check.js similarity index 100% rename from scripts/writer-core-check.js rename to checks/writer/writer-core-check.js diff --git a/scripts/writer-dark-check.js b/checks/writer/writer-dark-check.js similarity index 100% rename from scripts/writer-dark-check.js rename to checks/writer/writer-dark-check.js diff --git a/scripts/writer-edit-check.js b/checks/writer/writer-edit-check.js similarity index 100% rename from scripts/writer-edit-check.js rename to checks/writer/writer-edit-check.js diff --git a/scripts/writer-format-check.js b/checks/writer/writer-format-check.js similarity index 100% rename from scripts/writer-format-check.js rename to checks/writer/writer-format-check.js diff --git a/scripts/writer-huge-check.js b/checks/writer/writer-huge-check.js similarity index 100% rename from scripts/writer-huge-check.js rename to checks/writer/writer-huge-check.js diff --git a/scripts/writer-keyboard-check.js b/checks/writer/writer-keyboard-check.js similarity index 100% rename from scripts/writer-keyboard-check.js rename to checks/writer/writer-keyboard-check.js diff --git a/scripts/writer-kinds-check.js b/checks/writer/writer-kinds-check.js similarity index 100% rename from scripts/writer-kinds-check.js rename to checks/writer/writer-kinds-check.js diff --git a/scripts/writer-look-check.js b/checks/writer/writer-look-check.js similarity index 100% rename from scripts/writer-look-check.js rename to checks/writer/writer-look-check.js diff --git a/scripts/writer-objects-check.js b/checks/writer/writer-objects-check.js similarity index 100% rename from scripts/writer-objects-check.js rename to checks/writer/writer-objects-check.js diff --git a/scripts/writer-paper-check.js b/checks/writer/writer-paper-check.js similarity index 100% rename from scripts/writer-paper-check.js rename to checks/writer/writer-paper-check.js diff --git a/scripts/writer-paper-regression-check.js b/checks/writer/writer-paper-regression-check.js similarity index 100% rename from scripts/writer-paper-regression-check.js rename to checks/writer/writer-paper-regression-check.js diff --git a/scripts/writer-proof-check.js b/checks/writer/writer-proof-check.js similarity index 100% rename from scripts/writer-proof-check.js rename to checks/writer/writer-proof-check.js diff --git a/scripts/writer-sources-check.js b/checks/writer/writer-sources-check.js similarity index 100% rename from scripts/writer-sources-check.js rename to checks/writer/writer-sources-check.js diff --git a/scripts/writer-submit-check.js b/checks/writer/writer-submit-check.js similarity index 100% rename from scripts/writer-submit-check.js rename to checks/writer/writer-submit-check.js diff --git a/_locales/de/messages.json b/extension/_locales/de/messages.json similarity index 100% rename from _locales/de/messages.json rename to extension/_locales/de/messages.json diff --git a/_locales/en/messages.json b/extension/_locales/en/messages.json similarity index 100% rename from _locales/en/messages.json rename to extension/_locales/en/messages.json diff --git a/_locales/es/messages.json b/extension/_locales/es/messages.json similarity index 100% rename from _locales/es/messages.json rename to extension/_locales/es/messages.json diff --git a/_locales/fr/messages.json b/extension/_locales/fr/messages.json similarity index 100% rename from _locales/fr/messages.json rename to extension/_locales/fr/messages.json diff --git a/_locales/nl/messages.json b/extension/_locales/nl/messages.json similarity index 100% rename from _locales/nl/messages.json rename to extension/_locales/nl/messages.json diff --git a/_locales/pt_BR/messages.json b/extension/_locales/pt_BR/messages.json similarity index 100% rename from _locales/pt_BR/messages.json rename to extension/_locales/pt_BR/messages.json diff --git a/assets/art/README.md b/extension/assets/art/README.md similarity index 100% rename from assets/art/README.md rename to extension/assets/art/README.md diff --git a/assets/art/contact-sheet.png b/extension/assets/art/contact-sheet.png similarity index 100% rename from assets/art/contact-sheet.png rename to extension/assets/art/contact-sheet.png diff --git a/assets/art/cursors/aqua/default.png b/extension/assets/art/cursors/aqua/default.png similarity index 100% rename from assets/art/cursors/aqua/default.png rename to extension/assets/art/cursors/aqua/default.png diff --git a/assets/art/cursors/aqua/pointer.png b/extension/assets/art/cursors/aqua/pointer.png similarity index 100% rename from assets/art/cursors/aqua/pointer.png rename to extension/assets/art/cursors/aqua/pointer.png diff --git a/assets/art/cursors/aurora/default.png b/extension/assets/art/cursors/aurora/default.png similarity index 100% rename from assets/art/cursors/aurora/default.png rename to extension/assets/art/cursors/aurora/default.png diff --git a/assets/art/cursors/aurora/default.svg b/extension/assets/art/cursors/aurora/default.svg similarity index 100% rename from assets/art/cursors/aurora/default.svg rename to extension/assets/art/cursors/aurora/default.svg diff --git a/assets/art/cursors/aurora/default@2x.png b/extension/assets/art/cursors/aurora/default@2x.png similarity index 100% rename from assets/art/cursors/aurora/default@2x.png rename to extension/assets/art/cursors/aurora/default@2x.png diff --git a/assets/art/cursors/aurora/pointer.png b/extension/assets/art/cursors/aurora/pointer.png similarity index 100% rename from assets/art/cursors/aurora/pointer.png rename to extension/assets/art/cursors/aurora/pointer.png diff --git a/assets/art/cursors/aurora/pointer.svg b/extension/assets/art/cursors/aurora/pointer.svg similarity index 100% rename from assets/art/cursors/aurora/pointer.svg rename to extension/assets/art/cursors/aurora/pointer.svg diff --git a/assets/art/cursors/aurora/pointer@2x.png b/extension/assets/art/cursors/aurora/pointer@2x.png similarity index 100% rename from assets/art/cursors/aurora/pointer@2x.png rename to extension/assets/art/cursors/aurora/pointer@2x.png diff --git a/assets/art/cursors/clear/default.png b/extension/assets/art/cursors/clear/default.png similarity index 100% rename from assets/art/cursors/clear/default.png rename to extension/assets/art/cursors/clear/default.png diff --git a/assets/art/cursors/clear/default.svg b/extension/assets/art/cursors/clear/default.svg similarity index 100% rename from assets/art/cursors/clear/default.svg rename to extension/assets/art/cursors/clear/default.svg diff --git a/assets/art/cursors/clear/default@2x.png b/extension/assets/art/cursors/clear/default@2x.png similarity index 100% rename from assets/art/cursors/clear/default@2x.png rename to extension/assets/art/cursors/clear/default@2x.png diff --git a/assets/art/cursors/clear/pointer.png b/extension/assets/art/cursors/clear/pointer.png similarity index 100% rename from assets/art/cursors/clear/pointer.png rename to extension/assets/art/cursors/clear/pointer.png diff --git a/assets/art/cursors/clear/pointer.svg b/extension/assets/art/cursors/clear/pointer.svg similarity index 100% rename from assets/art/cursors/clear/pointer.svg rename to extension/assets/art/cursors/clear/pointer.svg diff --git a/assets/art/cursors/clear/pointer@2x.png b/extension/assets/art/cursors/clear/pointer@2x.png similarity index 100% rename from assets/art/cursors/clear/pointer@2x.png rename to extension/assets/art/cursors/clear/pointer@2x.png diff --git a/assets/art/cursors/frostdev/default.png b/extension/assets/art/cursors/frostdev/default.png similarity index 100% rename from assets/art/cursors/frostdev/default.png rename to extension/assets/art/cursors/frostdev/default.png diff --git a/assets/art/cursors/frostdev/default.svg b/extension/assets/art/cursors/frostdev/default.svg similarity index 100% rename from assets/art/cursors/frostdev/default.svg rename to extension/assets/art/cursors/frostdev/default.svg diff --git a/assets/art/cursors/frostdev/default@2x.png b/extension/assets/art/cursors/frostdev/default@2x.png similarity index 100% rename from assets/art/cursors/frostdev/default@2x.png rename to extension/assets/art/cursors/frostdev/default@2x.png diff --git a/assets/art/cursors/frostdev/pointer.png b/extension/assets/art/cursors/frostdev/pointer.png similarity index 100% rename from assets/art/cursors/frostdev/pointer.png rename to extension/assets/art/cursors/frostdev/pointer.png diff --git a/assets/art/cursors/frostdev/pointer.svg b/extension/assets/art/cursors/frostdev/pointer.svg similarity index 100% rename from assets/art/cursors/frostdev/pointer.svg rename to extension/assets/art/cursors/frostdev/pointer.svg diff --git a/assets/art/cursors/frostdev/pointer@2x.png b/extension/assets/art/cursors/frostdev/pointer@2x.png similarity index 100% rename from assets/art/cursors/frostdev/pointer@2x.png rename to extension/assets/art/cursors/frostdev/pointer@2x.png diff --git a/assets/art/cursors/ghost/default.png b/extension/assets/art/cursors/ghost/default.png similarity index 100% rename from assets/art/cursors/ghost/default.png rename to extension/assets/art/cursors/ghost/default.png diff --git a/assets/art/cursors/ghost/default.svg b/extension/assets/art/cursors/ghost/default.svg similarity index 100% rename from assets/art/cursors/ghost/default.svg rename to extension/assets/art/cursors/ghost/default.svg diff --git a/assets/art/cursors/ghost/default@2x.png b/extension/assets/art/cursors/ghost/default@2x.png similarity index 100% rename from assets/art/cursors/ghost/default@2x.png rename to extension/assets/art/cursors/ghost/default@2x.png diff --git a/assets/art/cursors/ghost/pointer.png b/extension/assets/art/cursors/ghost/pointer.png similarity index 100% rename from assets/art/cursors/ghost/pointer.png rename to extension/assets/art/cursors/ghost/pointer.png diff --git a/assets/art/cursors/ghost/pointer.svg b/extension/assets/art/cursors/ghost/pointer.svg similarity index 100% rename from assets/art/cursors/ghost/pointer.svg rename to extension/assets/art/cursors/ghost/pointer.svg diff --git a/assets/art/cursors/ghost/pointer@2x.png b/extension/assets/art/cursors/ghost/pointer@2x.png similarity index 100% rename from assets/art/cursors/ghost/pointer@2x.png rename to extension/assets/art/cursors/ghost/pointer@2x.png diff --git a/assets/art/cursors/lagoon/default.png b/extension/assets/art/cursors/lagoon/default.png similarity index 100% rename from assets/art/cursors/lagoon/default.png rename to extension/assets/art/cursors/lagoon/default.png diff --git a/assets/art/cursors/lagoon/default.svg b/extension/assets/art/cursors/lagoon/default.svg similarity index 100% rename from assets/art/cursors/lagoon/default.svg rename to extension/assets/art/cursors/lagoon/default.svg diff --git a/assets/art/cursors/lagoon/default@2x.png b/extension/assets/art/cursors/lagoon/default@2x.png similarity index 100% rename from assets/art/cursors/lagoon/default@2x.png rename to extension/assets/art/cursors/lagoon/default@2x.png diff --git a/assets/art/cursors/lagoon/pointer.png b/extension/assets/art/cursors/lagoon/pointer.png similarity index 100% rename from assets/art/cursors/lagoon/pointer.png rename to extension/assets/art/cursors/lagoon/pointer.png diff --git a/assets/art/cursors/lagoon/pointer.svg b/extension/assets/art/cursors/lagoon/pointer.svg similarity index 100% rename from assets/art/cursors/lagoon/pointer.svg rename to extension/assets/art/cursors/lagoon/pointer.svg diff --git a/assets/art/cursors/lagoon/pointer@2x.png b/extension/assets/art/cursors/lagoon/pointer@2x.png similarity index 100% rename from assets/art/cursors/lagoon/pointer@2x.png rename to extension/assets/art/cursors/lagoon/pointer@2x.png diff --git a/assets/art/cursors/lava/default.png b/extension/assets/art/cursors/lava/default.png similarity index 100% rename from assets/art/cursors/lava/default.png rename to extension/assets/art/cursors/lava/default.png diff --git a/assets/art/cursors/lava/default.svg b/extension/assets/art/cursors/lava/default.svg similarity index 100% rename from assets/art/cursors/lava/default.svg rename to extension/assets/art/cursors/lava/default.svg diff --git a/assets/art/cursors/lava/default@2x.png b/extension/assets/art/cursors/lava/default@2x.png similarity index 100% rename from assets/art/cursors/lava/default@2x.png rename to extension/assets/art/cursors/lava/default@2x.png diff --git a/assets/art/cursors/lava/pointer.png b/extension/assets/art/cursors/lava/pointer.png similarity index 100% rename from assets/art/cursors/lava/pointer.png rename to extension/assets/art/cursors/lava/pointer.png diff --git a/assets/art/cursors/lava/pointer.svg b/extension/assets/art/cursors/lava/pointer.svg similarity index 100% rename from assets/art/cursors/lava/pointer.svg rename to extension/assets/art/cursors/lava/pointer.svg diff --git a/assets/art/cursors/lava/pointer@2x.png b/extension/assets/art/cursors/lava/pointer@2x.png similarity index 100% rename from assets/art/cursors/lava/pointer@2x.png rename to extension/assets/art/cursors/lava/pointer@2x.png diff --git a/assets/art/cursors/lilac/default.png b/extension/assets/art/cursors/lilac/default.png similarity index 100% rename from assets/art/cursors/lilac/default.png rename to extension/assets/art/cursors/lilac/default.png diff --git a/assets/art/cursors/lilac/pointer.png b/extension/assets/art/cursors/lilac/pointer.png similarity index 100% rename from assets/art/cursors/lilac/pointer.png rename to extension/assets/art/cursors/lilac/pointer.png diff --git a/assets/art/cursors/neon/default.png b/extension/assets/art/cursors/neon/default.png similarity index 100% rename from assets/art/cursors/neon/default.png rename to extension/assets/art/cursors/neon/default.png diff --git a/assets/art/cursors/neon/default.svg b/extension/assets/art/cursors/neon/default.svg similarity index 100% rename from assets/art/cursors/neon/default.svg rename to extension/assets/art/cursors/neon/default.svg diff --git a/assets/art/cursors/neon/default@2x.png b/extension/assets/art/cursors/neon/default@2x.png similarity index 100% rename from assets/art/cursors/neon/default@2x.png rename to extension/assets/art/cursors/neon/default@2x.png diff --git a/assets/art/cursors/neon/pointer.png b/extension/assets/art/cursors/neon/pointer.png similarity index 100% rename from assets/art/cursors/neon/pointer.png rename to extension/assets/art/cursors/neon/pointer.png diff --git a/assets/art/cursors/neon/pointer.svg b/extension/assets/art/cursors/neon/pointer.svg similarity index 100% rename from assets/art/cursors/neon/pointer.svg rename to extension/assets/art/cursors/neon/pointer.svg diff --git a/assets/art/cursors/neon/pointer@2x.png b/extension/assets/art/cursors/neon/pointer@2x.png similarity index 100% rename from assets/art/cursors/neon/pointer@2x.png rename to extension/assets/art/cursors/neon/pointer@2x.png diff --git a/assets/art/cursors/pearl/default.png b/extension/assets/art/cursors/pearl/default.png similarity index 100% rename from assets/art/cursors/pearl/default.png rename to extension/assets/art/cursors/pearl/default.png diff --git a/assets/art/cursors/pearl/pointer.png b/extension/assets/art/cursors/pearl/pointer.png similarity index 100% rename from assets/art/cursors/pearl/pointer.png rename to extension/assets/art/cursors/pearl/pointer.png diff --git a/assets/art/cursors/pumpkin/default.png b/extension/assets/art/cursors/pumpkin/default.png similarity index 100% rename from assets/art/cursors/pumpkin/default.png rename to extension/assets/art/cursors/pumpkin/default.png diff --git a/assets/art/cursors/pumpkin/default.svg b/extension/assets/art/cursors/pumpkin/default.svg similarity index 100% rename from assets/art/cursors/pumpkin/default.svg rename to extension/assets/art/cursors/pumpkin/default.svg diff --git a/assets/art/cursors/pumpkin/default@2x.png b/extension/assets/art/cursors/pumpkin/default@2x.png similarity index 100% rename from assets/art/cursors/pumpkin/default@2x.png rename to extension/assets/art/cursors/pumpkin/default@2x.png diff --git a/assets/art/cursors/pumpkin/pointer.png b/extension/assets/art/cursors/pumpkin/pointer.png similarity index 100% rename from assets/art/cursors/pumpkin/pointer.png rename to extension/assets/art/cursors/pumpkin/pointer.png diff --git a/assets/art/cursors/pumpkin/pointer.svg b/extension/assets/art/cursors/pumpkin/pointer.svg similarity index 100% rename from assets/art/cursors/pumpkin/pointer.svg rename to extension/assets/art/cursors/pumpkin/pointer.svg diff --git a/assets/art/cursors/pumpkin/pointer@2x.png b/extension/assets/art/cursors/pumpkin/pointer@2x.png similarity index 100% rename from assets/art/cursors/pumpkin/pointer@2x.png rename to extension/assets/art/cursors/pumpkin/pointer@2x.png diff --git a/assets/art/cursors/quill/default.png b/extension/assets/art/cursors/quill/default.png similarity index 100% rename from assets/art/cursors/quill/default.png rename to extension/assets/art/cursors/quill/default.png diff --git a/assets/art/cursors/quill/default.svg b/extension/assets/art/cursors/quill/default.svg similarity index 100% rename from assets/art/cursors/quill/default.svg rename to extension/assets/art/cursors/quill/default.svg diff --git a/assets/art/cursors/quill/default@2x.png b/extension/assets/art/cursors/quill/default@2x.png similarity index 100% rename from assets/art/cursors/quill/default@2x.png rename to extension/assets/art/cursors/quill/default@2x.png diff --git a/assets/art/cursors/quill/pointer.png b/extension/assets/art/cursors/quill/pointer.png similarity index 100% rename from assets/art/cursors/quill/pointer.png rename to extension/assets/art/cursors/quill/pointer.png diff --git a/assets/art/cursors/quill/pointer.svg b/extension/assets/art/cursors/quill/pointer.svg similarity index 100% rename from assets/art/cursors/quill/pointer.svg rename to extension/assets/art/cursors/quill/pointer.svg diff --git a/assets/art/cursors/quill/pointer@2x.png b/extension/assets/art/cursors/quill/pointer@2x.png similarity index 100% rename from assets/art/cursors/quill/pointer@2x.png rename to extension/assets/art/cursors/quill/pointer@2x.png diff --git a/assets/art/cursors/rose/default.png b/extension/assets/art/cursors/rose/default.png similarity index 100% rename from assets/art/cursors/rose/default.png rename to extension/assets/art/cursors/rose/default.png diff --git a/assets/art/cursors/rose/default.svg b/extension/assets/art/cursors/rose/default.svg similarity index 100% rename from assets/art/cursors/rose/default.svg rename to extension/assets/art/cursors/rose/default.svg diff --git a/assets/art/cursors/rose/default@2x.png b/extension/assets/art/cursors/rose/default@2x.png similarity index 100% rename from assets/art/cursors/rose/default@2x.png rename to extension/assets/art/cursors/rose/default@2x.png diff --git a/assets/art/cursors/rose/pointer.png b/extension/assets/art/cursors/rose/pointer.png similarity index 100% rename from assets/art/cursors/rose/pointer.png rename to extension/assets/art/cursors/rose/pointer.png diff --git a/assets/art/cursors/rose/pointer.svg b/extension/assets/art/cursors/rose/pointer.svg similarity index 100% rename from assets/art/cursors/rose/pointer.svg rename to extension/assets/art/cursors/rose/pointer.svg diff --git a/assets/art/cursors/rose/pointer@2x.png b/extension/assets/art/cursors/rose/pointer@2x.png similarity index 100% rename from assets/art/cursors/rose/pointer@2x.png rename to extension/assets/art/cursors/rose/pointer@2x.png diff --git a/assets/art/cursors/terminal/default.png b/extension/assets/art/cursors/terminal/default.png similarity index 100% rename from assets/art/cursors/terminal/default.png rename to extension/assets/art/cursors/terminal/default.png diff --git a/assets/art/cursors/terminal/default.svg b/extension/assets/art/cursors/terminal/default.svg similarity index 100% rename from assets/art/cursors/terminal/default.svg rename to extension/assets/art/cursors/terminal/default.svg diff --git a/assets/art/cursors/terminal/default@2x.png b/extension/assets/art/cursors/terminal/default@2x.png similarity index 100% rename from assets/art/cursors/terminal/default@2x.png rename to extension/assets/art/cursors/terminal/default@2x.png diff --git a/assets/art/cursors/terminal/pointer.png b/extension/assets/art/cursors/terminal/pointer.png similarity index 100% rename from assets/art/cursors/terminal/pointer.png rename to extension/assets/art/cursors/terminal/pointer.png diff --git a/assets/art/cursors/terminal/pointer.svg b/extension/assets/art/cursors/terminal/pointer.svg similarity index 100% rename from assets/art/cursors/terminal/pointer.svg rename to extension/assets/art/cursors/terminal/pointer.svg diff --git a/assets/art/cursors/terminal/pointer@2x.png b/extension/assets/art/cursors/terminal/pointer@2x.png similarity index 100% rename from assets/art/cursors/terminal/pointer@2x.png rename to extension/assets/art/cursors/terminal/pointer@2x.png diff --git a/assets/art/pet-reactions.md b/extension/assets/art/pet-reactions.md similarity index 100% rename from assets/art/pet-reactions.md rename to extension/assets/art/pet-reactions.md diff --git a/assets/art/pets/bunny/reactions.webp b/extension/assets/art/pets/bunny/reactions.webp similarity index 100% rename from assets/art/pets/bunny/reactions.webp rename to extension/assets/art/pets/bunny/reactions.webp diff --git a/assets/art/pets/bunny/sprites.webp b/extension/assets/art/pets/bunny/sprites.webp similarity index 100% rename from assets/art/pets/bunny/sprites.webp rename to extension/assets/art/pets/bunny/sprites.webp diff --git a/assets/art/pets/cat/reactions.webp b/extension/assets/art/pets/cat/reactions.webp similarity index 100% rename from assets/art/pets/cat/reactions.webp rename to extension/assets/art/pets/cat/reactions.webp diff --git a/assets/art/pets/cat/sprites.webp b/extension/assets/art/pets/cat/sprites.webp similarity index 100% rename from assets/art/pets/cat/sprites.webp rename to extension/assets/art/pets/cat/sprites.webp diff --git a/assets/art/pets/ember/pet.json b/extension/assets/art/pets/ember/pet.json similarity index 100% rename from assets/art/pets/ember/pet.json rename to extension/assets/art/pets/ember/pet.json diff --git a/assets/art/pets/ember/reactions.webp b/extension/assets/art/pets/ember/reactions.webp similarity index 100% rename from assets/art/pets/ember/reactions.webp rename to extension/assets/art/pets/ember/reactions.webp diff --git a/assets/art/pets/ember/spritesheet.webp b/extension/assets/art/pets/ember/spritesheet.webp similarity index 100% rename from assets/art/pets/ember/spritesheet.webp rename to extension/assets/art/pets/ember/spritesheet.webp diff --git a/assets/art/pets/glaze/pet.json b/extension/assets/art/pets/glaze/pet.json similarity index 100% rename from assets/art/pets/glaze/pet.json rename to extension/assets/art/pets/glaze/pet.json diff --git a/assets/art/pets/glaze/reactions.webp b/extension/assets/art/pets/glaze/reactions.webp similarity index 100% rename from assets/art/pets/glaze/reactions.webp rename to extension/assets/art/pets/glaze/reactions.webp diff --git a/assets/art/pets/glaze/spritesheet.webp b/extension/assets/art/pets/glaze/spritesheet.webp similarity index 100% rename from assets/art/pets/glaze/spritesheet.webp rename to extension/assets/art/pets/glaze/spritesheet.webp diff --git a/assets/art/pets/luna/pet.json b/extension/assets/art/pets/luna/pet.json similarity index 100% rename from assets/art/pets/luna/pet.json rename to extension/assets/art/pets/luna/pet.json diff --git a/assets/art/pets/luna/reactions.webp b/extension/assets/art/pets/luna/reactions.webp similarity index 100% rename from assets/art/pets/luna/reactions.webp rename to extension/assets/art/pets/luna/reactions.webp diff --git a/assets/art/pets/luna/spritesheet.webp b/extension/assets/art/pets/luna/spritesheet.webp similarity index 100% rename from assets/art/pets/luna/spritesheet.webp rename to extension/assets/art/pets/luna/spritesheet.webp diff --git a/assets/art/pets/mochi/pet.json b/extension/assets/art/pets/mochi/pet.json similarity index 100% rename from assets/art/pets/mochi/pet.json rename to extension/assets/art/pets/mochi/pet.json diff --git a/assets/art/pets/mochi/reactions.webp b/extension/assets/art/pets/mochi/reactions.webp similarity index 100% rename from assets/art/pets/mochi/reactions.webp rename to extension/assets/art/pets/mochi/reactions.webp diff --git a/assets/art/pets/mochi/spritesheet.webp b/extension/assets/art/pets/mochi/spritesheet.webp similarity index 100% rename from assets/art/pets/mochi/spritesheet.webp rename to extension/assets/art/pets/mochi/spritesheet.webp diff --git a/assets/art/pets/pebble/pet.json b/extension/assets/art/pets/pebble/pet.json similarity index 100% rename from assets/art/pets/pebble/pet.json rename to extension/assets/art/pets/pebble/pet.json diff --git a/assets/art/pets/pebble/reactions.webp b/extension/assets/art/pets/pebble/reactions.webp similarity index 100% rename from assets/art/pets/pebble/reactions.webp rename to extension/assets/art/pets/pebble/reactions.webp diff --git a/assets/art/pets/pebble/spritesheet.webp b/extension/assets/art/pets/pebble/spritesheet.webp similarity index 100% rename from assets/art/pets/pebble/spritesheet.webp rename to extension/assets/art/pets/pebble/spritesheet.webp diff --git a/assets/art/pets/rime/pet.json b/extension/assets/art/pets/rime/pet.json similarity index 100% rename from assets/art/pets/rime/pet.json rename to extension/assets/art/pets/rime/pet.json diff --git a/assets/art/pets/rime/reactions.webp b/extension/assets/art/pets/rime/reactions.webp similarity index 100% rename from assets/art/pets/rime/reactions.webp rename to extension/assets/art/pets/rime/reactions.webp diff --git a/assets/art/pets/rime/spritesheet.webp b/extension/assets/art/pets/rime/spritesheet.webp similarity index 100% rename from assets/art/pets/rime/spritesheet.webp rename to extension/assets/art/pets/rime/spritesheet.webp diff --git a/assets/art/pets/sprout/pet.json b/extension/assets/art/pets/sprout/pet.json similarity index 100% rename from assets/art/pets/sprout/pet.json rename to extension/assets/art/pets/sprout/pet.json diff --git a/assets/art/pets/sprout/reactions.webp b/extension/assets/art/pets/sprout/reactions.webp similarity index 100% rename from assets/art/pets/sprout/reactions.webp rename to extension/assets/art/pets/sprout/reactions.webp diff --git a/assets/art/pets/sprout/spritesheet.webp b/extension/assets/art/pets/sprout/spritesheet.webp similarity index 100% rename from assets/art/pets/sprout/spritesheet.webp rename to extension/assets/art/pets/sprout/spritesheet.webp diff --git a/assets/art/pets/study-buddy/reactions.webp b/extension/assets/art/pets/study-buddy/reactions.webp similarity index 100% rename from assets/art/pets/study-buddy/reactions.webp rename to extension/assets/art/pets/study-buddy/reactions.webp diff --git a/assets/art/pets/study-buddy/sprites.webp b/extension/assets/art/pets/study-buddy/sprites.webp similarity index 100% rename from assets/art/pets/study-buddy/sprites.webp rename to extension/assets/art/pets/study-buddy/sprites.webp diff --git a/assets/art/stickers/cozy-autumn/acorn.webp b/extension/assets/art/stickers/cozy-autumn/acorn.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/acorn.webp rename to extension/assets/art/stickers/cozy-autumn/acorn.webp diff --git a/assets/art/stickers/cozy-autumn/candle.webp b/extension/assets/art/stickers/cozy-autumn/candle.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/candle.webp rename to extension/assets/art/stickers/cozy-autumn/candle.webp diff --git a/assets/art/stickers/cozy-autumn/cinnamon-bun.webp b/extension/assets/art/stickers/cozy-autumn/cinnamon-bun.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/cinnamon-bun.webp rename to extension/assets/art/stickers/cozy-autumn/cinnamon-bun.webp diff --git a/assets/art/stickers/cozy-autumn/cozy-sock.webp b/extension/assets/art/stickers/cozy-autumn/cozy-sock.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/cozy-sock.webp rename to extension/assets/art/stickers/cozy-autumn/cozy-sock.webp diff --git a/assets/art/stickers/cozy-autumn/folded-blanket.webp b/extension/assets/art/stickers/cozy-autumn/folded-blanket.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/folded-blanket.webp rename to extension/assets/art/stickers/cozy-autumn/folded-blanket.webp diff --git a/assets/art/stickers/cozy-autumn/knitted-mittens.webp b/extension/assets/art/stickers/cozy-autumn/knitted-mittens.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/knitted-mittens.webp rename to extension/assets/art/stickers/cozy-autumn/knitted-mittens.webp diff --git a/assets/art/stickers/cozy-autumn/maple-leaf.webp b/extension/assets/art/stickers/cozy-autumn/maple-leaf.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/maple-leaf.webp rename to extension/assets/art/stickers/cozy-autumn/maple-leaf.webp diff --git a/assets/art/stickers/cozy-autumn/mushroom.webp b/extension/assets/art/stickers/cozy-autumn/mushroom.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/mushroom.webp rename to extension/assets/art/stickers/cozy-autumn/mushroom.webp diff --git a/assets/art/stickers/cozy-autumn/oak-leaf.webp b/extension/assets/art/stickers/cozy-autumn/oak-leaf.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/oak-leaf.webp rename to extension/assets/art/stickers/cozy-autumn/oak-leaf.webp diff --git a/assets/art/stickers/cozy-autumn/pumpkin.webp b/extension/assets/art/stickers/cozy-autumn/pumpkin.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/pumpkin.webp rename to extension/assets/art/stickers/cozy-autumn/pumpkin.webp diff --git a/assets/art/stickers/cozy-autumn/scarf.webp b/extension/assets/art/stickers/cozy-autumn/scarf.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/scarf.webp rename to extension/assets/art/stickers/cozy-autumn/scarf.webp diff --git a/assets/art/stickers/cozy-autumn/steaming-cocoa-mug.webp b/extension/assets/art/stickers/cozy-autumn/steaming-cocoa-mug.webp similarity index 100% rename from assets/art/stickers/cozy-autumn/steaming-cocoa-mug.webp rename to extension/assets/art/stickers/cozy-autumn/steaming-cocoa-mug.webp diff --git a/assets/art/stickers/critters/bumblebee.webp b/extension/assets/art/stickers/critters/bumblebee.webp similarity index 100% rename from assets/art/stickers/critters/bumblebee.webp rename to extension/assets/art/stickers/critters/bumblebee.webp diff --git a/assets/art/stickers/critters/gentle-otter.webp b/extension/assets/art/stickers/critters/gentle-otter.webp similarity index 100% rename from assets/art/stickers/critters/gentle-otter.webp rename to extension/assets/art/stickers/critters/gentle-otter.webp diff --git a/assets/art/stickers/critters/lilac-hedgehog.webp b/extension/assets/art/stickers/critters/lilac-hedgehog.webp similarity index 100% rename from assets/art/stickers/critters/lilac-hedgehog.webp rename to extension/assets/art/stickers/critters/lilac-hedgehog.webp diff --git a/assets/art/stickers/critters/little-mouse.webp b/extension/assets/art/stickers/critters/little-mouse.webp similarity index 100% rename from assets/art/stickers/critters/little-mouse.webp rename to extension/assets/art/stickers/critters/little-mouse.webp diff --git a/assets/art/stickers/critters/mint-frog.webp b/extension/assets/art/stickers/critters/mint-frog.webp similarity index 100% rename from assets/art/stickers/critters/mint-frog.webp rename to extension/assets/art/stickers/critters/mint-frog.webp diff --git a/assets/art/stickers/critters/penguin-chick.webp b/extension/assets/art/stickers/critters/penguin-chick.webp similarity index 100% rename from assets/art/stickers/critters/penguin-chick.webp rename to extension/assets/art/stickers/critters/penguin-chick.webp diff --git a/assets/art/stickers/critters/red-panda.webp b/extension/assets/art/stickers/critters/red-panda.webp similarity index 100% rename from assets/art/stickers/critters/red-panda.webp rename to extension/assets/art/stickers/critters/red-panda.webp diff --git a/assets/art/stickers/critters/round-owl.webp b/extension/assets/art/stickers/critters/round-owl.webp similarity index 100% rename from assets/art/stickers/critters/round-owl.webp rename to extension/assets/art/stickers/critters/round-owl.webp diff --git a/assets/art/stickers/critters/seal-pup.webp b/extension/assets/art/stickers/critters/seal-pup.webp similarity index 100% rename from assets/art/stickers/critters/seal-pup.webp rename to extension/assets/art/stickers/critters/seal-pup.webp diff --git a/assets/art/stickers/critters/sitting-bunny.webp b/extension/assets/art/stickers/critters/sitting-bunny.webp similarity index 100% rename from assets/art/stickers/critters/sitting-bunny.webp rename to extension/assets/art/stickers/critters/sitting-bunny.webp diff --git a/assets/art/stickers/critters/small-fox.webp b/extension/assets/art/stickers/critters/small-fox.webp similarity index 100% rename from assets/art/stickers/critters/small-fox.webp rename to extension/assets/art/stickers/critters/small-fox.webp diff --git a/assets/art/stickers/critters/study-buddy-bandana.webp b/extension/assets/art/stickers/critters/study-buddy-bandana.webp similarity index 100% rename from assets/art/stickers/critters/study-buddy-bandana.webp rename to extension/assets/art/stickers/critters/study-buddy-bandana.webp diff --git a/assets/art/stickers/critters/study-buddy-happy.webp b/extension/assets/art/stickers/critters/study-buddy-happy.webp similarity index 100% rename from assets/art/stickers/critters/study-buddy-happy.webp rename to extension/assets/art/stickers/critters/study-buddy-happy.webp diff --git a/assets/art/stickers/critters/study-buddy-sleepy.webp b/extension/assets/art/stickers/critters/study-buddy-sleepy.webp similarity index 100% rename from assets/art/stickers/critters/study-buddy-sleepy.webp rename to extension/assets/art/stickers/critters/study-buddy-sleepy.webp diff --git a/assets/art/stickers/critters/study-buddy.webp b/extension/assets/art/stickers/critters/study-buddy.webp similarity index 100% rename from assets/art/stickers/critters/study-buddy.webp rename to extension/assets/art/stickers/critters/study-buddy.webp diff --git a/assets/art/stickers/critters/teal-turtle.webp b/extension/assets/art/stickers/critters/teal-turtle.webp similarity index 100% rename from assets/art/stickers/critters/teal-turtle.webp rename to extension/assets/art/stickers/critters/teal-turtle.webp diff --git a/assets/art/stickers/habitat/kelp.webp b/extension/assets/art/stickers/habitat/kelp.webp similarity index 100% rename from assets/art/stickers/habitat/kelp.webp rename to extension/assets/art/stickers/habitat/kelp.webp diff --git a/assets/art/stickers/habitat/pet-bed.webp b/extension/assets/art/stickers/habitat/pet-bed.webp similarity index 100% rename from assets/art/stickers/habitat/pet-bed.webp rename to extension/assets/art/stickers/habitat/pet-bed.webp diff --git a/assets/art/stickers/habitat/sandcastle.webp b/extension/assets/art/stickers/habitat/sandcastle.webp similarity index 100% rename from assets/art/stickers/habitat/sandcastle.webp rename to extension/assets/art/stickers/habitat/sandcastle.webp diff --git a/assets/art/stickers/habitat/shelf.webp b/extension/assets/art/stickers/habitat/shelf.webp similarity index 100% rename from assets/art/stickers/habitat/shelf.webp rename to extension/assets/art/stickers/habitat/shelf.webp diff --git a/assets/art/stickers/habitat/toy-ball.webp b/extension/assets/art/stickers/habitat/toy-ball.webp similarity index 100% rename from assets/art/stickers/habitat/toy-ball.webp rename to extension/assets/art/stickers/habitat/toy-ball.webp diff --git a/assets/art/stickers/habitat/yarn-ball.webp b/extension/assets/art/stickers/habitat/yarn-ball.webp similarity index 100% rename from assets/art/stickers/habitat/yarn-ball.webp rename to extension/assets/art/stickers/habitat/yarn-ball.webp diff --git a/assets/art/stickers/space-sky/comet.webp b/extension/assets/art/stickers/space-sky/comet.webp similarity index 100% rename from assets/art/stickers/space-sky/comet.webp rename to extension/assets/art/stickers/space-sky/comet.webp diff --git a/assets/art/stickers/space-sky/constellation.webp b/extension/assets/art/stickers/space-sky/constellation.webp similarity index 100% rename from assets/art/stickers/space-sky/constellation.webp rename to extension/assets/art/stickers/space-sky/constellation.webp diff --git a/assets/art/stickers/space-sky/crescent-moon.webp b/extension/assets/art/stickers/space-sky/crescent-moon.webp similarity index 100% rename from assets/art/stickers/space-sky/crescent-moon.webp rename to extension/assets/art/stickers/space-sky/crescent-moon.webp diff --git a/assets/art/stickers/space-sky/earthlike-planet.webp b/extension/assets/art/stickers/space-sky/earthlike-planet.webp similarity index 100% rename from assets/art/stickers/space-sky/earthlike-planet.webp rename to extension/assets/art/stickers/space-sky/earthlike-planet.webp diff --git a/assets/art/stickers/space-sky/fluffy-cloud.webp b/extension/assets/art/stickers/space-sky/fluffy-cloud.webp similarity index 100% rename from assets/art/stickers/space-sky/fluffy-cloud.webp rename to extension/assets/art/stickers/space-sky/fluffy-cloud.webp diff --git a/assets/art/stickers/space-sky/full-moon.webp b/extension/assets/art/stickers/space-sky/full-moon.webp similarity index 100% rename from assets/art/stickers/space-sky/full-moon.webp rename to extension/assets/art/stickers/space-sky/full-moon.webp diff --git a/assets/art/stickers/space-sky/rainbow.webp b/extension/assets/art/stickers/space-sky/rainbow.webp similarity index 100% rename from assets/art/stickers/space-sky/rainbow.webp rename to extension/assets/art/stickers/space-sky/rainbow.webp diff --git a/assets/art/stickers/space-sky/ringed-planet.webp b/extension/assets/art/stickers/space-sky/ringed-planet.webp similarity index 100% rename from assets/art/stickers/space-sky/ringed-planet.webp rename to extension/assets/art/stickers/space-sky/ringed-planet.webp diff --git a/assets/art/stickers/space-sky/rocket.webp b/extension/assets/art/stickers/space-sky/rocket.webp similarity index 100% rename from assets/art/stickers/space-sky/rocket.webp rename to extension/assets/art/stickers/space-sky/rocket.webp diff --git a/assets/art/stickers/space-sky/shooting-star.webp b/extension/assets/art/stickers/space-sky/shooting-star.webp similarity index 100% rename from assets/art/stickers/space-sky/shooting-star.webp rename to extension/assets/art/stickers/space-sky/shooting-star.webp diff --git a/assets/art/stickers/space-sky/sun.webp b/extension/assets/art/stickers/space-sky/sun.webp similarity index 100% rename from assets/art/stickers/space-sky/sun.webp rename to extension/assets/art/stickers/space-sky/sun.webp diff --git a/assets/art/stickers/space-sky/telescope.webp b/extension/assets/art/stickers/space-sky/telescope.webp similarity index 100% rename from assets/art/stickers/space-sky/telescope.webp rename to extension/assets/art/stickers/space-sky/telescope.webp diff --git a/assets/art/stickers/study/blank-sticky-note.webp b/extension/assets/art/stickers/study/blank-sticky-note.webp similarity index 100% rename from assets/art/stickers/study/blank-sticky-note.webp rename to extension/assets/art/stickers/study/blank-sticky-note.webp diff --git a/assets/art/stickers/study/checkmark.webp b/extension/assets/art/stickers/study/checkmark.webp similarity index 100% rename from assets/art/stickers/study/checkmark.webp rename to extension/assets/art/stickers/study/checkmark.webp diff --git a/assets/art/stickers/study/closed-books.webp b/extension/assets/art/stickers/study/closed-books.webp similarity index 100% rename from assets/art/stickers/study/closed-books.webp rename to extension/assets/art/stickers/study/closed-books.webp diff --git a/assets/art/stickers/study/coffee.webp b/extension/assets/art/stickers/study/coffee.webp similarity index 100% rename from assets/art/stickers/study/coffee.webp rename to extension/assets/art/stickers/study/coffee.webp diff --git a/assets/art/stickers/study/desk-lamp.webp b/extension/assets/art/stickers/study/desk-lamp.webp similarity index 100% rename from assets/art/stickers/study/desk-lamp.webp rename to extension/assets/art/stickers/study/desk-lamp.webp diff --git a/assets/art/stickers/study/graduation-cap.webp b/extension/assets/art/stickers/study/graduation-cap.webp similarity index 100% rename from assets/art/stickers/study/graduation-cap.webp rename to extension/assets/art/stickers/study/graduation-cap.webp diff --git a/assets/art/stickers/study/hourglass.webp b/extension/assets/art/stickers/study/hourglass.webp similarity index 100% rename from assets/art/stickers/study/hourglass.webp rename to extension/assets/art/stickers/study/hourglass.webp diff --git a/assets/art/stickers/study/notebook.webp b/extension/assets/art/stickers/study/notebook.webp similarity index 100% rename from assets/art/stickers/study/notebook.webp rename to extension/assets/art/stickers/study/notebook.webp diff --git a/assets/art/stickers/study/open-book.webp b/extension/assets/art/stickers/study/open-book.webp similarity index 100% rename from assets/art/stickers/study/open-book.webp rename to extension/assets/art/stickers/study/open-book.webp diff --git a/assets/art/stickers/study/pencil.webp b/extension/assets/art/stickers/study/pencil.webp similarity index 100% rename from assets/art/stickers/study/pencil.webp rename to extension/assets/art/stickers/study/pencil.webp diff --git a/assets/art/stickers/study/reading-glasses.webp b/extension/assets/art/stickers/study/reading-glasses.webp similarity index 100% rename from assets/art/stickers/study/reading-glasses.webp rename to extension/assets/art/stickers/study/reading-glasses.webp diff --git a/assets/art/stickers/study/star-cluster.webp b/extension/assets/art/stickers/study/star-cluster.webp similarity index 100% rename from assets/art/stickers/study/star-cluster.webp rename to extension/assets/art/stickers/study/star-cluster.webp diff --git a/assets/logo-dark.svg b/extension/assets/logo-dark.svg similarity index 100% rename from assets/logo-dark.svg rename to extension/assets/logo-dark.svg diff --git a/assets/logo.svg b/extension/assets/logo.svg similarity index 100% rename from assets/logo.svg rename to extension/assets/logo.svg diff --git a/assets/shaders/aurora.glsl b/extension/assets/shaders/aurora.glsl similarity index 100% rename from assets/shaders/aurora.glsl rename to extension/assets/shaders/aurora.glsl diff --git a/assets/shaders/liquid.glsl b/extension/assets/shaders/liquid.glsl similarity index 100% rename from assets/shaders/liquid.glsl rename to extension/assets/shaders/liquid.glsl diff --git a/assets/shaders/starfield.glsl b/extension/assets/shaders/starfield.glsl similarity index 100% rename from assets/shaders/starfield.glsl rename to extension/assets/shaders/starfield.glsl diff --git a/canvas.css b/extension/canvas.css similarity index 100% rename from canvas.css rename to extension/canvas.css diff --git a/content-canvas.js b/extension/content-canvas.js similarity index 100% rename from content-canvas.js rename to extension/content-canvas.js diff --git a/content-ultra.js b/extension/content-ultra.js similarity index 100% rename from content-ultra.js rename to extension/content-ultra.js diff --git a/content.js b/extension/content.js similarity index 100% rename from content.js rename to extension/content.js diff --git a/defaults.js b/extension/defaults.js similarity index 100% rename from defaults.js rename to extension/defaults.js diff --git a/extras.css b/extension/extras.css similarity index 100% rename from extras.css rename to extension/extras.css diff --git a/features/allow.css b/extension/features/allow.css similarity index 100% rename from features/allow.css rename to extension/features/allow.css diff --git a/features/allow.html b/extension/features/allow.html similarity index 100% rename from features/allow.html rename to extension/features/allow.html diff --git a/features/allow.js b/extension/features/allow.js similarity index 100% rename from features/allow.js rename to extension/features/allow.js diff --git a/features/announce-read.js b/extension/features/announce-read.js similarity index 100% rename from features/announce-read.js rename to extension/features/announce-read.js diff --git a/features/announce.js b/extension/features/announce.js similarity index 100% rename from features/announce.js rename to extension/features/announce.js diff --git a/features/apps.css b/extension/features/apps.css similarity index 100% rename from features/apps.css rename to extension/features/apps.css diff --git a/features/bbpreview.js b/extension/features/bbpreview.js similarity index 100% rename from features/bbpreview.js rename to extension/features/bbpreview.js diff --git a/features/calexport.js b/extension/features/calexport.js similarity index 100% rename from features/calexport.js rename to extension/features/calexport.js diff --git a/features/cards.js b/extension/features/cards.js similarity index 100% rename from features/cards.js rename to extension/features/cards.js diff --git a/features/cardtasks.js b/extension/features/cardtasks.js similarity index 100% rename from features/cardtasks.js rename to extension/features/cardtasks.js diff --git a/features/classes.js b/extension/features/classes.js similarity index 100% rename from features/classes.js rename to extension/features/classes.js diff --git a/features/consent.js b/extension/features/consent.js similarity index 100% rename from features/consent.js rename to extension/features/consent.js diff --git a/features/coursecards.js b/extension/features/coursecards.js similarity index 100% rename from features/coursecards.js rename to extension/features/coursecards.js diff --git a/features/coursemenu.js b/extension/features/coursemenu.js similarity index 100% rename from features/coursemenu.js rename to extension/features/coursemenu.js diff --git a/features/coursetabs.css b/extension/features/coursetabs.css similarity index 100% rename from features/coursetabs.css rename to extension/features/coursetabs.css diff --git a/features/coursetabs.js b/extension/features/coursetabs.js similarity index 100% rename from features/coursetabs.js rename to extension/features/coursetabs.js diff --git a/features/dashboard.js b/extension/features/dashboard.js similarity index 100% rename from features/dashboard.js rename to extension/features/dashboard.js diff --git a/features/discussion-nav.js b/extension/features/discussion-nav.js similarity index 100% rename from features/discussion-nav.js rename to extension/features/discussion-nav.js diff --git a/features/docreview.css b/extension/features/docreview.css similarity index 100% rename from features/docreview.css rename to extension/features/docreview.css diff --git a/features/docreview.js b/extension/features/docreview.js similarity index 100% rename from features/docreview.js rename to extension/features/docreview.js diff --git a/features/docs.js b/extension/features/docs.js similarity index 100% rename from features/docs.js rename to extension/features/docs.js diff --git a/features/docviewer.js b/extension/features/docviewer.js similarity index 100% rename from features/docviewer.js rename to extension/features/docviewer.js diff --git a/features/download.js b/extension/features/download.js similarity index 100% rename from features/download.js rename to extension/features/download.js diff --git a/features/drafts.js b/extension/features/drafts.js similarity index 100% rename from features/drafts.js rename to extension/features/drafts.js diff --git a/features/editor-menu.js b/extension/features/editor-menu.js similarity index 100% rename from features/editor-menu.js rename to extension/features/editor-menu.js diff --git a/features/folders.js b/extension/features/folders.js similarity index 100% rename from features/folders.js rename to extension/features/folders.js diff --git a/features/gcal.js b/extension/features/gcal.js similarity index 100% rename from features/gcal.js rename to extension/features/gcal.js diff --git a/features/gpaterms.js b/extension/features/gpaterms.js similarity index 100% rename from features/gpaterms.js rename to extension/features/gpaterms.js diff --git a/features/grades.js b/extension/features/grades.js similarity index 100% rename from features/grades.js rename to extension/features/grades.js diff --git a/features/gradescope.js b/extension/features/gradescope.js similarity index 100% rename from features/gradescope.js rename to extension/features/gradescope.js diff --git a/features/import.js b/extension/features/import.js similarity index 100% rename from features/import.js rename to extension/features/import.js diff --git a/features/insights.js b/extension/features/insights.js similarity index 100% rename from features/insights.js rename to extension/features/insights.js diff --git a/features/keys.js b/extension/features/keys.js similarity index 100% rename from features/keys.js rename to extension/features/keys.js diff --git a/features/light.js b/extension/features/light.js similarity index 100% rename from features/light.js rename to extension/features/light.js diff --git a/features/lookparts.js b/extension/features/lookparts.js similarity index 100% rename from features/lookparts.js rename to extension/features/lookparts.js diff --git a/features/lookx.css b/extension/features/lookx.css similarity index 100% rename from features/lookx.css rename to extension/features/lookx.css diff --git a/features/lookx.js b/extension/features/lookx.js similarity index 100% rename from features/lookx.js rename to extension/features/lookx.js diff --git a/features/mods.js b/extension/features/mods.js similarity index 100% rename from features/mods.js rename to extension/features/mods.js diff --git a/features/modui.js b/extension/features/modui.js similarity index 100% rename from features/modui.js rename to extension/features/modui.js diff --git a/features/modwidgets.js b/extension/features/modwidgets.js similarity index 100% rename from features/modwidgets.js rename to extension/features/modwidgets.js diff --git a/features/moodle.js b/extension/features/moodle.js similarity index 100% rename from features/moodle.js rename to extension/features/moodle.js diff --git a/features/motionx.js b/extension/features/motionx.js similarity index 100% rename from features/motionx.js rename to extension/features/motionx.js diff --git a/features/nav.js b/extension/features/nav.js similarity index 100% rename from features/nav.js rename to extension/features/nav.js diff --git a/features/navbar-canvas.js b/extension/features/navbar-canvas.js similarity index 100% rename from features/navbar-canvas.js rename to extension/features/navbar-canvas.js diff --git a/features/navbar-moodle.js b/extension/features/navbar-moodle.js similarity index 100% rename from features/navbar-moodle.js rename to extension/features/navbar-moodle.js diff --git a/features/navbar-widgets.js b/extension/features/navbar-widgets.js similarity index 100% rename from features/navbar-widgets.js rename to extension/features/navbar-widgets.js diff --git a/features/navbar.css b/extension/features/navbar.css similarity index 100% rename from features/navbar.css rename to extension/features/navbar.css diff --git a/features/navbar.js b/extension/features/navbar.js similarity index 100% rename from features/navbar.js rename to extension/features/navbar.js diff --git a/features/navlayout.css b/extension/features/navlayout.css similarity index 100% rename from features/navlayout.css rename to extension/features/navlayout.css diff --git a/features/notes-home.js b/extension/features/notes-home.js similarity index 100% rename from features/notes-home.js rename to extension/features/notes-home.js diff --git a/features/notes.js b/extension/features/notes.js similarity index 100% rename from features/notes.js rename to extension/features/notes.js diff --git a/features/palette.js b/extension/features/palette.js similarity index 100% rename from features/palette.js rename to extension/features/palette.js diff --git a/features/pet.js b/extension/features/pet.js similarity index 100% rename from features/pet.js rename to extension/features/pet.js diff --git a/features/petworld.js b/extension/features/petworld.js similarity index 100% rename from features/petworld.js rename to extension/features/petworld.js diff --git a/features/place.js b/extension/features/place.js similarity index 100% rename from features/place.js rename to extension/features/place.js diff --git a/features/popup.js b/extension/features/popup.js similarity index 100% rename from features/popup.js rename to extension/features/popup.js diff --git a/features/previews.js b/extension/features/previews.js similarity index 100% rename from features/previews.js rename to extension/features/previews.js diff --git a/features/reminders.js b/extension/features/reminders.js similarity index 100% rename from features/reminders.js rename to extension/features/reminders.js diff --git a/features/routes.js b/extension/features/routes.js similarity index 100% rename from features/routes.js rename to extension/features/routes.js diff --git a/features/rubric.js b/extension/features/rubric.js similarity index 100% rename from features/rubric.js rename to extension/features/rubric.js diff --git a/features/rules.css b/extension/features/rules.css similarity index 100% rename from features/rules.css rename to extension/features/rules.css diff --git a/features/rules.js b/extension/features/rules.js similarity index 100% rename from features/rules.js rename to extension/features/rules.js diff --git a/features/schedule.js b/extension/features/schedule.js similarity index 100% rename from features/schedule.js rename to extension/features/schedule.js diff --git a/features/search.js b/extension/features/search.js similarity index 100% rename from features/search.js rename to extension/features/search.js diff --git a/features/settingspage.js b/extension/features/settingspage.js similarity index 100% rename from features/settingspage.js rename to extension/features/settingspage.js diff --git a/features/sharing.js b/extension/features/sharing.js similarity index 100% rename from features/sharing.js rename to extension/features/sharing.js diff --git a/features/sidebar.css b/extension/features/sidebar.css similarity index 100% rename from features/sidebar.css rename to extension/features/sidebar.css diff --git a/features/sidebar.js b/extension/features/sidebar.js similarity index 100% rename from features/sidebar.js rename to extension/features/sidebar.js diff --git a/features/sticky-fold.js b/extension/features/sticky-fold.js similarity index 100% rename from features/sticky-fold.js rename to extension/features/sticky-fold.js diff --git a/features/stream.js b/extension/features/stream.js similarity index 100% rename from features/stream.js rename to extension/features/stream.js diff --git a/features/surfaces.css b/extension/features/surfaces.css similarity index 100% rename from features/surfaces.css rename to extension/features/surfaces.css diff --git a/features/syllabus.js b/extension/features/syllabus.js similarity index 100% rename from features/syllabus.js rename to extension/features/syllabus.js diff --git a/features/todo.js b/extension/features/todo.js similarity index 100% rename from features/todo.js rename to extension/features/todo.js diff --git a/features/welcome.css b/extension/features/welcome.css similarity index 100% rename from features/welcome.css rename to extension/features/welcome.css diff --git a/features/welcome.html b/extension/features/welcome.html similarity index 100% rename from features/welcome.html rename to extension/features/welcome.html diff --git a/features/welcome.js b/extension/features/welcome.js similarity index 100% rename from features/welcome.js rename to extension/features/welcome.js diff --git a/features/widgets.js b/extension/features/widgets.js similarity index 100% rename from features/widgets.js rename to extension/features/widgets.js diff --git a/features/writer-quote.js b/extension/features/writer-quote.js similarity index 100% rename from features/writer-quote.js rename to extension/features/writer-quote.js diff --git a/features/writer-submit.js b/extension/features/writer-submit.js similarity index 100% rename from features/writer-submit.js rename to extension/features/writer-submit.js diff --git a/features/writer.js b/extension/features/writer.js similarity index 100% rename from features/writer.js rename to extension/features/writer.js diff --git a/fonts/OFL-AtkinsonHyperlegible.txt b/extension/fonts/OFL-AtkinsonHyperlegible.txt similarity index 100% rename from fonts/OFL-AtkinsonHyperlegible.txt rename to extension/fonts/OFL-AtkinsonHyperlegible.txt diff --git a/fonts/OFL-Inter.txt b/extension/fonts/OFL-Inter.txt similarity index 100% rename from fonts/OFL-Inter.txt rename to extension/fonts/OFL-Inter.txt diff --git a/fonts/atkinson-400.woff2 b/extension/fonts/atkinson-400.woff2 similarity index 100% rename from fonts/atkinson-400.woff2 rename to extension/fonts/atkinson-400.woff2 diff --git a/fonts/atkinson-700.woff2 b/extension/fonts/atkinson-700.woff2 similarity index 100% rename from fonts/atkinson-700.woff2 rename to extension/fonts/atkinson-700.woff2 diff --git a/fonts/inter.woff2 b/extension/fonts/inter.woff2 similarity index 100% rename from fonts/inter.woff2 rename to extension/fonts/inter.woff2 diff --git a/i18n/de.js b/extension/i18n/de.js similarity index 100% rename from i18n/de.js rename to extension/i18n/de.js diff --git a/i18n/es.js b/extension/i18n/es.js similarity index 100% rename from i18n/es.js rename to extension/i18n/es.js diff --git a/i18n/fr.js b/extension/i18n/fr.js similarity index 100% rename from i18n/fr.js rename to extension/i18n/fr.js diff --git a/i18n/nl.js b/extension/i18n/nl.js similarity index 100% rename from i18n/nl.js rename to extension/i18n/nl.js diff --git a/i18n/pt-BR.js b/extension/i18n/pt-BR.js similarity index 100% rename from i18n/pt-BR.js rename to extension/i18n/pt-BR.js diff --git a/icons/icon.svg b/extension/icons/icon.svg similarity index 100% rename from icons/icon.svg rename to extension/icons/icon.svg diff --git a/icons/icon128.png b/extension/icons/icon128.png similarity index 100% rename from icons/icon128.png rename to extension/icons/icon128.png diff --git a/icons/icon16.png b/extension/icons/icon16.png similarity index 100% rename from icons/icon16.png rename to extension/icons/icon16.png diff --git a/icons/icon32.png b/extension/icons/icon32.png similarity index 100% rename from icons/icon32.png rename to extension/icons/icon32.png diff --git a/icons/icon48.png b/extension/icons/icon48.png similarity index 100% rename from icons/icon48.png rename to extension/icons/icon48.png diff --git a/lib/agent-frames.js b/extension/lib/agent-frames.js similarity index 100% rename from lib/agent-frames.js rename to extension/lib/agent-frames.js diff --git a/lib/agent-link.js b/extension/lib/agent-link.js similarity index 100% rename from lib/agent-link.js rename to extension/lib/agent-link.js diff --git a/lib/ai/broker.js b/extension/lib/ai/broker.js similarity index 100% rename from lib/ai/broker.js rename to extension/lib/ai/broker.js diff --git a/lib/ai/endpoint.js b/extension/lib/ai/endpoint.js similarity index 100% rename from lib/ai/endpoint.js rename to extension/lib/ai/endpoint.js diff --git a/lib/ai/failure.js b/extension/lib/ai/failure.js similarity index 100% rename from lib/ai/failure.js rename to extension/lib/ai/failure.js diff --git a/lib/ai/loop.js b/extension/lib/ai/loop.js similarity index 100% rename from lib/ai/loop.js rename to extension/lib/ai/loop.js diff --git a/lib/ai/models.js b/extension/lib/ai/models.js similarity index 100% rename from lib/ai/models.js rename to extension/lib/ai/models.js diff --git a/lib/ai/providers.js b/extension/lib/ai/providers.js similarity index 100% rename from lib/ai/providers.js rename to extension/lib/ai/providers.js diff --git a/lib/ai/providers/apple.js b/extension/lib/ai/providers/apple.js similarity index 100% rename from lib/ai/providers/apple.js rename to extension/lib/ai/providers/apple.js diff --git a/lib/ai/providers/device.js b/extension/lib/ai/providers/device.js similarity index 100% rename from lib/ai/providers/device.js rename to extension/lib/ai/providers/device.js diff --git a/lib/ai/providers/mock.js b/extension/lib/ai/providers/mock.js similarity index 100% rename from lib/ai/providers/mock.js rename to extension/lib/ai/providers/mock.js diff --git a/lib/ai/providers/openai.js b/extension/lib/ai/providers/openai.js similarity index 100% rename from lib/ai/providers/openai.js rename to extension/lib/ai/providers/openai.js diff --git a/lib/ai/request.js b/extension/lib/ai/request.js similarity index 100% rename from lib/ai/request.js rename to extension/lib/ai/request.js diff --git a/lib/ai/schema.js b/extension/lib/ai/schema.js similarity index 100% rename from lib/ai/schema.js rename to extension/lib/ai/schema.js diff --git a/lib/ai/settings.js b/extension/lib/ai/settings.js similarity index 100% rename from lib/ai/settings.js rename to extension/lib/ai/settings.js diff --git a/lib/ai/stream.js b/extension/lib/ai/stream.js similarity index 100% rename from lib/ai/stream.js rename to extension/lib/ai/stream.js diff --git a/lib/ai/task.js b/extension/lib/ai/task.js similarity index 100% rename from lib/ai/task.js rename to extension/lib/ai/task.js diff --git a/lib/ai/tasks/syllabus-read.js b/extension/lib/ai/tasks/syllabus-read.js similarity index 100% rename from lib/ai/tasks/syllabus-read.js rename to extension/lib/ai/tasks/syllabus-read.js diff --git a/lib/ai/tasks/writer-cite.js b/extension/lib/ai/tasks/writer-cite.js similarity index 100% rename from lib/ai/tasks/writer-cite.js rename to extension/lib/ai/tasks/writer-cite.js diff --git a/lib/ai/tasks/writer-complete.js b/extension/lib/ai/tasks/writer-complete.js similarity index 100% rename from lib/ai/tasks/writer-complete.js rename to extension/lib/ai/tasks/writer-complete.js diff --git a/lib/ai/tasks/writer-context.js b/extension/lib/ai/tasks/writer-context.js similarity index 100% rename from lib/ai/tasks/writer-context.js rename to extension/lib/ai/tasks/writer-context.js diff --git a/lib/ai/tasks/writer-decide.js b/extension/lib/ai/tasks/writer-decide.js similarity index 100% rename from lib/ai/tasks/writer-decide.js rename to extension/lib/ai/tasks/writer-decide.js diff --git a/lib/ai/tasks/writer-feedback.js b/extension/lib/ai/tasks/writer-feedback.js similarity index 100% rename from lib/ai/tasks/writer-feedback.js rename to extension/lib/ai/tasks/writer-feedback.js diff --git a/lib/ai/tasks/writer-image.js b/extension/lib/ai/tasks/writer-image.js similarity index 100% rename from lib/ai/tasks/writer-image.js rename to extension/lib/ai/tasks/writer-image.js diff --git a/lib/ai/tasks/writer-outline.js b/extension/lib/ai/tasks/writer-outline.js similarity index 100% rename from lib/ai/tasks/writer-outline.js rename to extension/lib/ai/tasks/writer-outline.js diff --git a/lib/ai/tasks/writer-requirements.js b/extension/lib/ai/tasks/writer-requirements.js similarity index 100% rename from lib/ai/tasks/writer-requirements.js rename to extension/lib/ai/tasks/writer-requirements.js diff --git a/lib/ai/tasks/writer-review.js b/extension/lib/ai/tasks/writer-review.js similarity index 100% rename from lib/ai/tasks/writer-review.js rename to extension/lib/ai/tasks/writer-review.js diff --git a/lib/ai/text.js b/extension/lib/ai/text.js similarity index 100% rename from lib/ai/text.js rename to extension/lib/ai/text.js diff --git a/lib/ai/tools.js b/extension/lib/ai/tools.js similarity index 100% rename from lib/ai/tools.js rename to extension/lib/ai/tools.js diff --git a/lib/ai/visual-cache.js b/extension/lib/ai/visual-cache.js similarity index 100% rename from lib/ai/visual-cache.js rename to extension/lib/ai/visual-cache.js diff --git a/lib/announce.js b/extension/lib/announce.js similarity index 100% rename from lib/announce.js rename to extension/lib/announce.js diff --git a/lib/background.js b/extension/lib/background.js similarity index 100% rename from lib/background.js rename to extension/lib/background.js diff --git a/lib/backup.js b/extension/lib/backup.js similarity index 100% rename from lib/backup.js rename to extension/lib/backup.js diff --git a/lib/bb-preview.js b/extension/lib/bb-preview.js similarity index 100% rename from lib/bb-preview.js rename to extension/lib/bb-preview.js diff --git a/lib/blocklist.js b/extension/lib/blocklist.js similarity index 100% rename from lib/blocklist.js rename to extension/lib/blocklist.js diff --git a/lib/calexport.js b/extension/lib/calexport.js similarity index 100% rename from lib/calexport.js rename to extension/lib/calexport.js diff --git a/lib/cards.js b/extension/lib/cards.js similarity index 100% rename from lib/cards.js rename to extension/lib/cards.js diff --git a/lib/classes.js b/extension/lib/classes.js similarity index 100% rename from lib/classes.js rename to extension/lib/classes.js diff --git a/lib/color.js b/extension/lib/color.js similarity index 100% rename from lib/color.js rename to extension/lib/color.js diff --git a/lib/coursetabs.js b/extension/lib/coursetabs.js similarity index 100% rename from lib/coursetabs.js rename to extension/lib/coursetabs.js diff --git a/lib/detect.js b/extension/lib/detect.js similarity index 100% rename from lib/detect.js rename to extension/lib/detect.js diff --git a/lib/doc-files.js b/extension/lib/doc-files.js similarity index 100% rename from lib/doc-files.js rename to extension/lib/doc-files.js diff --git a/lib/doc-home.js b/extension/lib/doc-home.js similarity index 100% rename from lib/doc-home.js rename to extension/lib/doc-home.js diff --git a/lib/doc-kinds.js b/extension/lib/doc-kinds.js similarity index 100% rename from lib/doc-kinds.js rename to extension/lib/doc-kinds.js diff --git a/lib/doctext.js b/extension/lib/doctext.js similarity index 100% rename from lib/doctext.js rename to extension/lib/doctext.js diff --git a/lib/download.js b/extension/lib/download.js similarity index 100% rename from lib/download.js rename to extension/lib/download.js diff --git a/lib/drafts.js b/extension/lib/drafts.js similarity index 100% rename from lib/drafts.js rename to extension/lib/drafts.js diff --git a/lib/editor-agents.js b/extension/lib/editor-agents.js similarity index 100% rename from lib/editor-agents.js rename to extension/lib/editor-agents.js diff --git a/lib/editor-ai.js b/extension/lib/editor-ai.js similarity index 100% rename from lib/editor-ai.js rename to extension/lib/editor-ai.js diff --git a/lib/editor-info.js b/extension/lib/editor-info.js similarity index 100% rename from lib/editor-info.js rename to extension/lib/editor-info.js diff --git a/lib/editor-mods.js b/extension/lib/editor-mods.js similarity index 100% rename from lib/editor-mods.js rename to extension/lib/editor-mods.js diff --git a/lib/editor-navbar.js b/extension/lib/editor-navbar.js similarity index 100% rename from lib/editor-navbar.js rename to extension/lib/editor-navbar.js diff --git a/lib/editor-pointer.js b/extension/lib/editor-pointer.js similarity index 100% rename from lib/editor-pointer.js rename to extension/lib/editor-pointer.js diff --git a/lib/editor-preview.js b/extension/lib/editor-preview.js similarity index 100% rename from lib/editor-preview.js rename to extension/lib/editor-preview.js diff --git a/lib/editor-widget.js b/extension/lib/editor-widget.js similarity index 100% rename from lib/editor-widget.js rename to extension/lib/editor-widget.js diff --git a/lib/editor-writer.js b/extension/lib/editor-writer.js similarity index 100% rename from lib/editor-writer.js rename to extension/lib/editor-writer.js diff --git a/lib/editor-x.js b/extension/lib/editor-x.js similarity index 100% rename from lib/editor-x.js rename to extension/lib/editor-x.js diff --git a/lib/editor-xbg.js b/extension/lib/editor-xbg.js similarity index 100% rename from lib/editor-xbg.js rename to extension/lib/editor-xbg.js diff --git a/lib/editor-xmotion.js b/extension/lib/editor-xmotion.js similarity index 100% rename from lib/editor-xmotion.js rename to extension/lib/editor-xmotion.js diff --git a/lib/editor-xparts.js b/extension/lib/editor-xparts.js similarity index 100% rename from lib/editor-xparts.js rename to extension/lib/editor-xparts.js diff --git a/lib/editor.js b/extension/lib/editor.js similarity index 100% rename from lib/editor.js rename to extension/lib/editor.js diff --git a/lib/folders.js b/extension/lib/folders.js similarity index 100% rename from lib/folders.js rename to extension/lib/folders.js diff --git a/lib/fonts.js b/extension/lib/fonts.js similarity index 100% rename from lib/fonts.js rename to extension/lib/fonts.js diff --git a/lib/fonts/LICENSE-arimo.txt b/extension/lib/fonts/LICENSE-arimo.txt similarity index 100% rename from lib/fonts/LICENSE-arimo.txt rename to extension/lib/fonts/LICENSE-arimo.txt diff --git a/lib/fonts/LICENSE-carlito.txt b/extension/lib/fonts/LICENSE-carlito.txt similarity index 100% rename from lib/fonts/LICENSE-carlito.txt rename to extension/lib/fonts/LICENSE-carlito.txt diff --git a/lib/fonts/LICENSE-cousine.txt b/extension/lib/fonts/LICENSE-cousine.txt similarity index 100% rename from lib/fonts/LICENSE-cousine.txt rename to extension/lib/fonts/LICENSE-cousine.txt diff --git a/lib/fonts/LICENSE-tinos.txt b/extension/lib/fonts/LICENSE-tinos.txt similarity index 100% rename from lib/fonts/LICENSE-tinos.txt rename to extension/lib/fonts/LICENSE-tinos.txt diff --git a/lib/fonts/arimo-400-italic.woff2 b/extension/lib/fonts/arimo-400-italic.woff2 similarity index 100% rename from lib/fonts/arimo-400-italic.woff2 rename to extension/lib/fonts/arimo-400-italic.woff2 diff --git a/lib/fonts/arimo-400-normal.woff2 b/extension/lib/fonts/arimo-400-normal.woff2 similarity index 100% rename from lib/fonts/arimo-400-normal.woff2 rename to extension/lib/fonts/arimo-400-normal.woff2 diff --git a/lib/fonts/arimo-700-italic.woff2 b/extension/lib/fonts/arimo-700-italic.woff2 similarity index 100% rename from lib/fonts/arimo-700-italic.woff2 rename to extension/lib/fonts/arimo-700-italic.woff2 diff --git a/lib/fonts/arimo-700-normal.woff2 b/extension/lib/fonts/arimo-700-normal.woff2 similarity index 100% rename from lib/fonts/arimo-700-normal.woff2 rename to extension/lib/fonts/arimo-700-normal.woff2 diff --git a/lib/fonts/carlito-400-italic.woff2 b/extension/lib/fonts/carlito-400-italic.woff2 similarity index 100% rename from lib/fonts/carlito-400-italic.woff2 rename to extension/lib/fonts/carlito-400-italic.woff2 diff --git a/lib/fonts/carlito-400-normal.woff2 b/extension/lib/fonts/carlito-400-normal.woff2 similarity index 100% rename from lib/fonts/carlito-400-normal.woff2 rename to extension/lib/fonts/carlito-400-normal.woff2 diff --git a/lib/fonts/carlito-700-italic.woff2 b/extension/lib/fonts/carlito-700-italic.woff2 similarity index 100% rename from lib/fonts/carlito-700-italic.woff2 rename to extension/lib/fonts/carlito-700-italic.woff2 diff --git a/lib/fonts/carlito-700-normal.woff2 b/extension/lib/fonts/carlito-700-normal.woff2 similarity index 100% rename from lib/fonts/carlito-700-normal.woff2 rename to extension/lib/fonts/carlito-700-normal.woff2 diff --git a/lib/fonts/cousine-400-italic.woff2 b/extension/lib/fonts/cousine-400-italic.woff2 similarity index 100% rename from lib/fonts/cousine-400-italic.woff2 rename to extension/lib/fonts/cousine-400-italic.woff2 diff --git a/lib/fonts/cousine-400-normal.woff2 b/extension/lib/fonts/cousine-400-normal.woff2 similarity index 100% rename from lib/fonts/cousine-400-normal.woff2 rename to extension/lib/fonts/cousine-400-normal.woff2 diff --git a/lib/fonts/cousine-700-italic.woff2 b/extension/lib/fonts/cousine-700-italic.woff2 similarity index 100% rename from lib/fonts/cousine-700-italic.woff2 rename to extension/lib/fonts/cousine-700-italic.woff2 diff --git a/lib/fonts/cousine-700-normal.woff2 b/extension/lib/fonts/cousine-700-normal.woff2 similarity index 100% rename from lib/fonts/cousine-700-normal.woff2 rename to extension/lib/fonts/cousine-700-normal.woff2 diff --git a/lib/fonts/tinos-400-italic.woff2 b/extension/lib/fonts/tinos-400-italic.woff2 similarity index 100% rename from lib/fonts/tinos-400-italic.woff2 rename to extension/lib/fonts/tinos-400-italic.woff2 diff --git a/lib/fonts/tinos-400-normal.woff2 b/extension/lib/fonts/tinos-400-normal.woff2 similarity index 100% rename from lib/fonts/tinos-400-normal.woff2 rename to extension/lib/fonts/tinos-400-normal.woff2 diff --git a/lib/fonts/tinos-700-italic.woff2 b/extension/lib/fonts/tinos-700-italic.woff2 similarity index 100% rename from lib/fonts/tinos-700-italic.woff2 rename to extension/lib/fonts/tinos-700-italic.woff2 diff --git a/lib/fonts/tinos-700-normal.woff2 b/extension/lib/fonts/tinos-700-normal.woff2 similarity index 100% rename from lib/fonts/tinos-700-normal.woff2 rename to extension/lib/fonts/tinos-700-normal.woff2 diff --git a/lib/gallery.js b/extension/lib/gallery.js similarity index 100% rename from lib/gallery.js rename to extension/lib/gallery.js diff --git a/lib/gcal.js b/extension/lib/gcal.js similarity index 100% rename from lib/gcal.js rename to extension/lib/gcal.js diff --git a/lib/glsl.js b/extension/lib/glsl.js similarity index 100% rename from lib/glsl.js rename to extension/lib/glsl.js diff --git a/lib/gpa.js b/extension/lib/gpa.js similarity index 100% rename from lib/gpa.js rename to extension/lib/gpa.js diff --git a/lib/gradecalc.js b/extension/lib/gradecalc.js similarity index 100% rename from lib/gradecalc.js rename to extension/lib/gradecalc.js diff --git a/lib/grades.js b/extension/lib/grades.js similarity index 100% rename from lib/grades.js rename to extension/lib/grades.js diff --git a/lib/gradescope.js b/extension/lib/gradescope.js similarity index 100% rename from lib/gradescope.js rename to extension/lib/gradescope.js diff --git a/lib/harper/BinaryModule-S5UABkWQ.js b/extension/lib/harper/BinaryModule-S5UABkWQ.js similarity index 100% rename from lib/harper/BinaryModule-S5UABkWQ.js rename to extension/lib/harper/BinaryModule-S5UABkWQ.js diff --git a/lib/harper/LICENSE b/extension/lib/harper/LICENSE similarity index 100% rename from lib/harper/LICENSE rename to extension/lib/harper/LICENSE diff --git a/lib/harper/harper_wasm_slim_bg.wasm b/extension/lib/harper/harper_wasm_slim_bg.wasm similarity index 100% rename from lib/harper/harper_wasm_slim_bg.wasm rename to extension/lib/harper/harper_wasm_slim_bg.wasm diff --git a/lib/harper/index.js b/extension/lib/harper/index.js similarity index 100% rename from lib/harper/index.js rename to extension/lib/harper/index.js diff --git a/lib/harper/shared.js b/extension/lib/harper/shared.js similarity index 100% rename from lib/harper/shared.js rename to extension/lib/harper/shared.js diff --git a/lib/harper/slimBinary.js b/extension/lib/harper/slimBinary.js similarity index 100% rename from lib/harper/slimBinary.js rename to extension/lib/harper/slimBinary.js diff --git a/lib/history.js b/extension/lib/history.js similarity index 100% rename from lib/history.js rename to extension/lib/history.js diff --git a/lib/i18n.js b/extension/lib/i18n.js similarity index 100% rename from lib/i18n.js rename to extension/lib/i18n.js diff --git a/lib/ics.js b/extension/lib/ics.js similarity index 100% rename from lib/ics.js rename to extension/lib/ics.js diff --git a/lib/ink.js b/extension/lib/ink.js similarity index 100% rename from lib/ink.js rename to extension/lib/ink.js diff --git a/lib/insights.js b/extension/lib/insights.js similarity index 100% rename from lib/insights.js rename to extension/lib/insights.js diff --git a/lib/lmsq.js b/extension/lib/lmsq.js similarity index 100% rename from lib/lmsq.js rename to extension/lib/lmsq.js diff --git a/lib/lookparts.js b/extension/lib/lookparts.js similarity index 100% rename from lib/lookparts.js rename to extension/lib/lookparts.js diff --git a/lib/looks.js b/extension/lib/looks.js similarity index 100% rename from lib/looks.js rename to extension/lib/looks.js diff --git a/lib/lookset.js b/extension/lib/lookset.js similarity index 100% rename from lib/lookset.js rename to extension/lib/lookset.js diff --git a/lib/lookx.js b/extension/lib/lookx.js similarity index 100% rename from lib/lookx.js rename to extension/lib/lookx.js diff --git a/lib/lottie/LICENSE b/extension/lib/lottie/LICENSE similarity index 100% rename from lib/lottie/LICENSE rename to extension/lib/lottie/LICENSE diff --git a/lib/lottie/lottie_light_canvas.js b/extension/lib/lottie/lottie_light_canvas.js similarity index 100% rename from lib/lottie/lottie_light_canvas.js rename to extension/lib/lottie/lottie_light_canvas.js diff --git a/lib/mcp/approve-page.js b/extension/lib/mcp/approve-page.js similarity index 100% rename from lib/mcp/approve-page.js rename to extension/lib/mcp/approve-page.js diff --git a/lib/mcp/approve.css b/extension/lib/mcp/approve.css similarity index 100% rename from lib/mcp/approve.css rename to extension/lib/mcp/approve.css diff --git a/lib/mcp/approve.html b/extension/lib/mcp/approve.html similarity index 100% rename from lib/mcp/approve.html rename to extension/lib/mcp/approve.html diff --git a/lib/mcp/approve.js b/extension/lib/mcp/approve.js similarity index 100% rename from lib/mcp/approve.js rename to extension/lib/mcp/approve.js diff --git a/lib/mcp/calls.js b/extension/lib/mcp/calls.js similarity index 100% rename from lib/mcp/calls.js rename to extension/lib/mcp/calls.js diff --git a/lib/mcp/grants.js b/extension/lib/mcp/grants.js similarity index 100% rename from lib/mcp/grants.js rename to extension/lib/mcp/grants.js diff --git a/lib/mcp/modroute.js b/extension/lib/mcp/modroute.js similarity index 100% rename from lib/mcp/modroute.js rename to extension/lib/mcp/modroute.js diff --git a/lib/mcp/registry.js b/extension/lib/mcp/registry.js similarity index 100% rename from lib/mcp/registry.js rename to extension/lib/mcp/registry.js diff --git a/lib/mcp/tab.js b/extension/lib/mcp/tab.js similarity index 100% rename from lib/mcp/tab.js rename to extension/lib/mcp/tab.js diff --git a/lib/mcp/tabs.js b/extension/lib/mcp/tabs.js similarity index 100% rename from lib/mcp/tabs.js rename to extension/lib/mcp/tabs.js diff --git a/lib/mcp/tools-assign.js b/extension/lib/mcp/tools-assign.js similarity index 100% rename from lib/mcp/tools-assign.js rename to extension/lib/mcp/tools-assign.js diff --git a/lib/mcp/tools-grades.js b/extension/lib/mcp/tools-grades.js similarity index 100% rename from lib/mcp/tools-grades.js rename to extension/lib/mcp/tools-grades.js diff --git a/lib/mcp/tools-study.js b/extension/lib/mcp/tools-study.js similarity index 100% rename from lib/mcp/tools-study.js rename to extension/lib/mcp/tools-study.js diff --git a/lib/mcp/wire.js b/extension/lib/mcp/wire.js similarity index 100% rename from lib/mcp/wire.js rename to extension/lib/mcp/wire.js diff --git a/lib/mcp/writer.js b/extension/lib/mcp/writer.js similarity index 100% rename from lib/mcp/writer.js rename to extension/lib/mcp/writer.js diff --git a/lib/media.js b/extension/lib/media.js similarity index 100% rename from lib/media.js rename to extension/lib/media.js diff --git a/lib/modapi.js b/extension/lib/modapi.js similarity index 100% rename from lib/modapi.js rename to extension/lib/modapi.js diff --git a/lib/modbroker.js b/extension/lib/modbroker.js similarity index 100% rename from lib/modbroker.js rename to extension/lib/modbroker.js diff --git a/lib/modbundle.js b/extension/lib/modbundle.js similarity index 100% rename from lib/modbundle.js rename to extension/lib/modbundle.js diff --git a/lib/modchannel.js b/extension/lib/modchannel.js similarity index 100% rename from lib/modchannel.js rename to extension/lib/modchannel.js diff --git a/lib/modtools.js b/extension/lib/modtools.js similarity index 100% rename from lib/modtools.js rename to extension/lib/modtools.js diff --git a/lib/modwidget.js b/extension/lib/modwidget.js similarity index 100% rename from lib/modwidget.js rename to extension/lib/modwidget.js diff --git a/lib/moodle.js b/extension/lib/moodle.js similarity index 100% rename from lib/moodle.js rename to extension/lib/moodle.js diff --git a/lib/motion.js b/extension/lib/motion.js similarity index 100% rename from lib/motion.js rename to extension/lib/motion.js diff --git a/lib/motionx.js b/extension/lib/motionx.js similarity index 100% rename from lib/motionx.js rename to extension/lib/motionx.js diff --git a/lib/nav-items.js b/extension/lib/nav-items.js similarity index 100% rename from lib/nav-items.js rename to extension/lib/nav-items.js diff --git a/lib/nav-store.js b/extension/lib/nav-store.js similarity index 100% rename from lib/nav-store.js rename to extension/lib/nav-store.js diff --git a/lib/nav.js b/extension/lib/nav.js similarity index 100% rename from lib/nav.js rename to extension/lib/nav.js diff --git a/lib/navbar.js b/extension/lib/navbar.js similarity index 100% rename from lib/navbar.js rename to extension/lib/navbar.js diff --git a/lib/navlayout.js b/extension/lib/navlayout.js similarity index 100% rename from lib/navlayout.js rename to extension/lib/navlayout.js diff --git a/lib/news.js b/extension/lib/news.js similarity index 100% rename from lib/news.js rename to extension/lib/news.js diff --git a/lib/notes.js b/extension/lib/notes.js similarity index 100% rename from lib/notes.js rename to extension/lib/notes.js diff --git a/lib/oauth.js b/extension/lib/oauth.js similarity index 100% rename from lib/oauth.js rename to extension/lib/oauth.js diff --git a/lib/ocr.js b/extension/lib/ocr.js similarity index 100% rename from lib/ocr.js rename to extension/lib/ocr.js diff --git a/lib/page.js b/extension/lib/page.js similarity index 100% rename from lib/page.js rename to extension/lib/page.js diff --git a/lib/parts.js b/extension/lib/parts.js similarity index 100% rename from lib/parts.js rename to extension/lib/parts.js diff --git a/lib/pdf-doc.js b/extension/lib/pdf-doc.js similarity index 100% rename from lib/pdf-doc.js rename to extension/lib/pdf-doc.js diff --git a/lib/pdf-l10n.js b/extension/lib/pdf-l10n.js similarity index 100% rename from lib/pdf-l10n.js rename to extension/lib/pdf-l10n.js diff --git a/lib/pdfjs/LICENSE b/extension/lib/pdfjs/LICENSE similarity index 100% rename from lib/pdfjs/LICENSE rename to extension/lib/pdfjs/LICENSE diff --git a/lib/pdfjs/LICENSE_BROTLI b/extension/lib/pdfjs/LICENSE_BROTLI similarity index 100% rename from lib/pdfjs/LICENSE_BROTLI rename to extension/lib/pdfjs/LICENSE_BROTLI diff --git a/lib/pdfjs/LICENSE_CORE_JS b/extension/lib/pdfjs/LICENSE_CORE_JS similarity index 100% rename from lib/pdfjs/LICENSE_CORE_JS rename to extension/lib/pdfjs/LICENSE_CORE_JS diff --git a/lib/pdfjs/LICENSE_JBIG2 b/extension/lib/pdfjs/LICENSE_JBIG2 similarity index 100% rename from lib/pdfjs/LICENSE_JBIG2 rename to extension/lib/pdfjs/LICENSE_JBIG2 diff --git a/lib/pdfjs/LICENSE_MPL b/extension/lib/pdfjs/LICENSE_MPL similarity index 100% rename from lib/pdfjs/LICENSE_MPL rename to extension/lib/pdfjs/LICENSE_MPL diff --git a/lib/pdfjs/LICENSE_OPENJPEG b/extension/lib/pdfjs/LICENSE_OPENJPEG similarity index 100% rename from lib/pdfjs/LICENSE_OPENJPEG rename to extension/lib/pdfjs/LICENSE_OPENJPEG diff --git a/lib/pdfjs/LICENSE_PDFJS_JBIG2 b/extension/lib/pdfjs/LICENSE_PDFJS_JBIG2 similarity index 100% rename from lib/pdfjs/LICENSE_PDFJS_JBIG2 rename to extension/lib/pdfjs/LICENSE_PDFJS_JBIG2 diff --git a/lib/pdfjs/LICENSE_PDFJS_OPENJPEG b/extension/lib/pdfjs/LICENSE_PDFJS_OPENJPEG similarity index 100% rename from lib/pdfjs/LICENSE_PDFJS_OPENJPEG rename to extension/lib/pdfjs/LICENSE_PDFJS_OPENJPEG diff --git a/lib/pdfjs/brotli-dictionary.bin b/extension/lib/pdfjs/brotli-dictionary.bin similarity index 100% rename from lib/pdfjs/brotli-dictionary.bin rename to extension/lib/pdfjs/brotli-dictionary.bin diff --git a/lib/pdfjs/images/altText_add.svg b/extension/lib/pdfjs/images/altText_add.svg similarity index 100% rename from lib/pdfjs/images/altText_add.svg rename to extension/lib/pdfjs/images/altText_add.svg diff --git a/lib/pdfjs/images/altText_disclaimer.svg b/extension/lib/pdfjs/images/altText_disclaimer.svg similarity index 100% rename from lib/pdfjs/images/altText_disclaimer.svg rename to extension/lib/pdfjs/images/altText_disclaimer.svg diff --git a/lib/pdfjs/images/altText_done.svg b/extension/lib/pdfjs/images/altText_done.svg similarity index 100% rename from lib/pdfjs/images/altText_done.svg rename to extension/lib/pdfjs/images/altText_done.svg diff --git a/lib/pdfjs/images/altText_spinner.svg b/extension/lib/pdfjs/images/altText_spinner.svg similarity index 100% rename from lib/pdfjs/images/altText_spinner.svg rename to extension/lib/pdfjs/images/altText_spinner.svg diff --git a/lib/pdfjs/images/altText_warning.svg b/extension/lib/pdfjs/images/altText_warning.svg similarity index 100% rename from lib/pdfjs/images/altText_warning.svg rename to extension/lib/pdfjs/images/altText_warning.svg diff --git a/lib/pdfjs/images/checkmark.svg b/extension/lib/pdfjs/images/checkmark.svg similarity index 100% rename from lib/pdfjs/images/checkmark.svg rename to extension/lib/pdfjs/images/checkmark.svg diff --git a/lib/pdfjs/images/comment-closeButton.svg b/extension/lib/pdfjs/images/comment-closeButton.svg similarity index 100% rename from lib/pdfjs/images/comment-closeButton.svg rename to extension/lib/pdfjs/images/comment-closeButton.svg diff --git a/lib/pdfjs/images/comment-popup-editButton.svg b/extension/lib/pdfjs/images/comment-popup-editButton.svg similarity index 100% rename from lib/pdfjs/images/comment-popup-editButton.svg rename to extension/lib/pdfjs/images/comment-popup-editButton.svg diff --git a/lib/pdfjs/images/cursor-editorFreeHighlight.svg b/extension/lib/pdfjs/images/cursor-editorFreeHighlight.svg similarity index 100% rename from lib/pdfjs/images/cursor-editorFreeHighlight.svg rename to extension/lib/pdfjs/images/cursor-editorFreeHighlight.svg diff --git a/lib/pdfjs/images/cursor-editorFreeText.svg b/extension/lib/pdfjs/images/cursor-editorFreeText.svg similarity index 100% rename from lib/pdfjs/images/cursor-editorFreeText.svg rename to extension/lib/pdfjs/images/cursor-editorFreeText.svg diff --git a/lib/pdfjs/images/cursor-editorInk.svg b/extension/lib/pdfjs/images/cursor-editorInk.svg similarity index 100% rename from lib/pdfjs/images/cursor-editorInk.svg rename to extension/lib/pdfjs/images/cursor-editorInk.svg diff --git a/lib/pdfjs/images/cursor-editorTextHighlight.svg b/extension/lib/pdfjs/images/cursor-editorTextHighlight.svg similarity index 100% rename from lib/pdfjs/images/cursor-editorTextHighlight.svg rename to extension/lib/pdfjs/images/cursor-editorTextHighlight.svg diff --git a/lib/pdfjs/images/editor-toolbar-delete.svg b/extension/lib/pdfjs/images/editor-toolbar-delete.svg similarity index 100% rename from lib/pdfjs/images/editor-toolbar-delete.svg rename to extension/lib/pdfjs/images/editor-toolbar-delete.svg diff --git a/lib/pdfjs/images/editor-toolbar-edit.svg b/extension/lib/pdfjs/images/editor-toolbar-edit.svg similarity index 100% rename from lib/pdfjs/images/editor-toolbar-edit.svg rename to extension/lib/pdfjs/images/editor-toolbar-edit.svg diff --git a/lib/pdfjs/images/loading-icon.gif b/extension/lib/pdfjs/images/loading-icon.gif similarity index 100% rename from lib/pdfjs/images/loading-icon.gif rename to extension/lib/pdfjs/images/loading-icon.gif diff --git a/lib/pdfjs/images/messageBar_closingButton.svg b/extension/lib/pdfjs/images/messageBar_closingButton.svg similarity index 100% rename from lib/pdfjs/images/messageBar_closingButton.svg rename to extension/lib/pdfjs/images/messageBar_closingButton.svg diff --git a/lib/pdfjs/images/messageBar_info.svg b/extension/lib/pdfjs/images/messageBar_info.svg similarity index 100% rename from lib/pdfjs/images/messageBar_info.svg rename to extension/lib/pdfjs/images/messageBar_info.svg diff --git a/lib/pdfjs/images/messageBar_warning.svg b/extension/lib/pdfjs/images/messageBar_warning.svg similarity index 100% rename from lib/pdfjs/images/messageBar_warning.svg rename to extension/lib/pdfjs/images/messageBar_warning.svg diff --git a/lib/pdfjs/images/pages_closeButton.svg b/extension/lib/pdfjs/images/pages_closeButton.svg similarity index 100% rename from lib/pdfjs/images/pages_closeButton.svg rename to extension/lib/pdfjs/images/pages_closeButton.svg diff --git a/lib/pdfjs/images/pages_selected.svg b/extension/lib/pdfjs/images/pages_selected.svg similarity index 100% rename from lib/pdfjs/images/pages_selected.svg rename to extension/lib/pdfjs/images/pages_selected.svg diff --git a/lib/pdfjs/images/pages_viewArrow.svg b/extension/lib/pdfjs/images/pages_viewArrow.svg similarity index 100% rename from lib/pdfjs/images/pages_viewArrow.svg rename to extension/lib/pdfjs/images/pages_viewArrow.svg diff --git a/lib/pdfjs/images/pages_viewButton.svg b/extension/lib/pdfjs/images/pages_viewButton.svg similarity index 100% rename from lib/pdfjs/images/pages_viewButton.svg rename to extension/lib/pdfjs/images/pages_viewButton.svg diff --git a/lib/pdfjs/images/toolbarButton-currentOutlineItem.svg b/extension/lib/pdfjs/images/toolbarButton-currentOutlineItem.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-currentOutlineItem.svg rename to extension/lib/pdfjs/images/toolbarButton-currentOutlineItem.svg diff --git a/lib/pdfjs/images/toolbarButton-editorHighlight.svg b/extension/lib/pdfjs/images/toolbarButton-editorHighlight.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-editorHighlight.svg rename to extension/lib/pdfjs/images/toolbarButton-editorHighlight.svg diff --git a/lib/pdfjs/images/toolbarButton-menuArrow.svg b/extension/lib/pdfjs/images/toolbarButton-menuArrow.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-menuArrow.svg rename to extension/lib/pdfjs/images/toolbarButton-menuArrow.svg diff --git a/lib/pdfjs/images/toolbarButton-pageDown.svg b/extension/lib/pdfjs/images/toolbarButton-pageDown.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-pageDown.svg rename to extension/lib/pdfjs/images/toolbarButton-pageDown.svg diff --git a/lib/pdfjs/images/toolbarButton-viewAttachments.svg b/extension/lib/pdfjs/images/toolbarButton-viewAttachments.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-viewAttachments.svg rename to extension/lib/pdfjs/images/toolbarButton-viewAttachments.svg diff --git a/lib/pdfjs/images/toolbarButton-viewLayers.svg b/extension/lib/pdfjs/images/toolbarButton-viewLayers.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-viewLayers.svg rename to extension/lib/pdfjs/images/toolbarButton-viewLayers.svg diff --git a/lib/pdfjs/images/toolbarButton-viewOutline.svg b/extension/lib/pdfjs/images/toolbarButton-viewOutline.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-viewOutline.svg rename to extension/lib/pdfjs/images/toolbarButton-viewOutline.svg diff --git a/lib/pdfjs/images/toolbarButton-zoomIn.svg b/extension/lib/pdfjs/images/toolbarButton-zoomIn.svg similarity index 100% rename from lib/pdfjs/images/toolbarButton-zoomIn.svg rename to extension/lib/pdfjs/images/toolbarButton-zoomIn.svg diff --git a/lib/pdfjs/images/treeitem-collapsed.svg b/extension/lib/pdfjs/images/treeitem-collapsed.svg similarity index 100% rename from lib/pdfjs/images/treeitem-collapsed.svg rename to extension/lib/pdfjs/images/treeitem-collapsed.svg diff --git a/lib/pdfjs/images/treeitem-expanded.svg b/extension/lib/pdfjs/images/treeitem-expanded.svg similarity index 100% rename from lib/pdfjs/images/treeitem-expanded.svg rename to extension/lib/pdfjs/images/treeitem-expanded.svg diff --git a/lib/pdfjs/jbig2.wasm b/extension/lib/pdfjs/jbig2.wasm similarity index 100% rename from lib/pdfjs/jbig2.wasm rename to extension/lib/pdfjs/jbig2.wasm diff --git a/lib/pdfjs/openjpeg.wasm b/extension/lib/pdfjs/openjpeg.wasm similarity index 100% rename from lib/pdfjs/openjpeg.wasm rename to extension/lib/pdfjs/openjpeg.wasm diff --git a/lib/pdfjs/pdf.mjs b/extension/lib/pdfjs/pdf.mjs similarity index 100% rename from lib/pdfjs/pdf.mjs rename to extension/lib/pdfjs/pdf.mjs diff --git a/lib/pdfjs/pdf.worker.mjs b/extension/lib/pdfjs/pdf.worker.mjs similarity index 100% rename from lib/pdfjs/pdf.worker.mjs rename to extension/lib/pdfjs/pdf.worker.mjs diff --git a/lib/pdfjs/pdf_viewer.css b/extension/lib/pdfjs/pdf_viewer.css similarity index 100% rename from lib/pdfjs/pdf_viewer.css rename to extension/lib/pdfjs/pdf_viewer.css diff --git a/lib/pdfjs/pdf_viewer.mjs b/extension/lib/pdfjs/pdf_viewer.mjs similarity index 100% rename from lib/pdfjs/pdf_viewer.mjs rename to extension/lib/pdfjs/pdf_viewer.mjs diff --git a/lib/pet.js b/extension/lib/pet.js similarity index 100% rename from lib/pet.js rename to extension/lib/pet.js diff --git a/lib/petbrain.js b/extension/lib/petbrain.js similarity index 100% rename from lib/petbrain.js rename to extension/lib/petbrain.js diff --git a/lib/petlibrary.js b/extension/lib/petlibrary.js similarity index 100% rename from lib/petlibrary.js rename to extension/lib/petlibrary.js diff --git a/lib/petphysics.js b/extension/lib/petphysics.js similarity index 100% rename from lib/petphysics.js rename to extension/lib/petphysics.js diff --git a/lib/petplayer.js b/extension/lib/petplayer.js similarity index 100% rename from lib/petplayer.js rename to extension/lib/petplayer.js diff --git a/lib/petsprites.js b/extension/lib/petsprites.js similarity index 100% rename from lib/petsprites.js rename to extension/lib/petsprites.js diff --git a/lib/pm.LICENSE b/extension/lib/pm.LICENSE similarity index 100% rename from lib/pm.LICENSE rename to extension/lib/pm.LICENSE diff --git a/lib/pm.css b/extension/lib/pm.css similarity index 100% rename from lib/pm.css rename to extension/lib/pm.css diff --git a/lib/pm.js b/extension/lib/pm.js similarity index 100% rename from lib/pm.js rename to extension/lib/pm.js diff --git a/lib/popup.js b/extension/lib/popup.js similarity index 100% rename from lib/popup.js rename to extension/lib/popup.js diff --git a/lib/pptx-geom.js b/extension/lib/pptx-geom.js similarity index 100% rename from lib/pptx-geom.js rename to extension/lib/pptx-geom.js diff --git a/lib/pptx.js b/extension/lib/pptx.js similarity index 100% rename from lib/pptx.js rename to extension/lib/pptx.js diff --git a/lib/relay.js b/extension/lib/relay.js similarity index 100% rename from lib/relay.js rename to extension/lib/relay.js diff --git a/lib/remind.js b/extension/lib/remind.js similarity index 100% rename from lib/remind.js rename to extension/lib/remind.js diff --git a/lib/rubric.js b/extension/lib/rubric.js similarity index 100% rename from lib/rubric.js rename to extension/lib/rubric.js diff --git a/lib/scene-host.js b/extension/lib/scene-host.js similarity index 100% rename from lib/scene-host.js rename to extension/lib/scene-host.js diff --git a/lib/scene-media.js b/extension/lib/scene-media.js similarity index 100% rename from lib/scene-media.js rename to extension/lib/scene-media.js diff --git a/lib/scene.js b/extension/lib/scene.js similarity index 100% rename from lib/scene.js rename to extension/lib/scene.js diff --git a/lib/search.js b/extension/lib/search.js similarity index 100% rename from lib/search.js rename to extension/lib/search.js diff --git a/lib/seek.js b/extension/lib/seek.js similarity index 100% rename from lib/seek.js rename to extension/lib/seek.js diff --git a/lib/sessions-ui.js b/extension/lib/sessions-ui.js similarity index 100% rename from lib/sessions-ui.js rename to extension/lib/sessions-ui.js diff --git a/lib/sessions.js b/extension/lib/sessions.js similarity index 100% rename from lib/sessions.js rename to extension/lib/sessions.js diff --git a/lib/share.js b/extension/lib/share.js similarity index 100% rename from lib/share.js rename to extension/lib/share.js diff --git a/lib/sharing.js b/extension/lib/sharing.js similarity index 100% rename from lib/sharing.js rename to extension/lib/sharing.js diff --git a/lib/sheet-calc.js b/extension/lib/sheet-calc.js similarity index 100% rename from lib/sheet-calc.js rename to extension/lib/sheet-calc.js diff --git a/lib/sheet-chart-xml.js b/extension/lib/sheet-chart-xml.js similarity index 100% rename from lib/sheet-chart-xml.js rename to extension/lib/sheet-chart-xml.js diff --git a/lib/sheet-chart.js b/extension/lib/sheet-chart.js similarity index 100% rename from lib/sheet-chart.js rename to extension/lib/sheet-chart.js diff --git a/lib/sheet-fn-database.js b/extension/lib/sheet-fn-database.js similarity index 100% rename from lib/sheet-fn-database.js rename to extension/lib/sheet-fn-database.js diff --git a/lib/sheet-fn-date.js b/extension/lib/sheet-fn-date.js similarity index 100% rename from lib/sheet-fn-date.js rename to extension/lib/sheet-fn-date.js diff --git a/lib/sheet-fn-financial.js b/extension/lib/sheet-fn-financial.js similarity index 100% rename from lib/sheet-fn-financial.js rename to extension/lib/sheet-fn-financial.js diff --git a/lib/sheet-fn-financial2.js b/extension/lib/sheet-fn-financial2.js similarity index 100% rename from lib/sheet-fn-financial2.js rename to extension/lib/sheet-fn-financial2.js diff --git a/lib/sheet-fn-info.js b/extension/lib/sheet-fn-info.js similarity index 100% rename from lib/sheet-fn-info.js rename to extension/lib/sheet-fn-info.js diff --git a/lib/sheet-fn-logical.js b/extension/lib/sheet-fn-logical.js similarity index 100% rename from lib/sheet-fn-logical.js rename to extension/lib/sheet-fn-logical.js diff --git a/lib/sheet-fn-logical2.js b/extension/lib/sheet-fn-logical2.js similarity index 100% rename from lib/sheet-fn-logical2.js rename to extension/lib/sheet-fn-logical2.js diff --git a/lib/sheet-fn-lookup.js b/extension/lib/sheet-fn-lookup.js similarity index 100% rename from lib/sheet-fn-lookup.js rename to extension/lib/sheet-fn-lookup.js diff --git a/lib/sheet-fn-lookup2.js b/extension/lib/sheet-fn-lookup2.js similarity index 100% rename from lib/sheet-fn-lookup2.js rename to extension/lib/sheet-fn-lookup2.js diff --git a/lib/sheet-fn-math.js b/extension/lib/sheet-fn-math.js similarity index 100% rename from lib/sheet-fn-math.js rename to extension/lib/sheet-fn-math.js diff --git a/lib/sheet-fn-math2.js b/extension/lib/sheet-fn-math2.js similarity index 100% rename from lib/sheet-fn-math2.js rename to extension/lib/sheet-fn-math2.js diff --git a/lib/sheet-fn-math3.js b/extension/lib/sheet-fn-math3.js similarity index 100% rename from lib/sheet-fn-math3.js rename to extension/lib/sheet-fn-math3.js diff --git a/lib/sheet-fn-stats.js b/extension/lib/sheet-fn-stats.js similarity index 100% rename from lib/sheet-fn-stats.js rename to extension/lib/sheet-fn-stats.js diff --git a/lib/sheet-fn-stats2.js b/extension/lib/sheet-fn-stats2.js similarity index 100% rename from lib/sheet-fn-stats2.js rename to extension/lib/sheet-fn-stats2.js diff --git a/lib/sheet-fn-text.js b/extension/lib/sheet-fn-text.js similarity index 100% rename from lib/sheet-fn-text.js rename to extension/lib/sheet-fn-text.js diff --git a/lib/sheet-fn-text2.js b/extension/lib/sheet-fn-text2.js similarity index 100% rename from lib/sheet-fn-text2.js rename to extension/lib/sheet-fn-text2.js diff --git a/lib/sheet-formula.js b/extension/lib/sheet-formula.js similarity index 100% rename from lib/sheet-formula.js rename to extension/lib/sheet-formula.js diff --git a/lib/sheet-model.js b/extension/lib/sheet-model.js similarity index 100% rename from lib/sheet-model.js rename to extension/lib/sheet-model.js diff --git a/lib/sheet-numfmt.js b/extension/lib/sheet-numfmt.js similarity index 100% rename from lib/sheet-numfmt.js rename to extension/lib/sheet-numfmt.js diff --git a/lib/sheet-xlsx.js b/extension/lib/sheet-xlsx.js similarity index 100% rename from lib/sheet-xlsx.js rename to extension/lib/sheet-xlsx.js diff --git a/lib/sidebar.js b/extension/lib/sidebar.js similarity index 100% rename from lib/sidebar.js rename to extension/lib/sidebar.js diff --git a/lib/site.js b/extension/lib/site.js similarity index 100% rename from lib/site.js rename to extension/lib/site.js diff --git a/lib/slides-render.js b/extension/lib/slides-render.js similarity index 100% rename from lib/slides-render.js rename to extension/lib/slides-render.js diff --git a/lib/stream.js b/extension/lib/stream.js similarity index 100% rename from lib/stream.js rename to extension/lib/stream.js diff --git a/lib/surfaces.js b/extension/lib/surfaces.js similarity index 100% rename from lib/surfaces.js rename to extension/lib/surfaces.js diff --git a/lib/syllabus-ai.js b/extension/lib/syllabus-ai.js similarity index 100% rename from lib/syllabus-ai.js rename to extension/lib/syllabus-ai.js diff --git a/lib/syllabus.js b/extension/lib/syllabus.js similarity index 100% rename from lib/syllabus.js rename to extension/lib/syllabus.js diff --git a/lib/tesseract/deu.traineddata.gz b/extension/lib/tesseract/deu.traineddata.gz similarity index 100% rename from lib/tesseract/deu.traineddata.gz rename to extension/lib/tesseract/deu.traineddata.gz diff --git a/lib/tesseract/eng.traineddata.gz b/extension/lib/tesseract/eng.traineddata.gz similarity index 100% rename from lib/tesseract/eng.traineddata.gz rename to extension/lib/tesseract/eng.traineddata.gz diff --git a/lib/tesseract/fra.traineddata.gz b/extension/lib/tesseract/fra.traineddata.gz similarity index 100% rename from lib/tesseract/fra.traineddata.gz rename to extension/lib/tesseract/fra.traineddata.gz diff --git a/lib/tesseract/nld.traineddata.gz b/extension/lib/tesseract/nld.traineddata.gz similarity index 100% rename from lib/tesseract/nld.traineddata.gz rename to extension/lib/tesseract/nld.traineddata.gz diff --git a/lib/tesseract/por.traineddata.gz b/extension/lib/tesseract/por.traineddata.gz similarity index 100% rename from lib/tesseract/por.traineddata.gz rename to extension/lib/tesseract/por.traineddata.gz diff --git a/lib/tesseract/spa.traineddata.gz b/extension/lib/tesseract/spa.traineddata.gz similarity index 100% rename from lib/tesseract/spa.traineddata.gz rename to extension/lib/tesseract/spa.traineddata.gz diff --git a/lib/tesseract/tessdata.LICENSE b/extension/lib/tesseract/tessdata.LICENSE similarity index 100% rename from lib/tesseract/tessdata.LICENSE rename to extension/lib/tesseract/tessdata.LICENSE diff --git a/lib/tesseract/tesseract-core-simd-lstm.js b/extension/lib/tesseract/tesseract-core-simd-lstm.js similarity index 100% rename from lib/tesseract/tesseract-core-simd-lstm.js rename to extension/lib/tesseract/tesseract-core-simd-lstm.js diff --git a/lib/tesseract/tesseract-core-simd-lstm.wasm b/extension/lib/tesseract/tesseract-core-simd-lstm.wasm similarity index 100% rename from lib/tesseract/tesseract-core-simd-lstm.wasm rename to extension/lib/tesseract/tesseract-core-simd-lstm.wasm diff --git a/lib/tesseract/tesseract-core.LICENSE b/extension/lib/tesseract/tesseract-core.LICENSE similarity index 100% rename from lib/tesseract/tesseract-core.LICENSE rename to extension/lib/tesseract/tesseract-core.LICENSE diff --git a/lib/three.LICENSE b/extension/lib/three.LICENSE similarity index 100% rename from lib/three.LICENSE rename to extension/lib/three.LICENSE diff --git a/lib/three.js b/extension/lib/three.js similarity index 100% rename from lib/three.js rename to extension/lib/three.js diff --git a/lib/todo.js b/extension/lib/todo.js similarity index 100% rename from lib/todo.js rename to extension/lib/todo.js diff --git a/lib/ultra.js b/extension/lib/ultra.js similarity index 100% rename from lib/ultra.js rename to extension/lib/ultra.js diff --git a/lib/widgets.js b/extension/lib/widgets.js similarity index 100% rename from lib/widgets.js rename to extension/lib/widgets.js diff --git a/lib/writer-bibliography.js b/extension/lib/writer-bibliography.js similarity index 100% rename from lib/writer-bibliography.js rename to extension/lib/writer-bibliography.js diff --git a/lib/writer-cite.js b/extension/lib/writer-cite.js similarity index 100% rename from lib/writer-cite.js rename to extension/lib/writer-cite.js diff --git a/lib/writer-color.js b/extension/lib/writer-color.js similarity index 100% rename from lib/writer-color.js rename to extension/lib/writer-color.js diff --git a/lib/writer-context-assignment.js b/extension/lib/writer-context-assignment.js similarity index 100% rename from lib/writer-context-assignment.js rename to extension/lib/writer-context-assignment.js diff --git a/lib/writer-context-discussion.js b/extension/lib/writer-context-discussion.js similarity index 100% rename from lib/writer-context-discussion.js rename to extension/lib/writer-context-discussion.js diff --git a/lib/writer-docx.js b/extension/lib/writer-docx.js similarity index 100% rename from lib/writer-docx.js rename to extension/lib/writer-docx.js diff --git a/lib/writer-editor.js b/extension/lib/writer-editor.js similarity index 100% rename from lib/writer-editor.js rename to extension/lib/writer-editor.js diff --git a/lib/writer-export.js b/extension/lib/writer-export.js similarity index 100% rename from lib/writer-export.js rename to extension/lib/writer-export.js diff --git a/lib/writer-format.js b/extension/lib/writer-format.js similarity index 100% rename from lib/writer-format.js rename to extension/lib/writer-format.js diff --git a/lib/writer-issues.js b/extension/lib/writer-issues.js similarity index 100% rename from lib/writer-issues.js rename to extension/lib/writer-issues.js diff --git a/lib/writer-lookup.js b/extension/lib/writer-lookup.js similarity index 100% rename from lib/writer-lookup.js rename to extension/lib/writer-lookup.js diff --git a/lib/writer-main.js b/extension/lib/writer-main.js similarity index 100% rename from lib/writer-main.js rename to extension/lib/writer-main.js diff --git a/lib/writer-objects-docx.js b/extension/lib/writer-objects-docx.js similarity index 100% rename from lib/writer-objects-docx.js rename to extension/lib/writer-objects-docx.js diff --git a/lib/writer-objects.js b/extension/lib/writer-objects.js similarity index 100% rename from lib/writer-objects.js rename to extension/lib/writer-objects.js diff --git a/lib/writer-outline.js b/extension/lib/writer-outline.js similarity index 100% rename from lib/writer-outline.js rename to extension/lib/writer-outline.js diff --git a/lib/writer-paginate.js b/extension/lib/writer-paginate.js similarity index 100% rename from lib/writer-paginate.js rename to extension/lib/writer-paginate.js diff --git a/lib/writer-papers.js b/extension/lib/writer-papers.js similarity index 100% rename from lib/writer-papers.js rename to extension/lib/writer-papers.js diff --git a/lib/writer-proof.js b/extension/lib/writer-proof.js similarity index 100% rename from lib/writer-proof.js rename to extension/lib/writer-proof.js diff --git a/lib/writer-schema.js b/extension/lib/writer-schema.js similarity index 100% rename from lib/writer-schema.js rename to extension/lib/writer-schema.js diff --git a/lib/writer-settings.js b/extension/lib/writer-settings.js similarity index 100% rename from lib/writer-settings.js rename to extension/lib/writer-settings.js diff --git a/lib/writer-sources.js b/extension/lib/writer-sources.js similarity index 100% rename from lib/writer-sources.js rename to extension/lib/writer-sources.js diff --git a/lib/writer-styles.js b/extension/lib/writer-styles.js similarity index 100% rename from lib/writer-styles.js rename to extension/lib/writer-styles.js diff --git a/lib/writer-submit.js b/extension/lib/writer-submit.js similarity index 100% rename from lib/writer-submit.js rename to extension/lib/writer-submit.js diff --git a/lib/writer-target.js b/extension/lib/writer-target.js similarity index 100% rename from lib/writer-target.js rename to extension/lib/writer-target.js diff --git a/lib/writer-thumb.js b/extension/lib/writer-thumb.js similarity index 100% rename from lib/writer-thumb.js rename to extension/lib/writer-thumb.js diff --git a/lib/writer-tools.js b/extension/lib/writer-tools.js similarity index 100% rename from lib/writer-tools.js rename to extension/lib/writer-tools.js diff --git a/lib/writer-ultra.js b/extension/lib/writer-ultra.js similarity index 100% rename from lib/writer-ultra.js rename to extension/lib/writer-ultra.js diff --git a/lib/zip.js b/extension/lib/zip.js similarity index 100% rename from lib/zip.js rename to extension/lib/zip.js diff --git a/manifest.json b/extension/manifest.json similarity index 100% rename from manifest.json rename to extension/manifest.json diff --git a/mods/boot.js b/extension/mods/boot.js similarity index 100% rename from mods/boot.js rename to extension/mods/boot.js diff --git a/mods/host.html b/extension/mods/host.html similarity index 100% rename from mods/host.html rename to extension/mods/host.html diff --git a/mods/host.js b/extension/mods/host.js similarity index 100% rename from mods/host.js rename to extension/mods/host.js diff --git a/moodle.css b/extension/moodle.css similarity index 100% rename from moodle.css rename to extension/moodle.css diff --git a/motion.css b/extension/motion.css similarity index 100% rename from motion.css rename to extension/motion.css diff --git a/options.html b/extension/options.html similarity index 100% rename from options.html rename to extension/options.html diff --git a/options.js b/extension/options.js similarity index 100% rename from options.js rename to extension/options.js diff --git a/papers-motion.js b/extension/papers-motion.js similarity index 100% rename from papers-motion.js rename to extension/papers-motion.js diff --git a/papers.css b/extension/papers.css similarity index 100% rename from papers.css rename to extension/papers.css diff --git a/papers.html b/extension/papers.html similarity index 100% rename from papers.html rename to extension/papers.html diff --git a/papers.js b/extension/papers.js similarity index 100% rename from papers.js rename to extension/papers.js diff --git a/pdf-kind.css b/extension/pdf-kind.css similarity index 100% rename from pdf-kind.css rename to extension/pdf-kind.css diff --git a/pdf-kind.js b/extension/pdf-kind.js similarity index 100% rename from pdf-kind.js rename to extension/pdf-kind.js diff --git a/popup.html b/extension/popup.html similarity index 100% rename from popup.html rename to extension/popup.html diff --git a/popup.js b/extension/popup.js similarity index 100% rename from popup.js rename to extension/popup.js diff --git a/preview.html b/extension/preview.html similarity index 100% rename from preview.html rename to extension/preview.html diff --git a/reader.html b/extension/reader.html similarity index 100% rename from reader.html rename to extension/reader.html diff --git a/reader.js b/extension/reader.js similarity index 100% rename from reader.js rename to extension/reader.js diff --git a/sharing/quick.css b/extension/sharing/quick.css similarity index 100% rename from sharing/quick.css rename to extension/sharing/quick.css diff --git a/sharing/quick.html b/extension/sharing/quick.html similarity index 100% rename from sharing/quick.html rename to extension/sharing/quick.html diff --git a/sharing/quick.js b/extension/sharing/quick.js similarity index 100% rename from sharing/quick.js rename to extension/sharing/quick.js diff --git a/sheet-data.js b/extension/sheet-data.js similarity index 100% rename from sheet-data.js rename to extension/sheet-data.js diff --git a/sheet-grid.js b/extension/sheet-grid.js similarity index 100% rename from sheet-grid.js rename to extension/sheet-grid.js diff --git a/sheet-kind.js b/extension/sheet-kind.js similarity index 100% rename from sheet-kind.js rename to extension/sheet-kind.js diff --git a/sheet.css b/extension/sheet.css similarity index 100% rename from sheet.css rename to extension/sheet.css diff --git a/slides-kind.css b/extension/slides-kind.css similarity index 100% rename from slides-kind.css rename to extension/slides-kind.css diff --git a/slides-kind.js b/extension/slides-kind.js similarity index 100% rename from slides-kind.js rename to extension/slides-kind.js diff --git a/theme.css b/extension/theme.css similarity index 100% rename from theme.css rename to extension/theme.css diff --git a/ui.css b/extension/ui.css similarity index 100% rename from ui.css rename to extension/ui.css diff --git a/ui.js b/extension/ui.js similarity index 100% rename from ui.js rename to extension/ui.js diff --git a/writer-ai.css b/extension/writer-ai.css similarity index 100% rename from writer-ai.css rename to extension/writer-ai.css diff --git a/writer-ai.js b/extension/writer-ai.js similarity index 100% rename from writer-ai.js rename to extension/writer-ai.js diff --git a/writer-cite.css b/extension/writer-cite.css similarity index 100% rename from writer-cite.css rename to extension/writer-cite.css diff --git a/writer-cite.js b/extension/writer-cite.js similarity index 100% rename from writer-cite.js rename to extension/writer-cite.js diff --git a/writer-compose.css b/extension/writer-compose.css similarity index 100% rename from writer-compose.css rename to extension/writer-compose.css diff --git a/writer-compose.js b/extension/writer-compose.js similarity index 100% rename from writer-compose.js rename to extension/writer-compose.js diff --git a/writer-counts.js b/extension/writer-counts.js similarity index 100% rename from writer-counts.js rename to extension/writer-counts.js diff --git a/writer-edit.css b/extension/writer-edit.css similarity index 100% rename from writer-edit.css rename to extension/writer-edit.css diff --git a/writer-find.js b/extension/writer-find.js similarity index 100% rename from writer-find.js rename to extension/writer-find.js diff --git a/writer-import.css b/extension/writer-import.css similarity index 100% rename from writer-import.css rename to extension/writer-import.css diff --git a/writer-import.js b/extension/writer-import.js similarity index 100% rename from writer-import.js rename to extension/writer-import.js diff --git a/writer-kinds.js b/extension/writer-kinds.js similarity index 100% rename from writer-kinds.js rename to extension/writer-kinds.js diff --git a/writer-mcp.js b/extension/writer-mcp.js similarity index 100% rename from writer-mcp.js rename to extension/writer-mcp.js diff --git a/writer-objects.css b/extension/writer-objects.css similarity index 100% rename from writer-objects.css rename to extension/writer-objects.css diff --git a/writer-objects.js b/extension/writer-objects.js similarity index 100% rename from writer-objects.js rename to extension/writer-objects.js diff --git a/writer-paper.css b/extension/writer-paper.css similarity index 100% rename from writer-paper.css rename to extension/writer-paper.css diff --git a/writer-paper.js b/extension/writer-paper.js similarity index 100% rename from writer-paper.js rename to extension/writer-paper.js diff --git a/writer-paste.js b/extension/writer-paste.js similarity index 100% rename from writer-paste.js rename to extension/writer-paste.js diff --git a/writer-print.js b/extension/writer-print.js similarity index 100% rename from writer-print.js rename to extension/writer-print.js diff --git a/writer-proof.css b/extension/writer-proof.css similarity index 100% rename from writer-proof.css rename to extension/writer-proof.css diff --git a/writer-proof.js b/extension/writer-proof.js similarity index 100% rename from writer-proof.js rename to extension/writer-proof.js diff --git a/writer-ruler.css b/extension/writer-ruler.css similarity index 100% rename from writer-ruler.css rename to extension/writer-ruler.css diff --git a/writer-ruler.js b/extension/writer-ruler.js similarity index 100% rename from writer-ruler.js rename to extension/writer-ruler.js diff --git a/writer-submit.css b/extension/writer-submit.css similarity index 100% rename from writer-submit.css rename to extension/writer-submit.css diff --git a/writer-submit.js b/extension/writer-submit.js similarity index 100% rename from writer-submit.js rename to extension/writer-submit.js diff --git a/writer.css b/extension/writer.css similarity index 100% rename from writer.css rename to extension/writer.css diff --git a/writer.html b/extension/writer.html similarity index 100% rename from writer.html rename to extension/writer.html diff --git a/writer.js b/extension/writer.js similarity index 100% rename from writer.js rename to extension/writer.js diff --git a/notes/codex-review-ultra-perf.md b/notes/codex-review-ultra-perf.md deleted file mode 100644 index 479bdd4e9..000000000 --- a/notes/codex-review-ultra-perf.md +++ /dev/null @@ -1,61 +0,0 @@ -# Ultra performance review - -Baseline: `f39b3ed`. Scope: all Ultra startup/SPA observers, native sweeps, extension storage/load traffic and existing -page data reuse; actual unpacked headless extension on local synthetic responses. - -Confirmed causes, ranked by measured impact: - -1. **SPA route hiding:** local fixture replies still left native content hidden for sampled 256–474 ms. Remove the - separate route hold/reveal and its 50 ms timer. Keep the initial document-start sheet gate. -2. **Internal UI updates schedule native sweeps:** 30 widget text updates caused four whole-document native - course-color scans. Filter mutations inside owned roots, preserving native-parent removals and mixed batches. -3. **Successful empty lateness results are not recorded on unchanged grades:** the short sequence asked one - gradebook three times. Save the empty result before content comparison; preserve failure retries and minute refresh. - -Pending B7c retains eager nav/dependency loading, default first-paint text and planned-cache work. Accepted B7b widget -and B9 folder fixes are inherited through integration `47a3301`; they are not counted as this audit's fixes. The f39 -outline-only one-frame folder observer fix is already in the baseline and did not run away in this harness. - -Cold first paint was 40 ms and there were no long tasks in the baseline synthetic sample. That is no proof of an -owner cold-load problem. The owner live numbers supplied by the coordinator are peer-reported, not this review's -observations. No identity cache refactor is justified by the one measured `/users/me` read. - -Causal fixes are implemented and measured separately. SPA hiding is zero, the owned-update native scans fall 4→0, -and three native due-row status updates issue 3→0 extra gradebook reads. Each causal increment used one paired browser -repetition, separately from the final three paired integrated samples. The regressions failed before; final full-suite -and package/resource checks pass. The initial causal three-fix chain cut 2,804 Ultra bytes and 293 shared bytes. - -The later owner reload report adds lifecycle scope: preserve integration `07b8390`'s core/card invalid-context guards, -make `GZ_CTX.on()` notice invalidation before a body mutation, stop an orphaned Google sync timer and safely close a -Download all port keepalive. A scene now checks its caller's liveness before another animation frame and stops/disposes -its WebGL resources. Editor preview uses the same caller; standalone store promo behavior is preserved. Its regression -failed before because another frame was scheduled, then both lifecycle/still-frame tests passed. - -Reload/request fixes now pass 55 focused checks after seven failures on saved pre-fix source and the separate stale -`GZ_CTX.on()` failure. This includes late card storage, stale sticker construction/action, safe injection and real key -listeners, JSON auth redirects, Google orphan timer and Port keepalive teardown. Integration 07/a694 guards/budget are -merged, followed by accepted `47a3301`. Generic core `X.gate` and synchronous native-added-subtree dispatch are restored for B7c's pending -`gate: discoverNav`; Ultra's removed hold/mask/timer are not restored. Pending nav/planner/widget optimizations are not -claimed here. - -Final integrated browser evidence: three plain/shim pairs, zero SPA hiding, zero extra empty-late gradebook reads, -zero owned-update native scans, 14 fixture API reads per sample, and no page errors or non-fixture requests. Plain -Courses/outline/Grades settle medians are 512/622/514 ms, with a 500 ms measurement floor; plain first paint is -28–44 ms and no long tasks were reported. Six route replacements keep the isolated observer count at four; CDP-visible -document/window listener counts stay 0/13. The latter is a limited CDP view, not a census of isolated-world listeners. - -Plain bundled-pumpkin animation uses 22.8 ms of renderer task time over two seconds; shim samples draw 60 frames in -two seconds. This is not GPU time. A real extension reload leaves `runtime` and `ctx.on()` false before any native body -mutation; old-scene draw counts remain 76/76/76 across two 300 ms quiet windows and the following native mutation. - -Final full gate: **1,080 tests; 1,079 pass, zero fail, one existing news/version TODO**, 30.94 seconds, nice15/concurrency1. -The first full attempts exposed test-only valid-runtime mocks and a flush slice ending at the removed section marker; -those fixtures were adapted without changing their behavior assertions. Package succeeds; all 201 staged resources -are bundled by both Safari targets. No Safari runtime or live school/account claim is made. - -Final first scripts: Ultra **214,310**, Canvas **201,443**, Moodle **171,517** bytes. Versus accepted `47a3301` alone, -this work is **−2,331 Ultra / +180 Canvas / +180 Moodle**; versus f39, the total integrated difference is -−1,422/+355/+355. Owner-supplied budget changes are inherited; this branch does not edit a size baseline. - -Raw JSON and failed/passing suite receipts are retained under the session notes' `artifacts/codex-ultra-perf/` folder -(full path in the spike report). Throwaway extension/profile/package stages are removed; nothing owned is running. diff --git a/assets/halloween/bat.svg b/store/art/halloween/bat.svg similarity index 100% rename from assets/halloween/bat.svg rename to store/art/halloween/bat.svg diff --git a/assets/halloween/ghost.svg b/store/art/halloween/ghost.svg similarity index 100% rename from assets/halloween/ghost.svg rename to store/art/halloween/ghost.svg diff --git a/assets/halloween/jack-o-lantern.svg b/store/art/halloween/jack-o-lantern.svg similarity index 100% rename from assets/halloween/jack-o-lantern.svg rename to store/art/halloween/jack-o-lantern.svg diff --git a/assets/halloween/pumpkin.svg b/store/art/halloween/pumpkin.svg similarity index 100% rename from assets/halloween/pumpkin.svg rename to store/art/halloween/pumpkin.svg diff --git a/assets/halloween/skeleton.svg b/store/art/halloween/skeleton.svg similarity index 100% rename from assets/halloween/skeleton.svg rename to store/art/halloween/skeleton.svg diff --git a/assets/store/marquee.png b/store/art/marquee.png similarity index 100% rename from assets/store/marquee.png rename to store/art/marquee.png diff --git a/assets/store/screenshots/1-blackboard-frost.jpg b/store/art/screenshots/1-blackboard-frost.jpg similarity index 100% rename from assets/store/screenshots/1-blackboard-frost.jpg rename to store/art/screenshots/1-blackboard-frost.jpg diff --git a/assets/store/screenshots/2-canvas-modules.jpg b/store/art/screenshots/2-canvas-modules.jpg similarity index 100% rename from assets/store/screenshots/2-canvas-modules.jpg rename to store/art/screenshots/2-canvas-modules.jpg diff --git a/assets/store/screenshots/3-canvas-dashboard.jpg b/store/art/screenshots/3-canvas-dashboard.jpg similarity index 100% rename from assets/store/screenshots/3-canvas-dashboard.jpg rename to store/art/screenshots/3-canvas-dashboard.jpg diff --git a/assets/store/screenshots/4-halloween-pumpkin-patch.jpg b/store/art/screenshots/4-halloween-pumpkin-patch.jpg similarity index 100% rename from assets/store/screenshots/4-halloween-pumpkin-patch.jpg rename to store/art/screenshots/4-halloween-pumpkin-patch.jpg diff --git a/assets/store/screenshots/5-backgrounds-editor.jpg b/store/art/screenshots/5-backgrounds-editor.jpg similarity index 100% rename from assets/store/screenshots/5-backgrounds-editor.jpg rename to store/art/screenshots/5-backgrounds-editor.jpg diff --git a/assets/store/small-tile.png b/store/art/small-tile.png similarity index 100% rename from assets/store/small-tile.png rename to store/art/small-tile.png diff --git a/test/agent-frames.test.js b/test/agents/agent-frames.test.js similarity index 100% rename from test/agent-frames.test.js rename to test/agents/agent-frames.test.js diff --git a/test/agent-link-check.test.js b/test/agents/agent-link-check.test.js similarity index 100% rename from test/agent-link-check.test.js rename to test/agents/agent-link-check.test.js diff --git a/test/agent-link.test.js b/test/agents/agent-link.test.js similarity index 100% rename from test/agent-link.test.js rename to test/agents/agent-link.test.js diff --git a/test/agents-docs.test.js b/test/agents/agents-docs.test.js similarity index 100% rename from test/agents-docs.test.js rename to test/agents/agents-docs.test.js diff --git a/test/mcp-agent.test.js b/test/agents/mcp-agent.test.js similarity index 100% rename from test/mcp-agent.test.js rename to test/agents/mcp-agent.test.js diff --git a/test/mcp-approve.test.js b/test/agents/mcp-approve.test.js similarity index 100% rename from test/mcp-approve.test.js rename to test/agents/mcp-approve.test.js diff --git a/test/mcp-assign.test.js b/test/agents/mcp-assign.test.js similarity index 100% rename from test/mcp-assign.test.js rename to test/agents/mcp-assign.test.js diff --git a/test/mcp-calls.test.js b/test/agents/mcp-calls.test.js similarity index 100% rename from test/mcp-calls.test.js rename to test/agents/mcp-calls.test.js diff --git a/test/mcp-conformance-check.test.js b/test/agents/mcp-conformance-check.test.js similarity index 100% rename from test/mcp-conformance-check.test.js rename to test/agents/mcp-conformance-check.test.js diff --git a/test/mcp-grants.test.js b/test/agents/mcp-grants.test.js similarity index 100% rename from test/mcp-grants.test.js rename to test/agents/mcp-grants.test.js diff --git a/test/mcp-host.test.js b/test/agents/mcp-host.test.js similarity index 100% rename from test/mcp-host.test.js rename to test/agents/mcp-host.test.js diff --git a/test/mcp-install.test.js b/test/agents/mcp-install.test.js similarity index 100% rename from test/mcp-install.test.js rename to test/agents/mcp-install.test.js diff --git a/test/mcp-mcpb.test.js b/test/agents/mcp-mcpb.test.js similarity index 100% rename from test/mcp-mcpb.test.js rename to test/agents/mcp-mcpb.test.js diff --git a/test/mcp-mods.test.js b/test/agents/mcp-mods.test.js similarity index 100% rename from test/mcp-mods.test.js rename to test/agents/mcp-mods.test.js diff --git a/test/mcp-native.test.js b/test/agents/mcp-native.test.js similarity index 100% rename from test/mcp-native.test.js rename to test/agents/mcp-native.test.js diff --git a/test/mcp-registry.test.js b/test/agents/mcp-registry.test.js similarity index 100% rename from test/mcp-registry.test.js rename to test/agents/mcp-registry.test.js diff --git a/test/mcp-release-fixes.test.js b/test/agents/mcp-release-fixes.test.js similarity index 100% rename from test/mcp-release-fixes.test.js rename to test/agents/mcp-release-fixes.test.js diff --git a/test/mcp-rendezvous.test.js b/test/agents/mcp-rendezvous.test.js similarity index 100% rename from test/mcp-rendezvous.test.js rename to test/agents/mcp-rendezvous.test.js diff --git a/test/mcp-tabs.test.js b/test/agents/mcp-tabs.test.js similarity index 100% rename from test/mcp-tabs.test.js rename to test/agents/mcp-tabs.test.js diff --git a/test/mcp-todo-write.test.js b/test/agents/mcp-todo-write.test.js similarity index 100% rename from test/mcp-todo-write.test.js rename to test/agents/mcp-todo-write.test.js diff --git a/test/mcp-tools-grades.test.js b/test/agents/mcp-tools-grades.test.js similarity index 100% rename from test/mcp-tools-grades.test.js rename to test/agents/mcp-tools-grades.test.js diff --git a/test/mcp-tools-study.test.js b/test/agents/mcp-tools-study.test.js similarity index 100% rename from test/mcp-tools-study.test.js rename to test/agents/mcp-tools-study.test.js diff --git a/test/mcp-windows-check.test.js b/test/agents/mcp-windows-check.test.js similarity index 100% rename from test/mcp-windows-check.test.js rename to test/agents/mcp-windows-check.test.js diff --git a/test/mcp-wire.test.js b/test/agents/mcp-wire.test.js similarity index 100% rename from test/mcp-wire.test.js rename to test/agents/mcp-wire.test.js diff --git a/test/mcp-worker.test.js b/test/agents/mcp-worker.test.js similarity index 100% rename from test/mcp-worker.test.js rename to test/agents/mcp-worker.test.js diff --git a/test/mcp-writer.test.js b/test/agents/mcp-writer.test.js similarity index 100% rename from test/mcp-writer.test.js rename to test/agents/mcp-writer.test.js diff --git a/test/ai-apple.test.js b/test/ai/ai-apple.test.js similarity index 100% rename from test/ai-apple.test.js rename to test/ai/ai-apple.test.js diff --git a/test/ai-broker.test.js b/test/ai/ai-broker.test.js similarity index 100% rename from test/ai-broker.test.js rename to test/ai/ai-broker.test.js diff --git a/test/ai-device.test.js b/test/ai/ai-device.test.js similarity index 100% rename from test/ai-device.test.js rename to test/ai/ai-device.test.js diff --git a/test/ai-failure.test.js b/test/ai/ai-failure.test.js similarity index 100% rename from test/ai-failure.test.js rename to test/ai/ai-failure.test.js diff --git a/test/ai-loop.test.js b/test/ai/ai-loop.test.js similarity index 100% rename from test/ai-loop.test.js rename to test/ai/ai-loop.test.js diff --git a/test/ai-models.test.js b/test/ai/ai-models.test.js similarity index 100% rename from test/ai-models.test.js rename to test/ai/ai-models.test.js diff --git a/test/ai-openai.test.js b/test/ai/ai-openai.test.js similarity index 100% rename from test/ai-openai.test.js rename to test/ai/ai-openai.test.js diff --git a/test/ai-request.test.js b/test/ai/ai-request.test.js similarity index 100% rename from test/ai-request.test.js rename to test/ai/ai-request.test.js diff --git a/test/ai-settings-store.test.js b/test/ai/ai-settings-store.test.js similarity index 100% rename from test/ai-settings-store.test.js rename to test/ai/ai-settings-store.test.js diff --git a/test/ai-settings.test.js b/test/ai/ai-settings.test.js similarity index 100% rename from test/ai-settings.test.js rename to test/ai/ai-settings.test.js diff --git a/test/ai-stream.test.js b/test/ai/ai-stream.test.js similarity index 100% rename from test/ai-stream.test.js rename to test/ai/ai-stream.test.js diff --git a/test/ai-syllabus.test.js b/test/ai/ai-syllabus.test.js similarity index 100% rename from test/ai-syllabus.test.js rename to test/ai/ai-syllabus.test.js diff --git a/test/ai-task.test.js b/test/ai/ai-task.test.js similarity index 100% rename from test/ai-task.test.js rename to test/ai/ai-task.test.js diff --git a/test/ai-text.test.js b/test/ai/ai-text.test.js similarity index 100% rename from test/ai-text.test.js rename to test/ai/ai-text.test.js diff --git a/test/ai-token-budget.test.js b/test/ai/ai-token-budget.test.js similarity index 100% rename from test/ai-token-budget.test.js rename to test/ai/ai-token-budget.test.js diff --git a/test/ai-tools-mcp.test.js b/test/ai/ai-tools-mcp.test.js similarity index 100% rename from test/ai-tools-mcp.test.js rename to test/ai/ai-tools-mcp.test.js diff --git a/test/ai-visual-cache.test.js b/test/ai/ai-visual-cache.test.js similarity index 100% rename from test/ai-visual-cache.test.js rename to test/ai/ai-visual-cache.test.js diff --git a/test/ai-visual-scope.test.js b/test/ai/ai-visual-scope.test.js similarity index 100% rename from test/ai-visual-scope.test.js rename to test/ai/ai-visual-scope.test.js diff --git a/test/ai-worker.test.js b/test/ai/ai-worker.test.js similarity index 100% rename from test/ai-worker.test.js rename to test/ai/ai-worker.test.js diff --git a/test/ai-writer-complete.test.js b/test/ai/ai-writer-complete.test.js similarity index 100% rename from test/ai-writer-complete.test.js rename to test/ai/ai-writer-complete.test.js diff --git a/test/ai-writer-frame.test.js b/test/ai/ai-writer-frame.test.js similarity index 100% rename from test/ai-writer-frame.test.js rename to test/ai/ai-writer-frame.test.js diff --git a/test/ai-writer-regressions.test.js b/test/ai/ai-writer-regressions.test.js similarity index 100% rename from test/ai-writer-regressions.test.js rename to test/ai/ai-writer-regressions.test.js diff --git a/test/ai-writer-tasks.test.js b/test/ai/ai-writer-tasks.test.js similarity index 100% rename from test/ai-writer-tasks.test.js rename to test/ai/ai-writer-tasks.test.js diff --git a/test/ai-writer-tools.test.js b/test/ai/ai-writer-tools.test.js similarity index 100% rename from test/ai-writer-tools.test.js rename to test/ai/ai-writer-tools.test.js diff --git a/test/color.test.js b/test/core/color.test.js similarity index 100% rename from test/color.test.js rename to test/core/color.test.js diff --git a/test/consent.test.js b/test/core/consent.test.js similarity index 100% rename from test/consent.test.js rename to test/core/consent.test.js diff --git a/test/docbutton.test.js b/test/core/docbutton.test.js similarity index 100% rename from test/docbutton.test.js rename to test/core/docbutton.test.js diff --git a/test/download-allow.test.js b/test/core/download-allow.test.js similarity index 100% rename from test/download-allow.test.js rename to test/core/download-allow.test.js diff --git a/test/download-check-waits.test.js b/test/core/download-check-waits.test.js similarity index 100% rename from test/download-check-waits.test.js rename to test/core/download-check-waits.test.js diff --git a/test/download.test.js b/test/core/download.test.js similarity index 100% rename from test/download.test.js rename to test/core/download.test.js diff --git a/test/fresh-defaults.test.js b/test/core/fresh-defaults.test.js similarity index 100% rename from test/fresh-defaults.test.js rename to test/core/fresh-defaults.test.js diff --git a/test/page.test.js b/test/core/page.test.js similarity index 100% rename from test/page.test.js rename to test/core/page.test.js diff --git a/test/site.test.js b/test/core/site.test.js similarity index 100% rename from test/site.test.js rename to test/core/site.test.js diff --git a/test/starter-flags.test.js b/test/core/starter-flags.test.js similarity index 100% rename from test/starter-flags.test.js rename to test/core/starter-flags.test.js diff --git a/test/tooltips.test.js b/test/core/tooltips.test.js similarity index 100% rename from test/tooltips.test.js rename to test/core/tooltips.test.js diff --git a/test/ultra-panel.test.js b/test/core/ultra-panel.test.js similarity index 100% rename from test/ultra-panel.test.js rename to test/core/ultra-panel.test.js diff --git a/test/widgets-checks.test.js b/test/core/widgets-checks.test.js similarity index 100% rename from test/widgets-checks.test.js rename to test/core/widgets-checks.test.js diff --git a/test/widgets-tiers.test.js b/test/core/widgets-tiers.test.js similarity index 100% rename from test/widgets-tiers.test.js rename to test/core/widgets-tiers.test.js diff --git a/test/widgets-ui.test.js b/test/core/widgets-ui.test.js similarity index 100% rename from test/widgets-ui.test.js rename to test/core/widgets-ui.test.js diff --git a/test/widgets.test.js b/test/core/widgets.test.js similarity index 100% rename from test/widgets.test.js rename to test/core/widgets.test.js diff --git a/test/zip.test.js b/test/core/zip.test.js similarity index 100% rename from test/zip.test.js rename to test/core/zip.test.js diff --git a/test/allow-tab.test.js b/test/courses/allow-tab.test.js similarity index 100% rename from test/allow-tab.test.js rename to test/courses/allow-tab.test.js diff --git a/test/allow.test.js b/test/courses/allow.test.js similarity index 100% rename from test/allow.test.js rename to test/courses/allow.test.js diff --git a/test/announce-sites.test.js b/test/courses/announce-sites.test.js similarity index 100% rename from test/announce-sites.test.js rename to test/courses/announce-sites.test.js diff --git a/test/announce-ui.test.js b/test/courses/announce-ui.test.js similarity index 100% rename from test/announce-ui.test.js rename to test/courses/announce-ui.test.js diff --git a/test/announce.test.js b/test/courses/announce.test.js similarity index 100% rename from test/announce.test.js rename to test/courses/announce.test.js diff --git a/test/bb-preview.test.js b/test/courses/bb-preview.test.js similarity index 100% rename from test/bb-preview.test.js rename to test/courses/bb-preview.test.js diff --git a/test/board-card-checks.test.js b/test/courses/board-card-checks.test.js similarity index 100% rename from test/board-card-checks.test.js rename to test/courses/board-card-checks.test.js diff --git a/test/board-checks.test.js b/test/courses/board-checks.test.js similarity index 100% rename from test/board-checks.test.js rename to test/courses/board-checks.test.js diff --git a/test/boards-check.test.js b/test/courses/boards-check.test.js similarity index 100% rename from test/boards-check.test.js rename to test/courses/boards-check.test.js diff --git a/test/canvas-boards.test.js b/test/courses/canvas-boards.test.js similarity index 100% rename from test/canvas-boards.test.js rename to test/courses/canvas-boards.test.js diff --git a/test/canvas-dash-audit-i18n-css.test.js b/test/courses/canvas-dash-audit-i18n-css.test.js similarity index 100% rename from test/canvas-dash-audit-i18n-css.test.js rename to test/courses/canvas-dash-audit-i18n-css.test.js diff --git a/test/canvas-dash-audit-ui.test.js b/test/courses/canvas-dash-audit-ui.test.js similarity index 100% rename from test/canvas-dash-audit-ui.test.js rename to test/courses/canvas-dash-audit-ui.test.js diff --git a/test/canvas-dash-audit.test.js b/test/courses/canvas-dash-audit.test.js similarity index 100% rename from test/canvas-dash-audit.test.js rename to test/courses/canvas-dash-audit.test.js diff --git a/test/canvas-fixtures.test.js b/test/courses/canvas-fixtures.test.js similarity index 100% rename from test/canvas-fixtures.test.js rename to test/courses/canvas-fixtures.test.js diff --git a/test/canvas-load-audit-contracts.test.js b/test/courses/canvas-load-audit-contracts.test.js similarity index 100% rename from test/canvas-load-audit-contracts.test.js rename to test/courses/canvas-load-audit-contracts.test.js diff --git a/test/canvas-load-audit-runtime.test.js b/test/courses/canvas-load-audit-runtime.test.js similarity index 100% rename from test/canvas-load-audit-runtime.test.js rename to test/courses/canvas-load-audit-runtime.test.js diff --git a/test/canvas-uiux-audit.test.js b/test/courses/canvas-uiux-audit.test.js similarity index 100% rename from test/canvas-uiux-audit.test.js rename to test/courses/canvas-uiux-audit.test.js diff --git a/test/cards-ui.test.js b/test/courses/cards-ui.test.js similarity index 100% rename from test/cards-ui.test.js rename to test/courses/cards-ui.test.js diff --git a/test/cards.test.js b/test/courses/cards.test.js similarity index 100% rename from test/cards.test.js rename to test/courses/cards.test.js diff --git a/test/cardtasks-ui.test.js b/test/courses/cardtasks-ui.test.js similarity index 100% rename from test/cardtasks-ui.test.js rename to test/courses/cardtasks-ui.test.js diff --git a/test/coursecards.test.js b/test/courses/coursecards.test.js similarity index 100% rename from test/coursecards.test.js rename to test/courses/coursecards.test.js diff --git a/test/docreview.test.js b/test/courses/docreview.test.js similarity index 100% rename from test/docreview.test.js rename to test/courses/docreview.test.js diff --git a/test/docs-dark.test.js b/test/courses/docs-dark.test.js similarity index 100% rename from test/docs-dark.test.js rename to test/courses/docs-dark.test.js diff --git a/test/docviewer-ui.test.js b/test/courses/docviewer-ui.test.js similarity index 100% rename from test/docviewer-ui.test.js rename to test/courses/docviewer-ui.test.js diff --git a/test/docviewer.test.js b/test/courses/docviewer.test.js similarity index 100% rename from test/docviewer.test.js rename to test/courses/docviewer.test.js diff --git a/test/folders-canvas.test.js b/test/courses/folders-canvas.test.js similarity index 100% rename from test/folders-canvas.test.js rename to test/courses/folders-canvas.test.js diff --git a/test/folders-ui.test.js b/test/courses/folders-ui.test.js similarity index 100% rename from test/folders-ui.test.js rename to test/courses/folders-ui.test.js diff --git a/test/folders.test.js b/test/courses/folders.test.js similarity index 100% rename from test/folders.test.js rename to test/courses/folders.test.js diff --git a/test/moodle-root.test.js b/test/courses/moodle-root.test.js similarity index 100% rename from test/moodle-root.test.js rename to test/courses/moodle-root.test.js diff --git a/test/moodle.test.js b/test/courses/moodle.test.js similarity index 100% rename from test/moodle.test.js rename to test/courses/moodle.test.js diff --git a/test/rubric-data.test.js b/test/courses/rubric-data.test.js similarity index 100% rename from test/rubric-data.test.js rename to test/courses/rubric-data.test.js diff --git a/test/rubric-ultra-state.test.js b/test/courses/rubric-ultra-state.test.js similarity index 100% rename from test/rubric-ultra-state.test.js rename to test/courses/rubric-ultra-state.test.js diff --git a/test/stream.test.js b/test/courses/stream.test.js similarity index 100% rename from test/stream.test.js rename to test/courses/stream.test.js diff --git a/test/ultra-boards.test.js b/test/courses/ultra-boards.test.js similarity index 100% rename from test/ultra-boards.test.js rename to test/courses/ultra-boards.test.js diff --git a/test/ultra-card-storage.test.js b/test/courses/ultra-card-storage.test.js similarity index 100% rename from test/ultra-card-storage.test.js rename to test/courses/ultra-card-storage.test.js diff --git a/test/ultra-fixtures.test.js b/test/courses/ultra-fixtures.test.js similarity index 100% rename from test/ultra-fixtures.test.js rename to test/courses/ultra-fixtures.test.js diff --git a/test/ultra-mock.test.js b/test/courses/ultra-mock.test.js similarity index 100% rename from test/ultra-mock.test.js rename to test/courses/ultra-mock.test.js diff --git a/test/ultra-perf.test.js b/test/courses/ultra-perf.test.js similarity index 100% rename from test/ultra-perf.test.js rename to test/courses/ultra-perf.test.js diff --git a/test/ultra-preview-ticket.test.js b/test/courses/ultra-preview-ticket.test.js similarity index 100% rename from test/ultra-preview-ticket.test.js rename to test/courses/ultra-preview-ticket.test.js diff --git a/test/ultra-test-attempt.test.js b/test/courses/ultra-test-attempt.test.js similarity index 100% rename from test/ultra-test-attempt.test.js rename to test/courses/ultra-test-attempt.test.js diff --git a/test/ultra.test.js b/test/courses/ultra.test.js similarity index 100% rename from test/ultra.test.js rename to test/courses/ultra.test.js diff --git a/test/doc-files.test.js b/test/documents/doc-files.test.js similarity index 100% rename from test/doc-files.test.js rename to test/documents/doc-files.test.js diff --git a/test/doc-kinds.test.js b/test/documents/doc-kinds.test.js similarity index 100% rename from test/doc-kinds.test.js rename to test/documents/doc-kinds.test.js diff --git a/test/doctext.test.js b/test/documents/doctext.test.js similarity index 100% rename from test/doctext.test.js rename to test/documents/doctext.test.js diff --git a/test/papers-motion-lifecycle.test.js b/test/documents/papers-motion-lifecycle.test.js similarity index 100% rename from test/papers-motion-lifecycle.test.js rename to test/documents/papers-motion-lifecycle.test.js diff --git a/test/papers-motion.test.js b/test/documents/papers-motion.test.js similarity index 100% rename from test/papers-motion.test.js rename to test/documents/papers-motion.test.js diff --git a/test/papers-page.test.js b/test/documents/papers-page.test.js similarity index 100% rename from test/papers-page.test.js rename to test/documents/papers-page.test.js diff --git a/test/papers-sources.test.js b/test/documents/papers-sources.test.js similarity index 100% rename from test/papers-sources.test.js rename to test/documents/papers-sources.test.js diff --git a/test/papers-theme.test.js b/test/documents/papers-theme.test.js similarity index 100% rename from test/papers-theme.test.js rename to test/documents/papers-theme.test.js diff --git a/test/pdf-doc.test.js b/test/documents/pdf-doc.test.js similarity index 100% rename from test/pdf-doc.test.js rename to test/documents/pdf-doc.test.js diff --git a/test/pdf-kind.test.js b/test/documents/pdf-kind.test.js similarity index 100% rename from test/pdf-kind.test.js rename to test/documents/pdf-kind.test.js diff --git a/test/pdf-l10n.test.js b/test/documents/pdf-l10n.test.js similarity index 100% rename from test/pdf-l10n.test.js rename to test/documents/pdf-l10n.test.js diff --git a/test/pptx-geom.test.js b/test/documents/pptx-geom.test.js similarity index 100% rename from test/pptx-geom.test.js rename to test/documents/pptx-geom.test.js diff --git a/test/pptx.test.js b/test/documents/pptx.test.js similarity index 100% rename from test/pptx.test.js rename to test/documents/pptx.test.js diff --git a/test/sheet-calc.test.js b/test/documents/sheet-calc.test.js similarity index 100% rename from test/sheet-calc.test.js rename to test/documents/sheet-calc.test.js diff --git a/test/sheet-chart-insert.test.js b/test/documents/sheet-chart-insert.test.js similarity index 100% rename from test/sheet-chart-insert.test.js rename to test/documents/sheet-chart-insert.test.js diff --git a/test/sheet-chart.test.js b/test/documents/sheet-chart.test.js similarity index 100% rename from test/sheet-chart.test.js rename to test/documents/sheet-chart.test.js diff --git a/test/sheet-excel.test.js b/test/documents/sheet-excel.test.js similarity index 100% rename from test/sheet-excel.test.js rename to test/documents/sheet-excel.test.js diff --git a/test/sheet-fn-database.test.js b/test/documents/sheet-fn-database.test.js similarity index 100% rename from test/sheet-fn-database.test.js rename to test/documents/sheet-fn-database.test.js diff --git a/test/sheet-fn-date.test.js b/test/documents/sheet-fn-date.test.js similarity index 100% rename from test/sheet-fn-date.test.js rename to test/documents/sheet-fn-date.test.js diff --git a/test/sheet-fn-financial.test.js b/test/documents/sheet-fn-financial.test.js similarity index 100% rename from test/sheet-fn-financial.test.js rename to test/documents/sheet-fn-financial.test.js diff --git a/test/sheet-fn-financial2.test.js b/test/documents/sheet-fn-financial2.test.js similarity index 100% rename from test/sheet-fn-financial2.test.js rename to test/documents/sheet-fn-financial2.test.js diff --git a/test/sheet-fn-info.test.js b/test/documents/sheet-fn-info.test.js similarity index 100% rename from test/sheet-fn-info.test.js rename to test/documents/sheet-fn-info.test.js diff --git a/test/sheet-fn-logical2.test.js b/test/documents/sheet-fn-logical2.test.js similarity index 100% rename from test/sheet-fn-logical2.test.js rename to test/documents/sheet-fn-logical2.test.js diff --git a/test/sheet-fn-lookup.test.js b/test/documents/sheet-fn-lookup.test.js similarity index 100% rename from test/sheet-fn-lookup.test.js rename to test/documents/sheet-fn-lookup.test.js diff --git a/test/sheet-fn-lookup2.test.js b/test/documents/sheet-fn-lookup2.test.js similarity index 100% rename from test/sheet-fn-lookup2.test.js rename to test/documents/sheet-fn-lookup2.test.js diff --git a/test/sheet-fn-math2.test.js b/test/documents/sheet-fn-math2.test.js similarity index 100% rename from test/sheet-fn-math2.test.js rename to test/documents/sheet-fn-math2.test.js diff --git a/test/sheet-fn-math3.test.js b/test/documents/sheet-fn-math3.test.js similarity index 100% rename from test/sheet-fn-math3.test.js rename to test/documents/sheet-fn-math3.test.js diff --git a/test/sheet-fn-stats.test.js b/test/documents/sheet-fn-stats.test.js similarity index 100% rename from test/sheet-fn-stats.test.js rename to test/documents/sheet-fn-stats.test.js diff --git a/test/sheet-fn-stats2.test.js b/test/documents/sheet-fn-stats2.test.js similarity index 100% rename from test/sheet-fn-stats2.test.js rename to test/documents/sheet-fn-stats2.test.js diff --git a/test/sheet-fn-text2.test.js b/test/documents/sheet-fn-text2.test.js similarity index 100% rename from test/sheet-fn-text2.test.js rename to test/documents/sheet-fn-text2.test.js diff --git a/test/sheet-formula.test.js b/test/documents/sheet-formula.test.js similarity index 100% rename from test/sheet-formula.test.js rename to test/documents/sheet-formula.test.js diff --git a/test/sheet-golden.test.js b/test/documents/sheet-golden.test.js similarity index 100% rename from test/sheet-golden.test.js rename to test/documents/sheet-golden.test.js diff --git a/test/sheet-grid-lifecycle.test.js b/test/documents/sheet-grid-lifecycle.test.js similarity index 100% rename from test/sheet-grid-lifecycle.test.js rename to test/documents/sheet-grid-lifecycle.test.js diff --git a/test/sheet-grid.test.js b/test/documents/sheet-grid.test.js similarity index 100% rename from test/sheet-grid.test.js rename to test/documents/sheet-grid.test.js diff --git a/test/sheet-kind.test.js b/test/documents/sheet-kind.test.js similarity index 100% rename from test/sheet-kind.test.js rename to test/documents/sheet-kind.test.js diff --git a/test/sheet-model.test.js b/test/documents/sheet-model.test.js similarity index 100% rename from test/sheet-model.test.js rename to test/documents/sheet-model.test.js diff --git a/test/sheet-numfmt.test.js b/test/documents/sheet-numfmt.test.js similarity index 100% rename from test/sheet-numfmt.test.js rename to test/documents/sheet-numfmt.test.js diff --git a/test/sheet-xlsx.test.js b/test/documents/sheet-xlsx.test.js similarity index 100% rename from test/sheet-xlsx.test.js rename to test/documents/sheet-xlsx.test.js diff --git a/test/slides-fidelity.test.js b/test/documents/slides-fidelity.test.js similarity index 100% rename from test/slides-fidelity.test.js rename to test/documents/slides-fidelity.test.js diff --git a/test/slides-kind.test.js b/test/documents/slides-kind.test.js similarity index 100% rename from test/slides-kind.test.js rename to test/documents/slides-kind.test.js diff --git a/test/slides-render.test.js b/test/documents/slides-render.test.js similarity index 100% rename from test/slides-render.test.js rename to test/documents/slides-render.test.js diff --git a/test/agent-link-firefox-check.test.js b/test/firefox/agent-link-firefox-check.test.js similarity index 100% rename from test/agent-link-firefox-check.test.js rename to test/firefox/agent-link-firefox-check.test.js diff --git a/test/firefox-html-sinks.test.js b/test/firefox/firefox-html-sinks.test.js similarity index 100% rename from test/firefox-html-sinks.test.js rename to test/firefox/firefox-html-sinks.test.js diff --git a/test/firefox-waits.test.js b/test/firefox/firefox-waits.test.js similarity index 100% rename from test/firefox-waits.test.js rename to test/firefox/firefox-waits.test.js diff --git a/test/firefox.test.js b/test/firefox/firefox.test.js similarity index 100% rename from test/firefox.test.js rename to test/firefox/firefox.test.js diff --git a/test/course-looks-sync.test.js b/test/looks/course-looks-sync.test.js similarity index 100% rename from test/course-looks-sync.test.js rename to test/looks/course-looks-sync.test.js diff --git a/test/courselook.test.js b/test/looks/courselook.test.js similarity index 100% rename from test/courselook.test.js rename to test/looks/courselook.test.js diff --git a/test/ctab-one-background.test.js b/test/looks/ctab-one-background.test.js similarity index 100% rename from test/ctab-one-background.test.js rename to test/looks/ctab-one-background.test.js diff --git a/test/fonts.test.js b/test/looks/fonts.test.js similarity index 100% rename from test/fonts.test.js rename to test/looks/fonts.test.js diff --git a/test/glass-remaining-check.test.js b/test/looks/glass-remaining-check.test.js similarity index 100% rename from test/glass-remaining-check.test.js rename to test/looks/glass-remaining-check.test.js diff --git a/test/glsl.test.js b/test/looks/glsl.test.js similarity index 100% rename from test/glsl.test.js rename to test/looks/glsl.test.js diff --git a/test/history.test.js b/test/looks/history.test.js similarity index 100% rename from test/history.test.js rename to test/looks/history.test.js diff --git a/test/ink.test.js b/test/looks/ink.test.js similarity index 100% rename from test/ink.test.js rename to test/looks/ink.test.js diff --git a/test/look-disclosure.test.js b/test/looks/look-disclosure.test.js similarity index 100% rename from test/look-disclosure.test.js rename to test/looks/look-disclosure.test.js diff --git a/test/look-editor.test.js b/test/looks/look-editor.test.js similarity index 100% rename from test/look-editor.test.js rename to test/looks/look-editor.test.js diff --git a/test/look-media-share.test.js b/test/looks/look-media-share.test.js similarity index 100% rename from test/look-media-share.test.js rename to test/looks/look-media-share.test.js diff --git a/test/look-offer-review.test.js b/test/looks/look-offer-review.test.js similarity index 100% rename from test/look-offer-review.test.js rename to test/looks/look-offer-review.test.js diff --git a/test/lookparts-page.test.js b/test/looks/lookparts-page.test.js similarity index 100% rename from test/lookparts-page.test.js rename to test/looks/lookparts-page.test.js diff --git a/test/lookparts.test.js b/test/looks/lookparts.test.js similarity index 100% rename from test/lookparts.test.js rename to test/looks/lookparts.test.js diff --git a/test/looks-catalog.test.js b/test/looks/looks-catalog.test.js similarity index 100% rename from test/looks-catalog.test.js rename to test/looks/looks-catalog.test.js diff --git a/test/lookset.test.js b/test/looks/lookset.test.js similarity index 100% rename from test/lookset.test.js rename to test/looks/lookset.test.js diff --git a/test/lookx-ui.test.js b/test/looks/lookx-ui.test.js similarity index 100% rename from test/lookx-ui.test.js rename to test/looks/lookx-ui.test.js diff --git a/test/lookx.test.js b/test/looks/lookx.test.js similarity index 100% rename from test/lookx.test.js rename to test/looks/lookx.test.js diff --git a/test/media-avif-validation.test.js b/test/looks/media-avif-validation.test.js similarity index 100% rename from test/media-avif-validation.test.js rename to test/looks/media-avif-validation.test.js diff --git a/test/media-broker.test.js b/test/looks/media-broker.test.js similarity index 100% rename from test/media-broker.test.js rename to test/looks/media-broker.test.js diff --git a/test/media-check.test.js b/test/looks/media-check.test.js similarity index 100% rename from test/media-check.test.js rename to test/looks/media-check.test.js diff --git a/test/media-cost-check.test.js b/test/looks/media-cost-check.test.js similarity index 100% rename from test/media-cost-check.test.js rename to test/looks/media-cost-check.test.js diff --git a/test/media-gallery.test.js b/test/looks/media-gallery.test.js similarity index 100% rename from test/media-gallery.test.js rename to test/looks/media-gallery.test.js diff --git a/test/media-host.test.js b/test/looks/media-host.test.js similarity index 100% rename from test/media-host.test.js rename to test/looks/media-host.test.js diff --git a/test/media-lottie-mattes.test.js b/test/looks/media-lottie-mattes.test.js similarity index 100% rename from test/media-lottie-mattes.test.js rename to test/looks/media-lottie-mattes.test.js diff --git a/test/media-lottie-validation.test.js b/test/looks/media-lottie-validation.test.js similarity index 100% rename from test/media-lottie-validation.test.js rename to test/looks/media-lottie-validation.test.js diff --git a/test/media-metadata.test.js b/test/looks/media-metadata.test.js similarity index 100% rename from test/media-metadata.test.js rename to test/looks/media-metadata.test.js diff --git a/test/media-parts.test.js b/test/looks/media-parts.test.js similarity index 100% rename from test/media-parts.test.js rename to test/looks/media-parts.test.js diff --git a/test/media-remote.test.js b/test/looks/media-remote.test.js similarity index 100% rename from test/media-remote.test.js rename to test/looks/media-remote.test.js diff --git a/test/media-store.test.js b/test/looks/media-store.test.js similarity index 100% rename from test/media-store.test.js rename to test/looks/media-store.test.js diff --git a/test/media-video-validation.test.js b/test/looks/media-video-validation.test.js similarity index 100% rename from test/media-video-validation.test.js rename to test/looks/media-video-validation.test.js diff --git a/test/media.test.js b/test/looks/media.test.js similarity index 100% rename from test/media.test.js rename to test/looks/media.test.js diff --git a/test/moments.test.js b/test/looks/moments.test.js similarity index 100% rename from test/moments.test.js rename to test/looks/moments.test.js diff --git a/test/motion-check.test.js b/test/looks/motion-check.test.js similarity index 100% rename from test/motion-check.test.js rename to test/looks/motion-check.test.js diff --git a/test/motion-default.test.js b/test/looks/motion-default.test.js similarity index 100% rename from test/motion-default.test.js rename to test/looks/motion-default.test.js diff --git a/test/motion-lib.test.js b/test/looks/motion-lib.test.js similarity index 100% rename from test/motion-lib.test.js rename to test/looks/motion-lib.test.js diff --git a/test/motionx-page.test.js b/test/looks/motionx-page.test.js similarity index 100% rename from test/motionx-page.test.js rename to test/looks/motionx-page.test.js diff --git a/test/motionx.test.js b/test/looks/motionx.test.js similarity index 100% rename from test/motionx.test.js rename to test/looks/motionx.test.js diff --git a/test/parts.test.js b/test/looks/parts.test.js similarity index 100% rename from test/parts.test.js rename to test/looks/parts.test.js diff --git a/test/pointer-check.test.js b/test/looks/pointer-check.test.js similarity index 100% rename from test/pointer-check.test.js rename to test/looks/pointer-check.test.js diff --git a/test/scene-lifecycle.test.js b/test/looks/scene-lifecycle.test.js similarity index 100% rename from test/scene-lifecycle.test.js rename to test/looks/scene-lifecycle.test.js diff --git a/test/scene-media.test.js b/test/looks/scene-media.test.js similarity index 100% rename from test/scene-media.test.js rename to test/looks/scene-media.test.js diff --git a/test/scene-modes.test.js b/test/looks/scene-modes.test.js similarity index 100% rename from test/scene-modes.test.js rename to test/looks/scene-modes.test.js diff --git a/test/scene-placement.test.js b/test/looks/scene-placement.test.js similarity index 100% rename from test/scene-placement.test.js rename to test/looks/scene-placement.test.js diff --git a/test/scene-sync.test.js b/test/looks/scene-sync.test.js similarity index 100% rename from test/scene-sync.test.js rename to test/looks/scene-sync.test.js diff --git a/test/surfaces.test.js b/test/looks/surfaces.test.js similarity index 100% rename from test/surfaces.test.js rename to test/looks/surfaces.test.js diff --git a/test/ultra-appearance.test.js b/test/looks/ultra-appearance.test.js similarity index 100% rename from test/ultra-appearance.test.js rename to test/looks/ultra-appearance.test.js diff --git a/test/ultra-header-surface.test.js b/test/looks/ultra-header-surface.test.js similarity index 100% rename from test/ultra-header-surface.test.js rename to test/looks/ultra-header-surface.test.js diff --git a/test/ultra-states.test.js b/test/looks/ultra-states.test.js similarity index 100% rename from test/ultra-states.test.js rename to test/looks/ultra-states.test.js diff --git a/test/blocklist.test.js b/test/mods/blocklist.test.js similarity index 100% rename from test/blocklist.test.js rename to test/mods/blocklist.test.js diff --git a/test/combined-check.test.js b/test/mods/combined-check.test.js similarity index 100% rename from test/combined-check.test.js rename to test/mods/combined-check.test.js diff --git a/test/mod-account.test.js b/test/mods/mod-account.test.js similarity index 100% rename from test/mod-account.test.js rename to test/mods/mod-account.test.js diff --git a/test/mod-nav.test.js b/test/mods/mod-nav.test.js similarity index 100% rename from test/mod-nav.test.js rename to test/mods/mod-nav.test.js diff --git a/test/mod-privacy.test.js b/test/mods/mod-privacy.test.js similarity index 100% rename from test/mod-privacy.test.js rename to test/mods/mod-privacy.test.js diff --git a/test/modapi-documents.test.js b/test/mods/modapi-documents.test.js similarity index 100% rename from test/modapi-documents.test.js rename to test/mods/modapi-documents.test.js diff --git a/test/modapi.test.js b/test/mods/modapi.test.js similarity index 100% rename from test/modapi.test.js rename to test/mods/modapi.test.js diff --git a/test/modbroker.test.js b/test/mods/modbroker.test.js similarity index 100% rename from test/modbroker.test.js rename to test/mods/modbroker.test.js diff --git a/test/modbundle.test.js b/test/mods/modbundle.test.js similarity index 100% rename from test/modbundle.test.js rename to test/mods/modbundle.test.js diff --git a/test/modchannel-worker.test.js b/test/mods/modchannel-worker.test.js similarity index 100% rename from test/modchannel-worker.test.js rename to test/mods/modchannel-worker.test.js diff --git a/test/modchannel.test.js b/test/mods/modchannel.test.js similarity index 100% rename from test/modchannel.test.js rename to test/mods/modchannel.test.js diff --git a/test/modexamples.test.js b/test/mods/modexamples.test.js similarity index 100% rename from test/modexamples.test.js rename to test/mods/modexamples.test.js diff --git a/test/modgallery.test.js b/test/mods/modgallery.test.js similarity index 100% rename from test/modgallery.test.js rename to test/mods/modgallery.test.js diff --git a/test/modpacks.test.js b/test/mods/modpacks.test.js similarity index 100% rename from test/modpacks.test.js rename to test/mods/modpacks.test.js diff --git a/test/mods-runtime.test.js b/test/mods/mods-runtime.test.js similarity index 100% rename from test/mods-runtime.test.js rename to test/mods/mods-runtime.test.js diff --git a/test/modtools.test.js b/test/mods/modtools.test.js similarity index 100% rename from test/modtools.test.js rename to test/mods/modtools.test.js diff --git a/test/modtypes.test.js b/test/mods/modtypes.test.js similarity index 100% rename from test/modtypes.test.js rename to test/mods/modtypes.test.js diff --git a/test/modui.test.js b/test/mods/modui.test.js similarity index 100% rename from test/modui.test.js rename to test/mods/modui.test.js diff --git a/test/modwidget.test.js b/test/mods/modwidget.test.js similarity index 100% rename from test/modwidget.test.js rename to test/mods/modwidget.test.js diff --git a/test/modwidgets-runtime.test.js b/test/mods/modwidgets-runtime.test.js similarity index 100% rename from test/modwidgets-runtime.test.js rename to test/mods/modwidgets-runtime.test.js diff --git a/test/canvas-coursemenu.test.js b/test/navigation/canvas-coursemenu.test.js similarity index 100% rename from test/canvas-coursemenu.test.js rename to test/navigation/canvas-coursemenu.test.js diff --git a/test/content-navigation.test.js b/test/navigation/content-navigation.test.js similarity index 100% rename from test/content-navigation.test.js rename to test/navigation/content-navigation.test.js diff --git a/test/context-lifecycle.test.js b/test/navigation/context-lifecycle.test.js similarity index 100% rename from test/context-lifecycle.test.js rename to test/navigation/context-lifecycle.test.js diff --git a/test/coursemenu.test.js b/test/navigation/coursemenu.test.js similarity index 100% rename from test/coursemenu.test.js rename to test/navigation/coursemenu.test.js diff --git a/test/coursetabs-canvas-audit-frames.test.js b/test/navigation/coursetabs-canvas-audit-frames.test.js similarity index 100% rename from test/coursetabs-canvas-audit-frames.test.js rename to test/navigation/coursetabs-canvas-audit-frames.test.js diff --git a/test/coursetabs-canvas-audit-routing.test.js b/test/navigation/coursetabs-canvas-audit-routing.test.js similarity index 100% rename from test/coursetabs-canvas-audit-routing.test.js rename to test/navigation/coursetabs-canvas-audit-routing.test.js diff --git a/test/coursetabs-canvas.test.js b/test/navigation/coursetabs-canvas.test.js similarity index 100% rename from test/coursetabs-canvas.test.js rename to test/navigation/coursetabs-canvas.test.js diff --git a/test/coursetabs-course-detail.test.js b/test/navigation/coursetabs-course-detail.test.js similarity index 100% rename from test/coursetabs-course-detail.test.js rename to test/navigation/coursetabs-course-detail.test.js diff --git a/test/coursetabs-frames.test.js b/test/navigation/coursetabs-frames.test.js similarity index 100% rename from test/coursetabs-frames.test.js rename to test/navigation/coursetabs-frames.test.js diff --git a/test/coursetabs-keep.test.js b/test/navigation/coursetabs-keep.test.js similarity index 100% rename from test/coursetabs-keep.test.js rename to test/navigation/coursetabs-keep.test.js diff --git a/test/coursetabs-label.test.js b/test/navigation/coursetabs-label.test.js similarity index 100% rename from test/coursetabs-label.test.js rename to test/navigation/coursetabs-label.test.js diff --git a/test/coursetabs-native.test.js b/test/navigation/coursetabs-native.test.js similarity index 100% rename from test/coursetabs-native.test.js rename to test/navigation/coursetabs-native.test.js diff --git a/test/coursetabs-pages.test.js b/test/navigation/coursetabs-pages.test.js similarity index 100% rename from test/coursetabs-pages.test.js rename to test/navigation/coursetabs-pages.test.js diff --git a/test/coursetabs-provenance.test.js b/test/navigation/coursetabs-provenance.test.js similarity index 100% rename from test/coursetabs-provenance.test.js rename to test/navigation/coursetabs-provenance.test.js diff --git a/test/coursetabs-repaint.test.js b/test/navigation/coursetabs-repaint.test.js similarity index 100% rename from test/coursetabs-repaint.test.js rename to test/navigation/coursetabs-repaint.test.js diff --git a/test/coursetabs-routed.test.js b/test/navigation/coursetabs-routed.test.js similarity index 100% rename from test/coursetabs-routed.test.js rename to test/navigation/coursetabs-routed.test.js diff --git a/test/coursetabs-sidebar.test.js b/test/navigation/coursetabs-sidebar.test.js similarity index 100% rename from test/coursetabs-sidebar.test.js rename to test/navigation/coursetabs-sidebar.test.js diff --git a/test/coursetabs-takeover.test.js b/test/navigation/coursetabs-takeover.test.js similarity index 100% rename from test/coursetabs-takeover.test.js rename to test/navigation/coursetabs-takeover.test.js diff --git a/test/coursetabs-turns.test.js b/test/navigation/coursetabs-turns.test.js similarity index 100% rename from test/coursetabs-turns.test.js rename to test/navigation/coursetabs-turns.test.js diff --git a/test/coursetabs-worker.test.js b/test/navigation/coursetabs-worker.test.js similarity index 100% rename from test/coursetabs-worker.test.js rename to test/navigation/coursetabs-worker.test.js diff --git a/test/coursetabs.test.js b/test/navigation/coursetabs.test.js similarity index 100% rename from test/coursetabs.test.js rename to test/navigation/coursetabs.test.js diff --git a/test/ctab-panel-close.test.js b/test/navigation/ctab-panel-close.test.js similarity index 100% rename from test/ctab-panel-close.test.js rename to test/navigation/ctab-panel-close.test.js diff --git a/test/ctab-stall-switch.test.js b/test/navigation/ctab-stall-switch.test.js similarity index 100% rename from test/ctab-stall-switch.test.js rename to test/navigation/ctab-stall-switch.test.js diff --git a/test/ctab-strip-glass.test.js b/test/navigation/ctab-strip-glass.test.js similarity index 100% rename from test/ctab-strip-glass.test.js rename to test/navigation/ctab-strip-glass.test.js diff --git a/test/load-recovery.test.js b/test/navigation/load-recovery.test.js similarity index 100% rename from test/load-recovery.test.js rename to test/navigation/load-recovery.test.js diff --git a/test/nav-a11y.test.js b/test/navigation/nav-a11y.test.js similarity index 100% rename from test/nav-a11y.test.js rename to test/navigation/nav-a11y.test.js diff --git a/test/nav-lifecycle-review.test.js b/test/navigation/nav-lifecycle-review.test.js similarity index 100% rename from test/nav-lifecycle-review.test.js rename to test/navigation/nav-lifecycle-review.test.js diff --git a/test/nav-logo-review.test.js b/test/navigation/nav-logo-review.test.js similarity index 100% rename from test/nav-logo-review.test.js rename to test/navigation/nav-logo-review.test.js diff --git a/test/nav-mod-rail.test.js b/test/navigation/nav-mod-rail.test.js similarity index 100% rename from test/nav-mod-rail.test.js rename to test/navigation/nav-mod-rail.test.js diff --git a/test/nav-overlay.test.js b/test/navigation/nav-overlay.test.js similarity index 100% rename from test/nav-overlay.test.js rename to test/navigation/nav-overlay.test.js diff --git a/test/nav-paint.test.js b/test/navigation/nav-paint.test.js similarity index 100% rename from test/nav-paint.test.js rename to test/navigation/nav-paint.test.js diff --git a/test/nav.test.js b/test/navigation/nav.test.js similarity index 100% rename from test/nav.test.js rename to test/navigation/nav.test.js diff --git a/test/navbar-canvas-audit-frames.test.js b/test/navigation/navbar-canvas-audit-frames.test.js similarity index 100% rename from test/navbar-canvas-audit-frames.test.js rename to test/navigation/navbar-canvas-audit-frames.test.js diff --git a/test/navbar-canvas-audit.test.js b/test/navigation/navbar-canvas-audit.test.js similarity index 100% rename from test/navbar-canvas-audit.test.js rename to test/navigation/navbar-canvas-audit.test.js diff --git a/test/navbar-canvas.test.js b/test/navigation/navbar-canvas.test.js similarity index 100% rename from test/navbar-canvas.test.js rename to test/navigation/navbar-canvas.test.js diff --git a/test/navbar-moodle-controls.test.js b/test/navigation/navbar-moodle-controls.test.js similarity index 100% rename from test/navbar-moodle-controls.test.js rename to test/navigation/navbar-moodle-controls.test.js diff --git a/test/navbar-moodle.test.js b/test/navigation/navbar-moodle.test.js similarity index 100% rename from test/navbar-moodle.test.js rename to test/navigation/navbar-moodle.test.js diff --git a/test/navbar-nav-perms.test.js b/test/navigation/navbar-nav-perms.test.js similarity index 100% rename from test/navbar-nav-perms.test.js rename to test/navigation/navbar-nav-perms.test.js diff --git a/test/navbar-panels.test.js b/test/navigation/navbar-panels.test.js similarity index 100% rename from test/navbar-panels.test.js rename to test/navigation/navbar-panels.test.js diff --git a/test/navbar-polish-review.test.js b/test/navigation/navbar-polish-review.test.js similarity index 100% rename from test/navbar-polish-review.test.js rename to test/navigation/navbar-polish-review.test.js diff --git a/test/navbar-widgets.test.js b/test/navigation/navbar-widgets.test.js similarity index 100% rename from test/navbar-widgets.test.js rename to test/navigation/navbar-widgets.test.js diff --git a/test/navbar.test.js b/test/navigation/navbar.test.js similarity index 100% rename from test/navbar.test.js rename to test/navigation/navbar.test.js diff --git a/test/navlayout-review.test.js b/test/navigation/navlayout-review.test.js similarity index 100% rename from test/navlayout-review.test.js rename to test/navigation/navlayout-review.test.js diff --git a/test/navlayout.test.js b/test/navigation/navlayout.test.js similarity index 100% rename from test/navlayout.test.js rename to test/navigation/navlayout.test.js diff --git a/test/place.test.js b/test/navigation/place.test.js similarity index 100% rename from test/place.test.js rename to test/navigation/place.test.js diff --git a/test/search.test.js b/test/navigation/search.test.js similarity index 100% rename from test/search.test.js rename to test/navigation/search.test.js diff --git a/test/seek.test.js b/test/navigation/seek.test.js similarity index 100% rename from test/seek.test.js rename to test/navigation/seek.test.js diff --git a/test/sidebar-absorb.test.js b/test/navigation/sidebar-absorb.test.js similarity index 100% rename from test/sidebar-absorb.test.js rename to test/navigation/sidebar-absorb.test.js diff --git a/test/sidebar-assessment-native.test.js b/test/navigation/sidebar-assessment-native.test.js similarity index 100% rename from test/sidebar-assessment-native.test.js rename to test/navigation/sidebar-assessment-native.test.js diff --git a/test/sidebar-broker.test.js b/test/navigation/sidebar-broker.test.js similarity index 100% rename from test/sidebar-broker.test.js rename to test/navigation/sidebar-broker.test.js diff --git a/test/sidebar-native-root.test.js b/test/navigation/sidebar-native-root.test.js similarity index 100% rename from test/sidebar-native-root.test.js rename to test/navigation/sidebar-native-root.test.js diff --git a/test/sidebar-rubric.test.js b/test/navigation/sidebar-rubric.test.js similarity index 100% rename from test/sidebar-rubric.test.js rename to test/navigation/sidebar-rubric.test.js diff --git a/test/sidebar-test-attempt.test.js b/test/navigation/sidebar-test-attempt.test.js similarity index 100% rename from test/sidebar-test-attempt.test.js rename to test/navigation/sidebar-test-attempt.test.js diff --git a/test/sidebar-ui.test.js b/test/navigation/sidebar-ui.test.js similarity index 100% rename from test/sidebar-ui.test.js rename to test/navigation/sidebar-ui.test.js diff --git a/test/sidebar-ultra.test.js b/test/navigation/sidebar-ultra.test.js similarity index 100% rename from test/sidebar-ultra.test.js rename to test/navigation/sidebar-ultra.test.js diff --git a/test/sidebar.test.js b/test/navigation/sidebar.test.js similarity index 100% rename from test/sidebar.test.js rename to test/navigation/sidebar.test.js diff --git a/test/sticky-fold.test.js b/test/navigation/sticky-fold.test.js similarity index 100% rename from test/sticky-fold.test.js rename to test/navigation/sticky-fold.test.js diff --git a/test/tabs-auto.test.js b/test/navigation/tabs-auto.test.js similarity index 100% rename from test/tabs-auto.test.js rename to test/navigation/tabs-auto.test.js diff --git a/test/ultra-course-room.test.js b/test/navigation/ultra-course-room.test.js similarity index 100% rename from test/ultra-course-room.test.js rename to test/navigation/ultra-course-room.test.js diff --git a/test/ultra-nav-review.test.js b/test/navigation/ultra-nav-review.test.js similarity index 100% rename from test/ultra-nav-review.test.js rename to test/navigation/ultra-nav-review.test.js diff --git a/test/ultra-nav.test.js b/test/navigation/ultra-nav.test.js similarity index 100% rename from test/ultra-nav.test.js rename to test/navigation/ultra-nav.test.js diff --git a/test/ultra-off.test.js b/test/navigation/ultra-off.test.js similarity index 100% rename from test/ultra-off.test.js rename to test/navigation/ultra-off.test.js diff --git a/test/ultra-search.test.js b/test/navigation/ultra-search.test.js similarity index 100% rename from test/ultra-search.test.js rename to test/navigation/ultra-search.test.js diff --git a/test/ultra-todo-line.test.js b/test/navigation/ultra-todo-line.test.js similarity index 100% rename from test/ultra-todo-line.test.js rename to test/navigation/ultra-todo-line.test.js diff --git a/test/pet-clock.test.js b/test/pet/pet-clock.test.js similarity index 100% rename from test/pet-clock.test.js rename to test/pet/pet-clock.test.js diff --git a/test/pet-review.test.js b/test/pet/pet-review.test.js similarity index 100% rename from test/pet-review.test.js rename to test/pet/pet-review.test.js diff --git a/test/pet-ui.test.js b/test/pet/pet-ui.test.js similarity index 100% rename from test/pet-ui.test.js rename to test/pet/pet-ui.test.js diff --git a/test/pet-yield.test.js b/test/pet/pet-yield.test.js similarity index 100% rename from test/pet-yield.test.js rename to test/pet/pet-yield.test.js diff --git a/test/pet.test.js b/test/pet/pet.test.js similarity index 100% rename from test/pet.test.js rename to test/pet/pet.test.js diff --git a/test/petlibrary.test.js b/test/pet/petlibrary.test.js similarity index 100% rename from test/petlibrary.test.js rename to test/pet/petlibrary.test.js diff --git a/test/petplayer.test.js b/test/pet/petplayer.test.js similarity index 100% rename from test/petplayer.test.js rename to test/pet/petplayer.test.js diff --git a/test/petsprites.test.js b/test/pet/petsprites.test.js similarity index 100% rename from test/petsprites.test.js rename to test/pet/petsprites.test.js diff --git a/test/petworld.test.js b/test/pet/petworld.test.js similarity index 100% rename from test/petworld.test.js rename to test/pet/petworld.test.js diff --git a/test/calendar.test.js b/test/planner/calendar.test.js similarity index 100% rename from test/calendar.test.js rename to test/planner/calendar.test.js diff --git a/test/calexport.test.js b/test/planner/calexport.test.js similarity index 100% rename from test/calexport.test.js rename to test/planner/calexport.test.js diff --git a/test/classes-review.test.js b/test/planner/classes-review.test.js similarity index 100% rename from test/classes-review.test.js rename to test/planner/classes-review.test.js diff --git a/test/classes-ui.test.js b/test/planner/classes-ui.test.js similarity index 100% rename from test/classes-ui.test.js rename to test/planner/classes-ui.test.js diff --git a/test/classes.test.js b/test/planner/classes.test.js similarity index 100% rename from test/classes.test.js rename to test/planner/classes.test.js diff --git a/test/gcal.test.js b/test/planner/gcal.test.js similarity index 100% rename from test/gcal.test.js rename to test/planner/gcal.test.js diff --git a/test/gpa.test.js b/test/planner/gpa.test.js similarity index 100% rename from test/gpa.test.js rename to test/planner/gpa.test.js diff --git a/test/gradecalc-parity.test.js b/test/planner/gradecalc-parity.test.js similarity index 100% rename from test/gradecalc-parity.test.js rename to test/planner/gradecalc-parity.test.js diff --git a/test/gradecalc.test.js b/test/planner/gradecalc.test.js similarity index 100% rename from test/gradecalc.test.js rename to test/planner/gradecalc.test.js diff --git a/test/grades-ui.test.js b/test/planner/grades-ui.test.js similarity index 100% rename from test/grades-ui.test.js rename to test/planner/grades-ui.test.js diff --git a/test/grades.test.js b/test/planner/grades.test.js similarity index 100% rename from test/grades.test.js rename to test/planner/grades.test.js diff --git a/test/gradescope.test.js b/test/planner/gradescope.test.js similarity index 100% rename from test/gradescope.test.js rename to test/planner/gradescope.test.js diff --git a/test/ics.test.js b/test/planner/ics.test.js similarity index 100% rename from test/ics.test.js rename to test/planner/ics.test.js diff --git a/test/import.test.js b/test/planner/import.test.js similarity index 100% rename from test/import.test.js rename to test/planner/import.test.js diff --git a/test/insights-ui.test.js b/test/planner/insights-ui.test.js similarity index 100% rename from test/insights-ui.test.js rename to test/planner/insights-ui.test.js diff --git a/test/insights.test.js b/test/planner/insights.test.js similarity index 100% rename from test/insights.test.js rename to test/planner/insights.test.js diff --git a/test/notes-ui.test.js b/test/planner/notes-ui.test.js similarity index 100% rename from test/notes-ui.test.js rename to test/planner/notes-ui.test.js diff --git a/test/notes.test.js b/test/planner/notes.test.js similarity index 100% rename from test/notes.test.js rename to test/planner/notes.test.js diff --git a/test/remind.test.js b/test/planner/remind.test.js similarity index 100% rename from test/remind.test.js rename to test/planner/remind.test.js diff --git a/test/reminders-ui.test.js b/test/planner/reminders-ui.test.js similarity index 100% rename from test/reminders-ui.test.js rename to test/planner/reminders-ui.test.js diff --git a/test/schedule.test.js b/test/planner/schedule.test.js similarity index 100% rename from test/schedule.test.js rename to test/planner/schedule.test.js diff --git a/test/syllabus-ai-ui.test.js b/test/planner/syllabus-ai-ui.test.js similarity index 100% rename from test/syllabus-ai-ui.test.js rename to test/planner/syllabus-ai-ui.test.js diff --git a/test/syllabus-sync-ui.test.js b/test/planner/syllabus-sync-ui.test.js similarity index 100% rename from test/syllabus-sync-ui.test.js rename to test/planner/syllabus-sync-ui.test.js diff --git a/test/syllabus-ui.test.js b/test/planner/syllabus-ui.test.js similarity index 100% rename from test/syllabus-ui.test.js rename to test/planner/syllabus-ui.test.js diff --git a/test/syllabus.test.js b/test/planner/syllabus.test.js similarity index 100% rename from test/syllabus.test.js rename to test/planner/syllabus.test.js diff --git a/test/todo-ui.test.js b/test/planner/todo-ui.test.js similarity index 100% rename from test/todo-ui.test.js rename to test/planner/todo-ui.test.js diff --git a/test/todo.test.js b/test/planner/todo.test.js similarity index 100% rename from test/todo.test.js rename to test/planner/todo.test.js diff --git a/test/ultra-assessment-results.test.js b/test/planner/ultra-assessment-results.test.js similarity index 100% rename from test/ultra-assessment-results.test.js rename to test/planner/ultra-assessment-results.test.js diff --git a/test/ultra-due-chip.test.js b/test/planner/ultra-due-chip.test.js similarity index 100% rename from test/ultra-due-chip.test.js rename to test/planner/ultra-due-chip.test.js diff --git a/test/ultra-plan-ui.test.js b/test/planner/ultra-plan-ui.test.js similarity index 100% rename from test/ultra-plan-ui.test.js rename to test/planner/ultra-plan-ui.test.js diff --git a/test/ultra-plan.test.js b/test/planner/ultra-plan.test.js similarity index 100% rename from test/ultra-plan.test.js rename to test/planner/ultra-plan.test.js diff --git a/test/ultra-submission.test.js b/test/planner/ultra-submission.test.js similarity index 100% rename from test/ultra-submission.test.js rename to test/planner/ultra-submission.test.js diff --git a/test/animation-imports.test.js b/test/platform/animation-imports.test.js similarity index 100% rename from test/animation-imports.test.js rename to test/platform/animation-imports.test.js diff --git a/test/background-pages-check.test.js b/test/platform/background-pages-check.test.js similarity index 100% rename from test/background-pages-check.test.js rename to test/platform/background-pages-check.test.js diff --git a/test/background-update.test.js b/test/platform/background-update.test.js similarity index 100% rename from test/background-update.test.js rename to test/platform/background-update.test.js diff --git a/test/editor-hosts.test.js b/test/platform/editor-hosts.test.js similarity index 100% rename from test/editor-hosts.test.js rename to test/platform/editor-hosts.test.js diff --git a/test/feature-hosts.test.js b/test/platform/feature-hosts.test.js similarity index 100% rename from test/feature-hosts.test.js rename to test/platform/feature-hosts.test.js diff --git a/test/fetch-hosts.test.js b/test/platform/fetch-hosts.test.js similarity index 100% rename from test/fetch-hosts.test.js rename to test/platform/fetch-hosts.test.js diff --git a/test/frame-tabs.test.js b/test/platform/frame-tabs.test.js similarity index 100% rename from test/frame-tabs.test.js rename to test/platform/frame-tabs.test.js diff --git a/test/frame-trust.test.js b/test/platform/frame-trust.test.js similarity index 100% rename from test/frame-trust.test.js rename to test/platform/frame-trust.test.js diff --git a/test/head.test.js b/test/platform/head.test.js similarity index 100% rename from test/head.test.js rename to test/platform/head.test.js diff --git a/test/i18n.test.js b/test/platform/i18n.test.js similarity index 100% rename from test/i18n.test.js rename to test/platform/i18n.test.js diff --git a/test/lms-requests.test.js b/test/platform/lms-requests.test.js similarity index 100% rename from test/lms-requests.test.js rename to test/platform/lms-requests.test.js diff --git a/test/loader.test.js b/test/platform/loader.test.js similarity index 100% rename from test/loader.test.js rename to test/platform/loader.test.js diff --git a/test/manifest.test.js b/test/platform/manifest.test.js similarity index 100% rename from test/manifest.test.js rename to test/platform/manifest.test.js diff --git a/test/oauth.test.js b/test/platform/oauth.test.js similarity index 100% rename from test/oauth.test.js rename to test/platform/oauth.test.js diff --git a/test/perf-budget.test.js b/test/platform/perf-budget.test.js similarity index 100% rename from test/perf-budget.test.js rename to test/platform/perf-budget.test.js diff --git a/test/privacy-claims.test.js b/test/platform/privacy-claims.test.js similarity index 100% rename from test/privacy-claims.test.js rename to test/platform/privacy-claims.test.js diff --git a/test/register-update.test.js b/test/platform/register-update.test.js similarity index 100% rename from test/register-update.test.js rename to test/platform/register-update.test.js diff --git a/test/runtime-audit-fixes.test.js b/test/platform/runtime-audit-fixes.test.js similarity index 100% rename from test/runtime-audit-fixes.test.js rename to test/platform/runtime-audit-fixes.test.js diff --git a/test/site-part.test.js b/test/platform/site-part.test.js similarity index 100% rename from test/site-part.test.js rename to test/platform/site-part.test.js diff --git a/test/vendor-pdfjs.test.js b/test/platform/vendor-pdfjs.test.js similarity index 100% rename from test/vendor-pdfjs.test.js rename to test/platform/vendor-pdfjs.test.js diff --git a/test/vendor-three.test.js b/test/platform/vendor-three.test.js similarity index 100% rename from test/vendor-three.test.js rename to test/platform/vendor-three.test.js diff --git a/test/worker-i18n.test.js b/test/platform/worker-i18n.test.js similarity index 100% rename from test/worker-i18n.test.js rename to test/platform/worker-i18n.test.js diff --git a/test/audit-documentation.test.js b/test/release/audit-documentation.test.js similarity index 100% rename from test/audit-documentation.test.js rename to test/release/audit-documentation.test.js diff --git a/test/audit-release-readiness.test.js b/test/release/audit-release-readiness.test.js similarity index 100% rename from test/audit-release-readiness.test.js rename to test/release/audit-release-readiness.test.js diff --git a/test/audit-ultra-completeness.test.js b/test/release/audit-ultra-completeness.test.js similarity index 100% rename from test/audit-ultra-completeness.test.js rename to test/release/audit-ultra-completeness.test.js diff --git a/test/audit.test.js b/test/release/audit.test.js similarity index 100% rename from test/audit.test.js rename to test/release/audit.test.js diff --git a/test/blocklist-sign.test.js b/test/release/blocklist-sign.test.js similarity index 100% rename from test/blocklist-sign.test.js rename to test/release/blocklist-sign.test.js diff --git a/test/browser-checks.test.js b/test/release/browser-checks.test.js similarity index 100% rename from test/browser-checks.test.js rename to test/release/browser-checks.test.js diff --git a/test/check-env.test.js b/test/release/check-env.test.js similarity index 100% rename from test/check-env.test.js rename to test/release/check-env.test.js diff --git a/test/check-readiness.test.js b/test/release/check-readiness.test.js similarity index 100% rename from test/check-readiness.test.js rename to test/release/check-readiness.test.js diff --git a/test/ci-browser.test.js b/test/release/ci-browser.test.js similarity index 100% rename from test/ci-browser.test.js rename to test/release/ci-browser.test.js diff --git a/test/narrow-check-seeds.test.js b/test/release/narrow-check-seeds.test.js similarity index 100% rename from test/narrow-check-seeds.test.js rename to test/release/narrow-check-seeds.test.js diff --git a/test/package.test.js b/test/release/package.test.js similarity index 100% rename from test/package.test.js rename to test/release/package.test.js diff --git a/test/release-copy.test.js b/test/release/release-copy.test.js similarity index 100% rename from test/release-copy.test.js rename to test/release/release-copy.test.js diff --git a/test/release-env.test.js b/test/release/release-env.test.js similarity index 100% rename from test/release-env.test.js rename to test/release/release-env.test.js diff --git a/test/release-listings.test.js b/test/release/release-listings.test.js similarity index 100% rename from test/release-listings.test.js rename to test/release/release-listings.test.js diff --git a/test/release-mcpb.test.js b/test/release/release-mcpb.test.js similarity index 100% rename from test/release-mcpb.test.js rename to test/release/release-mcpb.test.js diff --git a/test/release-redact.test.js b/test/release/release-redact.test.js similarity index 100% rename from test/release-redact.test.js rename to test/release/release-redact.test.js diff --git a/test/release-sources.test.js b/test/release/release-sources.test.js similarity index 100% rename from test/release-sources.test.js rename to test/release/release-sources.test.js diff --git a/test/release-store-api.test.js b/test/release/release-store-api.test.js similarity index 100% rename from test/release-store-api.test.js rename to test/release/release-store-api.test.js diff --git a/test/release-verify.test.js b/test/release/release-verify.test.js similarity index 100% rename from test/release-verify.test.js rename to test/release/release-verify.test.js diff --git a/test/stand-in-dom.test.js b/test/release/stand-in-dom.test.js similarity index 100% rename from test/stand-in-dom.test.js rename to test/release/stand-in-dom.test.js diff --git a/test/store-listing.test.js b/test/release/store-listing.test.js similarity index 100% rename from test/store-listing.test.js rename to test/release/store-listing.test.js diff --git a/test/workflows.test.js b/test/release/workflows.test.js similarity index 100% rename from test/workflows.test.js rename to test/release/workflows.test.js diff --git a/test/agent-bridge-native.test.js b/test/safari/agent-bridge-native.test.js similarity index 100% rename from test/agent-bridge-native.test.js rename to test/safari/agent-bridge-native.test.js diff --git a/test/agent-host-native.test.js b/test/safari/agent-host-native.test.js similarity index 100% rename from test/agent-host-native.test.js rename to test/safari/agent-host-native.test.js diff --git a/test/ai-apple-native.test.js b/test/safari/ai-apple-native.test.js similarity index 100% rename from test/ai-apple-native.test.js rename to test/safari/ai-apple-native.test.js diff --git a/test/make-dmg.test.js b/test/safari/make-dmg.test.js similarity index 100% rename from test/make-dmg.test.js rename to test/safari/make-dmg.test.js diff --git a/test/safari-keep-data.test.js b/test/safari/safari-keep-data.test.js similarity index 100% rename from test/safari-keep-data.test.js rename to test/safari/safari-keep-data.test.js diff --git a/test/safari-resources.test.js b/test/safari/safari-resources.test.js similarity index 100% rename from test/safari-resources.test.js rename to test/safari/safari-resources.test.js diff --git a/test/safari-source-resources.test.js b/test/safari/safari-source-resources.test.js similarity index 100% rename from test/safari-source-resources.test.js rename to test/safari/safari-source-resources.test.js diff --git a/test/safari-update-native.test.js b/test/safari/safari-update-native.test.js similarity index 100% rename from test/safari-update-native.test.js rename to test/safari/safari-update-native.test.js diff --git a/test/backup-check.test.js b/test/settings/backup-check.test.js similarity index 100% rename from test/backup-check.test.js rename to test/settings/backup-check.test.js diff --git a/test/backup-files.test.js b/test/settings/backup-files.test.js similarity index 100% rename from test/backup-files.test.js rename to test/settings/backup-files.test.js diff --git a/test/backup-media.test.js b/test/settings/backup-media.test.js similarity index 100% rename from test/backup-media.test.js rename to test/settings/backup-media.test.js diff --git a/test/backup-picker.test.js b/test/settings/backup-picker.test.js similarity index 100% rename from test/backup-picker.test.js rename to test/settings/backup-picker.test.js diff --git a/test/backup-worker.test.js b/test/settings/backup-worker.test.js similarity index 100% rename from test/backup-worker.test.js rename to test/settings/backup-worker.test.js diff --git a/test/backup.test.js b/test/settings/backup.test.js similarity index 100% rename from test/backup.test.js rename to test/settings/backup.test.js diff --git a/test/docs-settings-paths.test.js b/test/settings/docs-settings-paths.test.js similarity index 100% rename from test/docs-settings-paths.test.js rename to test/settings/docs-settings-paths.test.js diff --git a/test/editor-agents.test.js b/test/settings/editor-agents.test.js similarity index 100% rename from test/editor-agents.test.js rename to test/settings/editor-agents.test.js diff --git a/test/editor-info.test.js b/test/settings/editor-info.test.js similarity index 100% rename from test/editor-info.test.js rename to test/settings/editor-info.test.js diff --git a/test/editor-media-builder.test.js b/test/settings/editor-media-builder.test.js similarity index 100% rename from test/editor-media-builder.test.js rename to test/settings/editor-media-builder.test.js diff --git a/test/editor-media-size.test.js b/test/settings/editor-media-size.test.js similarity index 100% rename from test/editor-media-size.test.js rename to test/settings/editor-media-size.test.js diff --git a/test/editor-mods.test.js b/test/settings/editor-mods.test.js similarity index 100% rename from test/editor-mods.test.js rename to test/settings/editor-mods.test.js diff --git a/test/editor-pointer.test.js b/test/settings/editor-pointer.test.js similarity index 100% rename from test/editor-pointer.test.js rename to test/settings/editor-pointer.test.js diff --git a/test/editor-widget.test.js b/test/settings/editor-widget.test.js similarity index 100% rename from test/editor-widget.test.js rename to test/settings/editor-widget.test.js diff --git a/test/editor-x.test.js b/test/settings/editor-x.test.js similarity index 100% rename from test/editor-x.test.js rename to test/settings/editor-x.test.js diff --git a/test/editor-xparts-save.test.js b/test/settings/editor-xparts-save.test.js similarity index 100% rename from test/editor-xparts-save.test.js rename to test/settings/editor-xparts-save.test.js diff --git a/test/editor-xparts-ui.test.js b/test/settings/editor-xparts-ui.test.js similarity index 100% rename from test/editor-xparts-ui.test.js rename to test/settings/editor-xparts-ui.test.js diff --git a/test/editor-xparts.test.js b/test/settings/editor-xparts.test.js similarity index 100% rename from test/editor-xparts.test.js rename to test/settings/editor-xparts.test.js diff --git a/test/editor.test.js b/test/settings/editor.test.js similarity index 100% rename from test/editor.test.js rename to test/settings/editor.test.js diff --git a/test/embedded-settings-overlays.test.js b/test/settings/embedded-settings-overlays.test.js similarity index 100% rename from test/embedded-settings-overlays.test.js rename to test/settings/embedded-settings-overlays.test.js diff --git a/test/news.test.js b/test/settings/news.test.js similarity index 100% rename from test/news.test.js rename to test/settings/news.test.js diff --git a/test/popup-motion.test.js b/test/settings/popup-motion.test.js similarity index 100% rename from test/popup-motion.test.js rename to test/settings/popup-motion.test.js diff --git a/test/popup-site.test.js b/test/settings/popup-site.test.js similarity index 100% rename from test/popup-site.test.js rename to test/settings/popup-site.test.js diff --git a/test/rubric-settings.test.js b/test/settings/rubric-settings.test.js similarity index 100% rename from test/rubric-settings.test.js rename to test/settings/rubric-settings.test.js diff --git a/test/settings-ai-check.test.js b/test/settings/settings-ai-check.test.js similarity index 100% rename from test/settings-ai-check.test.js rename to test/settings/settings-ai-check.test.js diff --git a/test/settings-copy.test.js b/test/settings/settings-copy.test.js similarity index 100% rename from test/settings-copy.test.js rename to test/settings/settings-copy.test.js diff --git a/test/settings-page.test.js b/test/settings/settings-page.test.js similarity index 100% rename from test/settings-page.test.js rename to test/settings/settings-page.test.js diff --git a/test/settings-sites.test.js b/test/settings/settings-sites.test.js similarity index 100% rename from test/settings-sites.test.js rename to test/settings/settings-sites.test.js diff --git a/test/settings-writer.test.js b/test/settings/settings-writer.test.js similarity index 100% rename from test/settings-writer.test.js rename to test/settings/settings-writer.test.js diff --git a/test/settings.test.js b/test/settings/settings.test.js similarity index 100% rename from test/settings.test.js rename to test/settings/settings.test.js diff --git a/test/welcome.test.js b/test/settings/welcome.test.js similarity index 100% rename from test/welcome.test.js rename to test/settings/welcome.test.js diff --git a/test/quick-broker.test.js b/test/sharing/quick-broker.test.js similarity index 100% rename from test/quick-broker.test.js rename to test/sharing/quick-broker.test.js diff --git a/test/quick-ui.test.js b/test/sharing/quick-ui.test.js similarity index 100% rename from test/quick-ui.test.js rename to test/sharing/quick-ui.test.js diff --git a/test/relay-gesture.test.js b/test/sharing/relay-gesture.test.js similarity index 100% rename from test/relay-gesture.test.js rename to test/sharing/relay-gesture.test.js diff --git a/test/relay-quota-review.test.js b/test/sharing/relay-quota-review.test.js similarity index 100% rename from test/relay-quota-review.test.js rename to test/sharing/relay-quota-review.test.js diff --git a/test/relay-quota.test.js b/test/sharing/relay-quota.test.js similarity index 100% rename from test/relay-quota.test.js rename to test/sharing/relay-quota.test.js diff --git a/test/relay-resource.test.js b/test/sharing/relay-resource.test.js similarity index 100% rename from test/relay-resource.test.js rename to test/sharing/relay-resource.test.js diff --git a/test/relay-ui.test.js b/test/sharing/relay-ui.test.js similarity index 100% rename from test/relay-ui.test.js rename to test/sharing/relay-ui.test.js diff --git a/test/relay.test.js b/test/sharing/relay.test.js similarity index 100% rename from test/relay.test.js rename to test/sharing/relay.test.js diff --git a/test/sessions-create.test.js b/test/sharing/sessions-create.test.js similarity index 100% rename from test/sessions-create.test.js rename to test/sharing/sessions-create.test.js diff --git a/test/sessions-files.test.js b/test/sharing/sessions-files.test.js similarity index 100% rename from test/sessions-files.test.js rename to test/sharing/sessions-files.test.js diff --git a/test/sessions-media.test.js b/test/sharing/sessions-media.test.js similarity index 100% rename from test/sessions-media.test.js rename to test/sharing/sessions-media.test.js diff --git a/test/sessions-sync.test.js b/test/sharing/sessions-sync.test.js similarity index 100% rename from test/sessions-sync.test.js rename to test/sharing/sessions-sync.test.js diff --git a/test/sessions-ui-lifecycle.test.js b/test/sharing/sessions-ui-lifecycle.test.js similarity index 100% rename from test/sessions-ui-lifecycle.test.js rename to test/sharing/sessions-ui-lifecycle.test.js diff --git a/test/sessions.test.js b/test/sharing/sessions.test.js similarity index 100% rename from test/sessions.test.js rename to test/sharing/sessions.test.js diff --git a/test/share-keys.test.js b/test/sharing/share-keys.test.js similarity index 100% rename from test/share-keys.test.js rename to test/sharing/share-keys.test.js diff --git a/test/share-protocol.test.js b/test/sharing/share-protocol.test.js similarity index 100% rename from test/share-protocol.test.js rename to test/sharing/share-protocol.test.js diff --git a/test/sharing.test.js b/test/sharing/sharing.test.js similarity index 100% rename from test/sharing.test.js rename to test/sharing/sharing.test.js diff --git a/test/drafts-browser.test.js b/test/writer/drafts-browser.test.js similarity index 100% rename from test/drafts-browser.test.js rename to test/writer/drafts-browser.test.js diff --git a/test/drafts-ui.test.js b/test/writer/drafts-ui.test.js similarity index 100% rename from test/drafts-ui.test.js rename to test/writer/drafts-ui.test.js diff --git a/test/drafts.test.js b/test/writer/drafts.test.js similarity index 100% rename from test/drafts.test.js rename to test/writer/drafts.test.js diff --git a/test/editor-menu.test.js b/test/writer/editor-menu.test.js similarity index 100% rename from test/editor-menu.test.js rename to test/writer/editor-menu.test.js diff --git a/test/ondemand.test.js b/test/writer/ondemand.test.js similarity index 100% rename from test/ondemand.test.js rename to test/writer/ondemand.test.js diff --git a/test/practice-ai.test.js b/test/writer/practice-ai.test.js similarity index 100% rename from test/practice-ai.test.js rename to test/writer/practice-ai.test.js diff --git a/test/writer-attach-open.test.js b/test/writer/writer-attach-open.test.js similarity index 100% rename from test/writer-attach-open.test.js rename to test/writer/writer-attach-open.test.js diff --git a/test/writer-attach.test.js b/test/writer/writer-attach.test.js similarity index 100% rename from test/writer-attach.test.js rename to test/writer/writer-attach.test.js diff --git a/test/writer-audit-regressions.test.js b/test/writer/writer-audit-regressions.test.js similarity index 100% rename from test/writer-audit-regressions.test.js rename to test/writer/writer-audit-regressions.test.js diff --git a/test/writer-bibliography.test.js b/test/writer/writer-bibliography.test.js similarity index 100% rename from test/writer-bibliography.test.js rename to test/writer/writer-bibliography.test.js diff --git a/test/writer-cite-check.test.js b/test/writer/writer-cite-check.test.js similarity index 100% rename from test/writer-cite-check.test.js rename to test/writer/writer-cite-check.test.js diff --git a/test/writer-cite-frame.test.js b/test/writer/writer-cite-frame.test.js similarity index 100% rename from test/writer-cite-frame.test.js rename to test/writer/writer-cite-frame.test.js diff --git a/test/writer-cite-lookup.test.js b/test/writer/writer-cite-lookup.test.js similarity index 100% rename from test/writer-cite-lookup.test.js rename to test/writer/writer-cite-lookup.test.js diff --git a/test/writer-cite-worker.test.js b/test/writer/writer-cite-worker.test.js similarity index 100% rename from test/writer-cite-worker.test.js rename to test/writer/writer-cite-worker.test.js diff --git a/test/writer-cite.test.js b/test/writer/writer-cite.test.js similarity index 100% rename from test/writer-cite.test.js rename to test/writer/writer-cite.test.js diff --git a/test/writer-content.test.js b/test/writer/writer-content.test.js similarity index 100% rename from test/writer-content.test.js rename to test/writer/writer-content.test.js diff --git a/test/writer-context-compose.test.js b/test/writer/writer-context-compose.test.js similarity index 100% rename from test/writer-context-compose.test.js rename to test/writer/writer-context-compose.test.js diff --git a/test/writer-context-lifecycle.test.js b/test/writer/writer-context-lifecycle.test.js similarity index 100% rename from test/writer-context-lifecycle.test.js rename to test/writer/writer-context-lifecycle.test.js diff --git a/test/writer-context.test.js b/test/writer/writer-context.test.js similarity index 100% rename from test/writer-context.test.js rename to test/writer/writer-context.test.js diff --git a/test/writer-counts.test.js b/test/writer/writer-counts.test.js similarity index 100% rename from test/writer-counts.test.js rename to test/writer/writer-counts.test.js diff --git a/test/writer-discussion-sources.test.js b/test/writer/writer-discussion-sources.test.js similarity index 100% rename from test/writer-discussion-sources.test.js rename to test/writer/writer-discussion-sources.test.js diff --git a/test/writer-docx-import.test.js b/test/writer/writer-docx-import.test.js similarity index 100% rename from test/writer-docx-import.test.js rename to test/writer/writer-docx-import.test.js diff --git a/test/writer-docx.test.js b/test/writer/writer-docx.test.js similarity index 100% rename from test/writer-docx.test.js rename to test/writer/writer-docx.test.js diff --git a/test/writer-editor.test.js b/test/writer/writer-editor.test.js similarity index 100% rename from test/writer-editor.test.js rename to test/writer/writer-editor.test.js diff --git a/test/writer-export.test.js b/test/writer/writer-export.test.js similarity index 100% rename from test/writer-export.test.js rename to test/writer/writer-export.test.js diff --git a/test/writer-feedback-flow.test.js b/test/writer/writer-feedback-flow.test.js similarity index 100% rename from test/writer-feedback-flow.test.js rename to test/writer/writer-feedback-flow.test.js diff --git a/test/writer-feedback-grade.test.js b/test/writer/writer-feedback-grade.test.js similarity index 100% rename from test/writer-feedback-grade.test.js rename to test/writer/writer-feedback-grade.test.js diff --git a/test/writer-feedback-images.test.js b/test/writer/writer-feedback-images.test.js similarity index 100% rename from test/writer-feedback-images.test.js rename to test/writer/writer-feedback-images.test.js diff --git a/test/writer-feedback-local.test.js b/test/writer/writer-feedback-local.test.js similarity index 100% rename from test/writer-feedback-local.test.js rename to test/writer/writer-feedback-local.test.js diff --git a/test/writer-final-cite.test.js b/test/writer/writer-final-cite.test.js similarity index 100% rename from test/writer-final-cite.test.js rename to test/writer/writer-final-cite.test.js diff --git a/test/writer-final-docx.test.js b/test/writer/writer-final-docx.test.js similarity index 100% rename from test/writer-final-docx.test.js rename to test/writer/writer-final-docx.test.js diff --git a/test/writer-final-paginate.test.js b/test/writer/writer-final-paginate.test.js similarity index 100% rename from test/writer-final-paginate.test.js rename to test/writer/writer-final-paginate.test.js diff --git a/test/writer-final-paste.test.js b/test/writer/writer-final-paste.test.js similarity index 100% rename from test/writer-final-paste.test.js rename to test/writer/writer-final-paste.test.js diff --git a/test/writer-final-proof.test.js b/test/writer/writer-final-proof.test.js similarity index 100% rename from test/writer-final-proof.test.js rename to test/writer/writer-final-proof.test.js diff --git a/test/writer-final-submit.test.js b/test/writer/writer-final-submit.test.js similarity index 100% rename from test/writer-final-submit.test.js rename to test/writer/writer-final-submit.test.js diff --git a/test/writer-find.test.js b/test/writer/writer-find.test.js similarity index 100% rename from test/writer-find.test.js rename to test/writer/writer-find.test.js diff --git a/test/writer-format-commands.test.js b/test/writer/writer-format-commands.test.js similarity index 100% rename from test/writer-format-commands.test.js rename to test/writer/writer-format-commands.test.js diff --git a/test/writer-format-setup.test.js b/test/writer/writer-format-setup.test.js similarity index 100% rename from test/writer-format-setup.test.js rename to test/writer/writer-format-setup.test.js diff --git a/test/writer-frame-count.test.js b/test/writer/writer-frame-count.test.js similarity index 100% rename from test/writer-frame-count.test.js rename to test/writer/writer-frame-count.test.js diff --git a/test/writer-frame.test.js b/test/writer/writer-frame.test.js similarity index 100% rename from test/writer-frame.test.js rename to test/writer/writer-frame.test.js diff --git a/test/writer-from-documents-sheet.test.js b/test/writer/writer-from-documents-sheet.test.js similarity index 100% rename from test/writer-from-documents-sheet.test.js rename to test/writer/writer-from-documents-sheet.test.js diff --git a/test/writer-hostile-roundtrip.test.js b/test/writer/writer-hostile-roundtrip.test.js similarity index 100% rename from test/writer-hostile-roundtrip.test.js rename to test/writer/writer-hostile-roundtrip.test.js diff --git a/test/writer-huge-budget.test.js b/test/writer/writer-huge-budget.test.js similarity index 100% rename from test/writer-huge-budget.test.js rename to test/writer/writer-huge-budget.test.js diff --git a/test/writer-image.test.js b/test/writer/writer-image.test.js similarity index 100% rename from test/writer-image.test.js rename to test/writer/writer-image.test.js diff --git a/test/writer-import-paper-review.test.js b/test/writer/writer-import-paper-review.test.js similarity index 100% rename from test/writer-import-paper-review.test.js rename to test/writer/writer-import-paper-review.test.js diff --git a/test/writer-import.test.js b/test/writer/writer-import.test.js similarity index 100% rename from test/writer-import.test.js rename to test/writer/writer-import.test.js diff --git a/test/writer-kinds-submit.test.js b/test/writer/writer-kinds-submit.test.js similarity index 100% rename from test/writer-kinds-submit.test.js rename to test/writer/writer-kinds-submit.test.js diff --git a/test/writer-kinds.test.js b/test/writer/writer-kinds.test.js similarity index 100% rename from test/writer-kinds.test.js rename to test/writer/writer-kinds.test.js diff --git a/test/writer-local-fonts.test.js b/test/writer/writer-local-fonts.test.js similarity index 100% rename from test/writer-local-fonts.test.js rename to test/writer/writer-local-fonts.test.js diff --git a/test/writer-objects-docx.test.js b/test/writer/writer-objects-docx.test.js similarity index 100% rename from test/writer-objects-docx.test.js rename to test/writer/writer-objects-docx.test.js diff --git a/test/writer-objects.test.js b/test/writer/writer-objects.test.js similarity index 100% rename from test/writer-objects.test.js rename to test/writer/writer-objects.test.js diff --git a/test/writer-outline-ai.test.js b/test/writer/writer-outline-ai.test.js similarity index 100% rename from test/writer-outline-ai.test.js rename to test/writer/writer-outline-ai.test.js diff --git a/test/writer-paginate-scheduler.test.js b/test/writer/writer-paginate-scheduler.test.js similarity index 100% rename from test/writer-paginate-scheduler.test.js rename to test/writer/writer-paginate-scheduler.test.js diff --git a/test/writer-paginate-slices.test.js b/test/writer/writer-paginate-slices.test.js similarity index 100% rename from test/writer-paginate-slices.test.js rename to test/writer/writer-paginate-slices.test.js diff --git a/test/writer-paper-compact.test.js b/test/writer/writer-paper-compact.test.js similarity index 100% rename from test/writer-paper-compact.test.js rename to test/writer/writer-paper-compact.test.js diff --git a/test/writer-paper-content.test.js b/test/writer/writer-paper-content.test.js similarity index 100% rename from test/writer-paper-content.test.js rename to test/writer/writer-paper-content.test.js diff --git a/test/writer-paper-context.test.js b/test/writer/writer-paper-context.test.js similarity index 100% rename from test/writer-paper-context.test.js rename to test/writer/writer-paper-context.test.js diff --git a/test/writer-paper-format.test.js b/test/writer/writer-paper-format.test.js similarity index 100% rename from test/writer-paper-format.test.js rename to test/writer/writer-paper-format.test.js diff --git a/test/writer-paper-key.test.js b/test/writer/writer-paper-key.test.js similarity index 100% rename from test/writer-paper-key.test.js rename to test/writer/writer-paper-key.test.js diff --git a/test/writer-paper-paginate.test.js b/test/writer/writer-paper-paginate.test.js similarity index 100% rename from test/writer-paper-paginate.test.js rename to test/writer/writer-paper-paginate.test.js diff --git a/test/writer-paper-papers.test.js b/test/writer/writer-paper-papers.test.js similarity index 100% rename from test/writer-paper-papers.test.js rename to test/writer/writer-paper-papers.test.js diff --git a/test/writer-paper-styles.test.js b/test/writer/writer-paper-styles.test.js similarity index 100% rename from test/writer-paper-styles.test.js rename to test/writer/writer-paper-styles.test.js diff --git a/test/writer-paper-view-save.test.js b/test/writer/writer-paper-view-save.test.js similarity index 100% rename from test/writer-paper-view-save.test.js rename to test/writer/writer-paper-view-save.test.js diff --git a/test/writer-papers-files.test.js b/test/writer/writer-papers-files.test.js similarity index 100% rename from test/writer-papers-files.test.js rename to test/writer/writer-papers-files.test.js diff --git a/test/writer-papers-proxy.test.js b/test/writer/writer-papers-proxy.test.js similarity index 100% rename from test/writer-papers-proxy.test.js rename to test/writer/writer-papers-proxy.test.js diff --git a/test/writer-papers-versions.test.js b/test/writer/writer-papers-versions.test.js similarity index 100% rename from test/writer-papers-versions.test.js rename to test/writer/writer-papers-versions.test.js diff --git a/test/writer-paste.test.js b/test/writer/writer-paste.test.js similarity index 100% rename from test/writer-paste.test.js rename to test/writer/writer-paste.test.js diff --git a/test/writer-pm.test.js b/test/writer/writer-pm.test.js similarity index 100% rename from test/writer-pm.test.js rename to test/writer/writer-pm.test.js diff --git a/test/writer-practice.test.js b/test/writer/writer-practice.test.js similarity index 100% rename from test/writer-practice.test.js rename to test/writer/writer-practice.test.js diff --git a/test/writer-proof-frame.test.js b/test/writer/writer-proof-frame.test.js similarity index 100% rename from test/writer-proof-frame.test.js rename to test/writer/writer-proof-frame.test.js diff --git a/test/writer-proof-harper.test.js b/test/writer/writer-proof-harper.test.js similarity index 100% rename from test/writer-proof-harper.test.js rename to test/writer/writer-proof-harper.test.js diff --git a/test/writer-proof-worker.test.js b/test/writer/writer-proof-worker.test.js similarity index 100% rename from test/writer-proof-worker.test.js rename to test/writer/writer-proof-worker.test.js diff --git a/test/writer-proof.test.js b/test/writer/writer-proof.test.js similarity index 100% rename from test/writer-proof.test.js rename to test/writer/writer-proof.test.js diff --git a/test/writer-quote-lifecycle.test.js b/test/writer/writer-quote-lifecycle.test.js similarity index 100% rename from test/writer-quote-lifecycle.test.js rename to test/writer/writer-quote-lifecycle.test.js diff --git a/test/writer-round1-document.test.js b/test/writer/writer-round1-document.test.js similarity index 100% rename from test/writer-round1-document.test.js rename to test/writer/writer-round1-document.test.js diff --git a/test/writer-round1-docx.test.js b/test/writer/writer-round1-docx.test.js similarity index 100% rename from test/writer-round1-docx.test.js rename to test/writer/writer-round1-docx.test.js diff --git a/test/writer-round4-notes.test.js b/test/writer/writer-round4-notes.test.js similarity index 100% rename from test/writer-round4-notes.test.js rename to test/writer/writer-round4-notes.test.js diff --git a/test/writer-round4-ui.test.js b/test/writer/writer-round4-ui.test.js similarity index 100% rename from test/writer-round4-ui.test.js rename to test/writer/writer-round4-ui.test.js diff --git a/test/writer-schema.test.js b/test/writer/writer-schema.test.js similarity index 100% rename from test/writer-schema.test.js rename to test/writer/writer-schema.test.js diff --git a/test/writer-storage-failures.test.js b/test/writer/writer-storage-failures.test.js similarity index 100% rename from test/writer-storage-failures.test.js rename to test/writer/writer-storage-failures.test.js diff --git a/test/writer-submit-check.test.js b/test/writer/writer-submit-check.test.js similarity index 100% rename from test/writer-submit-check.test.js rename to test/writer/writer-submit-check.test.js diff --git a/test/writer-submit-review.test.js b/test/writer/writer-submit-review.test.js similarity index 100% rename from test/writer-submit-review.test.js rename to test/writer/writer-submit-review.test.js diff --git a/test/writer-submit-sheet-review.test.js b/test/writer/writer-submit-sheet-review.test.js similarity index 100% rename from test/writer-submit-sheet-review.test.js rename to test/writer/writer-submit-sheet-review.test.js diff --git a/test/writer-submit-tap.test.js b/test/writer/writer-submit-tap.test.js similarity index 100% rename from test/writer-submit-tap.test.js rename to test/writer/writer-submit-tap.test.js diff --git a/test/writer-submit-wait.test.js b/test/writer/writer-submit-wait.test.js similarity index 100% rename from test/writer-submit-wait.test.js rename to test/writer/writer-submit-wait.test.js diff --git a/test/writer-submit.test.js b/test/writer/writer-submit.test.js similarity index 100% rename from test/writer-submit.test.js rename to test/writer/writer-submit.test.js diff --git a/test/writer-target.test.js b/test/writer/writer-target.test.js similarity index 100% rename from test/writer-target.test.js rename to test/writer/writer-target.test.js diff --git a/test/writer-thumb.test.js b/test/writer/writer-thumb.test.js similarity index 100% rename from test/writer-thumb.test.js rename to test/writer/writer-thumb.test.js diff --git a/test/writer-ultra.test.js b/test/writer/writer-ultra.test.js similarity index 100% rename from test/writer-ultra.test.js rename to test/writer/writer-ultra.test.js diff --git a/scripts/appstore-ios.sh b/tools/build/appstore-ios.sh similarity index 100% rename from scripts/appstore-ios.sh rename to tools/build/appstore-ios.sh diff --git a/scripts/appstore-safari.sh b/tools/build/appstore-safari.sh similarity index 100% rename from scripts/appstore-safari.sh rename to tools/build/appstore-safari.sh diff --git a/scripts/build-safari.sh b/tools/build/build-safari.sh similarity index 100% rename from scripts/build-safari.sh rename to tools/build/build-safari.sh diff --git a/scripts/make-dmg.sh b/tools/build/make-dmg.sh similarity index 100% rename from scripts/make-dmg.sh rename to tools/build/make-dmg.sh diff --git a/scripts/package-firefox.sh b/tools/build/package-firefox.sh similarity index 100% rename from scripts/package-firefox.sh rename to tools/build/package-firefox.sh diff --git a/scripts/package-safari.sh b/tools/build/package-safari.sh similarity index 100% rename from scripts/package-safari.sh rename to tools/build/package-safari.sh diff --git a/package.sh b/tools/build/package.sh similarity index 100% rename from package.sh rename to tools/build/package.sh diff --git a/scripts/safari-build-ids.py b/tools/build/safari-build-ids.py similarity index 100% rename from scripts/safari-build-ids.py rename to tools/build/safari-build-ids.py diff --git a/scripts/safari-manifest.sh b/tools/build/safari-manifest.sh similarity index 100% rename from scripts/safari-manifest.sh rename to tools/build/safari-manifest.sh diff --git a/scripts/safari-resources.js b/tools/build/safari-resources.js similarity index 100% rename from scripts/safari-resources.js rename to tools/build/safari-resources.js diff --git a/scripts/stage-safari-dmg.py b/tools/build/stage-safari-dmg.py similarity index 100% rename from scripts/stage-safari-dmg.py rename to tools/build/stage-safari-dmg.py diff --git a/scripts/sync-safari-resources.sh b/tools/build/sync-safari-resources.sh similarity index 100% rename from scripts/sync-safari-resources.sh rename to tools/build/sync-safari-resources.sh diff --git a/scripts/audit.js b/tools/dev/audit.js similarity index 100% rename from scripts/audit.js rename to tools/dev/audit.js diff --git a/scripts/canvas-shots.js b/tools/dev/canvas-shots.js similarity index 100% rename from scripts/canvas-shots.js rename to tools/dev/canvas-shots.js diff --git a/scripts/check-env.js b/tools/dev/check-env.js similarity index 100% rename from scripts/check-env.js rename to tools/dev/check-env.js diff --git a/scripts/ci-browser-checks.js b/tools/dev/ci-browser-checks.js similarity index 100% rename from scripts/ci-browser-checks.js rename to tools/dev/ci-browser-checks.js diff --git a/scripts/ci-chromium.js b/tools/dev/ci-chromium.js similarity index 100% rename from scripts/ci-chromium.js rename to tools/dev/ci-chromium.js diff --git a/scripts/dev-gcal.sh b/tools/dev/dev-gcal.sh similarity index 100% rename from scripts/dev-gcal.sh rename to tools/dev/dev-gcal.sh diff --git a/scripts/docs-mirror.js b/tools/dev/docs-mirror.js similarity index 100% rename from scripts/docs-mirror.js rename to tools/dev/docs-mirror.js diff --git a/scripts/firefox-android.sh b/tools/dev/firefox-android.sh similarity index 100% rename from scripts/firefox-android.sh rename to tools/dev/firefox-android.sh diff --git a/scripts/practice-ai.js b/tools/dev/practice-ai.js similarity index 100% rename from scripts/practice-ai.js rename to tools/dev/practice-ai.js diff --git a/scripts/redact.js b/tools/dev/redact.js similarity index 100% rename from scripts/redact.js rename to tools/dev/redact.js diff --git a/scripts/wait-for-browser.js b/tools/dev/wait-for-browser.js similarity index 100% rename from scripts/wait-for-browser.js rename to tools/dev/wait-for-browser.js diff --git a/scripts/writer-practice.js b/tools/dev/writer-practice.js similarity index 100% rename from scripts/writer-practice.js rename to tools/dev/writer-practice.js diff --git a/scripts/import-fixtures.js b/tools/fidelity/import-fixtures.js similarity index 100% rename from scripts/import-fixtures.js rename to tools/fidelity/import-fixtures.js diff --git a/scripts/pdf-fidelity.js b/tools/fidelity/pdf-fidelity.js similarity index 100% rename from scripts/pdf-fidelity.js rename to tools/fidelity/pdf-fidelity.js diff --git a/scripts/pdf-fixtures.js b/tools/fidelity/pdf-fixtures.js similarity index 100% rename from scripts/pdf-fixtures.js rename to tools/fidelity/pdf-fixtures.js diff --git a/scripts/sheet-excel-fidelity.js b/tools/fidelity/sheet-excel-fidelity.js similarity index 100% rename from scripts/sheet-excel-fidelity.js rename to tools/fidelity/sheet-excel-fidelity.js diff --git a/scripts/sheet-excel-golden.js b/tools/fidelity/sheet-excel-golden.js similarity index 100% rename from scripts/sheet-excel-golden.js rename to tools/fidelity/sheet-excel-golden.js diff --git a/scripts/sheet-excel.js b/tools/fidelity/sheet-excel.js similarity index 100% rename from scripts/sheet-excel.js rename to tools/fidelity/sheet-excel.js diff --git a/scripts/sheet-fidelity-books.js b/tools/fidelity/sheet-fidelity-books.js similarity index 100% rename from scripts/sheet-fidelity-books.js rename to tools/fidelity/sheet-fidelity-books.js diff --git a/scripts/sheet-numbers-fidelity.js b/tools/fidelity/sheet-numbers-fidelity.js similarity index 100% rename from scripts/sheet-numbers-fidelity.js rename to tools/fidelity/sheet-numbers-fidelity.js diff --git a/scripts/slides-fidelity.js b/tools/fidelity/slides-fidelity.js similarity index 100% rename from scripts/slides-fidelity.js rename to tools/fidelity/slides-fidelity.js diff --git a/scripts/writer-import-fidelity.js b/tools/fidelity/writer-import-fidelity.js similarity index 100% rename from scripts/writer-import-fidelity.js rename to tools/fidelity/writer-import-fidelity.js diff --git a/scripts/writer-word-fidelity.js b/tools/fidelity/writer-word-fidelity.js similarity index 100% rename from scripts/writer-word-fidelity.js rename to tools/fidelity/writer-word-fidelity.js diff --git a/scripts/blocklist-sign.mjs b/tools/release/blocklist-sign.mjs similarity index 100% rename from scripts/blocklist-sign.mjs rename to tools/release/blocklist-sign.mjs diff --git a/scripts/clean-source.sh b/tools/release/clean-source.sh similarity index 100% rename from scripts/clean-source.sh rename to tools/release/clean-source.sh diff --git a/scripts/public-release.mjs b/tools/release/public-release.mjs similarity index 100% rename from scripts/public-release.mjs rename to tools/release/public-release.mjs diff --git a/scripts/publish-chrome.sh b/tools/release/publish-chrome.sh similarity index 100% rename from scripts/publish-chrome.sh rename to tools/release/publish-chrome.sh diff --git a/scripts/release.sh b/tools/release/release.sh similarity index 100% rename from scripts/release.sh rename to tools/release/release.sh diff --git a/scripts/safari-appcast.sh b/tools/release/safari-appcast.sh similarity index 100% rename from scripts/safari-appcast.sh rename to tools/release/safari-appcast.sh diff --git a/scripts/store-api.mjs b/tools/release/store-api.mjs similarity index 100% rename from scripts/store-api.mjs rename to tools/release/store-api.mjs diff --git a/scripts/store-firefox-source.py b/tools/release/store-firefox-source.py similarity index 100% rename from scripts/store-firefox-source.py rename to tools/release/store-firefox-source.py diff --git a/scripts/store-listings.mjs b/tools/release/store-listings.mjs similarity index 100% rename from scripts/store-listings.mjs rename to tools/release/store-listings.mjs diff --git a/scripts/store-release.env.example b/tools/release/store-release.env.example similarity index 100% rename from scripts/store-release.env.example rename to tools/release/store-release.env.example diff --git a/scripts/store-release.mjs b/tools/release/store-release.mjs similarity index 100% rename from scripts/store-release.mjs rename to tools/release/store-release.mjs diff --git a/scripts/store-screenshots.js b/tools/release/store-screenshots.js similarity index 100% rename from scripts/store-screenshots.js rename to tools/release/store-screenshots.js diff --git a/scripts/npm-integrity.js b/tools/vendor/npm-integrity.js similarity index 100% rename from scripts/npm-integrity.js rename to tools/vendor/npm-integrity.js diff --git a/scripts/vendor-harper.sh b/tools/vendor/vendor-harper.sh similarity index 100% rename from scripts/vendor-harper.sh rename to tools/vendor/vendor-harper.sh diff --git a/scripts/vendor-lottie.sh b/tools/vendor/vendor-lottie.sh similarity index 100% rename from scripts/vendor-lottie.sh rename to tools/vendor/vendor-lottie.sh diff --git a/scripts/vendor-paper-fonts.sh b/tools/vendor/vendor-paper-fonts.sh similarity index 100% rename from scripts/vendor-paper-fonts.sh rename to tools/vendor/vendor-paper-fonts.sh diff --git a/scripts/vendor-pdfjs-brotli.js b/tools/vendor/vendor-pdfjs-brotli.js similarity index 100% rename from scripts/vendor-pdfjs-brotli.js rename to tools/vendor/vendor-pdfjs-brotli.js diff --git a/scripts/vendor-pdfjs-limits.js b/tools/vendor/vendor-pdfjs-limits.js similarity index 100% rename from scripts/vendor-pdfjs-limits.js rename to tools/vendor/vendor-pdfjs-limits.js diff --git a/scripts/vendor-pdfjs.sh b/tools/vendor/vendor-pdfjs.sh similarity index 100% rename from scripts/vendor-pdfjs.sh rename to tools/vendor/vendor-pdfjs.sh diff --git a/scripts/vendor-prosemirror.sh b/tools/vendor/vendor-prosemirror.sh similarity index 100% rename from scripts/vendor-prosemirror.sh rename to tools/vendor/vendor-prosemirror.sh diff --git a/scripts/vendor-tesseract.sh b/tools/vendor/vendor-tesseract.sh similarity index 100% rename from scripts/vendor-tesseract.sh rename to tools/vendor/vendor-tesseract.sh diff --git a/scripts/vendor-three.sh b/tools/vendor/vendor-three.sh similarity index 100% rename from scripts/vendor-three.sh rename to tools/vendor/vendor-three.sh From 91bc94883013fb2f2fcf90cb74880ce9a9a0f4ee Mon Sep 17 00:00:00 2001 From: Frostdev Date: Fri, 30 Oct 2026 17:24:43 -0400 Subject: [PATCH 233/907] Point every script, check, test, workflow and doc at the new paths --- .github/pins/chromium.pins | 2 +- .github/pins/playwright.pins | 4 +- .github/workflows/browser.yml | 8 +- .github/workflows/package.yml | 4 +- .github/workflows/test.yml | 8 +- .gitignore | 2 +- CONTRIBUTING.md | 7 +- README.md | 12 +-- RELEASING.md | 48 ++++++------ checks/agents/agent-link-check.js | 32 ++++---- checks/agents/mcp-approve-check.js | 14 ++-- checks/agents/mcp-conformance-check.js | 32 ++++---- checks/agents/mcp-refresh-check.js | 16 ++-- checks/agents/mcp-windows-check.js | 14 ++-- checks/agents/mcp-writer-check.js | 14 ++-- checks/core/download-check.js | 14 ++-- checks/core/widgets-builder-check.js | 12 +-- checks/core/widgets-sizes-check.js | 18 ++--- checks/courses/allow-check.js | 10 +-- checks/courses/announce-check-sites.js | 8 +- checks/courses/announce-check.js | 22 +++--- checks/courses/announce-read-check.js | 12 +-- checks/courses/canvas-boards-check.js | 14 ++-- checks/courses/docreview-check.js | 14 ++-- checks/courses/docviewer-check.js | 16 ++-- checks/courses/rubric-check.js | 16 ++-- checks/courses/stream-check.js | 8 +- checks/courses/stream-runtime-check.js | 12 +-- .../courses/ultra-assignment-attempt-check.js | 12 +-- checks/courses/ultra-boards-check.js | 26 +++---- checks/courses/ultra-cards-check.js | 14 ++-- checks/courses/ultra-folders-check.js | 14 ++-- checks/courses/ultra-live-frames-check.js | 14 ++-- checks/courses/ultra-perf-check.js | 10 +-- checks/courses/ultra-preview-check.js | 18 ++--- checks/courses/ultra-test-attempt-check.js | 12 +-- checks/documents/documents-ai-check.js | 18 ++--- checks/documents/documents-check.js | 12 +-- checks/documents/documents-entry-check.js | 20 ++--- checks/documents/documents-page-check.js | 24 +++--- checks/documents/pdf-check.js | 18 ++--- checks/documents/sheets-check.js | 14 ++-- checks/documents/slides-check.js | 12 +-- checks/firefox/agent-link-firefox-check.mjs | 16 ++-- checks/firefox/firefox-check.mjs | 18 ++--- checks/looks/glass-remaining-check.js | 14 ++-- checks/looks/lookparts-check.js | 10 +-- checks/looks/looks-catalog-check.js | 28 +++---- checks/looks/media-check.js | 14 ++-- checks/looks/media-cost-check.js | 12 +-- checks/looks/motion-check.js | 8 +- checks/looks/motion-default-check.js | 12 +-- checks/looks/motionx-check.js | 14 ++-- checks/looks/pointer-check.js | 14 ++-- checks/looks/rules-check.js | 18 ++--- checks/looks/scene-check.html | 2 +- checks/looks/scene-check.mjs | 12 +-- checks/looks/scene-page-check.js | 12 +-- checks/looks/surfaces-check.js | 8 +- checks/mocks/gcal-mock.js | 6 +- checks/mocks/ultra-mock.js | 8 +- checks/mocks/ultra-offcanvas-fixture.js | 4 +- checks/mods/blocklist-check.js | 8 +- checks/mods/modbundle-check.js | 8 +- checks/mods/mods-api-check.js | 12 +-- checks/mods/mods-channel-check.js | 10 +-- checks/mods/mods-combined-check.js | 12 +-- checks/mods/mods-parts-check.js | 10 +-- checks/mods/mods-sandbox-check.js | 12 +-- checks/mods/mods-section-check.js | 8 +- checks/mods/mods-tools-check.js | 18 ++--- checks/navigation/coursetabs-sketch-check.js | 10 +-- checks/navigation/ctab-background-check.js | 20 ++--- checks/navigation/ctab-palette-check.js | 14 ++-- checks/navigation/ctab-panel-close-check.js | 12 +-- checks/navigation/ctab-strip-glass-check.js | 12 +-- checks/navigation/nav-layout-check.js | 16 ++-- checks/navigation/navbar-canvas-check.js | 12 +-- checks/navigation/navbar-check.js | 12 +-- checks/navigation/navbar-moodle-check.js | 8 +- checks/navigation/sidebar-assessment-check.js | 8 +- checks/navigation/sidebar-canvas-check.js | 16 ++-- .../navigation/sidebar-participants-check.js | 8 +- checks/navigation/sidebar-tabs-check.js | 16 ++-- .../navigation/sidebar-test-attempt-check.js | 12 +-- checks/navigation/sidebar-ultra-check.js | 10 +-- checks/navigation/sidebar-ultra-tabs-check.js | 12 +-- .../navigation/ultra-contents-drawer-check.js | 12 +-- checks/navigation/ultra-course-bar-check.js | 12 +-- checks/navigation/ultra-offcanvas-check.js | 8 +- .../ultra-panel-background-check.js | 8 +- checks/navigation/ultra-panels-check.js | 14 ++-- checks/navigation/ultra-tabs-check.js | 18 ++--- checks/planner/gradescope-switch-check.js | 10 +-- checks/planner/import-check.js | 18 ++--- checks/planner/import-truth.js | 12 +-- checks/planner/whatif-ultra-check.js | 14 ++-- checks/platform/background-pages-check.js | 16 ++-- checks/platform/background-shell-check.js | 12 +-- checks/platform/lit-check.js | 12 +-- checks/platform/register-update-check.js | 12 +-- checks/platform/registered-hosts-check.js | 10 +-- checks/platform/transition-guard-check.js | 12 +-- .../safari/content-navigation-safari-check.py | 2 +- checks/settings/backup-check.js | 8 +- checks/settings/editor-x-check.js | 8 +- checks/settings/embedded-settings-check.js | 18 ++--- checks/settings/popup-check.js | 12 +-- checks/settings/popup-motion-check.js | 10 +-- checks/settings/settings-agents-check.js | 16 ++-- checks/settings/settings-ai-check.js | 14 ++-- checks/settings/settings-ia-check.js | 10 +-- checks/settings/settings-wiring-check.js | 8 +- checks/settings/settings-writer-check.js | 12 +-- checks/settings/sites-check.js | 12 +-- checks/settings/welcome-check.js | 10 +-- checks/settings/xparts-editor-check.js | 8 +- checks/sharing/share-check.js | 16 ++-- checks/writer/writer-adversarial-check.js | 6 +- checks/writer/writer-ai-check.js | 12 +-- checks/writer/writer-check.js | 16 ++-- checks/writer/writer-cite-check.js | 18 ++--- checks/writer/writer-completion-check.js | 8 +- checks/writer/writer-compose-check.js | 22 +++--- checks/writer/writer-core-check.js | 10 +-- checks/writer/writer-dark-check.js | 14 ++-- checks/writer/writer-edit-check.js | 20 ++--- checks/writer/writer-format-check.js | 22 +++--- checks/writer/writer-huge-check.js | 18 ++--- checks/writer/writer-kinds-check.js | 30 ++++---- checks/writer/writer-look-check.js | 14 ++-- checks/writer/writer-objects-check.js | 20 ++--- checks/writer/writer-paper-check.js | 20 ++--- .../writer/writer-paper-regression-check.js | 8 +- checks/writer/writer-proof-check.js | 18 ++--- checks/writer/writer-sources-check.js | 10 +-- checks/writer/writer-submit-check.js | 26 +++---- docs/README.md | 2 +- docs/development.md | 66 ++++++++--------- docs/firefox-android.md | 8 +- docs/install.md | 4 +- extension/assets/art/pet-reactions.md | 2 +- mcp/scripts/pack-mcpb.js | 2 +- .../Glaze Extension/GlazeAgentBridge.swift | 4 +- safari/Glaze/Glaze.xcodeproj/project.pbxproj | 4 +- safari/dmg/GlazeAgentHost.swift | 2 +- store/promo.html | 8 +- test/agents/agent-frames.test.js | 10 +-- test/agents/agent-link-check.test.js | 2 +- test/agents/agent-link.test.js | 22 +++--- test/agents/agents-docs.test.js | 46 ++++++------ test/agents/mcp-agent.test.js | 18 ++--- test/agents/mcp-approve.test.js | 10 +-- test/agents/mcp-assign.test.js | 16 ++-- test/agents/mcp-calls.test.js | 28 +++---- test/agents/mcp-conformance-check.test.js | 4 +- test/agents/mcp-grants.test.js | 10 +-- test/agents/mcp-host.test.js | 18 ++--- test/agents/mcp-install.test.js | 28 +++---- test/agents/mcp-mcpb.test.js | 34 ++++----- test/agents/mcp-mods.test.js | 10 +-- test/agents/mcp-native.test.js | 6 +- test/agents/mcp-registry.test.js | 8 +- test/agents/mcp-release-fixes.test.js | 16 ++-- test/agents/mcp-rendezvous.test.js | 12 +-- test/agents/mcp-tabs.test.js | 6 +- test/agents/mcp-todo-write.test.js | 12 +-- test/agents/mcp-tools-grades.test.js | 12 +-- test/agents/mcp-tools-study.test.js | 10 +-- test/agents/mcp-windows-check.test.js | 2 +- test/agents/mcp-wire.test.js | 4 +- test/agents/mcp-worker.test.js | 8 +- test/agents/mcp-writer.test.js | 28 +++---- test/ai/ai-apple.test.js | 22 +++--- test/ai/ai-broker.test.js | 42 +++++------ test/ai/ai-device.test.js | 22 +++--- test/ai/ai-failure.test.js | 4 +- test/ai/ai-loop.test.js | 10 +-- test/ai/ai-models.test.js | 4 +- test/ai/ai-openai.test.js | 8 +- test/ai/ai-request.test.js | 8 +- test/ai/ai-settings-store.test.js | 4 +- test/ai/ai-settings.test.js | 28 +++---- test/ai/ai-stream.test.js | 4 +- test/ai/ai-syllabus.test.js | 16 ++-- test/ai/ai-task.test.js | 10 +-- test/ai/ai-text.test.js | 4 +- test/ai/ai-token-budget.test.js | 14 ++-- test/ai/ai-tools-mcp.test.js | 4 +- test/ai/ai-visual-cache.test.js | 6 +- test/ai/ai-visual-scope.test.js | 2 +- test/ai/ai-worker.test.js | 20 ++--- test/ai/ai-writer-complete.test.js | 14 ++-- test/ai/ai-writer-frame.test.js | 18 ++--- test/ai/ai-writer-regressions.test.js | 14 ++-- test/ai/ai-writer-tasks.test.js | 32 ++++---- test/ai/ai-writer-tools.test.js | 8 +- test/core/color.test.js | 2 +- test/core/consent.test.js | 20 ++--- test/core/docbutton.test.js | 2 +- test/core/download-allow.test.js | 2 +- test/core/download-check-waits.test.js | 4 +- test/core/download.test.js | 2 +- test/core/fresh-defaults.test.js | 12 +-- test/core/page.test.js | 4 +- test/core/site.test.js | 2 +- test/core/starter-flags.test.js | 4 +- test/core/tooltips.test.js | 2 +- test/core/ultra-panel.test.js | 8 +- test/core/widgets-checks.test.js | 4 +- test/core/widgets-tiers.test.js | 6 +- test/core/widgets-ui.test.js | 22 +++--- test/core/widgets.test.js | 10 +-- test/core/zip.test.js | 4 +- test/courses/allow-tab.test.js | 4 +- test/courses/allow.test.js | 8 +- test/courses/announce-sites.test.js | 34 ++++----- test/courses/announce-ui.test.js | 12 +-- test/courses/announce.test.js | 14 ++-- test/courses/bb-preview.test.js | 6 +- test/courses/board-card-checks.test.js | 8 +- test/courses/board-checks.test.js | 6 +- test/courses/boards-check.test.js | 2 +- test/courses/canvas-boards.test.js | 8 +- .../canvas-dash-audit-i18n-css.test.js | 6 +- test/courses/canvas-dash-audit-ui.test.js | 10 +-- test/courses/canvas-dash-audit.test.js | 12 +-- test/courses/canvas-fixtures.test.js | 12 +-- .../canvas-load-audit-contracts.test.js | 4 +- .../courses/canvas-load-audit-runtime.test.js | 6 +- test/courses/canvas-uiux-audit.test.js | 8 +- test/courses/cards-ui.test.js | 10 +-- test/courses/cards.test.js | 12 +-- test/courses/cardtasks-ui.test.js | 6 +- test/courses/coursecards.test.js | 14 ++-- test/courses/docreview.test.js | 26 +++---- test/courses/docs-dark.test.js | 4 +- test/courses/docviewer-ui.test.js | 10 +-- test/courses/docviewer.test.js | 8 +- test/courses/folders-canvas.test.js | 16 ++-- test/courses/folders-ui.test.js | 4 +- test/courses/folders.test.js | 14 ++-- test/courses/moodle-root.test.js | 10 +-- test/courses/moodle.test.js | 36 ++++----- test/courses/rubric-data.test.js | 8 +- test/courses/rubric-ultra-state.test.js | 10 +-- test/courses/stream.test.js | 4 +- test/courses/ultra-boards.test.js | 10 +-- test/courses/ultra-card-storage.test.js | 2 +- test/courses/ultra-fixtures.test.js | 10 +-- test/courses/ultra-mock.test.js | 26 +++---- test/courses/ultra-perf.test.js | 16 ++-- test/courses/ultra-preview-ticket.test.js | 2 +- test/courses/ultra-test-attempt.test.js | 4 +- test/courses/ultra.test.js | 26 +++---- test/documents/doc-files.test.js | 10 +-- test/documents/doc-kinds.test.js | 10 +-- test/documents/doctext.test.js | 4 +- .../documents/papers-motion-lifecycle.test.js | 6 +- test/documents/papers-motion.test.js | 6 +- test/documents/papers-page.test.js | 18 ++--- test/documents/papers-sources.test.js | 16 ++-- test/documents/papers-theme.test.js | 2 +- test/documents/pdf-doc.test.js | 18 ++--- test/documents/pdf-kind.test.js | 16 ++-- test/documents/pdf-l10n.test.js | 8 +- test/documents/pptx-geom.test.js | 10 +-- test/documents/pptx.test.js | 10 +-- test/documents/sheet-calc.test.js | 18 ++--- test/documents/sheet-chart-insert.test.js | 16 ++-- test/documents/sheet-chart.test.js | 14 ++-- test/documents/sheet-excel.test.js | 28 +++---- test/documents/sheet-fn-database.test.js | 18 ++--- test/documents/sheet-fn-date.test.js | 12 +-- test/documents/sheet-fn-financial.test.js | 10 +-- test/documents/sheet-fn-financial2.test.js | 14 ++-- test/documents/sheet-fn-info.test.js | 16 ++-- test/documents/sheet-fn-logical2.test.js | 18 ++--- test/documents/sheet-fn-lookup.test.js | 16 ++-- test/documents/sheet-fn-lookup2.test.js | 18 ++--- test/documents/sheet-fn-math2.test.js | 10 +-- test/documents/sheet-fn-math3.test.js | 16 ++-- test/documents/sheet-fn-stats.test.js | 14 ++-- test/documents/sheet-fn-stats2.test.js | 24 +++--- test/documents/sheet-fn-text2.test.js | 12 +-- test/documents/sheet-formula.test.js | 4 +- test/documents/sheet-golden.test.js | 20 ++--- test/documents/sheet-grid-lifecycle.test.js | 14 ++-- test/documents/sheet-grid.test.js | 4 +- test/documents/sheet-kind.test.js | 58 +++++++-------- test/documents/sheet-model.test.js | 2 +- test/documents/sheet-numfmt.test.js | 2 +- test/documents/sheet-xlsx.test.js | 26 +++---- test/documents/slides-fidelity.test.js | 2 +- test/documents/slides-kind.test.js | 18 ++--- test/documents/slides-render.test.js | 16 ++-- test/firefox/agent-link-firefox-check.test.js | 2 +- test/firefox/firefox-html-sinks.test.js | 2 +- test/firefox/firefox-waits.test.js | 2 +- test/firefox/firefox.test.js | 10 +-- test/fixtures/sheets/cases.js | 4 +- test/fixtures/sheets/charts.js | 8 +- test/fixtures/writer-docx-import.js | 4 +- test/fixtures/writer-hostile.js | 6 +- test/fixtures/writer-paper-fixtures.js | 2 +- test/helpers/coursetabs-page.cjs | 10 +-- test/helpers/css-cascade.cjs | 2 +- test/helpers/mcp-caches.cjs | 16 ++-- test/helpers/mcp-pipe.cjs | 2 +- test/helpers/mcp-tabs.cjs | 12 +-- test/helpers/media-env.cjs | 4 +- test/helpers/scene-engine.cjs | 4 +- test/helpers/worker-env.cjs | 4 +- test/helpers/writer-content.cjs | 4 +- test/helpers/writer-submit-page.cjs | 4 +- test/helpers/writer-submit-sheet.cjs | 4 +- test/helpers/xlsx-cache.cjs | 4 +- test/looks/course-looks-sync.test.js | 10 +-- test/looks/courselook.test.js | 2 +- test/looks/ctab-one-background.test.js | 2 +- test/looks/fonts.test.js | 4 +- test/looks/glass-remaining-check.test.js | 4 +- test/looks/glsl.test.js | 10 +-- test/looks/history.test.js | 24 +++--- test/looks/ink.test.js | 2 +- test/looks/look-disclosure.test.js | 30 ++++---- test/looks/look-editor.test.js | 34 ++++----- test/looks/look-media-share.test.js | 10 +-- test/looks/look-offer-review.test.js | 18 ++--- test/looks/lookparts-page.test.js | 10 +-- test/looks/lookparts.test.js | 16 ++-- test/looks/looks-catalog.test.js | 20 ++--- test/looks/lookset.test.js | 18 ++--- test/looks/lookx-ui.test.js | 60 +++++++-------- test/looks/lookx.test.js | 16 ++-- test/looks/media-avif-validation.test.js | 6 +- test/looks/media-broker.test.js | 26 +++---- test/looks/media-check.test.js | 2 +- test/looks/media-cost-check.test.js | 2 +- test/looks/media-gallery.test.js | 10 +-- test/looks/media-host.test.js | 2 +- test/looks/media-lottie-mattes.test.js | 4 +- test/looks/media-lottie-validation.test.js | 2 +- test/looks/media-metadata.test.js | 6 +- test/looks/media-parts.test.js | 8 +- test/looks/media-remote.test.js | 4 +- test/looks/media-store.test.js | 34 ++++----- test/looks/media-video-validation.test.js | 4 +- test/looks/media.test.js | 32 ++++---- test/looks/moments.test.js | 8 +- test/looks/motion-check.test.js | 2 +- test/looks/motion-default.test.js | 4 +- test/looks/motion-lib.test.js | 6 +- test/looks/motionx-page.test.js | 4 +- test/looks/motionx.test.js | 6 +- test/looks/parts.test.js | 14 ++-- test/looks/pointer-check.test.js | 6 +- test/looks/scene-lifecycle.test.js | 2 +- test/looks/scene-media.test.js | 6 +- test/looks/scene-modes.test.js | 20 ++--- test/looks/scene-placement.test.js | 4 +- test/looks/scene-sync.test.js | 4 +- test/looks/surfaces.test.js | 22 +++--- test/looks/ultra-appearance.test.js | 2 +- test/looks/ultra-header-surface.test.js | 2 +- test/looks/ultra-states.test.js | 4 +- test/mods/blocklist.test.js | 18 ++--- test/mods/combined-check.test.js | 2 +- test/mods/mod-account.test.js | 6 +- test/mods/mod-nav.test.js | 14 ++-- test/mods/mod-privacy.test.js | 8 +- test/mods/modapi-documents.test.js | 18 ++--- test/mods/modapi.test.js | 6 +- test/mods/modbroker.test.js | 20 ++--- test/mods/modbundle.test.js | 14 ++-- test/mods/modchannel-worker.test.js | 6 +- test/mods/modchannel.test.js | 10 +-- test/mods/modexamples.test.js | 14 ++-- test/mods/modgallery.test.js | 8 +- test/mods/modpacks.test.js | 18 ++--- test/mods/mods-runtime.test.js | 14 ++-- test/mods/modtools.test.js | 12 +-- test/mods/modtypes.test.js | 14 ++-- test/mods/modui.test.js | 2 +- test/mods/modwidget.test.js | 10 +-- test/mods/modwidgets-runtime.test.js | 4 +- test/navigation/canvas-coursemenu.test.js | 12 +-- test/navigation/content-navigation.test.js | 12 +-- test/navigation/context-lifecycle.test.js | 2 +- test/navigation/coursemenu.test.js | 4 +- .../coursetabs-canvas-audit-frames.test.js | 4 +- .../coursetabs-canvas-audit-routing.test.js | 4 +- test/navigation/coursetabs-canvas.test.js | 14 ++-- .../coursetabs-course-detail.test.js | 2 +- test/navigation/coursetabs-frames.test.js | 8 +- test/navigation/coursetabs-keep.test.js | 2 +- test/navigation/coursetabs-label.test.js | 2 +- test/navigation/coursetabs-native.test.js | 2 +- test/navigation/coursetabs-pages.test.js | 4 +- test/navigation/coursetabs-provenance.test.js | 2 +- test/navigation/coursetabs-repaint.test.js | 2 +- test/navigation/coursetabs-routed.test.js | 2 +- test/navigation/coursetabs-sidebar.test.js | 16 ++-- test/navigation/coursetabs-takeover.test.js | 2 +- test/navigation/coursetabs-turns.test.js | 6 +- test/navigation/coursetabs-worker.test.js | 4 +- test/navigation/coursetabs.test.js | 8 +- test/navigation/ctab-panel-close.test.js | 2 +- test/navigation/ctab-stall-switch.test.js | 4 +- test/navigation/ctab-strip-glass.test.js | 2 +- test/navigation/load-recovery.test.js | 2 +- test/navigation/nav-a11y.test.js | 16 ++-- test/navigation/nav-lifecycle-review.test.js | 4 +- test/navigation/nav-logo-review.test.js | 6 +- test/navigation/nav-mod-rail.test.js | 6 +- test/navigation/nav-overlay.test.js | 2 +- test/navigation/nav-paint.test.js | 2 +- test/navigation/nav.test.js | 24 +++--- .../navbar-canvas-audit-frames.test.js | 6 +- test/navigation/navbar-canvas-audit.test.js | 10 +-- test/navigation/navbar-canvas.test.js | 4 +- .../navigation/navbar-moodle-controls.test.js | 20 ++--- test/navigation/navbar-moodle.test.js | 24 +++--- test/navigation/navbar-nav-perms.test.js | 8 +- test/navigation/navbar-panels.test.js | 14 ++-- test/navigation/navbar-polish-review.test.js | 18 ++--- test/navigation/navbar-widgets.test.js | 18 ++--- test/navigation/navbar.test.js | 22 +++--- test/navigation/navlayout-review.test.js | 4 +- test/navigation/navlayout.test.js | 2 +- test/navigation/place.test.js | 6 +- test/navigation/search.test.js | 2 +- test/navigation/seek.test.js | 10 +-- test/navigation/sidebar-absorb.test.js | 2 +- .../sidebar-assessment-native.test.js | 2 +- test/navigation/sidebar-broker.test.js | 12 +-- test/navigation/sidebar-native-root.test.js | 2 +- test/navigation/sidebar-rubric.test.js | 6 +- test/navigation/sidebar-test-attempt.test.js | 4 +- test/navigation/sidebar-ui.test.js | 22 +++--- test/navigation/sidebar-ultra.test.js | 12 +-- test/navigation/sidebar.test.js | 12 +-- test/navigation/sticky-fold.test.js | 6 +- test/navigation/tabs-auto.test.js | 8 +- test/navigation/ultra-course-room.test.js | 4 +- test/navigation/ultra-nav-review.test.js | 12 +-- test/navigation/ultra-nav.test.js | 40 +++++----- test/navigation/ultra-off.test.js | 6 +- test/navigation/ultra-search.test.js | 16 ++-- test/navigation/ultra-todo-line.test.js | 2 +- test/pet/pet-clock.test.js | 4 +- test/pet/pet-review.test.js | 4 +- test/pet/pet-ui.test.js | 8 +- test/pet/pet-yield.test.js | 10 +-- test/pet/pet.test.js | 2 +- test/pet/petlibrary.test.js | 8 +- test/pet/petplayer.test.js | 4 +- test/pet/petsprites.test.js | 12 +-- test/pet/petworld.test.js | 6 +- test/planner/calendar.test.js | 2 +- test/planner/calexport.test.js | 10 +-- test/planner/classes-review.test.js | 8 +- test/planner/classes-ui.test.js | 14 ++-- test/planner/classes.test.js | 20 ++--- test/planner/gcal.test.js | 24 +++--- test/planner/gpa.test.js | 16 ++-- test/planner/gradecalc-parity.test.js | 4 +- test/planner/gradecalc.test.js | 4 +- test/planner/grades-ui.test.js | 18 ++--- test/planner/grades.test.js | 4 +- test/planner/gradescope.test.js | 10 +-- test/planner/ics.test.js | 6 +- test/planner/import.test.js | 32 ++++---- test/planner/insights-ui.test.js | 12 +-- test/planner/insights.test.js | 8 +- test/planner/notes-ui.test.js | 20 ++--- test/planner/notes.test.js | 8 +- test/planner/remind.test.js | 6 +- test/planner/reminders-ui.test.js | 12 +-- test/planner/schedule.test.js | 10 +-- test/planner/syllabus-ai-ui.test.js | 4 +- test/planner/syllabus-sync-ui.test.js | 10 +-- test/planner/syllabus-ui.test.js | 8 +- test/planner/syllabus.test.js | 16 ++-- test/planner/todo-ui.test.js | 8 +- test/planner/todo.test.js | 4 +- test/planner/ultra-assessment-results.test.js | 4 +- test/planner/ultra-due-chip.test.js | 8 +- test/planner/ultra-plan-ui.test.js | 8 +- test/planner/ultra-plan.test.js | 20 ++--- test/planner/ultra-submission.test.js | 6 +- test/platform/animation-imports.test.js | 2 +- test/platform/background-pages-check.test.js | 2 +- test/platform/background-update.test.js | 6 +- test/platform/editor-hosts.test.js | 6 +- test/platform/feature-hosts.test.js | 2 +- test/platform/fetch-hosts.test.js | 10 +-- test/platform/frame-tabs.test.js | 2 +- test/platform/frame-trust.test.js | 4 +- test/platform/head.test.js | 6 +- test/platform/i18n.test.js | 40 +++++----- test/platform/lms-requests.test.js | 2 +- test/platform/loader.test.js | 6 +- test/platform/manifest.test.js | 34 ++++----- test/platform/oauth.test.js | 2 +- test/platform/perf-budget.test.js | 8 +- test/platform/privacy-claims.test.js | 18 ++--- test/platform/register-update.test.js | 4 +- test/platform/runtime-audit-fixes.test.js | 8 +- test/platform/site-part.test.js | 2 +- test/platform/vendor-pdfjs.test.js | 22 +++--- test/platform/vendor-three.test.js | 10 +-- test/platform/worker-i18n.test.js | 4 +- test/release/audit-documentation.test.js | 22 +++--- test/release/audit-release-readiness.test.js | 26 +++---- test/release/audit-ultra-completeness.test.js | 2 +- test/release/audit.test.js | 4 +- test/release/blocklist-sign.test.js | 12 +-- test/release/browser-checks.test.js | 28 +++---- test/release/check-env.test.js | 68 ++++++++--------- test/release/check-readiness.test.js | 2 +- test/release/ci-browser.test.js | 30 ++++---- test/release/narrow-check-seeds.test.js | 3 +- test/release/package.test.js | 10 +-- test/release/release-copy.test.js | 26 +++---- test/release/release-env.test.js | 14 ++-- test/release/release-listings.test.js | 69 +++++++++--------- test/release/release-mcpb.test.js | 14 ++-- test/release/release-redact.test.js | 4 +- test/release/release-sources.test.js | 20 ++--- test/release/release-store-api.test.js | 10 +-- test/release/release-verify.test.js | 14 ++-- test/release/stand-in-dom.test.js | 2 +- test/release/store-listing.test.js | 16 ++-- test/release/workflows.test.js | 14 ++-- test/safari/agent-bridge-native.test.js | 14 ++-- test/safari/agent-host-native.test.js | 24 +++--- test/safari/ai-apple-native.test.js | 10 +-- test/safari/make-dmg.test.js | 2 +- test/safari/safari-keep-data.test.js | 8 +- test/safari/safari-resources.test.js | 26 +++---- test/safari/safari-source-resources.test.js | 10 +-- test/safari/safari-update-native.test.js | 73 ++++++++++--------- test/settings/backup-check.test.js | 6 +- test/settings/backup-files.test.js | 18 ++--- test/settings/backup-media.test.js | 14 ++-- test/settings/backup-picker.test.js | 4 +- test/settings/backup-worker.test.js | 4 +- test/settings/backup.test.js | 62 ++++++++-------- test/settings/docs-settings-paths.test.js | 10 +-- test/settings/editor-agents.test.js | 10 +-- test/settings/editor-info.test.js | 2 +- test/settings/editor-media-builder.test.js | 10 +-- test/settings/editor-media-size.test.js | 4 +- test/settings/editor-mods.test.js | 18 ++--- test/settings/editor-pointer.test.js | 12 +-- test/settings/editor-widget.test.js | 12 +-- test/settings/editor-x.test.js | 26 +++---- test/settings/editor-xparts-save.test.js | 2 +- test/settings/editor-xparts-ui.test.js | 2 +- test/settings/editor-xparts.test.js | 18 ++--- test/settings/editor.test.js | 4 +- .../embedded-settings-overlays.test.js | 6 +- test/settings/news.test.js | 4 +- test/settings/popup-motion.test.js | 2 +- test/settings/popup-site.test.js | 4 +- test/settings/rubric-settings.test.js | 6 +- test/settings/settings-ai-check.test.js | 6 +- test/settings/settings-copy.test.js | 6 +- test/settings/settings-page.test.js | 14 ++-- test/settings/settings-sites.test.js | 6 +- test/settings/settings-writer.test.js | 32 ++++---- test/settings/settings.test.js | 14 ++-- test/settings/welcome.test.js | 8 +- test/sharing/quick-broker.test.js | 4 +- test/sharing/quick-ui.test.js | 18 ++--- test/sharing/relay-gesture.test.js | 4 +- test/sharing/relay-quota-review.test.js | 2 +- test/sharing/relay-quota.test.js | 4 +- test/sharing/relay-resource.test.js | 4 +- test/sharing/relay-ui.test.js | 14 ++-- test/sharing/relay.test.js | 12 +-- test/sharing/sessions-create.test.js | 6 +- test/sharing/sessions-files.test.js | 14 ++-- test/sharing/sessions-media.test.js | 18 ++--- test/sharing/sessions-sync.test.js | 16 ++-- test/sharing/sessions-ui-lifecycle.test.js | 2 +- test/sharing/sessions.test.js | 36 ++++----- test/sharing/share-keys.test.js | 12 +-- test/sharing/share-protocol.test.js | 18 ++--- test/sharing/sharing.test.js | 10 +-- test/sidebar-ultra-page.js | 10 +-- test/writer/drafts-browser.test.js | 10 +-- test/writer/drafts-ui.test.js | 6 +- test/writer/drafts.test.js | 12 +-- test/writer/editor-menu.test.js | 4 +- test/writer/ondemand.test.js | 32 ++++---- test/writer/practice-ai.test.js | 12 +-- test/writer/writer-attach-open.test.js | 12 +-- test/writer/writer-attach.test.js | 18 ++--- test/writer/writer-audit-regressions.test.js | 12 +-- test/writer/writer-bibliography.test.js | 2 +- test/writer/writer-cite-check.test.js | 10 +-- test/writer/writer-cite-frame.test.js | 10 +-- test/writer/writer-cite-lookup.test.js | 10 +-- test/writer/writer-cite-worker.test.js | 2 +- test/writer/writer-cite.test.js | 2 +- test/writer/writer-content.test.js | 4 +- test/writer/writer-context-compose.test.js | 10 +-- test/writer/writer-context-lifecycle.test.js | 4 +- test/writer/writer-context.test.js | 14 ++-- test/writer/writer-counts.test.js | 8 +- test/writer/writer-discussion-sources.test.js | 16 ++-- test/writer/writer-docx-import.test.js | 18 ++--- test/writer/writer-docx.test.js | 18 ++--- test/writer/writer-editor.test.js | 6 +- test/writer/writer-export.test.js | 6 +- test/writer/writer-feedback-flow.test.js | 10 +-- test/writer/writer-feedback-grade.test.js | 6 +- test/writer/writer-feedback-images.test.js | 12 +-- test/writer/writer-feedback-local.test.js | 14 ++-- test/writer/writer-final-cite.test.js | 4 +- test/writer/writer-final-docx.test.js | 16 ++-- test/writer/writer-final-paginate.test.js | 6 +- test/writer/writer-final-paste.test.js | 20 ++--- test/writer/writer-final-proof.test.js | 8 +- test/writer/writer-final-submit.test.js | 28 +++---- test/writer/writer-find.test.js | 8 +- test/writer/writer-format-commands.test.js | 8 +- test/writer/writer-format-setup.test.js | 8 +- test/writer/writer-frame-count.test.js | 2 +- test/writer/writer-frame.test.js | 14 ++-- .../writer-from-documents-sheet.test.js | 6 +- test/writer/writer-hostile-roundtrip.test.js | 14 ++-- test/writer/writer-huge-budget.test.js | 2 +- test/writer/writer-image.test.js | 12 +-- .../writer/writer-import-paper-review.test.js | 4 +- test/writer/writer-import.test.js | 20 ++--- test/writer/writer-kinds-submit.test.js | 4 +- test/writer/writer-kinds.test.js | 14 ++-- test/writer/writer-local-fonts.test.js | 10 +-- test/writer/writer-objects-docx.test.js | 10 +-- test/writer/writer-objects.test.js | 8 +- test/writer/writer-outline-ai.test.js | 18 ++--- test/writer/writer-paginate-scheduler.test.js | 2 +- test/writer/writer-paginate-slices.test.js | 6 +- test/writer/writer-paper-compact.test.js | 14 ++-- test/writer/writer-paper-content.test.js | 2 +- test/writer/writer-paper-context.test.js | 18 ++--- test/writer/writer-paper-format.test.js | 10 +-- test/writer/writer-paper-key.test.js | 12 +-- test/writer/writer-paper-paginate.test.js | 4 +- test/writer/writer-paper-papers.test.js | 6 +- test/writer/writer-paper-styles.test.js | 8 +- test/writer/writer-paper-view-save.test.js | 2 +- test/writer/writer-papers-files.test.js | 8 +- test/writer/writer-papers-proxy.test.js | 26 +++---- test/writer/writer-papers-versions.test.js | 16 ++-- test/writer/writer-paste.test.js | 10 +-- test/writer/writer-pm.test.js | 6 +- test/writer/writer-practice.test.js | 14 ++-- test/writer/writer-proof-frame.test.js | 12 +-- test/writer/writer-proof-harper.test.js | 8 +- test/writer/writer-proof-worker.test.js | 2 +- test/writer/writer-proof.test.js | 14 ++-- test/writer/writer-quote-lifecycle.test.js | 2 +- test/writer/writer-round1-document.test.js | 14 ++-- test/writer/writer-round1-docx.test.js | 12 +-- test/writer/writer-round4-notes.test.js | 6 +- test/writer/writer-round4-ui.test.js | 14 ++-- test/writer/writer-schema.test.js | 4 +- test/writer/writer-storage-failures.test.js | 4 +- test/writer/writer-submit-check.test.js | 2 +- test/writer/writer-submit-review.test.js | 10 +-- .../writer/writer-submit-sheet-review.test.js | 4 +- test/writer/writer-submit-tap.test.js | 2 +- test/writer/writer-submit-wait.test.js | 12 +-- test/writer/writer-submit.test.js | 10 +-- test/writer/writer-target.test.js | 2 +- test/writer/writer-thumb.test.js | 10 +-- test/writer/writer-ultra.test.js | 10 +-- tools/build/appstore-ios.sh | 10 +-- tools/build/appstore-safari.sh | 14 ++-- tools/build/build-safari.sh | 18 ++--- tools/build/make-dmg.sh | 2 +- tools/build/package-firefox.sh | 8 +- tools/build/package-safari.sh | 8 +- tools/build/package.sh | 9 ++- tools/build/safari-manifest.sh | 2 +- tools/build/safari-resources.js | 8 +- tools/build/stage-safari-dmg.py | 10 +-- tools/build/sync-safari-resources.sh | 14 ++-- tools/dev/audit.js | 6 +- tools/dev/canvas-shots.js | 18 ++--- tools/dev/ci-browser-checks.js | 16 ++-- tools/dev/ci-chromium.js | 8 +- tools/dev/dev-gcal.sh | 14 ++-- tools/dev/docs-mirror.js | 8 +- tools/dev/firefox-android.sh | 6 +- tools/dev/practice-ai.js | 2 +- tools/dev/redact.js | 6 +- tools/dev/writer-practice.js | 20 ++--- tools/fidelity/import-fixtures.js | 10 +-- tools/fidelity/pdf-fidelity.js | 8 +- tools/fidelity/pdf-fixtures.js | 4 +- tools/fidelity/sheet-excel-fidelity.js | 14 ++-- tools/fidelity/sheet-excel-golden.js | 10 +-- tools/fidelity/sheet-excel.js | 4 +- tools/fidelity/sheet-fidelity-books.js | 14 ++-- tools/fidelity/sheet-numbers-fidelity.js | 6 +- tools/fidelity/slides-fidelity.js | 10 +-- tools/fidelity/writer-import-fidelity.js | 18 ++--- tools/fidelity/writer-word-fidelity.js | 4 +- tools/release/blocklist-sign.mjs | 8 +- tools/release/public-release.mjs | 6 +- tools/release/publish-chrome.sh | 10 +-- tools/release/release.sh | 18 ++--- tools/release/safari-appcast.sh | 6 +- tools/release/store-firefox-source.py | 12 +-- tools/release/store-release.mjs | 18 ++--- tools/release/store-screenshots.js | 12 +-- tools/vendor/npm-integrity.js | 2 +- tools/vendor/vendor-harper.sh | 4 +- tools/vendor/vendor-lottie.sh | 4 +- tools/vendor/vendor-paper-fonts.sh | 4 +- tools/vendor/vendor-pdfjs-brotli.js | 4 +- tools/vendor/vendor-pdfjs-limits.js | 2 +- tools/vendor/vendor-pdfjs.sh | 12 +-- tools/vendor/vendor-prosemirror.sh | 6 +- tools/vendor/vendor-tesseract.sh | 8 +- tools/vendor/vendor-three.sh | 6 +- 731 files changed, 4107 insertions(+), 4096 deletions(-) diff --git a/.github/pins/chromium.pins b/.github/pins/chromium.pins index 5023a3f9e..f510697a5 100644 --- a/.github/pins/chromium.pins +++ b/.github/pins/chromium.pins @@ -1,4 +1,4 @@ -# The Chromium that ci/playwright.pins's playwright-core installs, checked by scripts/ci-chromium.js before it runs. Each hash +# The Chromium that .github/pins/playwright.pins's playwright-core installs, checked by tools/dev/ci-chromium.js before it runs. Each hash # matches Google's Chrome for Testing zip of that version, unpacked. chromium-1243 153.0.8010.12 linux-x64 de81033ef46a2ebc03cd1865fccace6f7955ba3b8b45adb4e485ecf51ebdceac chromium-1243 153.0.8010.12 darwin-arm64 37eeed7899f5e4b2524e05af529c8ca9ca4dce83ffee579b85b92ec04dc797a4 diff --git a/.github/pins/playwright.pins b/.github/pins/playwright.pins index 43f018274..857d7121f 100644 --- a/.github/pins/playwright.pins +++ b/.github/pins/playwright.pins @@ -1,4 +1,4 @@ -# The Playwright the browser gate installs (.github/workflows/browser.yml), checked by scripts/npm-integrity.js. To upgrade, -# change PLAYWRIGHT there, copy each package's `npm view @ dist.integrity` here, and repin ci/chromium.pins and ci/ffmpeg.pins. +# The Playwright the browser gate installs (.github/workflows/browser.yml), checked by tools/vendor/npm-integrity.js. To upgrade, +# change PLAYWRIGHT there, copy each package's `npm view @ dist.integrity` here, and repin .github/pins/chromium.pins and .github/pins/ffmpeg.pins. playwright@1.63.0 sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg== playwright-core@1.63.0 sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg== diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml index 3f0483320..41f7cd560 100644 --- a/.github/workflows/browser.yml +++ b/.github/workflows/browser.yml @@ -20,21 +20,21 @@ jobs: with: node-version: 26 - # Install scripts off; every package checked against ci/playwright.pins, and Chromium/FFmpeg against ci/*.pins, + # Install scripts off; every package checked against .github/pins/playwright.pins, and Chromium/FFmpeg against .github/pins/*.pins, # before either runs. - name: Install the pinned Playwright run: | echo "NODE_PATH=$RUNNER_TEMP/playwright/node_modules" >> "$GITHUB_ENV" echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/ms-playwright" >> "$GITHUB_ENV" npm install --prefix "$RUNNER_TEMP/playwright" --no-save --no-package-lock --ignore-scripts --no-audit --no-fund --registry https://registry.npmjs.org/ "playwright@$PLAYWRIGHT" - node scripts/npm-integrity.js "$RUNNER_TEMP/playwright/node_modules/.package-lock.json" ci/playwright.pins playwright playwright-core + node tools/vendor/npm-integrity.js "$RUNNER_TEMP/playwright/node_modules/.package-lock.json" .github/pins/playwright.pins playwright playwright-core - name: Install its Chromium run: | PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1 "$RUNNER_TEMP/playwright/node_modules/.bin/playwright" install --with-deps --no-shell chromium - node scripts/ci-chromium.js + node tools/dev/ci-chromium.js - name: Settings, popup, sites and pointer checks - run: nice -n 10 node scripts/ci-browser-checks.js "$RUNNER_TEMP/browser-evidence" + run: nice -n 10 node tools/dev/ci-browser-checks.js "$RUNNER_TEMP/browser-evidence" - name: Keep the logs and screenshots if: failure() diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 263233fa1..919477e05 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -32,13 +32,13 @@ jobs: # A tag that names another version would attach a zip of the manifest's version to the wrong release. - name: Check the tag names the manifest's version - run: test "v$(node -p 'require("./manifest.json").version')" = "${GITHUB_REF_NAME%%-rc*}" + run: test "v$(node -p 'require("./extension/manifest.json").version')" = "${GITHUB_REF_NAME%%-rc*}" - name: Validate the tagged source run: node --test - name: Build the Chrome zip - run: ./package.sh + run: tools/build/package.sh - name: Hand the zip to the release job uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e12c7b8e..dc48890c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: # The worker's code takes crypto from the browser, and Node 18 has no global one: the flag gives the test files one. # The helper processes they start run without it. - name: MCP and agent tests - run: node ${{ matrix.node == 18 && '--experimental-global-webcrypto' || '' }} --test test/mcp-*.test.js test/agent-*.test.js + run: node ${{ matrix.node == 18 && '--experimental-global-webcrypto' || '' }} --test test/agents/mcp-*.test.js test/*/agent-*.test.js # As browser.yml installs it: scripts off, every package and both builds checked against their pins before any of # it runs. No --with-deps: on Windows it only adds a media pack. @@ -63,15 +63,15 @@ jobs: echo "NODE_PATH=$RUNNER_TEMP/playwright/node_modules" >> "$GITHUB_ENV" echo "PLAYWRIGHT_BROWSERS_PATH=$RUNNER_TEMP/ms-playwright" >> "$GITHUB_ENV" npm install --prefix "$RUNNER_TEMP/playwright" --no-save --no-package-lock --ignore-scripts --no-audit --no-fund --registry https://registry.npmjs.org/ "playwright@$PLAYWRIGHT" - node scripts/npm-integrity.js "$RUNNER_TEMP/playwright/node_modules/.package-lock.json" ci/playwright.pins playwright playwright-core + node tools/vendor/npm-integrity.js "$RUNNER_TEMP/playwright/node_modules/.package-lock.json" .github/pins/playwright.pins playwright playwright-core - name: Install its Chromium if: matrix.node == 26 run: | node "$RUNNER_TEMP/playwright/node_modules/playwright/cli.js" install --no-shell chromium - node scripts/ci-chromium.js + node tools/dev/ci-chromium.js - name: A real native-messaging host in Chromium if: matrix.node == 26 - run: node scripts/mcp-windows-check.js "$RUNNER_TEMP/mcp-windows-evidence" + run: node checks/agents/mcp-windows-check.js "$RUNNER_TEMP/mcp-windows-evidence" - name: Keep the check's evidence if: failure() && matrix.node == 26 uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 diff --git a/.gitignore b/.gitignore index 1eabed122..c59433994 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ dist/ release/ safari/**/xcuserdata/ *.xcuserstate -assets/ads/ +extension/assets/ads/ # Local secrets (e.g. cloudflare-r2.env for the share relay) *.env diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 616615bfd..2d1b1e640 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ## Development loop -1. `chrome://extensions` → **Developer mode** → **Load unpacked** → pick this folder. +1. `chrome://extensions` → **Developer mode** → **Load unpacked** → pick the `extension` folder. 2. Edit a file. 3. Hit the reload arrow on the Glaze card, then reload your Blackboard Ultra or Canvas tab. @@ -11,7 +11,8 @@ extension reload. Run `node --test` before sending a change. ## Layout -- `content.js` — runs on Ultra, Canvas and Moodle pages: adds the stylesheets, applies settings, the DOM sweep, tooltips and the extras. What only one site does is in its part, `content-ultra.js` or `content-canvas.js`, listed before `content.js` for that site (Moodle has none); each site's first script has its own budget (`test/perf-budget.test.js`). +- `extension/` — the extension as the stores ship it; the paths below are inside it. `checks/` holds the browser checks and `test/` the unit tests, each by feature area; `tools/` the build, release, vendor, fidelity and dev scripts. +- `content.js` — runs on Ultra, Canvas and Moodle pages: adds the stylesheets, applies settings, the DOM sweep, tooltips and the extras. What only one site does is in its part, `content-ultra.js` or `content-canvas.js`, listed before `content.js` for that site (Moodle has none); each site's first script has its own budget (`test/platform/perf-budget.test.js`). - `theme.css` (palette and page polish), `motion.css` (motion levels), `extras.css` (backgrounds and extras). - `defaults.js` — the settings schema, defaults, presets and `GZ_SANITIZE`. `lib/` — pure helpers with tests in `test/`, plus `scene.js` (the animated backgrounds) and the vendored `three.js`. - `popup.*`, `options.*`, `ui.*` — the popup, the editor and what they share. `preview.html` is the editor's mock Ultra page. @@ -19,7 +20,7 @@ extension reload. Run `node --test` before sending a change. ## Conventions -- No build step, no dependencies, no bundler. Vanilla JS, plain CSS, files loaded directly. The exceptions are `lib/three.js`, a checked-in, tree-shaken, readable three.js (`scripts/vendor-three.sh` rebuilds it), lottie-web's official readable light canvas player in `lib/lottie/` (`scripts/vendor-lottie.sh` fetches it by a pinned hash), pdf.js in `lib/pdfjs/` and Tesseract in `lib/tesseract/` (`scripts/vendor-pdfjs.sh`, `scripts/vendor-tesseract.sh`), ProseMirror as one readable bundle in `lib/pm.js` and `lib/pm.css` (`scripts/vendor-prosemirror.sh`), harper.js in `lib/harper/` (`scripts/vendor-harper.sh`), and Writer's paper fonts in `lib/fonts/` (`scripts/vendor-paper-fonts.sh`); only an upgrade needs any of them. +- No build step, no dependencies, no bundler. Vanilla JS, plain CSS, files loaded directly. The exceptions are `lib/three.js`, a checked-in, tree-shaken, readable three.js (`tools/vendor/vendor-three.sh` rebuilds it), lottie-web's official readable light canvas player in `lib/lottie/` (`tools/vendor/vendor-lottie.sh` fetches it by a pinned hash), pdf.js in `lib/pdfjs/` and Tesseract in `lib/tesseract/` (`tools/vendor/vendor-pdfjs.sh`, `tools/vendor/vendor-tesseract.sh`), ProseMirror as one readable bundle in `lib/pm.js` and `lib/pm.css` (`tools/vendor/vendor-prosemirror.sh`), harper.js in `lib/harper/` (`tools/vendor/vendor-harper.sh`), and Writer's paper fonts in `lib/fonts/` (`tools/vendor/vendor-paper-fonts.sh`); only an upgrade needs any of them. - Keep diffs small and focused. One change per PR. - Defaults live in exactly one place: `defaults.js`. Don't duplicate a colour or a toggle elsewhere. - Every setting reaches the page as a `--gz-*` variable or an `html[data-gz-*]` attribute. CSS matches on those; it never waits on JavaScript. diff --git a/README.md b/README.md index 47de5343d..eb60f6ceb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

      - Glaze + Glaze

      @@ -15,7 +15,7 @@

      - Blackboard Ultra's Courses page in Glaze's Frost Look + Blackboard Ultra's Courses page in Glaze's Frost Look

      Glaze restyles your courses with palettes, fonts, backgrounds and motion, and adds tools for planning and studying right @@ -47,12 +47,12 @@ them on the device). Sharing is optional and encrypted on your device before it - - + + - - + +
      Canvas Dashboard with Glaze's widgets and course cardsCanvas Modules with progress and new-item badgesCanvas Dashboard with Glaze's widgets and course cardsCanvas Modules with progress and new-item badges
      The Pumpkin Patch Halloween LookThe backgrounds editor with a live previewThe Pumpkin Patch Halloween LookThe backgrounds editor with a live preview
      diff --git a/RELEASING.md b/RELEASING.md index 54097f951..3d58dbfd5 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -15,9 +15,9 @@ Everything a release builds goes in `release//` (gitignored), one folde ## Complete automated release -1. Bump `manifest.json`, add the `CHANGELOG.md` entry, refresh the checked-in Safari resources, and commit the release source. -2. Configure the credentials below, then run `node scripts/store-release.mjs doctor`. -3. Run `node scripts/store-release.mjs plan`, then `node scripts/store-release.mjs release`. +1. Bump `extension/manifest.json`, add the `CHANGELOG.md` entry, refresh the checked-in Safari resources, and commit the release source. +2. Configure the credentials below, then run `node tools/release/store-release.mjs doctor`. +3. Run `node tools/release/store-release.mjs plan`, then `node tools/release/store-release.mjs release`. `release` builds and submits the stores **and** completes GitHub and frostdev.io publication. It does not report success until the pushed tag, GitHub assets, public website and signed update feed are verified. @@ -25,17 +25,17 @@ It never moves a published tag or replaces a published asset with different byte ### Individual build/publication tools (recovery only) -1. Bump `version` in `manifest.json`, add the `CHANGELOG.md` entry, commit. -2. `sh scripts/release.sh` runs the tests and builds the Chrome zip, both in a fresh checkout of the committed +1. Bump `version` in `extension/manifest.json`, add the `CHANGELOG.md` entry, commit. +2. `sh tools/release/release.sh` runs the tests and builds the Chrome zip, both in a fresh checkout of the committed `HEAD` (as CI builds a tag), and refuses a working tree with uncommitted or untracked files. With `MAC=1` and notarization credentials (`NOTARY_PROFILE`, or `ASC_KEY_PATH`/`ASC_KEY_ID`/`ASC_ISSUER_ID`) it also builds the notarized Mac app and DMG. -3. `sh scripts/safari-appcast.sh release//github/glaze--safari-macos.dmg /path/to/Sparkle/bin` signs the +3. `sh tools/release/safari-appcast.sh release//github/glaze--safari-macos.dmg /path/to/Sparkle/bin` signs the update entry into `release/appcast.xml`. 4. Tag `v` and push it. CI (`.github/workflows/package.yml`) builds the Chrome zip into a draft GitHub release. Merge the same main release commit into the permanent `dev/firefox` branch, retaining its compatibility fixes, and require its tests and zero-warning Firefox lint to pass. Build the Firefox upload and reviewer source ZIPs - from that exact Firefox commit (`sh scripts/package-firefox.sh`, then `git archive --format=zip -o HEAD`). + from that exact Firefox commit (`sh tools/build/package-firefox.sh`, then `git archive --format=zip -o HEAD`). Copy its Firefox package into `release//github/` and `release//firefox/`, with the source ZIP in `firefox/`. Upload the exact local Chrome ZIP used for the store over the CI Chrome asset while the release is still a draft, together with the Firefox package, notarized DMG, Mac zip and matching checksums, then publish the completed draft. @@ -44,15 +44,15 @@ It never moves a published tag or replaces a published asset with different byte Never publish a Firefox package built from main alone. Do not rebase, force-push or delete `dev/firefox`. 5. Publish the appcast on frostdev.io at `/projects/glaze/appcast.xml`, only after the DMG is on GitHub. 6. Stores, each from its folder: - - Chrome Web Store: `sh scripts/publish-chrome.sh release//chrome-web-store/glaze-v.zip`, or upload it in the dashboard. + - Chrome Web Store: `sh tools/release/publish-chrome.sh release//chrome-web-store/glaze-v.zip`, or upload it in the dashboard. Given no zip, the script packages a fresh checkout of the committed `HEAD` and refuses a dirty working tree. - Firefox Add-ons: upload `release//firefox/glaze-v-firefox.zip` at addons.mozilla.org. When it asks for source code, upload `glaze-v-source.zip` from the same Firefox commit: pdf.js is the readable legacy build with a compatibility - shim and core-js's old-IE fallback cut, three.js is a custom build, lottie-web's player is the official light canvas build with one comment line on top, ProseMirror (`lib/pm.js`) is an unminified esbuild bundle of its published packages, and harper.js (`lib/harper/`) is the published modules and slim wasm with two unused paths cut. Reviewers rebuild them with `scripts/vendor-pdfjs.sh`, `scripts/vendor-three.sh`, `scripts/vendor-lottie.sh`, `scripts/vendor-prosemirror.sh` and `scripts/vendor-harper.sh`. + shim and core-js's old-IE fallback cut, three.js is a custom build, lottie-web's player is the official light canvas build with one comment line on top, ProseMirror (`lib/pm.js`) is an unminified esbuild bundle of its published packages, and harper.js (`lib/harper/`) is the published modules and slim wasm with two unused paths cut. Reviewers rebuild them with `tools/vendor/vendor-pdfjs.sh`, `tools/vendor/vendor-three.sh`, `tools/vendor/vendor-lottie.sh`, `tools/vendor/vendor-prosemirror.sh` and `tools/vendor/vendor-harper.sh`. `vendor-three.sh` and `vendor-prosemirror.sh` install exact versions with install scripts off, check every installed package against a pinned SHA-512 integrity before running anything, and compare the result with the recorded hash of `lib/three.js` or `lib/pm.js` and `lib/pm.css`; `vendor-harper.sh` checks the npm tarball and its wasm against pinned SHA-256s. - - App Store: `sh scripts/appstore-safari.sh` (Mac) and `APPSTORE_UPLOAD=1 sh scripts/appstore-ios.sh`. + - App Store: `sh tools/build/appstore-safari.sh` (Mac) and `APPSTORE_UPLOAD=1 sh tools/build/appstore-ios.sh`. ### CI @@ -60,7 +60,7 @@ It never moves a published tag or replaces a published asset with different byte attaches the zip to the draft release without checking anything out. Every action is pinned by full commit SHA, with the tag it was resolved from in a comment, and checkout keeps no token. To move to a newer action, resolve its tag to a commit (`git ls-remote https://github.com//.git refs/tags/ 'refs/tags/^{}'`, and take the `^{}` line for an -annotated tag), replace the SHA and the comment together, and let `test/workflows.test.js` confirm the pin. +annotated tag), replace the SHA and the comment together, and let `test/release/workflows.test.js` confirm the pin. ## The mod blocklist @@ -69,7 +69,7 @@ key lives on the owner's own computer: never in the repo, on a server, or in a b Once, before the first release that includes mods: -1. `node scripts/blocklist-sign.mjs keygen ~/glaze-blocklist.key` (any path outside the repo). The file is made mode 600; an +1. `node tools/release/blocklist-sign.mjs keygen ~/glaze-blocklist.key` (any path outside the repo). The file is made mode 600; an existing file is never overwritten and a path inside the repo is refused. Back it up offline: with the key lost, only a new release with a new key can block anything again. 2. Paste the public key it prints over `PENDING_OWNER_KEY` in `lib/blocklist.js` (`OWNER_KEY`) and release that. Until @@ -84,7 +84,7 @@ To block something, and to renew (a list expires 30 days after it is signed): A mod's id, bundle hash and author key are in its `mod:` record and the gallery listing. A reason is shown to the student next to the stopped mod (at most 200 characters, one line, and only for something listed). Taking an entry out unblocks that mod; it stays off until the student turns it on. -2. `node scripts/blocklist-sign.mjs sign ~/glaze-blocklist.key ~/blocklist.json` writes `~/blocklist.signed.json` with +2. `node tools/release/blocklist-sign.mjs sign ~/glaze-blocklist.key ~/blocklist.json` writes `~/blocklist.signed.json` with `seq` one higher than the working file's (and records it there), `issued` now and `expires` 30 days later. It refuses a key other than the pinned one, a list the extension would reject, and a key file others can read. 3. Publish it: copy the signed file into the glaze-gallery volume on loothing-vps and run @@ -98,7 +98,7 @@ To block something, and to renew (a list expires 30 days after it is signed): After updating the manifest/changelog and committing the release source, run: ```sh -node scripts/store-release.mjs release +node tools/release/store-release.mjs release ``` This runs the Node suites, builds Chrome and Firefox packages, Mac and iPhone/iPad App Store packages, @@ -142,10 +142,10 @@ for store approval. The command does not install an app or change permanent `dev ```sh mkdir -p ~/.config/glaze -cp scripts/store-release.env.example ~/.config/glaze/stores.env +cp tools/release/store-release.env.example ~/.config/glaze/stores.env chmod 600 ~/.config/glaze/stores.env # Fill in the empty values locally, then check access without uploading: -node scripts/store-release.mjs doctor +node tools/release/store-release.mjs doctor ``` Keep this file and Apple's private key outside the repository. The config is parsed as data (shell @@ -191,7 +191,7 @@ info are being prepared, never in review; a new language shows the English scree Apple has no What's New field for a platform's first release. Apple keywords are limited to 100 UTF-8 bytes; promotional text may be omitted from a source. -`node scripts/store-release.mjs listings` reads the stores and prints, per store and language, what a release would +`node tools/release/store-release.mjs listings` reads the stores and prints, per store and language, what a release would change. It writes nothing. Submission stops on an unknown or noneditable Apple version state, or if required languages cannot be added because app info is not editable. An existing editable review draft receives the same listing sync before submission; a retry @@ -223,13 +223,13 @@ source/Firefox commit IDs, build number, notes and submission receipts are recor ### Preview, build only, and recovery ```sh -node scripts/store-release.mjs plan # no network, builds or uploads -node scripts/store-release.mjs build # store packages only; no public build or uploads -node scripts/store-release.mjs build --stores chrome,firefox # no Apple credentials needed -node scripts/store-release.mjs submit # resume stores only -node scripts/store-release.mjs publish # resume saved GitHub/website publication only -node scripts/store-release.mjs listings # what each store's listing would change; writes nothing -node scripts/store-release.mjs release --apple-platforms MAC_OS # omit iPhone/iPad +node tools/release/store-release.mjs plan # no network, builds or uploads +node tools/release/store-release.mjs build # store packages only; no public build or uploads +node tools/release/store-release.mjs build --stores chrome,firefox # no Apple credentials needed +node tools/release/store-release.mjs submit # resume stores only +node tools/release/store-release.mjs publish # resume saved GitHub/website publication only +node tools/release/store-release.mjs listings # what each store's listing would change; writes nothing +node tools/release/store-release.mjs release --apple-platforms MAC_OS # omit iPhone/iPad ``` All selected store and public-release prerequisites are checked before `release` builds or uploads anything. diff --git a/checks/agents/agent-link-check.js b/checks/agents/agent-link-check.js index 78d818cd7..754774cd1 100644 --- a/checks/agents/agent-link-check.js +++ b/checks/agents/agent-link-check.js @@ -16,22 +16,22 @@ // after its wait on Starting… (lib/editor-agents.js WAIT), which starts the worker that connects. // Evidence in the folder given: results.json, log.txt, each agent's lines, the bare browsers' logs, the probe's reports, // the approval window and the prompt. -// glaze-remote -o -- node scripts/agent-link-check.js /out +// glaze-remote -o -- node checks/agents/agent-link-check.js /out const { chromium } = require('playwright'); const fs = require('node:fs'), path = require('node:path'), os = require('node:os'), net = require('node:net'), zlib = require('node:zlib'); const { isDeepStrictEqual: same } = require('node:util'); const { spawn, execFileSync } = require('node:child_process'); -const C = require('../test/helpers/mcp-caches.cjs'); -const ST = require('../lib/mcp/tools-study.js'); -const IDS = require('../mcp/lib/ids.js'), BR = require('../mcp/lib/browsers.js'), L = require('../mcp/lib/launcher.js'), RV = require('../mcp/lib/rendezvous.js'); +const C = require('../../test/helpers/mcp-caches.cjs'); +const ST = require('../../extension/lib/mcp/tools-study.js'); +const IDS = require('../../mcp/lib/ids.js'), BR = require('../../mcp/lib/browsers.js'), L = require('../../mcp/lib/launcher.js'), RV = require('../../mcp/lib/rendezvous.js'); -const src = fs.realpathSync(path.join(__dirname, '..')); -const BIN = path.join(src, 'mcp', 'bin', 'glaze-mcp.js'); -const VERSION = require('../mcp/package.json').version; +const src = fs.realpathSync(path.join(__dirname, '../../extension')); +const BIN = path.join(__dirname, '../../mcp/bin/glaze-mcp.js'); +const VERSION = require('../../mcp/package.json').version; const OUT = path.resolve(process.argv[2] || fs.mkdtempSync(path.join(os.tmpdir(), 'agent-link-out-'))); const IDLE = process.env.SLOW ? 360e3 : 70e3; const WAIT = IDLE + 15e3; // the probe page's wait: the idle, plus the agent's first call after the page opens -const ASK = require('../lib/editor-agents.js').WAIT; // Settings' wait on Starting…, then its one ask +const ASK = require('../../extension/lib/editor-agents.js').WAIT; // Settings' wait on Starting…, then its one ask const PROBE = 'io.frostdev.glaze_t9_probe'; const NODE = fs.realpathSync(process.execPath); const CLIENT = { name: 'Glaze T9 check', version: '1.0.0' }; @@ -153,11 +153,11 @@ const extArgs = (ext) => [`--disable-extensions-except=${ext.dir}`, `--load-exte async function open(prof, ext, home, display) { if (interrupted) throw Error(interrupted); const env = { ...envFor(home), ...(display && { DISPLAY: `:${display}` }) }; - await require('./wait-for-browser.js')(); - const context = await chromium.launchPersistentContext(prof, { ...require('./check-env.js').chromeLaunch(), headless: !display, viewport: { width: 1000, height: 700 }, env, args: extArgs(ext) }); + await require('../../tools/dev/wait-for-browser.js')(); + const context = await chromium.launchPersistentContext(prof, { ...require('../../tools/dev/check-env.js').chromeLaunch(), headless: !display, viewport: { width: 1000, height: 700 }, env, args: extArgs(ext) }); owned.contexts.add(context); context.setDefaultTimeout(15000); - require('./check-env.js').noFirstRun(context); + require('../../tools/dev/check-env.js').noFirstRun(context); await context.route('**/*', (r) => (new URL(r.request().url()).protocol === 'chrome-extension:' ? r.continue() : r.abort())); // Everything goes through the options page, whose messages start the worker: Playwright's serviceworker event was // once missed in a headed launch. @@ -183,7 +183,7 @@ function bare(label, prof, ext, home, display, debug = false) { const args = bareArgs(prof, ext).filter((a) => !display || a !== '--headless'); if (debug) args.unshift('--remote-debugging-port=0'); let proc; - try { proc = spawn(require('./check-env.js').chromePath() || chromium.executablePath(), args, { detached: true, stdio: ['ignore', out, out], env: { ...envFor(home), ...(display && { DISPLAY: `:${display}` }) } }); } + try { proc = spawn(require('../../tools/dev/check-env.js').chromePath() || chromium.executablePath(), args, { detached: true, stdio: ['ignore', out, out], env: { ...envFor(home), ...(display && { DISPLAY: `:${display}` }) } }); } finally { fs.closeSync(out); } track(proc); return { proc, log, at: Date.now() }; @@ -570,7 +570,7 @@ async function restart() { nmh(prof, { name: BR.HOST, description: 'glaze-mcp (T9)', path: launcher, type: 'stdio', allowed_origins: [IDS.origin(ext.id)] }); ph.manifest = { permissions: ext.permissions, optional: ext.optional }; probeFor(prof, 'restart', ext); - await require('./wait-for-browser.js')(); + await require('../../tools/dev/wait-for-browser.js')(); const raw = bare('restart', prof, ext, home, X.n, true); const active = path.join(prof, 'DevToolsActivePort'); const port = await until(() => fs.existsSync(active) && +fs.readFileSync(active, 'utf8').split('\n')[0]); @@ -579,7 +579,7 @@ async function restart() { owned.attachments.add(browser); const context = browser.contexts()[0]; context.setDefaultTimeout(15000); - require('./check-env.js').noFirstRun(context); + require('../../tools/dev/check-env.js').noFirstRun(context); const opt = await context.newPage(); await opt.goto(`chrome-extension://${ext.id}/options.html`); await opt.bringToFront(); @@ -686,7 +686,7 @@ async function restart() { const guard = async (name, fn) => { if (interrupted) return; await fn().catch((e) => { say(`ERROR in ${name}:`, e.stack || String(e)); R.errors = { ...R.errors, [name]: String(e.stack || e) }; }); }; -const lock = require('./check-env.js').lockPath(); +const lock = require('../../tools/dev/check-env.js').lockPath(); let held = false, cleaning; const cleanup = () => cleaning ||= Promise.resolve().then(async () => { const all = scratch ? processes() : [], captured = new Map(); @@ -739,7 +739,7 @@ for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => { fs.writeFileSync(path.join(lock, 'owner'), `${process.pid}\nagent-link-check\n`); scratch = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-t9-'))); put(path.join(scratch, 'probe', 'host.js'), `(${probeHost})();\n`); - try { R.chrome = execFileSync(require('./check-env.js').chromePath() || chromium.executablePath(), ['--version'], { encoding: 'utf8' }).trim(); } catch (e) { R.chrome = String(e); } + try { R.chrome = execFileSync(require('../../tools/dev/check-env.js').chromePath() || chromium.executablePath(), ['--version'], { encoding: 'utf8' }).trim(); } catch (e) { R.chrome = String(e); } R.platform = `${os.platform()} ${os.release()}`; say('chrome', R.chrome, 'idle', IDLE); STORE = { todoCache: await C.canvasPlan('5', PLAN), seenCourses: await C.canvasSeen(SEEN) }; diff --git a/checks/agents/mcp-approve-check.js b/checks/agents/mcp-approve-check.js index abaf919e2..6bb3d073d 100644 --- a/checks/agents/mcp-approve-check.js +++ b/checks/agents/mcp-approve-check.js @@ -6,10 +6,10 @@ // allow, closing the window is Don't allow, a cancel moves the window on to the next ask, a click in the first moment and a // decision from another tab change nothing. Every request outside the extension is aborted. // Shots: pair-, grant- and action-light.png and -dark.png. -// NODE_PATH= nice -n 10 node scripts/mcp-approve-check.js [shots folder] (EXT selects the checkout) +// NODE_PATH= nice -n 10 node checks/agents/mcp-approve-check.js [shots folder] (EXT selects the checkout) const { chromium } = require('playwright'); const fs = require('node:fs'), os = require('node:os'), path = require('node:path'), assert = require('node:assert/strict'); -const root = fs.realpathSync(process.env.EXT || path.join(__dirname, '..')); +const root = fs.realpathSync(process.env.EXT || path.join(__dirname, '../../extension')); const CLAUDE = { id: 'q2yhd4emtekyo2kc', name: 'Claude', exe: '/Applications/Claude.app/Contents/MacOS/Claude' }; const CODE = { id: 'ioiih44jk25fd3hj', name: 'Visual Studio Code', exe: '/usr/share/code/code' }; const SUMMARY = 'Mark “Essay 2: The river in Beloved” done in BIO 101.'; @@ -37,7 +37,7 @@ const check = async (name, test) => { }; (async () => { - const lock = require('./check-env.js').lockPath(); + const lock = require('../../tools/dev/check-env.js').lockPath(); const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'glaze-mcp-approve-')); const shots = path.resolve(process.argv[2] || path.join(scratch, 'shots')); fs.mkdirSync(shots, { recursive: true }); @@ -47,17 +47,17 @@ const check = async (name, test) => { try { fs.mkdirSync(lock); held = true; } catch (e) { if (e.code !== 'EEXIST') throw e; await pause(1000); } } fs.writeFileSync(path.join(lock, 'owner'), `${process.pid}\nmcp-approve-check\n`); - const ext = require('./check-env.js').staged(root, [], scratch); + const ext = require('../../tools/dev/check-env.js').staged(root, [], scratch); // The staged files are hard links to the checkout: replace the worker's, never write through it. const worker0 = fs.readFileSync(path.join(ext, 'lib/background.js'), 'utf8'); fs.rmSync(path.join(ext, 'lib/background.js')); fs.writeFileSync(path.join(ext, 'lib/background.js'), worker0 + SHIM); - await require('./wait-for-browser.js')(); + await require('../../tools/dev/wait-for-browser.js')(); context = await chromium.launchPersistentContext(path.join(scratch, 'profile'), { - ...require('./check-env.js').chromeLaunch(), headless: true, viewport: null, reducedMotion: 'reduce', locale: 'en-US', + ...require('../../tools/dev/check-env.js').chromeLaunch(), headless: true, viewport: null, reducedMotion: 'reduce', locale: 'en-US', args: [`--disable-extensions-except=${ext}`, `--load-extension=${ext}`, '--disable-background-networking', '--host-resolver-rules=MAP * ~NOTFOUND', '--disable-quic'], }); - require('./check-env.js').noFirstRun(context); + require('../../tools/dev/check-env.js').noFirstRun(context); context.setDefaultTimeout(15000); await context.route('**/*', (r) => (new URL(r.request().url()).protocol === 'chrome-extension:' ? r.continue() : r.abort())); const worker = context.serviceWorkers().find((w) => w.url().endsWith('/lib/background.js')) || await context.waitForEvent('serviceworker'); diff --git a/checks/agents/mcp-conformance-check.js b/checks/agents/mcp-conformance-check.js index 05e69a7ac..0eb5e5b71 100644 --- a/checks/agents/mcp-conformance-check.js +++ b/checks/agents/mcp-conformance-check.js @@ -1,5 +1,5 @@ // MCP conformance (C17): the official TypeScript SDK client (@modelcontextprotocol/client, pinned exactly below; npm installs -// it into a scratch folder at check time and scripts/npm-integrity.js holds every package to its pinned integrity before it +// it into a scratch folder at check time and tools/vendor/npm-integrity.js holds every package to its pinned integrity before it // runs) talking to Glaze through the real path: glaze-mcp agent → glaze-mcp host → Chromium's worker, on Linux. As in // agent-link-check, Glaze is staged with nativeMessaging required (headless can't answer its prompt) and glaze-mcp setup // installs the helper under a scratch HOME whose Chromium folder is the profile; the caches are the production writers' @@ -17,20 +17,20 @@ // 9 Off: the subscription ends gracefully, every agent is let go and the host goes. // Evidence in the folder given: results.json (checks, s3, s4), log.txt, each client's wire-
    • >>0|t>>>0>l-p>>>0){break a}y[i+12>>2]=t;c=m+H|0;y[i+8>>2]=c;if(j>>>0>>0|d>>>0>j-q>>>0){break a}y[i+4>>2]=d;E=y[i+16>>2];y[i>>2]=E+(q<<2);if(A){if(t>>>0>>0){break a}y[i+12>>2]=x;y[i+8>>2]=c}e=i+8|0;c=Ya(e);f=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8);if(n){c=Ya(e);e=bc(i);h=f<>>8^Ae(c,8);c=y[e>>2];c=Ma(g,h|f>>>C,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),F);K=e,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L;e=y[i+4>>2]}else{e=d}if(e>>>0>>0){break a}c=y[i+8>>2];if(B[i+12>>2]>>0){break a}h=y[i>>2];I=r<<2;u=h+I|0;D=y[i+12>>2];k=c;while(1)if((h|0)==(u|0)){if(A){if(r>>>0>D>>>0){break a}h=f<>2];h=(((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8))>>>C|h}if((e|0)==(r|0)){break a}c=y[u>>2];c=Ma(g,h,((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),G);K=u,L=((Ae(c,16)^c)&-16711936)>>>8^Ae(c,8),y[K>>2]=L}c=y[a+28>>2]/4|0;if(c>>>0>l>>>0){break a}e=y[b+28>>2]/4|0;if(e>>>0>j>>>0){break a}y[i+16>>2]=E+(e<<2);j=j-e|0;l=l-c|0;m=(c<<2)+m|0;continue e}else{J=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);v=y[h>>2];v=yb(g,J|f>>>C,((Ae(v,16)^v)&-16711936)>>>8^Ae(v,8));K=h,L=((Ae(v,16)^v)&-16711936)>>>8^Ae(v,8),y[K>>2]=L;c=c+4|0;h=h+4|0;continue}}}Y=i+32|0;return}o()}function Gd(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;e=Y-128|0;Y=e;m=ua(a+8|0,a+12|0);y[e+124>>2]=m;f=y[m+16>>2];a:{if(!f){break a}Mb(y[m+12>>2],f,z[a+4|0]);j=td(b);y[e+116>>2]=j;g=0;b:{if(ya(j,y[a+152>>2],e+120|0)){break b}y[e+108>>2]=y[e+120>>2];w[e+104|0]=1;q=xb(e+104|0,y[a+20>>2]);g=q;h=y[e+108>>2];f=0-h|0;y[g>>2]=z[g|0]&(h|0)!=-2147483648;y[g+4>>2]=f;y[e+100>>2]=0;w[e+96|0]=1;t=a+28|0;c:{d:{e:while(1){if(B[a+16>>2]>r>>>0){if(ya(j,y[a+152>>2],e+92|0)){break c}y[e+84>>2]=y[e+92>>2];g=1;w[e+80|0]=1;h=xb(e+80|0,y[a+20>>2]);s=Zc(q,y[h>>2],y[h+4>>2]);y[e+76>>2]=0;w[e+72|0]=1;while(1){f:{if(g&1){if(ya(j,y[a+144>>2],e+12|0)){break c}g=La(e+96|0,y[e+12>>2]);f=y[g+4>>2];y[e+72>>2]=y[g>>2];y[e+76>>2]=f;break f}g:{switch(ya(j,y[a+148>>2],e+12|0)|0){case 0:break g;case 1:continue e;default:break c}}La(La(e+72|0,y[e+12>>2]),w[a+5|0])}g=0;f=y[a+20>>2];if((f|0)!=1){f=ib(f);if(Fa(b,f>>>0<=1?1:f,e+12|0)){break c}g=z[e+12|0]}f=y[s+4>>2];if(!(y[s>>2]&1)){break c}p=f+g|0;if((p&(f^-1))<0){break c}if(ya(j,t,e+68|0)){break c}h:{i:{j:{if(!z[a+1|0]){w[e+64|0]=0;y[e+60>>2]=0;break j}f=y[b>>2];g=y[b+8>>2];if(g>>>0>=B[b+4>>2]){break c}g=z[f+g|0];f=y[b+12>>2];Tb(b);w[e+64|0]=0;y[e+60>>2]=0;if(g>>>7-f&1){break i}}Yc(e+60|0,y[y[a+124>>2]+(y[e+68>>2]<<2)>>2]);break h}if(ya(j,y[a+156>>2],e+56|0)){break d}if(ya(j,y[a+160>>2],e+52|0)){break d}if(ya(j,y[a+164>>2],e+48|0)){break d}if(ya(j,y[a+168>>2],e+44|0)){break d}if(ya(j,y[a+172>>2],e+40|0)){break d}sa(b);k=y[y[a+124>>2]+(y[e+68>>2]<<2)>>2];if(!k){break d}l=y[e+56>>2];g=l>>31;f=y[k+20>>2]+l|0;if(f>>>0>>0?g+1|0:g){break d}i=y[e+52>>2];h=i>>31;g=i+y[k+24>>2]|0;if(g>>>0>>0?h+1|0:h){break d}h=y[b+8>>2];wb(e+12|0,l,2,y[e+48>>2]);wb(e+32|0,i,2,y[e+44>>2]);if(!(w[e+36|0]&1)|z[e+16|0]!=1){break d}i=kb();y[e+28>>2]=i;y[i+8>>2]=g;y[i+4>>2]=f;f=z[a+2|0];y[i+20>>2]=k;w[i|0]=f;y[i+12>>2]=y[e+12>>2];f=y[e+32>>2];g=0;w[i+1|0]=0;y[i+16>>2]=f;w[i+24|0]=z[a+176|0];w[i+25|0]=z[a+177|0];w[i+26|0]=z[a+178|0];w[i+27|0]=z[a+179|0];f=Na(b);y[e+24>>2]=f;if(ac(Xc(e+60|0,jb(i,f,c,d)))){sa(b);g=y[b+8>>2];if(g>>>0<=4294967293){g=g+2|0;f=y[b+4>>2];g=f>>>0>g>>>0?g:f;y[b+8>>2]=g}g=y[e+40>>2]==(g-h|0)}ta(e+24|0);Ua(e+28|0);if(!g){break d}}f=e+60|0;if(ac(f)){k=y[Ta(f)+20>>2];f=Ta(f);n=y[a+140>>2];l=y[f+24>>2];k:{l:{i=z[a+3|0];if(!i){f=z[e+72|0];if((n&-2)!=2){break l}if(!(f&1)){break d}h=y[e+76>>2];g=h>>31;f=k-1|0;o=f;f=f+h|0;g=o>>>0>f>>>0?g+1|0:g;h=f>>>0<2147483648&(g|0)<=0|(g|0)<0;y[e+72>>2]=0|h;y[e+76>>2]=h?f:0;if(!h){break d}break k}f=z[e+72|0];m:{switch(n|0){default:if(!(f&1)){break d}break k;case 0:case 2:break m}}if(!(f&1)){break d}h=y[e+76>>2];g=h>>31;f=l-1|0;o=f;f=f+h|0;g=o>>>0>f>>>0?g+1|0:g;h=f>>>0<2147483648&(g|0)<=0|(g|0)<0;y[e+72>>2]=0|h;y[e+76>>2]=h?f:0;if(h){break k}break d}if(!(f&1)){break d}}f=y[e+76>>2];Wc(e+12|0,i,n,f,p,k,l);n=Ta(e+60|0);g=y[e+12>>2];o=g;p=g>>31;g=y[e+16>>2];Ab(n,m,o,p,g,g>>31,y[a+136>>2]);h=y[e+20>>2];if(h){g=f;f=f>>31;g=g+h|0;f=g>>>0>>0?f+1|0:f;h=g>>>0<2147483648&(f|0)<=0|(f|0)<0;f=0;y[e+72>>2]=f|h;y[e+76>>2]=h?g:0}r=r+1|0}Xa(e+60|0);g=0;continue}}break}y[e+124>>2]=0;g=m;break b}Xa(e+60|0)}g=0}Ua(e+116|0)}la(e+124|0);Y=e+128|0;return g}function Ed(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0;f=Y-112|0;Y=f;m=ua(a+8|0,a+12|0);y[f+108>>2]=m;a:{b:{if(!y[m+16>>2]){break b}q=y[e>>2];g=0;if(!Aa(q,b,f+104|0)){break a}t=y[e+36>>2];u=y[e+32>>2];v=y[e+28>>2];x=y[e+24>>2];A=y[e+20>>2];C=y[e+16>>2];D=y[e+12>>2];E=y[e+8>>2];F=y[e+4>>2];Mb(y[m+12>>2],y[m+16>>2],z[a+4|0]);y[f+100>>2]=y[f+104>>2];w[f+96|0]=1;r=xb(f+96|0,y[a+20>>2]);g=r;h=y[f+100>>2];e=0-h|0;y[g>>2]=z[g|0]&(h|0)!=-2147483648;y[g+4>>2]=e;y[f+92>>2]=0;w[f+88|0]=1;c:{d:while(1){if(B[a+16>>2]>n>>>0){y[f+84>>2]=0;w[f+80|0]=1;if(!Aa(q,b,f+76|0)){break b}y[f+68>>2]=y[f+76>>2];e=1;w[f+64|0]=1;h=xb(f- -64|0,y[a+20>>2]);g=Zc(r,y[h>>2],y[h+4>>2]);h=y[g>>2];s=y[g+4>>2];G=h&1;while(1){e:{if(e&1){Aa(F,b,f+4|0);g=La(f+88|0,y[f+4>>2]);e=y[g+4>>2];y[f+80>>2]=y[g>>2];y[f+84>>2]=e;break e}if(!Aa(E,b,f+4|0)){continue d}La(La(f+80|0,y[f+4>>2]),w[a+5|0])}if(B[a+16>>2]<=n>>>0){continue d}y[f+60>>2]=0;if(y[a+20>>2]!=1){Aa(D,b,f+60|0)}if(!G){break b}e=y[f+60>>2];o=e+s|0;if(((o^s)&(e^o))<0){break b}Ac(t,b,f+56|0);g=y[f+56>>2];if(g>>>0>=B[a+24>>2]){break b}f:{g:{h:{if(!z[a+1|0]){y[f+52>>2]=0;w[f+48|0]=0;y[f+44>>2]=0;break h}Aa(C,b,f+52|0);e=y[f+52>>2];w[f+48|0]=0;y[f+44>>2]=0;if(e){break g}}Yc(f+44|0,y[y[a+124>>2]+(g<<2)>>2]);break f}Aa(A,b,f+40|0);Aa(x,b,f+36|0);Aa(v,b,f+32|0);Aa(u,b,f+28|0);i=y[y[a+124>>2]+(g<<2)>>2];if(!i){break c}j=y[f+40>>2];g=j>>31;e=y[i+20>>2]+j|0;if(e>>>0>>0?g+1|0:g){break c}k=y[f+36>>2];h=k>>31;g=k+y[i+24>>2]|0;if(g>>>0>>0?h+1|0:h){break c}wb(f+4|0,j,1,y[f+32>>2]);wb(f+20|0,k,1,y[f+28>>2]);if(!(w[f+24|0]&1)|z[f+8|0]!=1){break c}h=kb();y[f+16>>2]=h;y[h+8>>2]=g;y[h+4>>2]=e;e=z[a+2|0];y[h+20>>2]=i;w[h|0]=e;y[h+12>>2]=y[f+4>>2];e=y[f+20>>2];w[h+1|0]=0;y[h+16>>2]=e;w[h+24|0]=z[a+176|0];w[h+25|0]=z[a+177|0];w[h+26|0]=z[a+178|0];w[h+27|0]=z[a+179|0];Xc(f+44|0,jb(h,b,c,d));Ua(f+16|0)}e=f+44|0;if(!ac(e)){break c}j=y[Ta(e)+20>>2];e=Ta(e);p=y[a+140>>2];i=y[e+24>>2];i:{j:{k=z[a+3|0];if(!k){e=z[f+80|0];if((p&-2)!=2){break j}if(!(e&1)){break c}h=y[f+84>>2];e=h>>31;g=j-1|0;l=g;g=h+g|0;e=l>>>0>g>>>0?e+1|0:e;h=g>>>0<2147483648&(e|0)<=0|(e|0)<0;y[f+80>>2]=0|h;y[f+84>>2]=h?g:0;if(!h){break c}break i}e=z[f+80|0];k:{switch(p|0){default:if(!(e&1)){break c}break i;case 0:case 2:break k}}if(!(e&1)){break c}h=y[f+84>>2];g=h>>31;e=i-1|0;l=e;e=e+h|0;g=l>>>0>e>>>0?g+1|0:g;h=e>>>0<2147483648&(g|0)<=0|(g|0)<0;y[f+80>>2]=0|h;y[f+84>>2]=h?e:0;if(h){break i}break c}if(!(e&1)){break c}}h=y[f+84>>2];Wc(f+4|0,k,p,h,o,j,i);l=Ta(f+44|0);e=y[f+4>>2];g=e;i=e>>31;e=y[f+8>>2];Ab(l,m,g,i,e,e>>31,y[a+136>>2]);e=y[f+12>>2];if(e){g=h>>31;l=e;e=e+h|0;g=l>>>0>e>>>0?g+1|0:g;h=e>>>0<2147483648&(g|0)<=0|(g|0)<0;g=0;y[f+80>>2]=h|g;y[f+84>>2]=h?e:0}n=n+1|0;Xa(f+44|0);e=0;continue}}break}y[f+108>>2]=0;g=m;break a}Xa(f+44|0)}g=0}la(f+108|0);Y=f+112|0;return g}function he(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0;g=Y-96|0;Y=g;w[g+72|0]=0;y[g+68>>2]=0;i=c+31>>>3&536870908;k=E(i,d);j=vd(k,1);y[g+76>>2]=0;y[g>>2]=j;h=g+68|0;Uc(h);w[g+72|0]=1;y[g+68>>2]=j;y[g>>2]=0;Ja(g);Ja(g+76|0);a:{b:{if(!$b(h)){break b}j=$b(h);h=g+56|0;y[g+60>>2]=h;y[g+56>>2]=h;y[g+64>>2]=0;y[g+48>>2]=0;y[g+52>>2]=0;h=a;a=0;h=h|a;y[g+32>>2]=h;y[g+36>>2]=b;y[g>>2]=c;l=a|e;y[g+24>>2]=l;y[g+28>>2]=f;y[g+16>>2]=0;y[g+20>>2]=0;y[g+40>>2]=a|j;y[g+44>>2]=k;y[g+8>>2]=0;y[g+12>>2]=0;y[g+4>>2]=d;y[g+48>>2]=i;a=ze(i,0,d,0);if(Z|a>>>0>k>>>0){break a}e=j;while(1){if((a|0)>0){w[e|0]=0;a=a-1|0;e=e+1|0;continue}break}a=b;b=g+56|0;a=zc(na(116),h,a,b,0);if(f){b=zc(na(116),l,f,b,1);y[g+76>>2]=0;rb(a,b);sb(g+76|0)}f=c+7>>>3|0;y[g+76>>2]=0;h=g+52|0;rb(h,a);sb(g+76|0);c:{d:{b=y[g+52>>2];a=y[b>>2];if(!a){break d}if(!yc(a)){break d}y[b+56>>2]=-1;a=0;break c}y[b+52>>2]=0;e=na(32);a=e;y[a+24>>2]=0;y[a+28>>2]=0;y[a+16>>2]=0;y[a+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a>>2]=0;y[a+4>>2]=0;e:{if((c|d)<0|i>>>0>268435452){break e}l=i<<3;if((l|0)<(c|0)|(!(!i|(d|0)<=0|k)|(2147483616/(l>>>0)|0)<(d|0))){break e}y[a+28>>2]=i;y[a+24>>2]=d;y[a+20>>2]=c;a=ze(i,0,d,d>>31);c=Z;if((c|0)==1|c>>>0>1|a>>>0>k>>>0){break a}y[g+92>>2]=a;y[g+88>>2]=j;y[g+84>>2]=0;y[g+76>>2]=0;y[g+80>>2]=0;a=g+76|0;ec(e,a);va(a)}y[g+76>>2]=0;oa(b+32|0,e);la(g+76|0);w[b+50|0]=1;a=od(b)}f:{while(1){g:{a=a&1;h:{i:{b=y[y[g+52>>2]+56>>2];if((b|0)!=4){break h}rb(g+52|0,0);b=-1;if(!a){break h}b=E(y[g+48>>2],y[g+4>>2]);if(b>>>0>B[g+44>>2]){break i}a=y[g+40>>2];if(a&3){break i}c=(b&-4)+a|0;b=4;while(1){if((a|0)==(c|0)){break h}y[a>>2]=y[a>>2]^-1;a=a+4|0;continue}}o()}j:{switch(b-3|0){case 0:break j;case 1:break g;default:break f}}a=od(y[g+52>>2]);continue}break}X($b(g+68|0)|0,f|0,i|0,d|0)}sb(h);if(!y[g+64>>2]){break b}a=y[g+60>>2];b=y[a>>2];c=y[y[g+56>>2]+4>>2];y[b+4>>2]=c;y[c>>2]=b;y[g+64>>2]=0;while(1){if((g+56|0)==(a|0)){break b}b=y[a+4>>2];pc(a);a=b;continue}}Uc(g+68|0);Y=g+96|0;return}o()}function jc(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0;a:{b:{c:{d:{if(!z[a+1|0]){g=y[a+60>>2];break d}f=y[b+4>>2];if(z[f|0]){break a}h=y[c>>2];if(h>>>0>=B[b+12>>2]){break c}g=y[a+60>>2]^ka(f,y[b+8>>2]+(h<<1)|0);y[a+60>>2]=g}if(g){ld(a,y[y[b>>2]>>2]);break b}f=y[a+24>>2];if(f>>>0<=1){if((f|0)==1){if(!y[a+40>>2]){break c}j=z[y[a+36>>2]]}i=y[c+12>>2]&j>>>y[c+8>>2];h=(f|0)!=1;while(1){if((d|0)!=(k|0)){if(!h){f=k+1|0;if(f>>>0>=B[a+40>>2]){break c}j=z[f+y[a+36>>2]|0]|j<<8}g=7;l=0;while(1){if((g|0)>=0){f=y[b+4>>2];if(z[f|0]){break a}if(B[b+12>>2]<=i>>>0){break c}f=ka(f,y[b+8>>2]+(i<<1)|0);i=f|(y[c+16>>2]&j>>>y[c+8>>2]+g|(y[c+4>>2]&i)<<1);l=f<>2]<=k>>>0){break c}w[y[a+44>>2]+k|0]=l;k=k+1|0;continue}break}f=j<<8;g=0;l=0;while(1){if((e|0)!=(g|0)){h=y[b+4>>2];if(z[h|0]){break a}if(B[b+12>>2]<=i>>>0){break c}h=ka(h,y[b+8>>2]+(i<<1)|0);j=7-g|0;l=h<>2]&f>>>j+y[c+8>>2]|(y[c+4>>2]&i)<<1);g=g+1|0;continue}break}if(B[a+48>>2]<=d>>>0){break c}w[y[a+44>>2]+d|0]=l;break b}if(!y[a+32>>2]|!y[a+40>>2]){break c}j=z[y[a+36>>2]];k=z[y[a+28>>2]]<>2];i=y[c+12>>2]&j>>>y[c+8>>2]|k&y[c+24>>2];f=0;while(1){e:{if((d|0)!=(f|0)){h=f+1|0;if(h>>>0>=B[a+32>>2]|h>>>0>=B[a+40>>2]){break c}k=z[h+y[a+28>>2]|0]<>2]|k<<8;j=z[h+y[a+36>>2]|0]|j<<8;g=7;l=0;while(1){if((g|0)<0){break e}m=y[b+4>>2];if(z[m|0]){break a}if(B[b+12>>2]<=i>>>0){break c}m=ka(m,y[b+8>>2]+(i<<1)|0);i=m|(y[c+16>>2]&j>>>y[c+8>>2]+g|(y[c+28>>2]&k>>>g|(y[c+4>>2]&i)<<1));l=m<>2];if(z[h|0]){break a}if(B[b+12>>2]<=i>>>0){break c}k=ka(h,y[b+8>>2]+(i<<1)|0);h=7-g|0;f=k<>2]&l>>>h+y[c+8>>2]|(y[c+28>>2]&j>>>h|(y[c+4>>2]&i)<<1));g=g+1|0;continue}break}if(B[a+48>>2]<=d>>>0){break c}w[y[a+44>>2]+d|0]=f;break b}if(B[a+48>>2]<=f>>>0){break c}w[y[a+44>>2]+f|0]=l;f=h;continue}}o()}return 1}return 0}function we(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0;c=Y-48|0;Y=c;m=y[b+4>>2];t=y[b+12>>2];u=y[b+8>>2];h=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,h,d-1|0);ja(c+32|0,h,d-2|0);A=u+79434|0;n=y[c+36>>2];p=y[c+32>>2];C=t>>>0>39717;q=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,h,d);f=y[c+36>>2];r=y[c+32>>2];y[c+40>>2]=r;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[m|0]){break b}if(!C){break e}d=y[a+60>>2]^ka(m,A);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=r;y[c+28>>2]=f;y[c+8>>2]=r;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];i=0;v=0;x=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);x=y[c+32>>2];v=y[c+36>>2]}e=c+32|0;ja(e,h,d+w[a+17|0]|0);D=y[c+36>>2];E=y[c+32>>2];ja(e,h,d+w[a+19|0]|0);F=y[c+36>>2];G=y[c+32>>2];ja(e,h,d+w[a+21|0]|0);H=y[c+36>>2];I=y[c+32>>2];ja(e,h,d+w[a+23|0]|0);J=y[c+36>>2];K=y[c+32>>2];e=y[h+20>>2];i=!n|(e|0)<2?i:z[p|0]>>>6&1;i=!n|(e|0)<=0?i:z[p|0]>>>6&2|i;g=0;g=!j|(e|0)<3?g:z[k|0]>>>5&1;g=!j|(e|0)<2?g:z[k|0]>>>5&2|g;g=!j|(e|0)<=0?g:z[k|0]>>>5&4|g;s=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,x,v)){break h}}l=ia(e,w[a+16|0]+d|0,E,D);L=ia(e,w[a+18|0]+d|0,G,F);M=ia(e,w[a+20|0]+d|0,I,H);e=ia(e,w[a+22|0]+d|0,K,J);if(z[m|0]){break b}e=g<<5|i<<12|l<<4|L<<10|M<<11|e<<15|s;if(e>>>0>=t>>>0){break e}l=ka(m,(e<<1)+u|0);e=y[h+20>>2];if(!l){break h}Ea(e,d,r,f,1);e=y[h+20>>2];l=1;break g}l=0}i=ia(e,d+2|0,p,n)|i<<1&6;g=ia(e,d+3|0,k,j)|g<<1&30;d=d+1|0;s=s<<1&14|l;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;q=3}y[a+52>>2]=q;break a}d=d+1|0;y[a+24>>2]=d;p=k;n=j;continue}break}q=-1}Y=c+48|0;return q|0}function re(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0;i=y[b+4>>2];k=y[b+12>>2];l=y[b+8>>2];m=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[m+16>>2];y[a+44>>2]=y[m+12>>2];y[a+48>>2]=c}a:{b:{c=y[a+4>>2];if(!c){break b}q=l+810|0;c=c-1|0;j=c>>>3|0;n=(c&7)+1|0;d=y[a+24>>2];r=k>>>0>405;while(1){c:{if(B[a+8>>2]<=d>>>0){b=4}else{d:{if(!z[a+1|0]){d=y[a+60>>2];break d}if(z[i|0]){break a}if(!r){break b}d=y[a+60>>2]^ka(i,q);y[a+60>>2]=d}e:{if(d){ld(a,m);break e}d=0;g=0;f:{if(!y[a+24>>2]){g:{while(1){e=7;f=0;if((g|0)==(j|0)){break g}h:{while(1){if((e|0)>=0){if(z[i|0]){break h}if(d>>>0>=k>>>0){break b}h=d<<1;c=ka(i,h+l|0);d=c|h&1006;f=c<>2]<=g>>>0){break b}w[y[a+44>>2]+g|0]=f;g=g+1|0;continue}break}if(g>>>0>>0){break a}}e=0;f=0;while(1){if((e|0)==(n|0)){break f}if(z[i|0]){break a}if(d>>>0>=k>>>0){break b}h=d<<1;c=ka(i,h+l|0);d=c|h&1006;f=c<<7-e|f;e=e+1|0;continue}}if(!y[a+40>>2]){break b}g=z[y[a+36>>2]];e=g>>>1&112;c=0;while(1){i:{if((c|0)!=(j|0)){h=c+1|0;if(h>>>0>=B[a+40>>2]){break b}g=z[h+y[a+36>>2]|0]|g<<8;d=7;f=0;while(1){if((d|0)<0){break i}if(z[i|0]){break a}if(e>>>0>=k>>>0){break b}e=e<<1;p=ka(i,e+l|0);e=p|(g>>>d+1&16|e&1006);f=p<>>0>=k>>>0){break b}e=e<<1;c=ka(i,e+l|0);e=c|(h>>>8-d&16|e&1006);f=c<<7-d|f;d=d+1|0;continue}break}if(B[a+48>>2]<=j>>>0){break b}w[y[a+44>>2]+j|0]=f;break e}if(B[a+48>>2]<=c>>>0){break b}w[y[a+44>>2]+c|0]=f;c=h;continue}}if(B[a+48>>2]<=j>>>0){break b}w[y[a+44>>2]+j|0]=f}Db(a,m);c=y[b+16>>2];if(!c){break c}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break c}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}d=y[a+24>>2]+1|0;y[a+24>>2]=d;continue}}o()}return-1}function hb(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;a:{b:{while(1){if(a>>>0>4294967239){break b}b=y[1691];c=b;f=y[1690];a=a>>>0<=8?8:a+3&-4;c:{if(a>>>0<=127){d=(a>>>3|0)-1|0;break c}e=H(a);d=((a>>>29-e^4)-(e<<2)|0)+110|0;if(a>>>0<=4095){break c}e=((a>>>30-e^2)-(e<<1)|0)+71|0;d=e>>>0>=63?63:e}g=d;d=g&31;if((g&63)>>>0>=32){e=0;b=b>>>d|0}else{e=b>>>d|0;b=((1<>>d}if(b|e){while(1){d=e;d:{if(d|b){c=d-1|0;e=c+1|0;f=c;c=b-1|0;f=(c|0)!=-1?e:f;e=H(d^f);e=(e|0)==32?H(b^c)+32|0:e;c=63-e|0;Z=0-(e>>>0>63)|0;break d}Z=0;c=64}f=c;c=f&31;if((f&63)>>>0>=32){e=0;h=d>>>c|0}else{e=d>>>c|0;h=((1<>>c}g=f+g|0;b=g<<4;d=y[b+5736>>2];c=b+5728|0;e:{if((d|0)!=(c|0)){b=_b(d,a);if(b){break a}b=y[d+4>>2];f=y[d+8>>2];y[b+8>>2]=f;y[f+4>>2]=b;y[d+8>>2]=c;y[d+4>>2]=y[c+4>>2];y[c+4>>2]=d;y[y[d+4>>2]+8>>2]=d;g=g+1|0;b=(e&1)<<31|h>>>1;e=e>>>1|0;break e}j=y[1691];f=g&63;b=f;d=b&31;if(b>>>0>=32){b=0;c=-1>>>d|0}else{b=-1>>>d|0;c=b|(1<>>0>=32){b=c<>>32-d|b<>>0>=32){b=-1<>>32-b}k=c&-2;c=f&31;if(f>>>0>=32){f=0;c=b>>>c|0}else{f=b>>>c|0;c=((1<>>c}b=c|i;Z=d|f;y[1690]=y[1690]&b;y[1691]=Z&j;b=h^1}if(b|e){continue}break}f=y[1690];c=y[1691]}e=H(c);d=63-((e|0)==32?H(f)+32|0:e)|0;f:{if(!(c|f)){g=0;break f}b=d<<4;g=y[b+5736>>2];if(!c&f>>>0<1073741824){break f}e=98;c=b+5728|0;if((c|0)==(g|0)){break f}while(1){b=_b(g,a);if(b){break a}g=y[g+8>>2];if((c|0)==(g|0)){break f}b=e;e=b-1|0;if(b){continue}break}}if(Md(a+48|0)){continue}break}if(!g){break b}e=(d<<4)+5728|0;if((e|0)==(g|0)){break b}while(1){b=_b(g,a);if(b){break a}g=y[g+8>>2];if((e|0)!=(g|0)){continue}break}}b=0}return b}function nb(a,b,c,d,e){var f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0;g=Y-32|0;Y=g;a:{i=hc(d,e);if(!y[i+16>>2]|(b|0)<0|(!y[a+16>>2]|(c|0)<0)|y[a+20>>2]<=(b|0)){break a}f=y[a+24>>2];if((f|0)<=(c|0)){break a}b:{if(!(b&7)){e=y[a+28>>2];d=b>>>3|0;if(e>>>0<=d>>>0){break b}b=e-d|0;e=y[i+28>>2];h=b>>>0>>0?b:e;if(!h){break b}e=0;b=f-c|0;f=y[i+24>>2];b=(b|0)<(f|0)?b:f;k=(b|0)>0?b:0;while(1){if((e|0)==(k|0)){break a}dc(g+24|0,a,c+e|0);f=0;b=0;if(z[g+28|0]==1){j=y[a+16>>2];b=y[g+24>>2];if(j>>>0>>0){break b}f=y[a+28>>2];if(f>>>0>j-b>>>0){break b}b=b+y[a+12>>2]|0}if(d>>>0>f>>>0|h>>>0>f-d>>>0){break b}ja(g+16|0,i,e);if(h>>>0>B[g+20>>2]){break b}if(h){p(y[g+16>>2],b+d|0,h)}e=e+1|0;continue}}d=y[a+28>>2]/4|0;j=b>>>5|0;if(d>>>0<=j>>>0){break b}n=d-j|0;d=y[i+28>>2]/4|0;l=d>>>0>n>>>0?n:d;if(!l){break b}e=c;c=f-c|0;d=y[i+24>>2];c=(c|0)<(d|0)?c:d;ad(g+24|0,a,e,c);cc(g+16|0,i,0,c);h=y[g+20>>2];if(!h){break b}t=b&31;k=y[g+28>>2];u=j<<2;v=b^-1;c:while(1){if(!h){break a}if(k>>>0>>0|k-j>>>0>>0){break b}y[g+12>>2]=n;r=y[g+24>>2];y[g+8>>2]=r+u;if(h>>>0>>0){break b}c=y[g+16>>2];e=Ya(g+8|0);d=y[g+8>>2];q=y[g+12>>2];f=l>>>0>q>>>0?q:l;if(f>>>0>B[g+12>>2]){break b}s=c+(f<<2)|0;f=((Ae(e,16)^e)&-16711936)>>>8^Ae(e,8);e=c;while(1)if((e|0)==(s|0)){if(l>>>0>q>>>0){d=f<>>8^Ae(d,8),y[w>>2]=x}d=y[a+28>>2]/4|0;if(d>>>0>k>>>0){break b}y[g+24>>2]=(d<<2)+r;e=y[i+28>>2]/4|0;if(e>>>0>h>>>0){break b}y[g+16>>2]=c+(e<<2);h=h-e|0;k=k-d|0;continue c}else{m=f<>2];f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);m=m|f>>>1>>>v;w=e,x=((m^Ae(m,16))&-16711936)>>>8^Ae(m,8),y[w>>2]=x;d=d+4|0;e=e+4|0;continue}}}o()}Y=g+32|0;return i}function ue(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0,F=0;c=Y-48|0;Y=c;n=y[b+4>>2];t=y[b+12>>2];u=y[b+8>>2];i=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,i,d-1|0);ja(c+32|0,i,d-2|0);C=u+3882|0;l=y[c+36>>2];m=y[c+32>>2];D=t>>>0>1941;p=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,i,d);f=y[c+36>>2];q=y[c+32>>2];y[c+40>>2]=q;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[n|0]){break b}if(!D){break e}d=y[a+60>>2]^ka(n,C);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=q;y[c+28>>2]=f;y[c+8>>2]=q;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;v=0;x=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);x=y[c+32>>2];v=y[c+36>>2]}ja(c+32|0,i,d+w[a+17|0]|0);E=y[c+36>>2];F=y[c+32>>2];e=y[i+20>>2];g=!l|(e|0)<3?g:z[m|0]>>>5&1;g=!l|(e|0)<2?g:z[m|0]>>>5&2|g;g=!l|(e|0)<=0?g:z[m|0]>>>5&4|g;h=0;h=!j|(e|0)<3?h:z[k|0]>>>5&1;h=!j|(e|0)<2?h:z[k|0]>>>5&2|h;h=!j|(e|0)<=0?h:z[k|0]>>>5&4|h;s=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,x,v)){break h}}e=ia(e,w[a+16|0]+d|0,F,E);if(z[n|0]){break b}e=h<<4|g<<9|e<<3|s;if(e>>>0>=t>>>0){break e}r=ka(n,(e<<1)+u|0);e=y[i+20>>2];if(!r){break h}Ea(e,d,q,f,1);e=y[i+20>>2];r=1;break g}r=0}A=d+3|0;g=ia(e,A,m,l)|g<<1&14;h=ia(e,A,k,j)|h<<1&30;d=d+1|0;s=s<<1&6|r;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;p=3}y[a+52>>2]=p;break a}d=d+1|0;y[a+24>>2]=d;m=k;l=j;continue}break}p=-1}Y=c+48|0;return p|0}function Sd(a,b){a=a|0;b=+b;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;c=a<<3;o=c+6784|0;j=D[c+6816>>3];if(j!=0){l=D[o>>3];m=J(l,b);b=m-l;d=~~b>>>0;if(G(b)>=1){c=~~(b>0?I(K(b*2.3283064365386963e-10),4294967295):L((b-+(~~b>>>0>>>0))*2.3283064365386963e-10))>>>0}else{c=0}h=~~j>>>0;if(G(j)>=1){f=~~(j>0?I(K(j*2.3283064365386963e-10),4294967295):L((j-+(~~j>>>0>>>0))*2.3283064365386963e-10))>>>0}else{f=0}a:{b:{c:{d:{e:{f:{g:{h:{i:{j:{k:{if(c){if(!h){break k}if(!f){break j}e=H(f)-H(c)|0;if(e>>>0<=31){break i}break c}if((f|0)==1|f>>>0>1){break c}Z=0;c=(d>>>0)/(h>>>0)|0;break a}if(!d){break h}if(!f|f-1&f){break g}c=c>>>Be(f)|0;Z=0;break a}if(!(h-1&h)){break f}k=(H(h)+33|0)-H(c)|0;i=0-k|0;break d}k=e+1|0;i=63-e|0;break d}Z=0;c=(c>>>0)/(f>>>0)|0;break a}e=H(f)-H(c)|0;if(e>>>0<31){break e}break c}if((h|0)==1){break b}h=Be(h);f=h&31;if((h&63)>>>0>=32){c=c>>>f|0}else{e=c>>>f|0;c=((1<>>f}Z=e;break a}k=e+1|0;i=63-e|0}e=k&63;g=e&31;if(e>>>0>=32){e=0;n=c>>>g|0}else{e=c>>>g|0;n=((1<>>g}i=i&63;g=i&31;if(i>>>0>=32){c=d<>>32-g|c<>>31;e=n<<1|c>>>31;g=s-(p+(e>>>0>i>>>0)|0)>>31;q=g&h;n=e-q|0;e=p-((f&g)+(e>>>0>>0)|0)|0;c=c<<1|d>>>31;d=r|d<<1;r=g&1;k=k-1|0;if(k){continue}break}}Z=c<<1|d>>>31;c=r|d<<1;break a}d=0;c=0}Z=c;c=d}d=Z;c=c+1|0;d=c?d:d+1|0;l=(+(c>>>0)+ +(d>>>0)*4294967296)*j+l;m=l-m}D[o>>3]=l;Q(a|0,+m)|0;a=(a|0)==2?27:(a|0)==1?26:14;c=a-1|0;l:{if(y[1692]>>>c&1){y[1694]=y[1694]|1<>2];if(c){_[c|0](a)}}}function se(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0,C=0,D=0,E=0;c=Y-48|0;Y=c;l=y[b+4>>2];s=y[b+12>>2];t=y[b+8>>2];i=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,i,d-1|0);ja(c+32|0,i,d-2|0);A=t+458|0;m=y[c+36>>2];n=y[c+32>>2];C=s>>>0>229;a:{while(1){b:{c:{if(B[a+8>>2]<=d>>>0){d=4}else{j=y[c+44>>2];k=y[c+40>>2];ja(c+32|0,i,d);f=y[c+36>>2];p=y[c+32>>2];y[c+40>>2]=p;y[c+44>>2]=f;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[l|0]){break b}if(!C){break e}d=y[a+60>>2]^ka(l,A);y[a+60>>2]=d}if(d){y[c+20>>2]=j;y[c+16>>2]=k;y[c+24>>2]=p;y[c+28>>2]=f;y[c+8>>2]=p;y[c+12>>2]=f;f=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=f;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;u=0;v=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);v=y[c+32>>2];u=y[c+36>>2]}ja(c+32|0,i,d+w[a+17|0]|0);D=y[c+36>>2];E=y[c+32>>2];e=y[i+20>>2];g=!m|(e|0)<2?g:z[n|0]>>>6&1;g=!m|(e|0)<=0?g:z[n|0]>>>6&2|g;h=0;h=!j|(e|0)<2?h:z[k|0]>>>6&1;h=!j|(e|0)<=0?h:z[k|0]>>>6&2|h;r=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,v,u)){break h}}e=ia(e,w[a+16|0]+d|0,E,D);if(z[l|0]){break b}e=h<<3|g<<7|e<<2|r;if(e>>>0>=s>>>0){break e}q=ka(l,(e<<1)+t|0);e=y[i+20>>2];if(!q){break h}Ea(e,d,p,f,1);e=y[i+20>>2];q=1;break g}q=0}x=d+2|0;g=ia(e,x,n,m)|g<<1&6;h=ia(e,x,k,j)|h<<1&14;d=d+1|0;r=r<<1&2|q;continue}}o()}f=y[b+16>>2];if(!f){d=y[a+24>>2];break c}f=_[y[y[f>>2]+8>>2]](f)|0;d=y[a+24>>2];if(!f){break c}y[a+24>>2]=d+1;d=3}y[a+52>>2]=d;break a}d=d+1|0;y[a+24>>2]=d;n=k;m=j;continue}break}d=-1}Y=c+48|0;return d|0}function vc(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0;f=Y-32|0;Y=f;k=ua(a,a+4|0);y[f+28>>2]=k;a:{c=y[k+16>>2];if(!c){k=0;break a}Mb(y[k+12>>2],c,z[a+20|0]);b:{while(1){if(!m&l>>>0>=B[a+36>>2]|m){y[f+28>>2]=0;break a}e=y[b>>2];c=y[b+4>>2];d=0;y[f+24>>2]=0;y[f+16>>2]=0;y[f+20>>2]=0;c:{d:{if((c|0)==(e|0)){j=0;e=c;break d}c=c-e|0;e=c>>2;if(e>>>0>=536870912){break c}j=na(c<<1);y[f+16>>2]=j;d=(e<<3)+j|0;y[f+24>>2]=d;c=j;while(1){if((c|0)!=(d|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}y[f+20>>2]=d;c=y[b>>2];e=y[b+4>>2]}e=e-c|0;if(e>>2>>>0>d-j>>3>>>0){break b}e=c+e|0;d=j;while(1){if((c|0)==(e|0)){e:{c=y[a+44>>2];d=ze(l,m,A[a+48>>1],0);e=c+d|0;c=Z+(c>>31)|0;n=e;r=d>>>0>e>>>0?c+1|0:c;c=y[a+40>>2];d=ze(l,m,A[a+50>>1],0);e=c+d|0;c=Z+(c>>31)|0;s=e;t=d>>>0>e>>>0?c+1|0:c;e=0;c=0;f:while(1){if(!c&B[a+32>>2]<=e>>>0|c){break e}h=y[b>>2];p=y[b+4>>2]-h>>2;d=0;g=0;while(1){i=d&255;if(p>>>0<=i>>>0){d=y[a+12>>2]-1|0;d=y[y[y[a+16>>2]>>2]+((d>>>0>>0?d:g)<<2)>>2];h=ze(e,c,A[a+48>>1],0);i=h+s|0;g=Z+t|0;g=h>>>0>i>>>0?g+1|0:g;h=ze(e,c,A[a+50>>1],0);p=n-h|0;i=(g&255)<<24|i>>>8;q=g>>8;h=r-(Z+(h>>>0>n>>>0)|0)|0;g=h>>8;Ab(d,k,i,q,(h&255)<<24|p>>>8,g,y[a+24>>2]);e=e+1|0;c=e?c:c+1|0;continue f}else{q=g;g=(i<<3)+j|0;g=q|ia(y[y[(i<<2)+h>>2]+20>>2],e,y[g>>2],y[g+4>>2])<>2],l);n=y[f+12>>2];y[d>>2]=y[f+8>>2];y[d+4>>2]=n;d=d+8|0;c=c+4|0;continue}break}d=m;l=l+1|0;m=l?d:d+1|0;va(f+16|0);continue}break}za()}o()}la(f+28|0);Y=f+32|0;return k}function Zb(a,b,c,d,e,f,g,h){var i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;j=Y-48|0;Y=j;m=((b&536870911)<<3|a>>>29)&-8;o=d;d=-1;p=1;a:{b:while(1){k=p&1;q=k?326:325;r=k?1392:1056;s=k?325:326;t=k?1056:1392;c:while(1){i=y[c>>2];while(1){if(i>>>0>=m>>>0){break a}d:{i=0;e:{if(Ia(a,b,c)){break e}if(B[c>>2]>=m>>>0){break a}l=Ia(a,b,c);if(B[c>>2]>=m>>>0){break a}n=Ia(a,b,c);i=n?1:-1;if(l){break e}if(n){i=0;while(1){Yb(j+44|0,t,s,a,b,c);if(z[j+46|0]!=1){break a}l=A[j+44>>1];i=l+i|0;if((i|0)>(h|0)){break a}if(l>>>0>=64){continue}break}l=((d>>>31|0)+d|0)+i|0;if(!k){gb(o,e,h,d,l)}i=0;while(1){Yb(j+44|0,r,q,a,b,c);if(z[j+46|0]!=1){break a}d=A[j+44>>1];i=d+i|0;if((i|0)>(h|0)){break a}if(d>>>0>=64){continue}break}d=i+l|0;if(k){gb(o,e,h,l,d)}if((d|0)<(h|0)){continue c}break a}if(B[c>>2]>=m>>>0){break a}if(Ia(a,b,c)){y[j+32>>2]=f;y[j+36>>2]=g;y[j+16>>2]=f;y[j+20>>2]=g;Gc(j+16|0,h,d,k,j+44|0,j+40|0);i=y[j+40>>2];if(!k){gb(o,e,h,d,i)}d=i;if((i|0)<(h|0)){continue c}break a}if(B[c>>2]>=m>>>0){break a}l=Ia(a,b,c);if(B[c>>2]>=m>>>0){break a}n=Ia(a,b,c);i=n?2:-2;if(l){break e}i=y[c>>2];if(!n){break d}if(i>>>0>=m>>>0){break a}i=Ia(a,b,c)?3:-3}y[j+24>>2]=f;y[j+28>>2]=g;y[j+8>>2]=f;y[j+12>>2]=g;Gc(j+8|0,h,d,k,j+44|0,j+40|0);i=i+y[j+44>>2]|0;if(!k){gb(o,e,h,d,i)}if((h|0)<=(i|0)){break a}p=p^1;k=(d|0)<(i|0);d=i;if(k){continue b}break a}if(i>>>0>=m>>>0){break a}l=Ia(a,b,c);i=y[c>>2];if(l){i=i+3|0;y[c>>2]=i;continue}break}break}break}y[c>>2]=i+5}Y=j+48|0}function qe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,x=0,A=0;c=Y-48|0;Y=c;i=y[b+4>>2];q=y[b+12>>2];r=y[b+8>>2];h=y[y[b>>2]>>2];d=y[a+24>>2];ja(c+40|0,h,d-1|0);u=r+810|0;v=q>>>0>405;j=4;a:{while(1){b:{c:{if(B[a+8>>2]>d>>>0){k=y[c+44>>2];l=y[c+40>>2];ja(c+32|0,h,d);e=y[c+36>>2];m=y[c+32>>2];y[c+40>>2]=m;y[c+44>>2]=e;d:{e:{f:{if(!z[a+1|0]){d=y[a+60>>2];break f}if(z[i|0]){break b}if(!v){break e}d=y[a+60>>2]^ka(i,u);y[a+60>>2]=d}if(d){y[c+20>>2]=k;y[c+16>>2]=l;y[c+24>>2]=m;y[c+28>>2]=e;y[c+8>>2]=m;y[c+12>>2]=e;e=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=e;Ca(c+8|0,c);break d}d=y[a+24>>2];g=0;s=0;t=0;if(z[a+2|0]==1){ja(c+32|0,y[a+12>>2],d);t=y[c+32>>2];s=y[c+36>>2]}ja(c+32|0,h,d+w[a+17|0]|0);x=y[c+36>>2];A=y[c+32>>2];f=y[h+20>>2];g=!k|(f|0)<2?g:z[l|0]>>>6&1;g=!k|(f|0)<=0?g:z[l|0]>>>6&2|g;p=0;d=0;while(1){if(B[a+4>>2]<=d>>>0){break d}g:{h:{if(z[a+2|0]==1){if(ia(y[y[a+12>>2]+20>>2],d,t,s)){break h}}f=ia(f,w[a+16|0]+d|0,A,x);if(z[i|0]){break b}f=g<<5|f<<4|p;if(f>>>0>=q>>>0){break e}n=ka(i,(f<<1)+r|0);f=y[h+20>>2];if(!n){break h}Ea(f,d,m,e,1);f=y[h+20>>2];n=1;break g}n=0}g=ia(f,d+2|0,l,k)|g<<1&30;d=d+1|0;p=p<<1&14|n;continue}}o()}e=y[b+16>>2];if(!e){d=y[a+24>>2];break c}e=_[y[y[e>>2]+8>>2]](e)|0;d=y[a+24>>2];if(!e){break c}y[a+24>>2]=d+1;j=3}y[a+52>>2]=j;break a}d=d+1|0;y[a+24>>2]=d;continue}break}j=-1}Y=c+48|0;return j|0}function Od(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;c=y[b+60>>2];d=y[b+64>>2];f=b+52|0;Jc(c,d,f);j=((d&536870911)<<3|c>>>29)&-8;if(j>>>0<=B[b+52>>2]){y[a>>2]=0;y[a+4>>2]=0;return}Kc(y[b+68>>2],y[b+72>>2]);h=b+68|0;c=y[b+48>>2];a:{if((c|0)<0){e=y[b+68>>2];c=y[b+72>>2]-e|0;d=0;i=c;g=y[b+80>>2];c=y[b+84>>2]-g|0;Zb(y[b+60>>2],y[b+64>>2],f,e|d,i,d|g,c,y[b+4>>2]);Ic(b+80|0,h);break a}if(!c){d=y[b+68>>2];c=y[b+72>>2]-d|0;Hc(y[b+60>>2],y[b+64>>2],f,d,c,y[b+4>>2]);break a}e=Ia(y[b+60>>2],y[b+64>>2],f);g=y[b+68>>2];c=y[b+72>>2]-g|0;d=y[b+60>>2];k=y[b+64>>2];b:{if(e){Hc(d,k,f,g,c,y[b+4>>2]);break b}i=d;d=0;l=c;e=y[b+80>>2];c=y[b+84>>2]-e|0;Zb(i,k,f,d|g,l,d|e,c,y[b+4>>2])}Ic(b+80|0,h)}if(z[b+57|0]==1){Jc(y[b+60>>2],y[b+64>>2],f)}c:{d:{e:{if(z[b+56|0]!=1){break e}d=y[f>>2];if(j>>>0<=d>>>0){break e}e=d+7&-8;while(1){h=d>>>3|0;g=128>>>(d&7)|0;c=1;f:{while(1){if(!c|d>>>0>=e>>>0){break f}if(h>>>0>=B[b+64>>2]){break d}if(g&z[h+y[b+60>>2]|0]){c=0;w[b+56|0]=0;continue}break}d=d+1|0;continue}break}if(!c){break e}y[f>>2]=e}c=y[b+68>>2];if(!z[b+58|0]){d=y[b+72>>2]-c|0;break c}if(c&3){break d}d=y[b+72>>2]-c|0;e=c+(d&-4)|0;b=c;while(1){if((b|0)==(e|0)){break c}y[b>>2]=y[b>>2]^-1;b=b+4|0;continue}}o()}y[a+4>>2]=d;y[a>>2]=c}function ab(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;i=Y-32|0;Y=i;a:{b:{if(!y[a+16>>2]|y[a+24>>2]>=(b|0)){break b}e=y[a+28>>2];if((268435452/(e|0)|0)<(b|0)){break b}if((b|0)<0){break a}e=ze(e,e>>31,b,0);g=Z;if((g|0)==1|g>>>0>1){break a}k=y[a+12>>2];g=0;j=y[a+16>>2];d=y[a>>2];h=y[a+4>>2];c:{if((d|0)!=(h|0)){y[a+12>>2]=0;y[a+16>>2]=0;f=h-d|0;d:{if(f>>>0>>0){g=y[a+8>>2];if(e>>>0<=g-d>>>0){kd(a,e-f|0);break d}h=Fc(d,g,e);if(h){l=Ad(h)}k=e+l|0;g=f+l|0;d=g;while(1){if((d|0)!=(k|0)){w[d|0]=0;d=d+1|0;continue}break}d=y[a>>2];e=y[a+4>>2];g=g+(d-e|0)|0;f=g;while(1){if((d|0)!=(e|0)){w[f|0]=z[d|0];f=f+1|0;d=d+1|0;continue}break}y[a+4>>2]=k;y[a+8>>2]=h+l;e=y[a>>2];y[a>>2]=g;ma(e);break d}if(e>>>0>=f>>>0){break d}y[a+4>>2]=d+e}e=y[a>>2];y[a+12>>2]=e;d=y[a+4>>2]-e|0;y[a+16>>2]=d;break c}f=Gb(i,e);h=y[f>>2];y[i+12>>2]=h;e=y[f+4>>2];y[i+16>>2]=e;y[i+20>>2]=y[f+8>>2];y[f+8>>2]=0;y[f>>2]=0;y[f+4>>2]=0;y[i+28>>2]=e-h;y[i+24>>2]=h;e=i+12|0;ec(a,e);va(e);va(f);d=y[a+16>>2];if(j>>>0>d>>>0){break a}if(!(g|j)){break c}if(j){p(y[a+12>>2],k,j)}d=y[a+16>>2]}if(d>>>0>>0){break a}c=0-c|0;d=d-j|0;f=y[a+12>>2]+j|0;while(1){if((d|0)>0){w[f|0]=c;d=d-1|0;f=f+1|0;continue}break}y[a+24>>2]=b}Y=i+32|0;return}o()}function nd(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;d=Y-32|0;Y=d;h=y[a+24>>2];y[d+16>>2]=0;a:{b:{switch(z[a+3|0]){case 0:c=Bd(a)?3:4;break a;case 1:c=yd(a)?5:6;break a;case 2:c=Hb(a)?7:8;break a;default:break b}}c=Hb(a)?9:10}e=Y-48|0;Y=e;y[e+24>>2]=0;c:{if(c){y[e+16>>2]=0;y[e+12>>2]=c;y[e+8>>2]=2244;g=e+8|0;y[e+24>>2]=g;if(y[d+16>>2]==(d|0)){f=e+32|0;_[y[y[e+8>>2]+12>>2]](g,f);c=y[e+24>>2];_[y[y[c>>2]+16>>2]](c);y[e+24>>2]=0;c=y[d+16>>2];_[y[y[c>>2]+12>>2]](c,g);c=y[d+16>>2];_[y[y[c>>2]+16>>2]](c);y[d+16>>2]=0;y[e+24>>2]=g;_[y[y[e+32>>2]+12>>2]](f,d);_[y[y[e+32>>2]+16>>2]](f);y[d+16>>2]=d;break c}_[y[y[e+8>>2]+12>>2]](e+8|0,d);c=y[e+24>>2];_[y[y[c>>2]+16>>2]](c);y[e+24>>2]=y[d+16>>2];y[d+16>>2]=d;break c}c=y[d+16>>2];if((c|0)==(d|0)){f=e+8|0;_[y[y[c>>2]+12>>2]](c,f);c=y[d+16>>2];_[y[y[c>>2]+16>>2]](c);y[d+16>>2]=y[e+24>>2];y[e+24>>2]=f;break c}y[e+24>>2]=c;y[d+16>>2]=0}md(e+8|0);Y=e+48|0;c=y[y[b>>2]>>2];f=y[d+16>>2];y[d+28>>2]=b;if(f){f=_[y[y[f>>2]+24>>2]](f,a,d+28|0)|0;y[a+64>>2]=0;y[a+52>>2]=f;b=y[c+20>>2];y[a+68>>2]=h;y[a+72>>2]=b;y[a+76>>2]=y[a+24>>2];if((f|0)==4){y[a+24>>2]=0}md(d);Y=d+32|0;return f}a=vb(4);y[a>>2]=4876;P(a|0,4888,11);o()}function ie(a,b,c,d,e,f,g,h,i,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;j=j|0;var k=0,l=0;k=Y-16|0;Y=k;l=i?i:c;a:{if((l|0)<=0){break a}j=j?j:d;if((j|0)<=0|(j|l)>>>0>65535){break a}i=na(92);y[i+44>>2]=0;y[i+36>>2]=-1;y[i+40>>2]=0;w[i+32|0]=0;y[i+20>>2]=1;y[i+24>>2]=1;y[i+16>>2]=j;y[i+12>>2]=l;y[i+8>>2]=j;y[i+4>>2]=l;y[i+60>>2]=a;y[i+64>>2]=b;w[i+58|0]=(h|0)!=0;w[i+57|0]=(f|0)!=0;w[i+56|0]=(g|0)!=0;y[i+52>>2]=0;y[i+48>>2]=e;y[i>>2]=1728;a=(l>>>3|0)+3|0;a=((l<<29)-536870912>>>0<3758096384?a+1|0:a)&16380;y[i+28>>2]=a;Gb(i+68|0,a);Gb(i+80|0,y[i+28>>2]);e=c+7>>>3|0;W(E(e,d)|0);c=0;while(1){b:{if((c|0)==(d|0)){break b}c:{j=y[i+36>>2];a=c+1|0;if((j|0)==(a|0)){b=y[i+44>>2];j=y[i+40>>2];break c}d:{if(!((j|0)>=0&(c|0)>=(j|0))){j=0;if(!(_[y[y[i>>2]+12>>2]](i)|0)){break b}b=0;break d}b=1}while(1){if(!b){y[i+36>>2]=j;b=1;continue}e:{if((c|0)>(j|0)){_[y[y[i>>2]+16>>2]](k+8|0,i);j=y[i+36>>2]+1|0;break e}_[y[y[i>>2]+16>>2]](k+8|0,i);b=y[k+12>>2];j=y[k+8>>2];y[i+40>>2]=j;y[i+44>>2]=b;y[i+36>>2]=y[i+36>>2]+1;break c}b=0;continue}}if(!b){break b}V(j|0,e|0,E(c,e)|0);c=a;continue}break}_[y[y[i>>2]+4>>2]](i)}Y=k+16|0}function db(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,q=0,r=0,s=0,t=0,u=0;d=Y-32|0;Y=d;h=ua(a+4|0,a+8|0);y[d+8>>2]=h;i=y[h+12>>2];f=y[h+16>>2];g=f;a:{if(!f){oa(b,0);y[a+52>>2]=-1;break a}b:{f=y[h+28>>2];if((f|0)<0|!f){break b}j=y[c+12>>2]+(y[c+8>>2]<<3)|0;l=g;k=y[a+8>>2];q=y[a+4>>2];r=y[c>>2];s=y[c+4>>2];y[d+20>>2]=0;y[d+12>>2]=0;y[d+16>>2]=0;g=d+12|0;y[d+24>>2]=g;Eb(g,f);e=y[d+16>>2];g=f+e|0;while(1){if((e|0)!=(g|0)){w[e|0]=255;e=e+1|0;continue}break}w[d+28|0]=1;y[d+16>>2]=g;gc(d+24|0);m=y[d+12>>2];g=y[d+16>>2];y[d+24>>2]=j;t=(k|0)>0?k:0;e=0;g=g-m|0;k=e|m;while(1){if((n|0)!=(t|0)){j=i;e=f;if(l>>>0>>0){break b}while(1){if((e|0)>0){w[j|0]=255;e=e-1|0;j=j+1|0;continue}break}Zb(r,s,d+24|0,i|u,f,k,g,q);if(f>>>0>g>>>0){break b}if(f){p(m,i,f)}n=n+1|0;l=l-f|0;i=f+i|0;continue}break}i=y[d+24>>2];va(d+12|0);y[c+12>>2]=i&7;y[c+8>>2]=i>>>3;e=y[h+12>>2];c=e+y[h+16>>2]|0;while(1)if((c|0)==(e|0)){y[a+64>>2]=0;y[a+52>>2]=4;c=y[h+20>>2];y[a+68>>2]=0;y[a+72>>2]=c;y[a+76>>2]=y[h+24>>2];y[d+8>>2]=0;oa(b,h);break a}else{w[e|0]=z[e|0]^-1;e=e+1|0;continue}}o()}la(d+8|0);Y=d+32|0}function Ld(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0;f=Y-16|0;Y=f;y[a+44>>2]=0;e=y[a+12>>2];b=y[a+8>>2];while(1){if((b|0)==(e|0)){a:{y[f+4>>2]=0;d=c+1|0;fc(a+60|0,d,f+4|0);e=y[a+12>>2];b=y[a+8>>2];while(1)if((b|0)==(e|0)){y[y[a+60>>2]>>2]=0;y[f+4>>2]=0;b=f+4|0;fc(a+48|0,d,b);y[f+4>>2]=0;fc(a+72|0,d,b);c=0;e=0;b=1;while(1){j=B[a+44>>2]>>0;if(!j){g=b<<2;h=g+y[a+60>>2]|0;i=y[h-4>>2];c=i+c|0;if((c|0)<0|c>>>0>>0){break a}c=c<<1;y[g+y[a+48>>2]>>2]=c;y[g+y[a+72>>2]>>2]=e;b=b+1|0;e=y[h>>2]+e|0;continue}break}Rb(a+84|0,e);b=0;y[f+12>>2]=0;y[f+4>>2]=0;y[f+8>>2]=0;e=0;if(d){e=f+4|0;ed(e,d);qd(e,d);e=y[f+4>>2]}g=y[a+8>>2];h=y[a+12>>2]-g>>3;while(1){if((b|0)==(h|0)){va(f+4|0);break a}c=g+(b<<3)|0;d=y[c>>2];if(d){i=c;d=d<<2;c=d+e|0;y[i+4>>2]=y[c>>2]+y[d+y[a+48>>2]>>2];y[(y[a+84>>2]+(y[d+y[a+72>>2]>>2]<<2)|0)+(y[c>>2]<<2)>>2]=b;y[c>>2]=y[c>>2]+1}b=b+1|0;continue}}else{c=y[a+60>>2]+(y[b>>2]<<2)|0;y[c>>2]=y[c>>2]+1;b=b+8|0;continue}}}else{d=y[b>>2];c=c>>>0>>0?d:c;y[a+44>>2]=c;b=b+8|0;continue}break}Y=f+16|0;return j}function Md(a){var b=0,c=0,d=0,e=0,f=0,g=0,h=0;b=y[1420];c=a+7&-8;a=c+7|0;d=a>>>0<7?1:d;e=a&-8;a=e+b|0;a:{b:{if(!(a>>>0>>0?d+1|0:d)){if(a>>>0<=$()<<16>>>0){break b}if(R(a|0)|0){break b}}y[1428]=48;b=-1;break a}y[1420]=a}d=b;if((d|0)!=-1){a=d+c|0;y[a-4>>2]=16;f=a-16|0;y[f>>2]=16;b=y[1688];if(b){e=y[b+8>>2]}else{e=0}c:{d:{if((e|0)==(d|0)){e=y[d-4>>2]&-2;g=d-e|0;h=y[g-4>>2];y[b+8>>2]=a;b=h&-2;a=g-b|0;if(y[(a+y[a>>2]|0)-4>>2]&1){f=y[a+4>>2];g=y[a+8>>2];y[f+8>>2]=g;y[g+4>>2]=f;b=(b+(c+e|0)|0)-16|0;y[a>>2]=b;break c}a=d-16|0;break d}y[d>>2]=16;y[d+8>>2]=a;y[d+4>>2]=b;y[d+12>>2]=16;y[1688]=d;a=d+16|0}b=f-a|0;y[a>>2]=b}y[((b&-4)+a|0)-4>>2]=b|1;c=y[a>>2]-8|0;e:{if(c>>>0<=127){b=(c>>>3|0)-1|0;break e}e=H(c);b=((c>>>29-e^4)-(e<<2)|0)+110|0;if(c>>>0<=4095){break e}b=((c>>>30-e^2)-(e<<1)|0)+71|0;b=b>>>0>=63?63:b}c=b<<4;y[a+4>>2]=c+5728;c=c+5736|0;y[a+8>>2]=y[c>>2];y[c>>2]=a;y[y[a+8>>2]+4>>2]=a;c=y[1691];a=b&31;if((b&63)>>>0>=32){b=1<>>32-a}y[1690]=e|y[1690];y[1691]=b|c}return(d|0)!=-1}function tb(a,b){var c=0,d=0,e=0;c=Y+-64|0;Y=c;d=y[a>>2];e=y[d-8>>2];d=y[d-4>>2];a:{if(y[d+4>>2]==y[b+4>>2]){a=e?0:a;break a}e=a+e|0;if((e|0)<=(a|0)){y[c+16>>2]=0;y[c+20>>2]=0;y[c+12>>2]=b;y[c+8>>2]=a;y[c+4>>2]=d;y[c+24>>2]=0;y[c+28>>2]=0;y[c+32>>2]=0;y[c+36>>2]=0;y[c+40>>2]=0;y[c+44>>2]=0;y[c+48>>2]=0;y[c+60>>2]=0;y[c+52>>2]=1;y[c+56>>2]=16777216;_[y[y[d>>2]+20>>2]](d,c+4|0,e,e,1,0);if(y[c+28>>2]){break a}}y[c+16>>2]=0;y[c+20>>2]=0;y[c+12>>2]=4984;y[c+8>>2]=a;y[c+4>>2]=b;y[c+24>>2]=0;y[c+28>>2]=0;y[c+32>>2]=0;y[c+36>>2]=0;y[c+40>>2]=0;y[c+44>>2]=0;y[c+48>>2]=0;y[c+52>>2]=0;a=0;w[c+55|0]=0;w[c+56|0]=0;w[c+57|0]=0;w[c+58|0]=0;y[c+60>>2]=0;w[c+59|0]=1;_[y[y[d>>2]+24>>2]](d,c+4|0,e,1,0);b:{switch(y[c+40>>2]){case 0:a=y[c+44>>2]==1?y[c+32>>2]==1?y[c+36>>2]==1?y[c+24>>2]:0:0:0;break a;case 1:break b;default:break a}}if(y[c+28>>2]!=1){if(y[c+44>>2]|y[c+32>>2]!=1|y[c+36>>2]!=1){break a}}a=y[c+20>>2]}Y=c- -64|0;return a}function _b(a,b){var c=0,d=0,e=0,f=0,g=0;e=a+4|0;d=e+7&-8;c=y[a>>2];if(d+b>>>0<=(c+a|0)-4>>>0){f=y[a+4>>2];g=y[a+8>>2];y[f+8>>2]=g;y[g+4>>2]=f;if((d|0)!=(e|0)){d=d-e|0;f=a-(y[a-4>>2]&-2)|0;e=d+y[f>>2]|0;y[f>>2]=e;y[(f+(e&-4)|0)-4>>2]=e;a=a+d|0;c=c-d|0;y[a>>2]=c}a:{if(b+24>>>0<=c>>>0){e=a+b|0;c=(c-b|0)-8|0;y[e+8>>2]=c;g=e+8|0;y[(g+(c&-4)|0)-4>>2]=c|1;d=y[e+8>>2]-8|0;b:{if(d>>>0<=127){c=(d>>>3|0)-1|0;break b}f=H(d);c=((d>>>29-f^4)-(f<<2)|0)+110|0;if(d>>>0<=4095){break b}c=((d>>>30-f^2)-(f<<1)|0)+71|0;c=c>>>0>=63?63:c}d=c<<4;y[e+12>>2]=d+5728;d=d+5736|0;y[e+16>>2]=y[d>>2];y[d>>2]=g;y[y[e+16>>2]+4>>2]=g;d=y[1690];f=y[1691];e=c&31;if((c&63)>>>0>=32){c=1<>>32-e}y[1690]=g|d;y[1691]=c|f;c=b+8|0;y[a>>2]=c;b=(c&-4)+a|0;break a}b=a+c|0}y[b-4>>2]=c;a=a+4|0}else{a=0}return a}function ya(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;g=Y-16|0;Y=g;k=y[b+44>>2];f=1;while(1){a:{b:{if(f>>>0>k>>>0){e=-1;break b}e=-1;h=y[a>>2];j=y[h+8>>2];d=y[h>>2];if(B[h+4>>2]<=j>>>0){break b}d=z[d+j|0]>>>7-y[h+12>>2]&1;y[g+12>>2]=d;Tb(h);if((i|0)<0){break b}i=d|i<<1;if(B[b+44>>2]>>0){break a}d=f<<2;e=y[d+y[b+48>>2]>>2];if(e>>>0>i>>>0){break a}e=i-e|0;if(e>>>0>=B[d+y[b+60>>2]>>2]){break a}d=y[(y[b+84>>2]+(y[d+y[b+72>>2]>>2]<<2)|0)+(e<<2)>>2];e=1;if(z[b+1|0]==1&(d|0)==(y[b+4>>2]-1|0)){break b}e=-1;f=y[a>>2];a=d<<2;if((Fa(f,y[a+y[b+20>>2]>>2],g+12|0)|0)==-1){break b}e=0;f=y[a+y[b+32>>2]>>2];a=y[g+12>>2];y[c>>2]=f+((d|0)==(y[b+4>>2]+(z[b+1|0]?-3:-2)|0)?0-a|0:a)}Y=g+16|0;return e}f=f+1|0;continue}}function ma(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;if(a){b=a-4|0;f=y[b>>2];c=f;d=b;e=y[a-8>>2];a=e&-2;if((a|0)!=(e|0)){d=b-a|0;c=y[d+4>>2];e=y[d+8>>2];y[c+8>>2]=e;y[e+4>>2]=c;c=a+f|0}a=b+f|0;b=y[a>>2];if((b|0)!=y[(a+b|0)-4>>2]){f=y[a+4>>2];a=y[a+8>>2];y[f+8>>2]=a;y[a+4>>2]=f;c=b+c|0}y[d>>2]=c;y[((c&-4)+d|0)-4>>2]=c|1;b=y[d>>2]-8|0;a:{if(b>>>0<=127){a=(b>>>3|0)-1|0;break a}c=H(b);a=((b>>>29-c^4)-(c<<2)|0)+110|0;if(b>>>0<=4095){break a}a=((b>>>30-c^2)-(c<<1)|0)+71|0;a=a>>>0>=63?63:a}b=a<<4;y[d+4>>2]=b+5728;b=b+5736|0;y[d+8>>2]=y[b>>2];y[b>>2]=d;y[y[d+8>>2]+4>>2]=d;b=y[1690];c=y[1691];d=a&31;if((a&63)>>>0>=32){a=1<>>32-d}y[1690]=e|b;y[1691]=a|c}}function fd(a,b){var c=0,d=0,e=0,f=0,g=0;a:{b:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>3;c:{if(f>>>0>>0){d=y[a+8>>2]-d|0;if(d>>3>>>0>=b>>>0){b=(b-f<<3)+c|0;while(1){if((b|0)!=(c|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}y[a+4>>2]=b;return}if(b>>>0>=536870912){break b}c=d>>2;d=d>>>0>=2147483640?536870911:b>>>0>>0?c:b;if(d>>>0>=536870912){break a}c=b-f<<3;g=na(d<<3);b=g+e|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){y[c>>2]=0;y[c+4>>2]=0;c=c+8|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=g+(d<<3);if(!b){break c}ma(b);return}if(b>>>0>=f>>>0){break c}y[a+4>>2]=d+(b<<3)}return}za();o()}Wa();o()}function Wb(a,b,c,d,e){var f=0,g=0,h=0;a:{b:{c:{if((d|0)<0){break c}if((c|0)<=(d|0)){break a}g=0-(e^1)|0;e=d&7;if(e){f=d>>>3|0;if(f>>>0>=b>>>0){break c}e=(z[a+f|0]^g)&255>>>e;if(e){break b}d=d+7|0}e=d>>>3|0;h=c+7>>>3|0;while(1){d=e;e=d+4|0;if(h>>>0>>0){e=g&255;while(1){if(d>>>0>=h>>>0){break a}if(b>>>0<=d>>>0){break c}f=z[a+d|0];if((f|0)!=(e|0)){a=H((f^g)<<24)|d<<3;return(a|0)>(c|0)?c:a}else{d=d+1|0;continue}}}if(b>>>0>>0|b-d>>>0<=3){break c}f=a+d|0;f=z[f|0]|z[f+1|0]<<8|(z[f+2|0]<<16|z[f+3|0]<<24);f=((Ae(f,16)^f)&-16711936)>>>8^Ae(f,8);if((f|0)==(g|0)){continue}break}a=H(f^g)+(d<<3)|0;return(a|0)>(c|0)?c:a}o()}c=H(e<<24)|d&2147483640}return c}function gb(a,b,c,d,e){var f=0,g=0,h=0;a:{d=(d|0)>0?d:0;f=(e|0)>=0?(c|0)<(e|0)?c:e:0;if((d|0)>=(f|0)){break a}b:{h=d>>>3|0;if(h>>>0>=b>>>0){break b}e=d&7;c=a+h|0;g=f-1|0;f=g>>>3|0;if((f|0)==(h|0)){a=g&7;while(1){if(a>>>0>>0){break a}w[c|0]=z[c|0]+(-128>>e);e=e+1|0;continue}}d=z[c|0];while(1)if((e|0)==8){w[c|0]=d;if(b>>>0<=f>>>0){break b}d=(g&7)+1|0;g=a+f|0;c=z[g|0];e=0;while(1)if((d|0)==(e|0)){w[g|0]=c;c=h+1|0;if(c>>>0>=f>>>0){break a}e=f+(h^-1)|0;if(e>>>0>b-c>>>0){break b}c=a+c|0;while(1){if((e|0)<=0){break a}w[c|0]=0;e=e-1|0;c=c+1|0;continue}}else{c=(-128>>e)+c|0;e=e+1|0;continue}}else{d=(-128>>e)+d|0;e=e+1|0;continue}}o()}}function zb(a,b){var c=0,d=0,e=0,f=0,g=0;a:{b:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>2;c:{if(f>>>0>>0){d=y[a+8>>2]-d|0;if(d>>2>>>0>=b>>>0){b=(b-f<<2)+c|0;while(1){if((b|0)!=(c|0)){y[c>>2]=0;c=c+4|0;continue}break}y[a+4>>2]=b;return}if(b>>>0>=1073741824){break b}c=d>>1;d=d>>>0>=2147483644?1073741823:b>>>0>>0?c:b;if(d>>>0>=1073741824){break a}c=b-f<<2;g=na(d<<2);b=g+e|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){y[c>>2]=0;c=c+4|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=g+(d<<2);if(!b){break c}ma(b);return}if(b>>>0>=f>>>0){break c}y[a+4>>2]=d+(b<<2)}return}za();o()}Wa();o()}function ka(a,b){var c=0,d=0,e=0,f=0;c=z[b+1|0];if(c>>>0<47){c=E(c,6);e=A[c+1808>>1];d=y[a+8>>2]-e|0;y[a+8>>2]=d;c=c+1808|0;f=y[a+4>>2];a:{if(d>>>0>f>>>16>>>0){if(d&32768){return z[b|0]}if(d>>>0>>0){d=z[b|0]^1;e=z[c+3|0];if(z[c+4|0]==1){w[b|0]=d&1}w[b+1|0]=e;b=d&1;break a}w[b+1|0]=z[c+2|0];b=z[b|0];break a}y[a+4>>2]=f-(d<<16);b:{if(d>>>0>>0){w[b+1|0]=z[c+2|0];b=z[b|0];break b}d=z[b|0]^1;f=z[c+3|0];if(z[c+4|0]==1){w[b|0]=d&1}w[b+1|0]=f;b=d&1}y[a+8>>2]=e}c=y[a+12>>2];while(1){if(!c){Ec(a);c=y[a+12>>2]}c=c-1|0;y[a+12>>2]=c;d=y[a+8>>2];y[a+8>>2]=d<<1;y[a+4>>2]=y[a+4>>2]<<1;if(!(d&16384)){continue}break}return b}o()}function tc(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0;e=Y-32|0;Y=e;x[a>>1]=0;t(a+4|0,0,92);y[e+28>>2]=0;y[e+20>>2]=0;y[e+24>>2]=0;a:{b:{d=c+2|0;if(d){if(d>>>0>=536870912){break b}d=d<<3;f=na(d);y[e+20>>2]=f;g=d+f|0;y[e+28>>2]=g;d=f;while(1){if((d|0)!=(g|0)){y[d>>2]=0;y[d+4>>2]=0;d=d+8|0;continue}break}y[e+24>>2]=g}i=b+c|0;d=0;while(1){if((b|0)==(i|0)){break a}j=z[b|0];h=(d<<3)+f|0;y[h+4>>2]=d;w[h+1|0]=0;w[h|0]=j;b=b+1|0;d=d+1|0;continue}}za();o()}b=(c<<3)+f|0;y[b+4>>2]=0;x[b>>1]=0;y[b+12>>2]=0;x[b+8>>1]=0;y[e+16>>2]=g-f>>3;y[e+12>>2]=f;w[e+8|0]=0;k=a,l=gd(a,e+8|0),w[k|0]=l;va(e+20|0);Y=e+32|0;return a}function _d(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;a:{if(Pa(a,y[b+8>>2],e)){if(y[b+28>>2]==1|y[b+4>>2]!=(c|0)){break a}y[b+28>>2]=d;return}if(Pa(a,y[b>>2],e)){if(!(y[b+16>>2]!=(c|0)&y[b+20>>2]!=(c|0))){if((d|0)!=1){break a}y[b+32>>2]=1;return}y[b+32>>2]=d;b:{if(y[b+44>>2]==4){break b}x[b+52>>1]=0;a=y[a+8>>2];_[y[y[a>>2]+20>>2]](a,b,c,c,1,e);if(z[b+53|0]==1){y[b+44>>2]=3;if(!z[b+52|0]){break b}break a}y[b+44>>2]=4}y[b+20>>2]=c;y[b+40>>2]=y[b+40>>2]+1;if(y[b+36>>2]!=1|y[b+24>>2]!=2){break a}w[b+54|0]=1;return}a=y[a+8>>2];_[y[y[a>>2]+24>>2]](a,b,c,d,e)}}function Yb(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0,l=0;l=((e&536870911)<<3|d>>>29)&-8;a:{b:{c:while(1){d:{if(c>>>0<=g>>>0){break d}j=z[b+g|0];if((j|0)==255){break b}h=y[f>>2];if(h>>>0>=l>>>0){break b}i=h>>>3|0;if(i>>>0>=e>>>0){break d}i=z[d+i|0];y[f>>2]=h+1;k=k<<1|(i<<(h&7)&128)>>>7;g=g+1|0;h=g+E(j,3)|0;while(1){if((g|0)>=(h|0)){continue c}if(c>>>0<=g>>>0){break d}if(z[b+g|0]==(k|0)){d=g+1|0;if(d>>>0>=c>>>0){break d}e=c;c=g+2|0;if(e>>>0<=c>>>0){break d}x[a>>1]=z[b+d|0]|z[b+c|0]<<8;b=1;break a}else{g=g+3|0;continue}}}break}o()}w[a|0]=0;b=0}w[a+2|0]=b}function Wc(a,b,c,d,e,f,g){y[a+8>>2]=0;a:{b:{if(!(b&255)){c:{switch(c|0){case 1:y[a+4>>2]=e;y[a>>2]=d;y[a+8>>2]=f-1;return;case 3:y[a+4>>2]=e;break a;case 0:y[a>>2]=d;y[a+8>>2]=f-1;y[a+4>>2]=(e-g|0)+1;return;case 2:break c;default:break b}}y[a+4>>2]=(e-g|0)+1;break a}d:{switch(c|0){case 1:y[a+4>>2]=d;y[a>>2]=e;y[a+8>>2]=g-1;return;case 3:y[a+4>>2]=d;y[a+8>>2]=g-1;y[a>>2]=(e-f|0)+1;return;case 0:y[a>>2]=e;y[a+4>>2]=(d-g|0)+1;return;case 2:break d;default:break b}}y[a+4>>2]=(d-g|0)+1;y[a>>2]=(e-f|0)+1}return}y[a>>2]=(d-f|0)+1}function fb(a,b){var c=0,d=0,e=0,f=0,g=0,h=0;a:{c=y[a+4>>2];d=y[a>>2];e=c-d|0;f=e>>1;if(f>>>0>>0){g=y[a+8>>2];if(g-d>>1>>>0>=b>>>0){b=(b-f<<1)+c|0;while(1){if((b|0)!=(c|0)){w[c|0]=0;w[c+1|0]=0;c=c+2|0;continue}break}y[a+4>>2]=b;return}d=Cc(d,g,b);if(d){h=Bc(d)}c=b-f<<1;b=e+h|0;f=c+b|0;c=b;while(1){if((c|0)!=(f|0)){w[c|0]=0;w[c+1|0]=0;c=c+2|0;continue}break}e=b;b=y[a>>2];c=y[a+4>>2]-b|0;e=e-c|0;if(c){p(e,b,c)}y[a+4>>2]=f;y[a>>2]=e;y[a+8>>2]=(d<<1)+h;if(!b){break a}ma(b);return}if(b>>>0>=f>>>0){break a}y[a+4>>2]=d+(b<<1)}}function sd(a,b,c){var d=0,e=0;d=Y-32|0;Y=d;y[a+24>>2]=0;y[a+28>>2]=0;y[a+16>>2]=0;y[a+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{b:{if(b-2147483617>>>0<2147483680|(c|0)<=0){break b}e=b+31&2147483616;if(2147483616/(e>>>0)>>>0>>0){break b}y[a+24>>2]=c;y[a+20>>2]=b;b=e>>>3|0;y[a+28>>2]=b;b=ze(c,0,b,0);if(Z){break a}b=Gb(d,b);c=y[b>>2];y[d+12>>2]=c;e=y[b+4>>2];y[d+16>>2]=e;y[d+20>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;y[d+28>>2]=e-c;y[d+24>>2]=c;c=d+12|0;ec(a,c);va(c);va(b)}Y=d+32|0;return a}o()}function Fa(a,b,c){var d=0,e=0,f=0,g=0;e=-1;g=y[a>>2];a:{f=y[a+8>>2];d=y[a+4>>2];if(f>>>0>=d>>>0){break a}b:{if((d|0)==536870912|d>>>0>536870912){break b}f=y[a+12>>2]+(f<<3)|0;if(f>>>0>(((d&536870911)<<3|g>>>29)&-8)>>>0){break a}y[c>>2]=0;g=y[a>>2];d=y[a+4>>2];if((d|0)==536870912|d>>>0>536870912){break b}d=((d&536870911)<<3|g>>>29)&-8;e=d>>>0>>0?d-f|0:b;while(1){if(!e){e=0;break a}b=y[a+8>>2];if(b>>>0>=B[a+4>>2]){break b}y[c>>2]=z[b+y[a>>2]|0]>>>7-y[a+12>>2]&1|y[c>>2]<<1;Tb(a);e=e-1|0;continue}}o()}return e}function Aa(a,b,c){var d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0;f=Y-16|0;Y=f;i=ka(b,y[a>>2]+2|0);d=i|2;while(1){a:{if((e|0)==5){e=5;break a}d=d<<1;g=ka(b,d+y[a>>2]|0);d=d|g;if(!g){break a}e=e+1|0;continue}break}g=0;e=e<<3;h=y[e+2092>>2];h=(h|0)>0?h:0;k=e+2092|0;e=0;while(1){if((e|0)!=(h|0)){d=d<<1;j=ka(b,d+y[a>>2]|0);d=j|d;d=(d|0)>255?d&255|256:d;g=g<<1|j;e=e+1|0;continue}break}w[f+8|0]=1;y[f+12>>2]=y[k+4>>2];e=0;d=0;if(z[La(f+8|0,g)|0]){a=y[f+12>>2];b=a>>31;e=i?b-(a^b)|0:a;d=!i|(e|0)!=0}y[c>>2]=e;Y=f+16|0;return d}function zc(a,b,c,d,e){var f=0,g=0;y[a>>2]=0;f=na(24);y[f+8>>2]=0;y[f+12>>2]=0;g=b;b=(c|0)==268435456|c>>>0<268435456;y[f>>2]=b?g:0;y[f+4>>2]=b?c:0;y[f+16>>2]=0;y[f+20>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0;y[a+4>>2]=f;y[a+16>>2]=0;y[a+20>>2]=0;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;c=na(64);y[a+36>>2]=c;b=c- -64|0;y[a+44>>2]=b;f=0;while(1){if((f|0)!=64){y[c+f>>2]=0;f=f+4|0;continue}break}y[a+52>>2]=10;w[a+49|0]=0;w[a+50|0]=0;w[a+48|0]=e;y[a+40>>2]=b;t(a+56|0,0,49);w[a+112|0]=0;y[a+108>>2]=d;return a}function Ob(a,b){var c=0,d=0,e=0,f=0;e=Y-16|0;Y=e;c=4;a:{b:{if(y[a+4>>2]-1>>>0>65534|y[a+8>>2]-1>>>0>=65535){break b}y[a+52>>2]=2;f=y[b>>2];d=y[f>>2];if(!d){c=ua(a+4|0,a+8|0);y[e+12>>2]=0;oa(f,c);la(e+12|0);d=y[f>>2]}c=y[d+16>>2];if(!c){oa(f,0);c=-1;break b}d=y[d+12>>2];while(1){if((c|0)>0){w[d|0]=0;c=c-1|0;d=d+1|0;continue}break}y[a+60>>2]=0;x[a+56>>1]=1;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;y[a+36>>2]=0;y[a+40>>2]=0;y[a+44>>2]=0;y[a+48>>2]=0;c=nd(a,b);break a}y[a+52>>2]=c}Y=e+16|0;return c}function _c(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;a:{c=y[a+4>>2];d=y[a+8>>2];b:{if(c>>>0>>0){e=y[b>>2];y[b>>2]=0;y[c>>2]=e;b=c+4|0;break b}f=y[a>>2];e=c-f|0;h=e>>2;c=h+1|0;if(c>>>0>=1073741824){break a}d=d-f|0;g=d>>1;g=d>>>0>=2147483644?1073741823:c>>>0>>0?g:c;c:{if(!g){c=e;d=0;break c}d=Fb(g);f=y[a>>2];c=y[a+4>>2]-f|0;h=c>>2}i=y[b>>2];y[b>>2]=0;b=d+e|0;y[b>>2]=i;e=b-(h<<2)|0;if(c){p(e,f,c)}y[a+8>>2]=(g<<2)+d;b=b+4|0;y[a+4>>2]=b;y[a>>2]=e;if(!f){break b}ma(f)}y[a+4>>2]=b;return}za();o()}function rb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Vb(c+80|0);ta(c+76|0);ta(c+72|0);Ha(c+60|0);b=y[c+36>>2];if(b){a=y[c+40>>2];while(1){if((a|0)!=(b|0)){a=pa(a-4|0);continue}break}y[c+40>>2]=b;ma(y[c+36>>2])}la(c+32|0);b=y[c+20>>2];if(b){a=y[c+24>>2];while(1){if((a|0)!=(b|0)){a=Ub(a-4|0);continue}break}y[c+24>>2]=b;ma(y[c+20>>2])}b=y[c+8>>2];if(b){a=y[c+12>>2];while(1){if((a|0)!=(b|0)){a=Vb(a-4|0);continue}break}y[c+12>>2]=b;ma(y[c+8>>2])}a=y[c+4>>2];y[c+4>>2]=0;if(a){ma(a)}ma(sb(c))}}function xd(a,b){var c=0,d=0,e=0,f=0,g=0,h=0,i=0;d=Y-16|0;Y=d;a:{e=y[a+4>>2];b:{if(e>>>0>b>>>0){break b}c=e;if(y[a+8>>2]<<5>>>0>>0){if((b|0)<0){break a}f=(b-1>>>5|0)+1|0;g=na(f<<2);h=y[a>>2];c=y[a+4>>2];i=c?(c-1>>>3&536870908)+4|0:0;if(i){p(g,h,i)}y[a>>2]=g;y[d+4>>2]=h;y[a+4>>2]=c;y[d+8>>2]=c;c=y[a+8>>2];y[a+8>>2]=f;y[d+12>>2]=c;lc(d+4|0);c=y[a+4>>2]}if((b|0)==(e|0)){break b}$c(y[a>>2]+(c>>>3&536870908)|0,c&31,b-e|0)}y[a+4>>2]=b;Y=d+16|0;return}za();o()}function Hc(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0;h=Y-16|0;Y=h;l=((b&536870911)<<3|a>>>29)&-8;j=1;while(1){a:{if(B[c>>2]>=l>>>0){break a}k=j&1;n=k?325:326;o=k?1056:1392;i=0;while(1){Yb(h+12|0,o,n,a,b,c);if(!z[h+14|0]){while(1){if(B[c>>2]>=l>>>0){break a}if(!Ia(a,b,c)){continue}break}break a}g=A[h+12>>1];i=g+i|0;if((i|0)>(f|0)){break a}if(g>>>0>=64){continue}break}g=i+m|0;if(!k){gb(d,e,f,m,g)}j=j^1;m=g;if((f|0)>(g|0)){continue}}break}Y=h+16|0}function te(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;e=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[e+16>>2];y[a+44>>2]=y[e+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){c=c-1|0;f=c>>>3|0;g=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(B[a+8>>2]<=c>>>0){b=4}else{if(!jc(a,b,2204,f,g)){return-1}Db(a,e);d=y[b+16>>2];c=y[a+24>>2];if(!d|(c>>>0)%50){break a}d=_[y[y[d>>2]+8>>2]](d)|0;c=y[a+24>>2];if(!d){break a}y[a+24>>2]=c+1;b=3}y[a+52>>2]=b;return b|0}c=c+1|0;y[a+24>>2]=c;continue}}o()}function xe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;d=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[d+16>>2];y[a+44>>2]=y[d+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){e=y[a+8>>2]&2147483647;c=c-1|0;f=c>>>3|0;g=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(c>>>0>=e>>>0){b=4}else{if(!jc(a,b,2140,f,g)){return-1}Db(a,d);c=y[b+16>>2];if(!c){break a}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break a}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}c=y[a+24>>2]+1|0;y[a+24>>2]=c;continue}}o()}function Xd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(y[b+4>>2]==5252){b=y[a+12>>2];a:{if(b){a=5240;if(tb(b,5128)){break a}}a=5248}y[c>>2]=a;return 1}e=y[a+8>>2];b:{c:{if(!(e&24)){d=tb(b,5080);if(!d){break c}c=1;if(!Pa(a,b,(y[d+8>>2]&24)!=0)){break c}break b}c=1;if(Pa(a,b,1)){break b}}c=0;b=tb(b,5180);if(!b){break b}d=y[b+8>>2];if(d&(e^-1)&7|(d^-1)&e&96|y[y[a+12>>2]+4>>2]!=y[y[b+12>>2]+4>>2]){break b}c=y[y[a+16>>2]+4>>2]==y[y[b+16>>2]+4>>2]}return c|0}function za(){var a=0,b=0,c=0,d=0;c=vb(8);y[c>>2]=5476;a=4523;b=0;a:{if(!z[4523]){break a}b:{c:{while(1){a=a+1|0;if(!(a&3)){break c}if(z[a|0]){continue}break}break b}while(1){b=a;a=a+4|0;d=y[b>>2];if(((16843008-d|d)&-2139062144)==-2139062144){continue}break}while(1){a=b;b=a+1|0;if(z[a|0]){continue}break}}b=a-4523|0}a=na(b+13|0);y[a+8>>2]=0;y[a+4>>2]=b;y[a>>2]=b;a=a+12|0;b=b+1|0;if(b){p(a,4523,b)}y[c>>2]=5524;y[c+4>>2]=a;P(c|0,5536,1);o()}function ve(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;d=y[y[b>>2]>>2];if(!y[a+48>>2]){c=y[d+16>>2];y[a+44>>2]=y[d+12>>2];y[a+48>>2]=c}c=y[a+4>>2];if(c){c=c-1|0;e=c>>>3|0;f=(c&7)+1|0;c=y[a+24>>2];while(1){a:{if(B[a+8>>2]<=c>>>0){b=4}else{if(!jc(a,b,2172,e,f)){return-1}Db(a,d);c=y[b+16>>2];if(!c){break a}if(!(_[y[y[c>>2]+8>>2]](c)|0)){break a}y[a+24>>2]=y[a+24>>2]+1;b=3}y[a+52>>2]=b;return b|0}c=y[a+24>>2]+1|0;y[a+24>>2]=c;continue}}o()}function Ec(a){var b=0,c=0,d=0,e=0,f=0;e=255;b=y[a+16>>2];a:{if(z[a+1|0]==255){f=8;d=y[b+8>>2];c=d+1|0;if(c>>>0>=B[b+4>>2]){break a}c=z[c+y[b>>2]|0];if(c>>>0>143){break a}Dc(b);w[a+1|0]=c;y[a+4>>2]=(y[a+4>>2]-(c<<9)|0)+65024;b=y[a+16>>2];d=y[b+8>>2];f=7;break a}Dc(b);f=8;b=y[a+16>>2];d=y[b+8>>2];c=y[b>>2];e=B[b+4>>2]>d>>>0?z[c+d|0]:e;w[a+1|0]=e;y[a+4>>2]=(y[a+4>>2]-(e<<8)|0)+65280}y[a+12>>2]=f;if(B[b+4>>2]<=d>>>0){w[a|0]=1}}function Jb(a,b){var c=0,d=0,e=0,f=0;a:{b:{if((a|0)!=(b|0)){f=y[b+4>>2];d=y[b>>2];b=f-d|0;c=y[a>>2];if(b>>>0<=y[a+8>>2]-c>>>0){e=y[a+4>>2]-c|0;if(e>>>0>>0){if(e){p(c,d,e)}b=y[a+4>>2];d=d+e|0;c=f-d|0;if(c){p(b,d,c)}break a}if(b){p(c,d,b)}break a}kc(a);e=Cc(y[a>>2],y[a+8>>2],b>>1);if((e|0)<0){break b}c=Bc(e);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=c+(e<<1);if(b){p(c,d,b)}y[a+4>>2]=b+c}return a}za();o()}y[a+4>>2]=b+c;return a}function Bb(a,b,c,d){var e=0,f=0,g=0,h=0,i=0;f=b-1|0;g=y[d>>2];e=y[d+4>>2];a:{if((ia(a,f,g,e)|0)!=(c|0)){break a}if((ia(a,b,g,e)|0)!=(c|0)){break a}h=b+1|0;if((ia(a,h,g,e)|0)!=(c|0)){break a}g=y[d+8>>2];e=y[d+12>>2];if((ia(a,f,g,e)|0)!=(c|0)){break a}if((ia(a,h,g,e)|0)!=(c|0)){break a}e=f;f=y[d+16>>2];d=y[d+20>>2];if((ia(a,e,f,d)|0)!=(c|0)){break a}if((ia(a,b,f,d)|0)!=(c|0)){break a}i=(ia(a,h,f,d)|0)==(c|0)}return i}function Rb(a,b){var c=0,d=0,e=0,f=0,g=0;a:{c=y[a>>2];d=y[a+4>>2]-c|0;e=d>>2;if(e>>>0>>0){f=y[a+8>>2];if(f-c>>2>>>0>=b>>>0){qd(a,b-e|0);return}f=pd(c,f,b);g=Fb(f);c=d+g|0;e=c+(b-e<<2)|0;b=c;while(1){if((b|0)!=(e|0)){y[b>>2]=0;b=b+4|0;continue}break}d=c;b=y[a>>2];c=y[a+4>>2]-b|0;d=d-c|0;if(c){p(d,b,c)}y[a+4>>2]=e;y[a>>2]=d;y[a+8>>2]=(f<<2)+g;if(!b){break a}ma(b);return}if(b>>>0>=e>>>0){break a}y[a+4>>2]=c+(b<<2)}}function hd(a,b,c,d,e,f,g,h,i,j){var k=0,l=0;Ea(y[b+20>>2],d,i,j,e);i=y[a+12>>2];j=y[c>>2];b=ia(y[b+20>>2],d+2|0,g,h);y[c+4>>2]=e;y[c>>2]=b|j<<1&2;e=y[c+8>>2];a=y[a+20>>2];b=(d-i|0)+2|0;k=c,l=ia(y[a+20>>2],b,y[f>>2],y[f+4>>2])|e<<1&2,y[k+8>>2]=l;d=y[c+12>>2];k=c,l=ia(y[a+20>>2],b,y[f+8>>2],y[f+12>>2])|d<<1&6,y[k+12>>2]=l;d=y[c+16>>2];k=c,l=ia(y[a+20>>2],b,y[f+16>>2],y[f+20>>2])|d<<1&6,y[k+16>>2]=l}function mc(a){var b=0,c=0,d=0,e=0;e=Y-16|0;Y=e;b=na(32);y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;c=y[a+4>>2];d=y[a>>2];y[e+8>>2]=b;if((c|0)!=(d|0)){Eb(b,c-d|0);Xb(b,d,c)}w[e+12|0]=1;gc(e+8|0);c=y[b>>2];d=y[b+4>>2];a:{if((c|0)!=(d|0)){y[b+12>>2]=c;y[b+16>>2]=d-c;break a}c=y[a+16>>2];y[b+12>>2]=y[a+12>>2];y[b+16>>2]=c}y[b+20>>2]=y[a+20>>2];y[b+24>>2]=y[a+24>>2];y[b+28>>2]=y[a+28>>2];Y=e+16|0;return b}function Gc(a,b,c,d,e,f){var g=0,h=0,i=0,j=0,k=0;i=y[a+4>>2];a:{b:{c:{if((c|0)<0){h=y[a>>2];a=1;break c}g=c>>>3|0;if(g>>>0>=i>>>0){break b}h=y[a>>2];a=(z[g+h|0]<<(c&7)&128)>>>7|0}g=a^1;c=Wb(h,i,b,c+1|0,g);y[e>>2]=c;if((b|0)<=(c|0)){break a}d:{if((a|0)==(d|0)){g=a;break d}c=Wb(h,i,b,c+1|0,a);y[e>>2]=c}if((b|0)<=(c|0)){break a}j=f,k=Wb(h,i,b,c+1|0,g),y[j>>2]=k;return}o()}y[f>>2]=b;y[e>>2]=b}function Id(a,b){var c=0;c=y[b+4>>2];y[a>>2]=y[b>>2];y[a+4>>2]=c;c=y[a+8>>2];if(c){y[a+12>>2]=c;ma(c);y[a+16>>2]=0;y[a+8>>2]=0;y[a+12>>2]=0}y[a+8>>2]=y[b+8>>2];y[a+12>>2]=y[b+12>>2];y[a+16>>2]=y[b+16>>2];y[b+16>>2]=0;y[b+8>>2]=0;y[b+12>>2]=0;$a(a+20|0,b+20|0);$a(a+32|0,b+32|0);y[a+44>>2]=y[b+44>>2];_a(a+48|0,b+48|0);_a(a+60|0,b+60|0);_a(a+72|0,b+72|0);_a(a+84|0,b+84|0)}function Wd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;a:{if(Pa(a,y[b+8>>2],e)){if(y[b+28>>2]==1|y[b+4>>2]!=(c|0)){break a}y[b+28>>2]=d;return}if(!Pa(a,y[b>>2],e)){break a}if(!(y[b+16>>2]!=(c|0)&y[b+20>>2]!=(c|0))){if((d|0)!=1){break a}y[b+32>>2]=1;return}y[b+20>>2]=c;y[b+32>>2]=d;y[b+40>>2]=y[b+40>>2]+1;if(!(y[b+36>>2]!=1|y[b+24>>2]!=2)){w[b+54|0]=1}y[b+44>>2]=4}}function ae(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=Y+-64|0;Y=d;a:{e=1;b:{if(y[a+4>>2]==y[b+4>>2]){break b}b=tb(b,5032);e=0;if(!b){break b}e=y[c>>2];if(!e){break a}t(d+8|0,0,56);w[d+59|0]=1;y[d+16>>2]=-1;y[d+12>>2]=a;y[d+4>>2]=b;y[d+52>>2]=1;_[y[y[b>>2]+28>>2]](b,d+4|0,e,1);a=y[d+28>>2];if((a|0)==1){y[c>>2]=y[d+20>>2]}e=(a|0)==1}Y=d- -64|0;return e|0}Sc();o()}function wd(a,b,c,d,e){var f=0,g=0,h=0,i=0;a:{if(!d){break a}g=c;h=b;if(e&1){if(f|g){f=32-g|0;e=d>>>0>>0?d:f;y[h>>2]=y[h>>2]|-1<>>f-e;h=h+4|0;e=d-e|0}else{e=d}f=e;i=f>>>5|0;e=i;g=h;while(1){if(e){y[g>>2]=-1;e=e-1|0;g=g+4|0;continue}break}f=f&31;if(!f){break a}e=(i<<2)+h|0;y[e>>2]=y[e>>2]|-1>>>32-f;break a}$c(h,g,d)}y[a>>2]=b;y[a+4>>2]=c;oc(a,d)}function ad(a,b,c,d){var e=0,f=0;e=Y-16|0;Y=e;dc(e+8|0,b,c);a:{b:{if(!z[e+12|0]){y[a>>2]=0;y[a+4>>2]=0;break b}if(!((d|0)>0&(y[b+24>>2]-c|0)>=(d|0))){y[a>>2]=0;y[a+4>>2]=0;break b}f=y[b+16>>2];c=y[e+8>>2];if(f>>>0>>0){break a}d=E(y[b+28>>2],d);if(d>>>0>f-c>>>0){break a}b=c+y[b+12>>2]|0;if(b&3){break a}y[a>>2]=b;y[a+4>>2]=d>>>2}Y=e+16|0;return}o()}function Rc(a,b,c,d){w[a+53|0]=1;a:{if(y[a+4>>2]!=(c|0)){break a}w[a+52|0]=1;c=y[a+16>>2];b:{if(!c){y[a+36>>2]=1;y[a+24>>2]=d;y[a+16>>2]=b;if((d|0)!=1){break a}if(y[a+48>>2]==1){break b}break a}if((b|0)==(c|0)){c=y[a+24>>2];if((c|0)==2){y[a+24>>2]=d;c=d}if(y[a+48>>2]!=1){break a}if((c|0)==1){break b}break a}y[a+36>>2]=y[a+36>>2]+1}w[a+54|0]=1}}function Fd(a,b){var c=0,d=0,e=0;d=a,e=Da(),y[d>>2]=e;d=a,e=Da(),y[d+4>>2]=e;d=a,e=Da(),y[d+8>>2]=e;d=a,e=Da(),y[d+12>>2]=e;d=a,e=Da(),y[d+16>>2]=e;d=a,e=Da(),y[d+20>>2]=e;d=a,e=Da(),y[d+24>>2]=e;d=a,e=Da(),y[d+28>>2]=e;d=a,e=Da(),y[d+32>>2]=e;c=na(16);w[c+12|0]=b;y[c+8>>2]=0;y[c>>2]=0;y[c+4>>2]=0;fb(c,1<>2]=c;return a}function ld(a,b){var c=0,d=0,e=0;c=Y-32|0;Y=c;a:{d=y[a+40>>2];if(d){e=y[a+48>>2];y[c+24>>2]=y[a+44>>2];y[c+28>>2]=e;b=y[b+28>>2];if(b>>>0>d>>>0){break a}a=y[a+36>>2];y[c+20>>2]=b;y[c+16>>2]=a;a=y[c+28>>2];y[c+8>>2]=y[c+24>>2];y[c+12>>2]=a;a=y[c+20>>2];y[c>>2]=y[c+16>>2];y[c+4>>2]=a;Ca(c+8|0,c)}Y=c+32|0;return}o()}function gd(a,b){var c=0,d=0,e=0;w[a+1|0]=z[b|0];c=y[b+8>>2];y[a+4>>2]=c;b=y[b+4>>2];fd(a+8|0,c);zb(a+20|0,c);zb(a+32|0,c);e=(c<<3)+b|0;c=0;while(1){if((b|0)==(e|0)){a=Ld(a)}else{y[y[a+8>>2]+(c<<3)>>2]=z[b|0];d=c<<2;y[d+y[a+20>>2]>>2]=z[b+1|0];y[y[a+32>>2]+d>>2]=y[b+4>>2];b=b+8|0;c=c+1|0;continue}break}return a}function qa(a,b){var c=0,d=0,e=0,f=0;a:{c=y[a+4>>2];d=y[a+8>>2];if(c>>>0>d+3>>>0){if(d>>>0>=c>>>0|d+1>>>0>=c>>>0|d+2>>>0>=c>>>0){break a}c=b;b=y[a>>2]+d|0;b=z[b|0]|z[b+1|0]<<8|(z[b+2|0]<<16|z[b+3|0]<<24);e=c,f=((Ae(b,16)^b)&-16711936)>>>8^Ae(b,8),y[e>>2]=f;y[a+8>>2]=y[a+8>>2]+4;a=0}else{a=-1}return a}o()}function ec(a,b){var c=0,d=0;if((a|0)!=(b|0)){kc(a);y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0;c=y[a>>2];d=y[a+4>>2];a:{if((c|0)!=(d|0)){y[a+12>>2]=c;y[a+16>>2]=d-c;break a}c=y[b+16>>2];y[a+12>>2]=y[b+12>>2];y[a+16>>2]=c}y[b+12>>2]=0;y[b+16>>2]=0}}function fc(a,b,c){var d=0,e=0,f=0,g=0;d=y[a>>2];if(y[a+8>>2]-d>>2>>>0>=b>>>0){e=y[a+4>>2]-d>>2;f=b>>>0>e>>>0?e:b;g=d;while(1){if(f){y[g>>2]=y[c>>2];f=f-1|0;g=g+4|0;continue}break}if(b>>>0>e>>>0){dd(a,b-e|0,c);return}y[a+4>>2]=(b<<2)+d;return}kc(a);ed(a,pd(y[a>>2],y[a+8>>2],b));dd(a,b,c)}function Pa(a,b,c){var d=0;if(!c){return y[a+4>>2]==y[b+4>>2]}if((a|0)==(b|0)){return 1}c=y[a+4>>2];a=z[c|0];b=y[b+4>>2];d=z[b|0];a:{if(!a|(d|0)!=(a|0)){break a}while(1){d=z[b+1|0];a=z[c+1|0];if(!a){break a}b=b+1|0;c=c+1|0;if((a|0)==(d|0)){continue}break}}return(a|0)==(d|0)}function Ic(a,b){var c=0,d=0,e=0,f=0;if((a|0)!=(b|0)){f=y[b+4>>2];c=y[b>>2];b=f-c|0;d=y[a>>2];if(b>>>0<=y[a+8>>2]-d>>>0){e=y[a+4>>2]-d|0;if(b>>>0>e>>>0){if(e){p(d,c,e)}Xb(a,c+e|0,f);return}if(b){p(d,c,b)}y[a+4>>2]=b+d;return}kc(a);Eb(a,Fc(y[a>>2],y[a+8>>2],b));Xb(a,c,f)}}function Ca(a,b){var c=0,d=0,e=0;a:{c=y[a+4>>2];b:{if(!c){break b}if(!y[b+4>>2]){a=y[a>>2];while(1){if((c|0)<=0){break b}w[a|0]=0;c=c-1|0;a=a+1|0;continue}}e=y[b>>2];d=y[b+4>>2];c=y[a>>2];b=0;if(1&B[a+4>>2]>>0){break a}if(!(b|d)|!d){break b}p(c,e,d)}return}o()}function Dd(a){var b=0,c=0,d=0,e=0,f=0,g=0;d=Y-16|0;Y=d;e=nc();f=e+24|0;g=y[a+28>>2];c=y[a+24>>2];while(1){if((c|0)!=(g|0)){b=y[c>>2];if(b){b=mc(b)}else{b=0}y[d+12>>2]=b;b=d+12|0;_c(f,b);c=c+4|0;la(b);continue}break}c=Jb(e,a);Jb(c+12|0,a+12|0);Y=d+16|0;return c}function ja(a,b,c){var d=0,e=0,f=0;d=Y-16|0;Y=d;dc(d+8|0,b,c);a:{b:{if(!z[d+12|0]){y[a>>2]=0;y[a+4>>2]=0;break b}e=y[b+16>>2];c=y[d+8>>2];if(e>>>0>>0){break a}f=y[b+28>>2];if(f>>>0>e-c>>>0){break a}b=y[b+12>>2];y[a+4>>2]=f;y[a>>2]=b+c}Y=d+16|0;return}o()}function Qc(a,b,c){var d=0;d=y[a+36>>2];if(!d){y[a+24>>2]=c;y[a+16>>2]=b;y[a+36>>2]=1;y[a+20>>2]=y[a+56>>2];return}a:{if(!(y[a+20>>2]!=y[a+56>>2]|y[a+16>>2]!=(b|0))){if(y[a+24>>2]!=2){break a}y[a+24>>2]=c;return}w[a+54|0]=1;y[a+24>>2]=2;y[a+36>>2]=d+1}}function Na(a){var b=0,c=0,d=0,e=0;b=na(20);y[b+16>>2]=a;w[b|0]=0;c=255;d=y[a>>2];e=y[a+4>>2];a=y[a+8>>2];if(e>>>0>a>>>0){c=z[a+d|0]}w[b+1|0]=c;y[b+4>>2]=((c^-1)&255)<<16;Ec(b);y[b+8>>2]=32768;y[b+4>>2]=y[b+4>>2]<<7;y[b+12>>2]=y[b+12>>2]-7;return b}function $c(a,b,c){var d=0,e=0;if(b){d=32-b|0;e=c>>>0>>0?c:d;y[a>>2]=y[a>>2]&(-1<>>d-e^-1);c=c-e|0;a=a+4|0}e=c>>>5|0;b=e;d=a;while(1){if(b){y[d>>2]=0;b=b-1|0;d=d+4|0;continue}break}b=c&31;if(b){a=(e<<2)+a|0;y[a>>2]=y[a>>2]&(-1>>>32-b^-1)}}function ob(a,b){var c=0;c=1;a:{if(qa(y[a+4>>2],b)){break a}if(qa(y[a+4>>2],b+4|0)){break a}if(qa(y[a+4>>2],b+8|0)){break a}if(qa(y[a+4>>2],b+12|0)){break a}if(wa(y[a+4>>2],b+16|0)|(y[b>>2]>4096|y[b+4>>2]>4096)&z[a+112|0]==1){break a}c=0}return c}function jd(a,b,c,d){var e=0;e=Y-32|0;Y=e;ja(e+24|0,c,(b^-1)+d|0);y[a>>2]=y[e+24>>2];y[a+4>>2]=y[e+28>>2];b=d-b|0;ja(e+16|0,c,b);y[a+8>>2]=y[e+16>>2];y[a+12>>2]=y[e+20>>2];ja(e+8|0,c,b+1|0);y[a+16>>2]=y[e+8>>2];y[a+20>>2]=y[e+12>>2];Y=e+32|0}function Cd(a,b,c){var d=0,e=0;d=na(32);e=y[b+12>>2];y[d+16>>2]=y[b+8>>2];y[d+20>>2]=e;e=y[b+4>>2];y[d+8>>2]=y[b>>2];y[d+12>>2]=e;b=y[c>>2];y[c>>2]=0;y[d>>2]=a;y[d+24>>2]=b;b=y[a+4>>2];y[d+4>>2]=b;y[b>>2]=d;y[a+4>>2]=d;y[a+8>>2]=y[a+8>>2]+1}function Ga(a,b,c){var d=0,e=0,f=0,g=0;d=y[b+8>>2];g=(d|0)>0?d:0;d=0;while(1){a:{if((d|0)==(g|0)){e=0;break a}e=Ka(a,y[y[b+12>>2]+(d<<2)>>2]);if(!(!e|(z[e+4|0]&63)!=53)){if((c|0)==(f|0)){break a}f=f+1|0}d=d+1|0;continue}break}return e}function Kb(a,b){var c=0;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{if(b){if(b>>>0>=1073741824){break a}c=b<<2;b=na(c);y[a>>2]=b;c=b+c|0;y[a+8>>2]=c;while(1){if((b|0)!=(c|0)){y[b>>2]=0;b=b+4|0;continue}break}y[a+4>>2]=c}return a}za();o()}function cb(a,b){var c=0;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;a:{if(b){if(b>>>0>=1073741824){break a}c=Fb(b);y[a>>2]=c;b=(b<<2)+c|0;y[a+8>>2]=b;while(1){if((b|0)!=(c|0)){y[c>>2]=0;c=c+4|0;continue}break}y[a+4>>2]=b}return a}za();o()}function Cb(a,b,c){var d=0;d=Y-32|0;Y=d;ja(d+24|0,b,c-1|0);y[a>>2]=y[d+24>>2];y[a+4>>2]=y[d+28>>2];ja(d+16|0,b,c);y[a+8>>2]=y[d+16>>2];y[a+12>>2]=y[d+20>>2];ja(d+8|0,b,c+1|0);y[a+16>>2]=y[d+8>>2];y[a+20>>2]=y[d+12>>2];Y=d+32|0}function Mc(a,b){var c=0,d=0,e=0;c=Y-16|0;Y=c;d=z[a+4|0];if((d|0)==255){a=vb(4);y[a>>2]=4936;P(a|0,4948,2);o()}y[c+4>>2]=c+3;e=y[b+4>>2];y[c+8>>2]=y[b>>2];y[c+12>>2]=e;a=_[y[(c+8|0)+(d<<2)>>2]](c+4|0,a)|0;Y=c+16|0;return a}function lb(){var a=0;a=na(80);x[a+56>>1]=0;y[a+12>>2]=0;y[a+24>>2]=0;y[a+28>>2]=0;y[a+32>>2]=0;y[a+36>>2]=0;y[a+40>>2]=0;y[a+44>>2]=0;y[a+48>>2]=0;y[a+60>>2]=0;y[a+64>>2]=0;y[a+68>>2]=0;y[a+72>>2]=0;y[a+76>>2]=0;return a}function ze(a,b,c,d){var e=0,f=0,g=0,h=0,i=0,j=0;e=c>>>16|0;f=a>>>16|0;j=E(e,f);g=c&65535;h=a&65535;i=E(g,h);f=(i>>>16|0)+E(f,g)|0;e=(f&65535)+E(e,h)|0;Z=(E(b,c)+j|0)+E(a,d)+(f>>>16)+(e>>>16)|0;return i&65535|e<<16}function Ea(a,b,c,d,e){var f=0;a:{if(!(!d|(b|0)<0|(a|0)<=(b|0))){f=b&7;a=b>>>3|0;if(e){if(a>>>0>=d>>>0){break a}a=a+c|0;w[a|0]=z[a|0]|128>>>f;return}if(a>>>0>=d>>>0){break a}a=a+c|0;w[a|0]=z[a|0]&-129>>f}return}o()}function od(a){var b=0,c=0,d=0;y[a+56>>2]=2;c=1;b=4;d=y[a+52>>2];a:{b:{if((d|0)==5){break b}if((d|0)>=3){y[a+52>>2]=5;break b}b=yc(a);c=!b;if(y[a+56>>2]==3){break a}y[a+52>>2]=5;b=b?-1:4}y[a+56>>2]=b}return c}function Ka(a,b){var c=0,d=0;c=y[a>>2];a:{if(c){c=Ka(c,b);if(c){break a}}d=y[a+12>>2];a=y[a+8>>2];while(1){if((a|0)==(d|0)){return 0}c=y[a>>2];a=a+4|0;if(y[c>>2]!=(b|0)){continue}break}}return c}function Bd(a){var b=0;a:{if(z[a+16|0]!=3|z[a+17|0]!=255|(z[a+18|0]!=253|z[a+19|0]!=255)){break a}if(z[a+20|0]!=2|z[a+21|0]!=254|(z[a+22|0]!=254|z[a+23|0]!=254)){break a}b=z[a+2|0]^1}return b&1}function Sa(a,b){var c=0,d=0,e=0;a:{c=y[a+8>>2];e=c+1|0;d=y[a+4>>2];if(e>>>0>>0){if(c>>>0>=d>>>0){break a}d=b;b=y[a>>2];x[d>>1]=z[b+c|0]<<8|z[b+e|0];y[a+8>>2]=c+2;a=0}else{a=-1}return a}o()}function id(a,b,c,d,e,f,g,h){return y[c+16>>2]|y[c+12>>2]<<3|y[c+8>>2]<<6|ia(y[y[a+20>>2]+20>>2],w[a+26|0]+(d-y[a+12>>2]|0)|0,e,f)<<8|y[c+4>>2]<<9|y[c>>2]<<10|ia(b,w[a+24|0]+d|0,g,h)<<12} +function Db(a,b){var c=0;c=y[a+40>>2];y[a+28>>2]=y[a+36>>2];y[a+32>>2]=c;c=y[a+48>>2];b=y[b+28>>2];if(c>>>0>>0){o()}y[a+40>>2]=c;y[a+48>>2]=c-b;c=y[a+44>>2];y[a+36>>2]=c;y[a+44>>2]=b+c}function ra(a,b){var c=0,d=0,e=0;d=Y-16|0;Y=d;e=b<<2;c=y[e+y[a+36>>2]>>2];if(!c){b=rd(na(96),b);c=y[a+36>>2];y[d+12>>2]=0;pb(c+e|0,b);pa(d+12|0);c=y[y[a+36>>2]+e>>2]}Y=d+16|0;return c}function $a(a,b){var c=0;c=y[a>>2];if(c){y[a+4>>2]=c;ma(c);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0}y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0}function Qb(){var a=0;a=na(180);y[a+144>>2]=0;y[a+148>>2]=0;x[a+28>>1]=0;y[a+152>>2]=0;y[a+156>>2]=0;y[a+160>>2]=0;y[a+164>>2]=0;y[a+168>>2]=0;y[a+172>>2]=0;t(a+32|0,0,104);return a}function Jc(a,b,c){var d=0,e=0;e=((b&536870911)<<3|a>>>29)&-8;d=y[c>>2];a:{while(1){if(B[c>>2]>=e>>>0){break a}if(!Ia(a,b,c)){continue}break}if(y[c>>2]-d>>>0>11){break a}y[c>>2]=d}}function xb(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];b=ze(c,c>>31,b,0);c=Z;d=!(b- -2147483648>>>0<2147483648?c+1|0:c);c=0|d;b=d?b:0}else{b=0}y[a>>2]=c;y[a+4>>2]=b;return a}function oc(a,b){var c=0;c=y[a+4>>2];a:{if((b|0)>=0){b=b+c|0;c=y[a>>2]+(b>>>3&536870908)|0;break a}b=b+c|0;c=y[a>>2]+((b-31|0)/32<<2)|0}y[a>>2]=c;y[a+4>>2]=b&31;return a}function Lc(a,b){var c=0,d=0,e=0;c=Y-16|0;Y=c;d=z[a+4|0];if((d|0)!=255){e=y[b+4>>2];y[c+8>>2]=y[b>>2];y[c+12>>2]=e;_[y[(c+8|0)+(d<<2)>>2]](c+7|0,a)}w[a+4|0]=255;Y=c+16|0}function Ab(a,b,c,d,e,f,g){var h=0,i=0;h=Y-16|0;Y=h;if(y[a+16>>2]){i=y[a+24>>2];y[h+8>>2]=y[a+20>>2];y[h+12>>2]=i;y[h>>2]=0;y[h+4>>2]=0;cd(a,b,c,d,e,f,g,h)}Y=h+16|0}function Xc(a,b){var c=0;c=Y-16|0;Y=c;y[c+8>>2]=0;a:{if(z[a+4|0]==1){oa(a,b);break a}Xa(a);w[a+4|0]=1;y[a>>2]=b}y[c+12>>2]=0;la(c+12|0);la(c+8|0);Y=c+16|0;return a}function Ac(a,b,c){var d=0,e=0,f=0;d=1;while(1){e=z[a+12|0];if(e>>>0<=(f&255)>>>0){y[c>>2]=(-1<>2]|0)|d;f=f+1|0;continue}break}}function wb(a,b,c,d){var e=0;e=Y-16|0;Y=e;y[e+12>>2]=d;d=1;w[e+8|0]=1;a:{if(!z[La(e+8|0,b>>c)|0]){d=0;w[a|0]=0;break a}y[a>>2]=y[e+12>>2]}w[a+4|0]=d;Y=e+16|0}function rc(a){var b=0;b=y[a+36>>2];y[a+36>>2]=0;if(b){ma(Ha(b))}Ba(a+32|0);Ba(a+28|0);Ba(a+24|0);Ba(a+20|0);Ba(a+16|0);Ba(a+12|0);Ba(a+8|0);Ba(a+4|0);Ba(a)}function Lb(a,b){var c=0;c=y[a+4>>2];if(b){c=c+1|0;y[a+4>>2]=c}b=y[a+12>>2]-y[a+8>>2]>>3;if(b>>>0<=c>>>0){b=b+16|0;fd(a+8|0,b);zb(a+20|0,b);zb(a+32|0,b)}}function yb(a,b,c){a:{switch(a|0){case 1:return b&c;case 2:return b^c;case 3:return b^c^-1;default:o();case 0:b=b|c;break;case 4:break a}}return b}function $d(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if(Pa(a,y[b+8>>2],f)){Rc(b,c,d,e);return}a=y[a+8>>2];_[y[y[a>>2]+20>>2]](a,b,c,d,e,f)}function Gb(a,b){var c=0;c=Y-16|0;Y=c;y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;y[c+8>>2]=a;if(b){Eb(a,b);kd(a,b)}w[c+12|0]=1;gc(c+8|0);Y=c+16|0;return a}function xc(a){var b=0,c=0,d=0;c=E(z[a+1|0],y[a+4>>2]+1|0);if(c>>>0<=65535){d=z[a+2|0];b=lb();a=z[a|0];y[b+8>>2]=d;y[b+4>>2]=c;w[b|0]=a}return b}function Zc(a,b,c){var d=0;if(!(b&1)|z[a|0]!=1){b=0;c=0}else{b=y[a+4>>2];d=b;b=b+c|0;c=((d^b)&(b^c))>=0;b=c?b:0}y[a>>2]=c;y[a+4>>2]=b;return a}function me(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;d=y[a+4>>2];a=y[a+8>>2];b=(a>>1)+b|0;c=y[c>>2];if(a&1){d=y[d+y[b>>2]>>2]}return _[d|0](b,c)|0}function Zd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if(y[a+4>>2]==y[y[b+8>>2]+4>>2]){Qc(b,c,d);return}a=y[a+8>>2];_[y[y[a>>2]+28>>2]](a,b,c,d)}function zd(a,b,c,d){if(b>>>0<=d>>>0){b=(b<<2)+a|0;while(1){if((a|0)!=(b|0)){y[c>>2]=y[a>>2];c=c+4|0;a=a+4|0;continue}break}return}o()}function ia(a,b,c,d){a:{if(!d|(b|0)<0|(a|0)<=(b|0)){a=0}else{a=b>>>3|0;if(a>>>0>=d>>>0){break a}a=z[a+c|0]>>>((b^-1)&7)&1}return a}o()}function dd(a,b,c){var d=0;d=y[a+4>>2];b=d+(b<<2)|0;while(1){if((b|0)==(d|0)){y[a+4>>2]=b}else{y[d>>2]=y[c>>2];d=d+4|0;continue}break}}function Oa(a){var b=0;va(a+84|0);va(a+72|0);va(a+60|0);va(a+48|0);Ha(a+32|0);Ha(a+20|0);b=y[a+8>>2];if(b){y[a+12>>2]=b;ma(b)}return a}function bd(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];d=c-b|0;c=((c^d)&(b^c))>=0;b=c?d:0}else{b=0;c=0}y[a>>2]=c;y[a+4>>2]=b;return a}function La(a,b){var c=0,d=0;if(z[a|0]==1){c=y[a+4>>2];d=c+b|0;c=((d^c)&(b^d))>=0;b=c?d:0}else{b=0;c=0}y[a>>2]=c;y[a+4>>2]=b;return a}function dc(a,b,c){var d=0;a:{if(!(!(!y[b+16>>2]|(c|0)<0)&y[b+24>>2]>(c|0))){w[a|0]=0;break a}y[a>>2]=E(y[b+28>>2],c);d=1}w[a+4|0]=d}function Za(a){var b=0,c=0;b=y[a>>2];if(b){c=y[a+4>>2];while(1){if((c|0)!=(b|0)){c=la(c-4|0);continue}break}y[a+4>>2]=b;ma(y[a>>2])}}function wa(a,b){var c=0,d=0;d=y[a>>2];c=y[a+8>>2];if(c>>>0>2]){w[b|0]=z[d+c|0];y[a+8>>2]=y[a+8>>2]+1;a=0}else{a=-1}return a}function ye(){var a=0,b=0,c=0;while(1){b=a<<4;c=b+5728|0;y[b+5732>>2]=c;y[b+5736>>2]=c;a=a+1|0;if((a|0)!=64){continue}break}Md(48)}function Tc(a){a=a|0;var b=0,c=0,d=0;y[a>>2]=5476;b=y[a+4>>2];c=b-4|0;d=y[c>>2]-1|0;y[c>>2]=d;if((d|0)<0){ma(b-12|0)}return a|0}function qd(a,b){var c=0;c=y[a+4>>2];b=c+(b<<2)|0;while(1){if((b|0)==(c|0)){y[a+4>>2]=b}else{y[c>>2]=0;c=c+4|0;continue}break}}function kd(a,b){var c=0;c=b;b=y[a+4>>2];c=c+b|0;while(1){if((b|0)==(c|0)){y[a+4>>2]=c}else{w[b|0]=0;b=b+1|0;continue}break}}function Xb(a,b,c){var d=0;d=y[a+4>>2];while(1){if((b|0)!=(c|0)){w[d|0]=z[b|0];d=d+1|0;b=b+1|0;continue}break}y[a+4>>2]=d}function Ia(a,b,c){var d=0;d=c;c=y[c>>2];y[d>>2]=c+1;d=b;b=c>>>3|0;if(d>>>0<=b>>>0){o()}return(z[a+b|0]<<(c&7)&128)>>>7|0}function vd(a,b){var c=0;c=ze(b,0,a,0);if(Z|c>>>0>2147479550){a=0}else{b=E(a,b);a=hb(b);if(b?a:0){t(a,0,b)}}return a}function pd(a,b,c){if(c>>>0>=1073741824){za();o()}a=b-a|0;b=a>>1;return a>>>0>=2147483644?1073741823:b>>>0>c>>>0?b:c}function sa(a){var b=0,c=0;if(y[a+12>>2]){b=y[a+8>>2]+1|0;if(b){c=y[a+4>>2];y[a+8>>2]=b>>>0>>0?b:c}y[a+12>>2]=0}}function eb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){pa(c+68|0);la(c- -64|0);Sb(c+60|0);Va(c+56|0);va(c+12|0);ma(c)}}function md(a){var b=0;a:{b=a;a=y[a+16>>2];if((b|0)==(a|0)){b=16}else{if(!a){break a}b=20}_[y[b+y[a>>2]>>2]](a)}}function _a(a,b){kc(a);y[a>>2]=y[b>>2];y[a+4>>2]=y[b+4>>2];y[a+8>>2]=y[b+8>>2];y[b+8>>2]=0;y[b>>2]=0;y[b+4>>2]=0}function Nb(a,b){var c=0;c=y[a+52>>2];if((c|0)==3){if(A[a+56>>1]!=1){y[a+52>>2]=-1;return-1}c=nd(a,b)}return c}function pe(a){a=a|0;var b=0,c=0;b=na(12);y[b>>2]=2244;c=y[a+8>>2];y[b+4>>2]=y[a+4>>2];y[b+8>>2]=c;return b|0}function cc(a,b,c,d){var e=0;e=Y-16|0;Y=e;ad(e+8|0,b,c,d);b=y[e+12>>2];y[a>>2]=y[e+8>>2];y[a+4>>2]=b;Y=e+16|0}function Tb(a){var b=0;b=y[a+12>>2];a:{if((b|0)==7){y[a+8>>2]=y[a+8>>2]+1;b=0;break a}b=b+1|0}y[a+12>>2]=b}function Fc(a,b,c){if((c|0)<0){za();o()}a=b-a|0;b=a<<1;return a>>>0>=1073741823?2147483647:b>>>0>c>>>0?b:c}function ed(a,b){var c=0;if(b>>>0>=1073741824){za();o()}c=Fb(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=(b<<2)+c}function Ya(a){var b=0,c=0;b=y[a+4>>2];if(!b){o()}c=y[a>>2];y[a>>2]=c+4;y[a+4>>2]=b-1;return y[c>>2]}function Yc(a,b){a:{b:{if(!z[a+4|0]){if(y[a>>2]!=(b|0)){break b}break a}Xa(a);w[a+4|0]=0}y[a>>2]=b}}function Cc(a,b,c){if((c|0)<0){za();o()}a=b-a|0;return a>>>0>=2147483646?2147483647:a>>>0>c>>>0?a:c}function rd(a,b){var c=0,d=0;x[a>>1]=0;t(a+4|0,0,92);c=a,d=gd(a,E(b,12)+2708|0),w[c|0]=d;return a}function Vd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if(Pa(a,y[b+8>>2],f)){Rc(b,c,d,e)}}function ib(a){var b=0,c=0;while(1){b=c;c=b+1|0;if(1<>>0>>0){continue}break}return b&255}function bc(a){var b=0,c=0;b=y[a+4>>2];if(!b){o()}c=y[a>>2];y[a>>2]=c+4;y[a+4>>2]=b-1;return c}function oe(a,b){a=a|0;b=b|0;var c=0;y[b>>2]=2244;c=y[a+8>>2];y[b+4>>2]=y[a+4>>2];y[b+8>>2]=c}function ud(a,b){var c=0;c=Y-16|0;Y=c;y[c+12>>2]=b;b=a+24|0;a=c+12|0;_c(b,a);la(a);Y=c+16|0}function kc(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0}}function Dc(a){var b=0,c=0;b=y[a+8>>2]+1|0;if(b){c=a;a=y[a+4>>2];y[c+8>>2]=a>>>0>b>>>0?b:a}}function Kd(a,b){var c=0;if((b|0)<0){za();o()}c=na(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=b+c}function Eb(a,b){var c=0;if((b|0)<0){za();o()}c=Ad(b);y[a+4>>2]=c;y[a>>2]=c;y[a+8>>2]=b+c}function Ae(a,b){var c=0,d=0;c=b&31;d=(-1<>>c|0;c=a;a=0-b&31;return d|(c&-1>>>a)<>2]==y[y[b+8>>2]+4>>2]){Qc(b,c,d)}}function Qd(a){a=a|0;var b=0;b=y[a+64>>2];a=y[a+52>>2]+7>>>3|0;return(a>>>0>b>>>0?b:a)|0}function Mb(a,b,c){c=0-c|0;while(1){if((b|0)>0){w[a|0]=c;b=b-1|0;a=a+1|0;continue}break}}function Kc(a,b){b=b-a|0;while(1){if((b|0)>0){w[a|0]=255;b=b-1|0;a=a+1|0;continue}break}}function Ib(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Za(c+24|0);Ha(c+12|0);ma(Ha(c))}}function Da(){var a=0;a=na(12);y[a+8>>2]=0;y[a>>2]=0;y[a+4>>2]=0;fb(a,512);return a}function uc(a,b,c,d,e,f,g,h){if(!(!y[a+16>>2]|!y[f+16>>2])){cd(f,a,b,c,d,e,h,g)}}function Ad(a){a:{if(a>>>0<=2147479550){a=hb(a);if(a){break a}}Jd();o()}return a}function Oc(a,b){var c=0;return(z[a+16|0]!=(b|0)|z[a+17|0]!=255?c:z[a+2|0]^1)&1}function Nc(a){a=a|0;y[a+40>>2]=0;y[a+44>>2]=0;va(a+80|0);va(a+68|0);return a|0}function qc(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){va(b+124|0);Oa(b+28|0);ma(b)}}function mb(a,b){var c=0;c=vd(b,2);if(!c){Jd();o()}y[a+4>>2]=b;y[a>>2]=c}function Pd(a){a=a|0;Kc(y[a+80>>2],y[a+84>>2]);y[a+52>>2]=0;return 1}function Pb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){Za(c+4|0);ma(c)}}function Ha(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b)}return a}function oa(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){va(c);ma(c)}}function Ub(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(b)}return a}function wc(a){var b=0;b=na(16);y[b>>2]=a;cb(b+4|0,a);return b}function le(a,b){a=a|0;b=b|0;return(y[b+4>>2]==2532?a+4|0:0)|0}function pb(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){ma(Oa(c))}}function Fb(a){if(a>>>0>=1073741824){Wa();o()}return na(a<<2)}function bb(a,b,c,d,e,f,g){if(y[a+16>>2]){Ab(f,a,b,c,d,e,g)}}function Wa(){var a=0;a=vb(4);y[a>>2]=5372;P(a|0,5428,2);o()}function Ba(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(Ha(b))}}function na(a){a=hb(a>>>0<=1?1:a);if(!a){Sc();o()}return a}function va(a){var b=0;b=y[a>>2];if(b){y[a+4>>2]=b;ma(b)}}function Ra(a,b){var c=0;c=y[a>>2];y[a>>2]=b;if(c){ma(c)}}function Ua(a){var b=0;b=y[a>>2];y[a>>2]=0;if(b){ma(b)}}function kb(){var a=0;a=na(28);y[a+20>>2]=0;return a}function Be(a){if(a){return 31-H(a-1^a)|0}return 32} +function Bc(a){if((a|0)<0){Wa();o()}return na(a<<1)}function ua(a,b){return sd(na(32),y[a>>2],y[b>>2])}function Hd(a,b){var c=0;c=y[a>>2];y[a>>2]=b;ma(c)}function td(a){var b=0;b=na(4);y[b>>2]=a;return b}function nc(){var a=0;a=na(36);t(a,0,36);return a}function be(a,b,c){a=a|0;b=b|0;c=c|0;return 0}function Jd(){y[(Y-16|0)+12>>2]=5684;sc();o()}function Ma(a,b,c,d){return(yb(a,b,c)^c)&d^c}function ub(a,b){a=a|0;b=b|0;return y[b>>2]}function ne(a){a=a|0;_[y[y[a>>2]+4>>2]](a)}function gc(a){if(!z[a+4|0]){va(y[a>>2])}}function ee(a){a=a|0;T();S(a+128|0);o()}function hc(a,b){return sd(na(32),a,b)}function lc(a){a=y[a>>2];if(a){ma(a)}}function Td(a){a=a|0;return y[a+4>>2]}function vb(a){return hb(a|80)+80|0}function Nd(a){a=a|0;return hb(a)|0}function xa(a){return rd(na(96),a)}function je(a,b){a=a|0;b=b|0;la(b)}function ge(a,b){a=a|0;b=b|0;Ja(b)}function ac(a){return(Ta(a)|0)!=0}function ke(a){a=a|0;return 2512}function de(a){a=a|0;return 4530}function ce(a){a=a|0;return 4504}function Ud(a){a=a|0;return 4553}function Ta(a){return Mc(a,4584)}function $b(a){return Mc(a,4600)}function sb(a){rb(a,0);return a}function qb(a){a=a|0;return a|0}function pc(a){Va(a+24|0);ma(a)}function pa(a){pb(a,0);return a}function la(a){oa(a,0);return a}function Vb(a){eb(a,0);return a}function Rd(a){a=a|0;ma(Nc(a))}function Pc(a){a=a|0;ma(Tc(a))}function yd(a){return Oc(a,3)}function fe(a){a=a|0;sc();o()}function Hb(a){return Oc(a,2)}function Vc(a,b){a=a|0;b=b|0}function Qa(a){a=a|0;ma(a)}function Xa(a){Lc(a,4576)}function Uc(a){Lc(a,4592)}function ta(a){Ra(a,0)}function Va(a){Ib(a,0)}function Sc(){sc();o()}function Sb(a){Pb(a,0)}function Ja(a){Hd(a,0)}function sc(){U();o()}function ic(a){a=a|0} +// EMSCRIPTEN_END_FUNCS +a=z;m(n);var _=[null,Tc,qb,xe,we,ve,ue,te,se,re,qe,qb,Nc,Rd,Qd,Pd,Od,qb,Qa,pe,oe,ic,ne,me,le,ke,Vc,je,ub,ub,Vc,ge,ub,ub,ee,fe,Qa,de,Qa,ce,qb,Qa,ic,ic,be,Qa,Xd,Qa,Ud,Pc,Td,Pc,Qa,ae,Vd,Wd,Yd,Qa,$d,_d,Zd];function $(){return v.byteLength>>16}function ea(fa){fa=fa|0;var aa=$()|0;var ba=aa+fa|0;if(aa{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=instantiateSync(wasmBinaryFile,info);return receiveInstance(result[0])}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{noExitRuntime=false;runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>noExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};function _createImageData(size){Module.imageData=new Uint8Array(size)}var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};function _setImageData(array_ptr,pitch8,pitch32,height){if(pitch32===pitch8){Module.imageData=new Uint8ClampedArray(HEAPU8.subarray(array_ptr,array_ptr+pitch32*height));return}const destSize=pitch8*height;const imageData=Module.imageData=new Uint8ClampedArray(destSize);for(let srcStart=array_ptr,destStart=0;destStart{HEAP8.set(array,buffer)};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["writeArrayToMemory"]=writeArrayToMemory;var _malloc,_free,_jbig2_decode,_ccitt_decode,__emscripten_timeout,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_malloc=Module["_malloc"]=wasmExports["l"];_free=Module["_free"]=wasmExports["m"];_jbig2_decode=Module["_jbig2_decode"]=wasmExports["n"];_ccitt_decode=Module["_ccitt_decode"]=wasmExports["o"];__emscripten_timeout=wasmExports["p"];memory=wasmMemory=wasmExports["j"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={a:___cxa_throw,f:__abort_js,e:__emscripten_runtime_keepalive_clear,b:__setitimer_js,h:_createImageData,c:_emscripten_resize_heap,d:_proc_exit,i:_setImageData,g:_setLineData};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=createWasm();await run(); +;return Module}export default JBig2; diff --git a/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/openjpeg_nowasm_fallback.js b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/openjpeg_nowasm_fallback.js new file mode 100644 index 000000000..3bc22534f --- /dev/null +++ b/safari/Glaze/Glaze Extension/Resources/lib/pdfjs/openjpeg_nowasm_fallback.js @@ -0,0 +1,17 @@ +/* THIS FILE IS GENERATED - DO NOT EDIT */ +async function OpenJPEG(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";var readAsync;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=( +// EMSCRIPTEN_START_ASM +function instantiate(Aa){var a;var b=new Uint8Array(123);for(var c=25;c>=0;--c){b[48+c]=52+c;b[65+c]=c;b[97+c]=26+c}b[43]=62;b[47]=63;function i(j,k,l){var d,e,c=0,f=k,g=l.length,h=k+(g*3>>2)-(l[g-2]=="=")-(l[g-1]=="=");for(;c>4;if(f>2;if(f>>0;A=A>>>0;if(z+A>a.length)throw"trap: invalid memory.fill";a.fill(v,z,z+A)}function B(z,C,A){a.copyWithin(z,C,C+A)}function D(){throw new Error("abort")}function za(n){var E=new ArrayBuffer(16777216);var F=new Int8Array(E);var G=new Int16Array(E);var H=new Int32Array(E);var I=new Uint8Array(E);var J=new Uint16Array(E);var K=new Uint32Array(E);var L=new Float32Array(E);var M=new Float64Array(E);var N=Math.imul;var O=Math.fround;var P=Math.abs;var Q=Math.clz32;var R=Math.min;var S=Math.max;var T=Math.floor;var U=Math.ceil;var V=Math.trunc;var W=Math.sqrt;var X=n.a;var Y=X.a;var Z=X.b;var _=X.c;var $=X.d;var aa=X.e;var ba=X.f;var ca=X.g;var da=X.h;var ea=X.i;var fa=X.j;var ga=X.k;var ha=X.l;var ia=X.m;var ja=X.n;var ka=X.o;var la=X.p;var ma=X.q;var na=94272;var oa=0;var pa=0;var qa=0; +// EMSCRIPTEN_START_FUNCS +function Sc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,M=0,P=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ha=0,ia=O(0),ja=0,ka=0,la=0,ma=0,oa=0,pa=0,qa=0,sa=0,ta=0,ua=0,va=0,wa=0;aa=na-96|0;na=aa;D=H[a+8>>2];a:{b:{c:{if(!H[a>>2]){g=N(H[D+16>>2]-H[D+8>>2]|0,H[D+20>>2]-H[D+12>>2]|0)<<2;c=Ia(g);H[D+60>>2]=c;if(!c){Ba(H[a+32>>2],1,8023,0);d=a+28|0;break b}if(!g){break c}y(c,0,g);break c}c=H[D+60>>2];if(!c){break c}Ca(c);H[D+60>>2]=0}if(!H[H[a+28>>2]>>2]){break a}ma=H[a+16>>2];c=H[ma+28>>2]+N(H[ma+24>>2],152)|0;va=H[c-152>>2];wa=H[c-144>>2];oa=H[a+20>>2];pa=H[a+12>>2];ua=H[a+4>>2];d=a+28|0;d:{p=H[b+4>>2];e=0;e:{if((p|0)<=0){break e}l=H[b>>2];c=0;f:{while(1){g=l+N(c,12)|0;if(!H[g>>2]){break f}c=c+1|0;if((p|0)!=(c|0)){continue}break}e=0;break e}e=H[g+4>>2]}if(e){break d}e=Ea(1,156);if(!e){Ba(H[a+32>>2],1,6313,0);break b}H[e+140>>2]=0;c=0;l=H[b+4>>2];g:{if((l|0)==2147483647){break g}g=H[b>>2];if((l|0)>0){while(1){p=g+N(c,12)|0;if(!H[p>>2]){l=H[p+8>>2];if(l){ra[l|0](H[p+4>>2]);g=H[b>>2]}b=g+N(c,12)|0;H[b+8>>2]=15;H[b+4>>2]=e;c=1;break g}c=c+1|0;if((l|0)!=(c|0)){continue}break}}g=Ga(g,N(l,12)+12|0);c=0;if(!g){break g}H[b>>2]=g;c=H[b+4>>2];g=g+N(c,12)|0;H[g+8>>2]=15;H[g+4>>2]=e;H[g>>2]=0;H[b+4>>2]=c+1;c=1}if(c){break d}Ba(H[a+32>>2],1,8338,0);b=H[e+116>>2];if(b){Ca(b);H[e+116>>2]=0}b=H[e+120>>2];if(b){Ca(b);H[e+120>>2]=0}Ca(H[e+148>>2]);Ca(e);break b}H[e+144>>2]=H[a+24>>2];$=H[a+40>>2];ca=H[a+36>>2];W=H[a+32>>2];t=H[oa+808>>2];b=H[pa+16>>2];h:{ba=H[oa+16>>2];i:{if(ba&64){l=na-304|0;na=l;j:{if(t){if(ca){Ba(W,1,3219,0);break j}Ba(W,1,3219,0);break j}i=H[e+116>>2];c=H[D+20>>2]-H[D+12>>2]|0;b=H[D+16>>2]-H[D+8>>2]|0;g=N(c,b);k:{l:{if(g>>>0>K[e+132>>2]){Ca(i);f=g<<2;i=Ia(f);H[e+116>>2]=i;if(!i){i=0;break j}H[e+132>>2]=g;break l}if(!i){break k}f=g<<2}if(!f){break k}y(i,0,f)}i=H[e+120>>2];m:{if(K[e+136>>2]>2639){break m}Ca(i);i=Ia(10560);H[e+120>>2]=i;if(i){break m}i=0;break j}H[e+136>>2]=2640;y(i,0,10560);H[e+128>>2]=c;H[e+124>>2]=b;m=H[D+24>>2];if(!m){i=1;break j}p=H[D+28>>2];i=1;n:{o:{p:{q:{f=H[D+52>>2];r:{if(f){g=f&3;c=H[D+4>>2];i=0;s:{if(f>>>0>=4){b=f&-4;while(1){k=c+(i<<3)|0;j=H[k+28>>2]+(H[k+20>>2]+(H[k+12>>2]+(H[k+4>>2]+j|0)|0)|0)|0;i=i+4|0;r=r+4|0;if((b|0)!=(r|0)){continue}break}if(!g){break s}}while(1){j=H[(c+(i<<3)|0)+4>>2]+j|0;i=i+1|0;o=o+1|0;if((g|0)!=(o|0)){continue}break}}if(!H[e+144>>2]&(f|0)==1){break o}if(K[e+152>>2]>=j>>>0){break r}r=Ga(H[e+148>>2],j);if(r){break q}i=0;break j}if(!H[e+144>>2]){break j}}r=H[e+148>>2];if(r){break p}i=0;break j}H[e+152>>2]=j;H[e+148>>2]=r}if(!H[D+52>>2]){j=0;break n}f=H[D+4>>2];j=0;i=0;while(1){g=i<<3;c=g+f|0;b=H[c+4>>2];if(b){B(j+r|0,H[c>>2],b)}f=H[D+4>>2];j=H[(g+f|0)+4>>2]+j|0;i=i+1|0;if(i>>>0>2]){continue}break}break n}r=H[H[D+4>>2]>>2]}i=0;f=0;c=H[D+40>>2];g=0;t:{if(!c){break t}b=H[D>>2];f=H[b+8>>2];g=0;if((c|0)==1){break t}g=H[b+32>>2]}c=m-p|0;f=f+g|0;u:{if(!f){o=0;break u}i=1;b=H[D>>2];q=H[b>>2];o=0;if((f|0)==1){i=0;break u}o=H[b+24>>2]}Z=c+1|0;ja=H[e+116>>2];$=H[e+120>>2];S=H[D+12>>2];z=H[D+20>>2];ea=H[D+8>>2];A=H[D+16>>2];v:{w:{x:{y:{z:{A:{B:{C:{if(!(!i|o)){if(!ca){break C}Ba(W,2,10806,0);f=1;break B}if(f>>>0<4){break B}if(ca){H[l+112>>2]=f;Ba(W,1,9590,l+112|0);break v}H[l+96>>2]=f;Ba(W,1,9590,l+96|0);i=0;break j}Ba(W,2,10806,0);i=H[D+24>>2];if(i>>>0>30){break A}n=1;if(i>>>0>=Z>>>0){break y}break w}i=H[D+24>>2];if(i>>>0<=30){break z}if(!ca){break A}H[l+32>>2]=H[D+24>>2];Ba(W,1,12302,l+32|0);break v}H[l>>2]=i;Ba(W,1,12302,l);i=0;break j}if(i>>>0>>0){break x}if(f>>>0<2){n=f;break y}if((i|0)!=(Z|0)){n=f;break y}n=1;if(I[26416]){break y}if(!ca){F[26416]=1;H[l+64>>2]=f;Ba(W,2,10299,l- -64|0);break y}if(!I[26416]){F[26416]=1;H[l+80>>2]=f;Ba(W,2,10299,l+80|0)}}if(!(!(q>>>0<2|j>>>0>>0)&o+q>>>0<=j>>>0)){if(ca){i=0;Ba(W,1,9532,0);break j}i=0;Ba(W,1,9532,0);break j}V=q+r|0;b=I[V-1|0];k=b<<4|I[V-2|0]&15;if(!(!(k>>>0<2|(b|0)==255)&(k|0)<=(q|0))){if(ca){i=0;Ba(W,1,15305,0);break j}i=0;Ba(W,1,15305,0);break j}qa=H[D+28>>2];H[l+272>>2]=0;H[l+280>>2]=0;H[l+264>>2]=0;H[l+268>>2]=0;H[l+296>>2]=0;H[l+300>>2]=0;H[l+284>>2]=0;H[l+288>>2]=0;b=k-1|0;H[l+276>>2]=b;p=(q+r|0)-k|0;H[l+256>>2]=p;i=I[p|0];c=8;H[l+272>>2]=8;t=p+1|0;H[l+256>>2]=t;g=k-2|0;H[l+276>>2]=g;f=(b|0)==1?i|15:i;H[l+264>>2]=f;b=0;H[l+268>>2]=b;H[l+280>>2]=(f|0)==255;T=p&3;D:{E:{if((T|0)==3){break E}p=0;if(!((f|0)!=255|I[t|0]<=143)){break D}m=255;m=k>>>0>=3?I[t|0]:m;u=k-3|0;H[l+276>>2]=u;p=(f|0)==255;c=p?15:16;H[l+272>>2]=c;v=t+(k>>>0>2)|0;H[l+256>>2]=v;g=(g|0)==1?m|15:m;H[l+280>>2]=(g|0)==255;i=g;m=f;g=p?7:8;p=g&31;if((g&63)>>>0>=32){t=f<>>32-p|b<>2]=f;b=t|x;H[l+268>>2]=b;if((T|0)==2){break E}m=255;p=0;if(!((i|0)!=255|I[v|0]<=143)){break D}m=k>>>0>=4?I[v|0]:m;j=k-4|0;H[l+276>>2]=j;v=v+(k>>>0>3)|0;H[l+256>>2]=v;g=(u|0)==1?m|15:m;p=0;u=p;H[l+280>>2]=(g|0)==255;p=(i|0)==255;c=(p?7:8)+c|0;H[l+272>>2]=c;i=g;m=f;g=p?7:8;p=g&31;if((g&63)>>>0>=32){t=f<>>32-p|b<>2]=f;b=t|u;H[l+268>>2]=b;if((T|0)==1){break E}p=0;if(!((i|0)!=255|I[v|0]<=143)){break D}m=255;m=k>>>0>=5?I[v|0]:m;H[l+276>>2]=k-5;H[l+256>>2]=v+(k>>>0>4);p=0;g=(j|0)==1?m|15:m;H[l+280>>2]=(g|0)==255;i=(i|0)==255;c=(i?7:8)+c|0;H[l+272>>2]=c;m=f;i=i?7:8;t=i&31;if((i&63)>>>0>=32){T=f<>>32-t|b<>2]=f;b=p|T;H[l+268>>2]=b}g=64-c|0;c=f;i=g&31;if((g&63)>>>0>=32){x=c<>>32-i|b<>2]=b;H[l+268>>2]=x;p=1}if(!p){if(ca){i=0;Ba(W,1,11470,0);break j}i=0;Ba(W,1,11470,0);break j}C=A-ea|0;R=k-2|0;H[l+244>>2]=R;x=q+r|0;b=x-3|0;H[l+224>>2]=b;c=I[x-2|0];f=c>>>0>143;H[l+248>>2]=f;p=0;m=c>>>4|0;H[l+232>>2]=m;H[l+236>>2]=0;v=(m&7)==7?3:4;H[l+240>>2]=v;c=(b&3)+1|0;E=c>>>0>>0?c:R;F:{G:{if(!R){H[l+244>>2]=R-E;break G}c=x-4|0;H[l+224>>2]=c;g=I[b|0];i=g>>>0>143;H[l+248>>2]=i;p=v&31;if((v&63)>>>0>=32){t=g<>>32-p;p=g<>2]=m;p=t;H[l+236>>2]=p;v=(f?(g&127)==127?7:8:8)+v|0;H[l+240>>2]=v;H:{if((E|0)==1){f=i;break H}i=x-5|0;H[l+224>>2]=i;b=I[c|0];f=b>>>0>143;H[l+248>>2]=f;j=v&31;if((v&63)>>>0>=32){t=b<>>32-j;M=b<>2]=m;p=p|t;H[l+236>>2]=p;v=(g>>>0<=143?8:(b&127)==127?7:8)+v|0;H[l+240>>2]=v;if((E|0)==2){b=c;c=i;break H}g=x-6|0;H[l+224>>2]=g;j=I[i|0];f=j>>>0>143;H[l+248>>2]=f;c=j;u=c&255;T=v&31;if((v&63)>>>0>=32){t=u<>>32-T;M=u<>2]=m;p=p|t;H[l+236>>2]=p;v=(b>>>0<=143?8:(c&127)==127?7:8)+v|0;H[l+240>>2]=v;if((E|0)==3){b=i;c=g;break H}c=x-7|0;H[l+224>>2]=c;b=I[g|0];f=b>>>0>143;H[l+248>>2]=f;i=v&31;if((v&63)>>>0>=32){T=b<>>32-i;i=b<>2]=m;H[l+236>>2]=i;v=(j>>>0<=143?8:(b&127)==127?7:8)+v|0;H[l+240>>2]=v;b=g}g=R-E|0;H[l+244>>2]=g;if(v>>>0>32){break F}if(g>>>0>=4){h=H[b-4>>2];H[l+224>>2]=b-5;H[l+244>>2]=g-4;break G}if(!g){break G}I:{if((E|0)==(k-3|0)){t=24;break I}T=g&1;u=g&2;t=24;b=0;while(1){i=c-1|0;H[l+224>>2]=i;j=I[c|0];c=c-2|0;H[l+224>>2]=c;H[l+244>>2]=g-1;i=I[i|0];g=g-2|0;H[l+244>>2]=g;h=j<>2]=c-1;b=I[c|0];H[l+244>>2]=g-1;h=b<>2]=j>>>0>143;g=f?(h&2130706432)==2130706432?7:8:8;c=g+(h>>>0<=2415919103?8:(h&8323072)==8323072?7:8)|0;i=h>>>16&255;b=c+(i>>>0<=143?8:(h&32512)==32512?7:8)|0;t=h>>>8&255;H[l+240>>2]=b+((t>>>0<=143?8:(h&127)==127?7:8)+v|0);b=i<>>24|t<>>0>=32){t=b<>>32-c;b=b<>2]=b|m;H[l+236>>2]=p|t}Wb(l+192|0,r,q-k|0,255);u=0;J:{if(n>>>0<2){break J}Wb(l+160|0,V,o,0);u=0;if((n|0)==2){break J}m=0;p=0;f=0;u=0;H[l+152>>2]=1;H[l+144>>2]=0;H[l+136>>2]=0;H[l+140>>2]=0;i=o;c=i-1|0;H[l+148>>2]=c;b=(q+r|0)+i|0;g=b-1|0;H[l+128>>2]=g;q=g&3;K:{if((i|0)<=0){b=g;break K}b=b-2|0;H[l+128>>2]=b;m=I[g|0]}H[l+136>>2]=m;H[l+140>>2]=0;o=m>>>0>143;H[l+152>>2]=o;j=(m&127)==127?7:8;H[l+144>>2]=j;L:{if(!q){break L}h=i-2|0;H[l+148>>2]=h;M:{if((i|0)<2){k=b;break M}k=b-1|0;H[l+128>>2]=k;f=I[b|0]}o=f>>>0>143;H[l+152>>2]=o;b=j&31;if((j&63)>>>0>=32){t=f<>>32-b;b=f<>2]=p;b=t;H[l+140>>2]=b;j=(m>>>0<=143?8:(f&127)==127?7:8)+j|0;H[l+144>>2]=j;if((q|0)==1){b=k;m=p;p=t;i=c;c=h;break L}t=i-3|0;H[l+148>>2]=t;N:{if((i|0)<3){g=k;break N}g=k-1|0;H[l+128>>2]=g;u=I[k|0]}o=u>>>0>143;H[l+152>>2]=o;c=j&31;if((j&63)>>>0>=32){T=u<>>32-c;c=u<>2]=m;H[l+140>>2]=b;j=(f>>>0<=143?8:(u&127)==127?7:8)+j|0;H[l+144>>2]=j;if((q|0)==2){b=g;i=h;c=t;break L}c=i-4|0;H[l+148>>2]=c;f=0;O:{if((i|0)<4){b=g;break O}b=g-1|0;H[l+128>>2]=b;f=I[g|0]}o=f>>>0>143;H[l+152>>2]=o;g=j&31;if((j&63)>>>0>=32){x=f<>>32-g;g=f<>2]=m;H[l+140>>2]=g;j=(u>>>0<=143?8:(f&127)==127?7:8)+j|0;H[l+144>>2]=j;i=t}if(j>>>0<=32){P:{if((i|0)>=5){k=H[b-3>>2];H[l+148>>2]=i-5;H[l+128>>2]=b-4;break P}k=0;if((i|0)<2){break P}i=24;while(1){f=b-1|0;H[l+128>>2]=f;b=I[b|0];g=c-1|0;H[l+148>>2]=g;k=b<>>0>1;b=f;i=i-8|0;c=g;if(t){continue}break}}t=k&255;H[l+152>>2]=t>>>0>143;g=o?(k&2130706432)==2130706432?7:8:8;c=g+(k>>>0<=2415919103?8:(k&8323072)==8323072?7:8)|0;i=k>>>16&255;b=c+(i>>>0<=143?8:(k&32512)==32512?7:8)|0;f=k>>>8&255;H[l+144>>2]=b+((f>>>0<=143?8:(k&127)==127?7:8)+j|0);b=i<>>24|f<>>0>=32){t=b<>>32-c;b=b<>2]=b|m;H[l+140>>2]=p|t}u=1}da=z-S|0;U=Z+1|0;F[$+2112|0]=0;h=$+2112|0;g=Xa(l+256|0);if((C|0)>0){E=qa-1|0;o=$;f=h;b=0;c=ja;r=0;while(1){T=r;j=J[(b<<8|(kb(l+224|0)&127)<<1)+16656>>1];Q:{if(b){break Q}b=g-2|0;j=(b|0)==-1?j:0;if((g|0)>1){g=b;break Q}g=Xa(l+256|0)}m=H[l+232>>2];p=H[l+236>>2];i=H[l+240>>2];v=j>>>4|0;q=H[o>>2]|(v&3|j>>>2&48)<>2]=q;S=j&16;b=j>>>5&7|S>>>4;k=i;i=j&7;r=k-i|0;m=((1<>>i;p=p>>>i|0;k=m;i=0;if((C|0)>(T|2)){i=J[(b<<8|(k&127)<<1)+16656>>1];R:{if(b){break R}b=g-2|0;i=(b|0)==-1?i:0;if((g|0)>1){g=b;break R}g=Xa(l+256|0)}b=i&7;r=r-b|0;m=((1<>>b;p=p>>>b|0;k=m;b=i>>>4&1|i>>>5&7}H[o>>2]=q|(i<<2&768|i&48)<>>2&2|j>>>3&1;S:{if((q|0)!=3){break S}t=g-2|0;q=(t|0)==-1?4:3;if((g|0)>1){g=t;break S}g=Xa(l+256|0)}T:{if(!q){H[l+120>>2]=1;H[l+124>>2]=1;k=0;break T}if(q>>>0<=2){t=I[(k&7)+20804|0];x=t>>>2&7;z=t&3;t=(((-1<>>z)+(t>>>5|0)|0)+1|0;k=(q|0)==1;H[l+124>>2]=k?1:t;H[l+120>>2]=k?t:1;k=x+z|0;break T}x=k;k=I[(k&7)+20804|0];R=k&3;t=x>>>R|0;if((q|0)==3){V=(k>>>5|0)+1|0;if((R|0)==3){H[l+124>>2]=t&1|2;k=k>>>2&7;H[l+120>>2]=V+((-1<>>1);k=k+4|0;break T}z=I[(t&7)+20804|0];q=z&3;t=t>>>q|0;x=k>>>2&7;H[l+120>>2]=V+(t&(-1<>>2&7;H[l+124>>2]=(((-1<>>x)+(z>>>5|0)|0)+1;k=k+(q+(x+R|0)|0)|0;break T}z=I[(t&7)+20804|0];q=z&3;t=t>>>q|0;x=k>>>2&7;H[l+120>>2]=((t&(-1<>>5|0)|0)+3;k=z>>>2&7;H[l+124>>2]=(((-1<>>x)+(z>>>5|0)|0)+3;k=k+(x+(q+R|0)|0)|0}U:{V=H[l+120>>2];if(V>>>0<=U>>>0){x=H[l+124>>2];if(x>>>0<=U>>>0){break U}}if(ca){i=0;Ba(W,1,15756,0);break j}i=0;Ba(W,1,15756,0);break j}H[l+240>>2]=r-k;q=k&31;if((k&63)>>>0>=32){t=0;p=p>>>q|0}else{t=p>>>q|0;p=((1<>>q}H[l+232>>2]=p;H[l+236>>2]=t;r=T+4|0;p=(r|0)<=(C|0)?255:255>>>(r-C<<1)|0;R=(da|0)>1?p:p&85;if((i&240|v&15)&(R^-1)){if(ca){i=0;Ba(W,1,12194,0);break j}i=0;Ba(W,1,12194,0);break j}V:{W:{if(S){m=Ma(l+192|0);q=V-(j>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;v=(m&(-1<>>8&1)<>2]=v}X:{if(j&32){m=Ma(l+192|0);q=V-(j>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break X}if(!(R&2)){break X}H[(C<<2)+c>>2]=0}q=c+4|0;Y:{Z:{if(j&64){m=Ma(l+192|0);z=V-(j>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-z;k=H[l+204>>2];p=H[l+200>>2];S=z&31;if((z&63)>>>0>=32){t=0;p=k>>>S|0}else{t=k>>>S|0;p=((1<>>S}H[l+200>>2]=p;H[l+204>>2]=t;v=(m&(-1<>>10&1)<>2]=v}F[f+1|0]=0;_:{if(j&128){m=Ma(l+192|0);z=V-(j>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-z;k=H[l+204>>2];p=H[l+200>>2];S=z&31;if((z&63)>>>0>=32){t=0;p=k>>>S|0}else{t=k>>>S|0;p=((1<>>S}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>11&1)<>2]=p+2<>2]=0}j=c+8|0;$:{aa:{if(i&16){m=Ma(l+192|0);q=x-(i>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;q=(m&(-1<>>8&1)<>2]=q}ba:{if(i&32){m=Ma(l+192|0);q=x-(i>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break ba}if(!(R&32)){break ba}H[j+(C<<2)>>2]=0}j=c+12|0;ca:{da:{if(i&64){m=Ma(l+192|0);q=x-(i>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;q=(m&(-1<>>10&1)<>2]=q}f=f+2|0;F[f|0]=0;ea:{if(i&128){m=Ma(l+192|0);q=x-(i>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-q;k=H[l+204>>2];p=H[l+200>>2];z=q&31;if((q&63)>>>0>=32){t=0;p=k>>>z|0}else{t=k>>>z|0;p=((1<>>z}H[l+200>>2]=p;H[l+204>>2]=t;i=m&(-1<>>11&1)<>2]=i+2<>>0<128){break ea}H[j+(C<<2)>>2]=0}s=s^16;o=(T&4)+o|0;c=c+16|0;if((r|0)<(C|0)){continue}break}}ka=ba&8;sa=$+1584|0;ta=$+1056|0;ga=$+528|0;if((da|0)>=3){M=N(C,12);la=C<<3;fa=qa-1|0;b=qa-2|0;Y=3<>>1&2147483644)+4|0;T=2;while(1){z=T;v=I[h|0];F[h|0]=0;s=s&-17^2;fa:{if((C|0)<=0){T=z+2|0;break fa}b=z&4?ga:$;T=z+2|0;f=ja+(N(z,C)<<2)|0;q=0;c=h;S=0;while(1){ba=q;v=v&255;i=I[c+1|0]>>>5&4|(v>>>7|S);j=J[(i<<8|(kb(l+224|0)&127)<<1)+18704>>1];ga:{if(i){break ga}i=g-2|0;j=(i|0)==-1?j:0;if((g|0)>1){g=i;break ga}g=Xa(l+256|0)}m=H[l+232>>2];p=H[l+236>>2];i=H[l+240>>2];o=H[b>>2]|(j>>>4&3|j>>>2&48)<>2]=o;R=j&64;V=j&128;S=R>>>5|V>>>6;k=i;i=j&7;X=k-i|0;m=((1<>>i;p=p>>>i|0;q=m;i=0;if((C|0)>(ba|2)){k=I[c+2|0]>>>5&4|I[c+1|0]>>>7|S;i=J[(k<<8|(m&127)<<1)+18704>>1];ha:{if(k){break ha}k=g-2|0;i=(k|0)==-1?i:0;if((g|0)>1){g=k;break ha}g=Xa(l+256|0)}k=i&7;X=X-k|0;S=(i>>>5|i>>>6)&2;m=((1<>>k;q=m;p=p>>>k|0}H[b>>2]=o|(i<<2&768|i&48)<>>2&2|j>>>3&1;switch(r|0){case 0:break ia;case 3:break ja;default:break ka}}k=I[(q&7)+20804|0];x=k>>>2&7;o=q;q=k&3;o=(((-1<>>q)+(k>>>5|0)|0)+1|0;t=(r|0)==1;k=t?1:o;o=t?o:1;r=q+x|0;break ia}E=I[(q&7)+20804|0];t=E&3;k=q>>>t|0;x=I[(k&7)+20804|0];o=x&3;q=E>>>2&7;r=q+(o+t|0)|0;t=x>>>2&7;r=r+t|0;k=k>>>o|0;o=((k&(-1<>>5|0)|0)+1|0;k=(((-1<>>q)+(x>>>5|0)|0)+1|0}H[l+240>>2]=X-r;t=r&31;if((r&63)>>>0>=32){x=0;p=p>>>t|0}else{x=p>>>t|0;p=((1<>>t}H[l+232>>2]=p;H[l+236>>2]=x;q=j&240;if(q-1&q){m=v&127;p=I[c+1|0]&127;m=m>>>0>p>>>0?m:p;p=m-2|0;o=(m>>>0>=p>>>0?p:0)+o|0}t=i&240;if(t-1&t){m=I[c+1|0]&127;p=I[c+2|0]&127;p=m>>>0>p>>>0?m:p;k=(p>>>0>2?p-2|0:0)+k|0}if(!(o>>>0<=U>>>0&k>>>0<=U>>>0)){if(ca){i=0;Ba(W,1,15856,0);break j}i=0;Ba(W,1,15856,0);break j}v=I[c+2|0];F[c+1|0]=0;F[c+2|0]=0;m=t|q>>>4;q=ba+4|0;p=(q|0)<=(C|0)?255:255>>>(q-C<<1)|0;P=(T|0)>(da|0)?p&85:p;if(m&(P^-1)){if(ca){i=0;Ba(W,1,12194,0);break j}i=0;Ba(W,1,12194,0);break j}la:{ma:{if(j&16){m=Ma(l+192|0);r=o-(j>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-r;t=H[l+204>>2];p=H[l+200>>2];E=r&31;if((r&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;X=(m&(-1<>>8&1)<>2]=X}na:{if(j&32){m=Ma(l+192|0);r=o-(j>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-r;t=H[l+204>>2];p=H[l+200>>2];E=r&31;if((r&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break na}if(!(P&2)){break na}H[(C<<2)+f>>2]=0}r=f+4|0;oa:{pa:{if(R){m=Ma(l+192|0);R=o-(j>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-R;t=H[l+204>>2];p=H[l+200>>2];E=R&31;if((R&63)>>>0>=32){x=0;p=t>>>E|0}else{x=t>>>E|0;p=((1<>>E}H[l+200>>2]=p;H[l+204>>2]=x;X=(m&(-1<>>10&1)<>2]=X}qa:{if(V){m=Ma(l+192|0);o=o-(j>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];V=o&31;if((o&63)>>>0>=32){x=0;p=t>>>V|0}else{x=t>>>V|0;p=((1<>>V}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>11&1)<>2]=p+2<>2]=0}j=f+8|0;ra:{sa:{if(i&16){m=Ma(l+192|0);o=k-(i>>>12&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;o=(m&(-1<>>8&1)<>2]=o}ta:{if(i&32){m=Ma(l+192|0);o=k-(i>>>13&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;p=m&(-1<>>9&1)<>2]=p+2<>>0>p>>>0?m:p)|128;break ta}if(!(P&32)){break ta}H[j+(C<<2)>>2]=0}j=f+12|0;ua:{va:{if(i&64){m=Ma(l+192|0);o=k-(i>>>14&1)|0;H[l+208>>2]=H[l+208>>2]-o;t=H[l+204>>2];p=H[l+200>>2];r=o&31;if((o&63)>>>0>=32){x=0;p=t>>>r|0}else{x=t>>>r|0;p=((1<>>r}H[l+200>>2]=p;H[l+204>>2]=x;o=(m&(-1<>>10&1)<>2]=o}c=c+2|0;wa:{if(i&128){m=Ma(l+192|0);t=k-(i>>>15|0)|0;H[l+208>>2]=H[l+208>>2]-t;k=H[l+204>>2];p=H[l+200>>2];o=t&31;if((t&63)>>>0>=32){x=0;p=k>>>o|0}else{x=k>>>o|0;p=((1<>>o}H[l+200>>2]=p;H[l+204>>2]=x;i=m&(-1<>>11&1)<>2]=i+2<>>0<128){break wa}H[j+(C<<2)>>2]=0}s=s^16;b=(ba&4)+b|0;f=f+16|0;if((q|0)<(C|0)){continue}break}}xa:{if(!(z&2)|n>>>0<2){break xa}q=T&4;ya:{za:{Aa:{if(u){t=q?$:ga;r=0;if((C|0)<=0){break Aa}p=ja+(N(C,z-2|0)<<2)|0;while(1){i=kb(l+128|0);j=0;f=H[t>>2];if(f){j=p+(r<<2)|0;c=15;k=0;while(1){Ba:{if(!(c&f)){break Ba}m=c&286331153;if(m&f){H[j>>2]=w|H[j>>2]^((i^-1)&1)<>>1|0}if(f&m<<1){b=(C<<2)+j|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}if(f&m<<2){b=j+la|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}if(!(f&m<<3)){break Ba}b=j+M|0;H[b>>2]=w|H[b>>2]^((i^-1)&1)<>>1|0}j=j+4|0;c=c<<4;k=k+1|0;if((k|0)!=8){continue}break}j=he(f)}t=t+4|0;H[l+144>>2]=H[l+144>>2]-j;c=H[l+140>>2];b=H[l+136>>2];i=j&31;if((j&63)>>>0>=32){x=0;b=c>>>i|0}else{x=c>>>i|0;b=((1<>>i}H[l+136>>2]=b;H[l+140>>2]=x;r=r+8|0;if((C|0)>(r|0)){continue}break}}o=0;i=0;ha=q?ta:sa;j=ha;t=q?$:ga;c=t;if((C|0)>0){break za}}if(z>>>0<=5){break xa}v=q?ga:$;break ya}while(1){b=i>>>28|0;i=H[c>>2];b=i|(b|i<<4|i>>>4);H[j>>2]=b;b=b|H[c+4>>2]<<28;H[j>>2]=(b>>>1&2004318071|b<<1&-286331154|b)&(i^-1);j=j+4|0;c=c+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}if(z>>>0<6){break xa}b=0;k=0;j=t;X=q?sa:ta;i=X;v=q?ga:$;c=v;while(1){m=j+4|0;p=H[i>>2];f=H[j>>2];if(!ka){p=p|(f|(f<<4|b>>>28|f>>>4|H[m>>2]<<28))<<3&-2004318072}H[i>>2]=p&(H[c>>2]^-1);c=c+4|0;i=i+4|0;b=f;j=m;k=k+8|0;if((C|0)>(k|0)){continue}break}V=ja+(N(C,z-6|0)<<2)|0;_=0;b=v;while(1){f=0;c=H[X>>2];if(c){z=C-_|0;ba=(_<<2)+V|0;i=0;S=0;while(1){p=i;i=Ma(l+160|0);m=S+4|0;R=(C|0)>(m+_|0)?m:z;Ca:{if((R|0)<=(S|0)){j=0;break Ca}P=H[b>>2]^-1;x=S<<2;r=x+ba|0;j=0;k=S;q=15<>>1|0}m=E<<1;if(m&c){if(i&1){f=f|m;c=P&116<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=E<<2;if(m&c){if(i&1){f=f|m;c=P&232<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=E<<3;if(!(m&c)){break Da}if(i&1){f=f|m;c=P&192<<(k<<2)|c}j=j+1|0;i=i>>>1|0}o=o<<4;k=k+1|0;if((R|0)>(k|0)){continue}break}if(!(f>>>x&65535)){break Ca}while(1){Ea:{if(!(f&q)){break Ea}k=q&286331153;if(k&f){H[r>>2]=Y|(H[r>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<1&f){m=(C<<2)+r|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<2&f){m=r+la|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(!(k<<3&f)){break Ea}m=r+M|0;H[m>>2]=Y|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}q=q<<4;r=r+4|0;S=S+1|0;if((R|0)>(S|0)){continue}break}}H[l+176>>2]=H[l+176>>2]-j;m=H[l+172>>2];i=H[l+168>>2];k=j&31;if((j&63)>>>0>=32){x=0;i=m>>>k|0}else{x=m>>>k|0;i=((1<>>k}H[l+168>>2]=i;H[l+172>>2]=x;i=1;S=4;if(!(p&1)){continue}break}H[X+4>>2]=H[X+4>>2]|(f>>>27&14|f>>>29|f>>>28)&(H[b+4>>2]^-1)}p=H[b>>2]|f;m=p>>>3&286331153;i=m>>>4|m<<4|m;if(_){c=ha-4|0;H[c>>2]=H[c>>2]|(H[t-4>>2]^-1)&m<<28}H[ha>>2]=H[ha>>2]|i&(H[t>>2]^-1);H[ha+4>>2]=H[ha+4>>2]|(H[t+4>>2]^-1)&p>>>31;X=X+4|0;b=b+4|0;ha=ha+4|0;t=t+4|0;_=_+8|0;if((C|0)>(_|0)){continue}break}}if(!Z){break xa}y(v,0,Z)}if((T|0)<(da|0)){continue}break}}Fa:{if(n>>>0<2){break Fa}f=(da&3)-1|0;Ga:{if(u&f>>>0<2){if((C|0)<=0){break Ga}h=1<>2];if(q){j=m+(S<<2)|0;c=15;k=0;while(1){Ha:{if(!(c&q)){break Ha}t=c&286331153;if(t&q){H[j>>2]=h|H[j>>2]^((i^-1)&1)<>>1|0}if(q&t<<1){b=(C<<2)+j|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}if(q&t<<2){b=g+j|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}if(!(q&t<<3)){break Ha}b=j+p|0;H[b>>2]=h|H[b>>2]^((i^-1)&1)<>>1|0}j=j+4|0;c=c<<4;k=k+1|0;if((k|0)!=8){continue}break}j=he(q)}n=n+4|0;H[l+144>>2]=H[l+144>>2]-j;c=H[l+140>>2];b=H[l+136>>2];i=j&31;if((j&63)>>>0>=32){x=0;b=c>>>i|0}else{x=c>>>i|0;b=((1<>>i}H[l+136>>2]=b;H[l+140>>2]=x;S=S+8|0;if((C|0)>(S|0)){continue}break}}if((C|0)<=0|f>>>0>1){break Ga}b=da&4;j=b?ga:$;c=b?sa:ta;o=0;i=0;while(1){b=i>>>28|0;i=H[j>>2];b=i|(b|i<<4|i>>>4);H[c>>2]=b;b=b|H[j+4>>2]<<28;H[c>>2]=(b>>>1&2004318071|b<<1&-286331154|b)&(i^-1);c=c+4|0;j=j+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}}b=(da|0)>6?(da-(da+1&3)|0)-3|0:0;if((da|0)<=(b|0)){break Fa}T=N(C,12);ba=C<<3;E=3<>>0>=3){s=-1;if((g|0)<5){break Ka}if(v){break Ia}g=b&4;j=g?ga:$;i=g?sa:ta;c=0;if(!ka){c=g?$:ga;o=0;f=0;while(1){g=f>>>28|0;s=-1;f=H[c>>2];H[i>>2]=(H[i>>2]|(f|(g|f<<4|f>>>4|H[c+4>>2]<<28))<<3&-2004318072)&(H[j>>2]^-1);j=j+4|0;i=i+4|0;c=c+4|0;o=o+8|0;if((C|0)>(o|0)){continue}break}break Ja}while(1){s=-1;H[i>>2]=H[i>>2]&(H[j>>2]^-1);j=j+4|0;i=i+4|0;c=c+8|0;if((C|0)>(c|0)){continue}break}break Ja}s=H[(c<<2)+20812>>2]}if(v){break Ia}}c=b&4;Z=c?ga:$;g=c?sa:ta;X=c?$:ga;_=c?ta:sa;u=ja+(N(b,C)<<2)|0;q=0;while(1){f=0;c=H[g>>2]&s;if(c){h=C-q|0;t=u+(q<<2)|0;i=0;n=0;while(1){p=i;i=Ma(l+160|0);m=n+4|0;S=(C|0)>(m+q|0)?m:h;La:{if((S|0)<=(n|0)){j=0;break La}z=n<<2;r=z+t|0;R=(H[Z>>2]^-1)&s;j=0;k=n;V=15<>>1|0}m=x<<1;if(m&c){if(i&1){f=f|m;c=R&116<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=x<<2;if(m&c){if(i&1){f=f|m;c=R&232<<(k<<2)|c}j=j+1|0;i=i>>>1|0}m=x<<3;if(!(m&c)){break Ma}if(i&1){f=f|m;c=R&192<<(k<<2)|c}j=j+1|0;i=i>>>1|0}o=o<<4;k=k+1|0;if((S|0)>(k|0)){continue}break}if(!(f>>>z&65535)){break La}while(1){Na:{if(!(f&V)){break Na}k=V&286331153;if(k&f){H[r>>2]=E|(H[r>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<1&f){m=(C<<2)+r|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(k<<2&f){m=r+ba|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}if(!(k<<3&f)){break Na}m=r+T|0;H[m>>2]=E|(H[m>>2]|i<<31);j=j+1|0;i=i>>>1|0}V=V<<4;r=r+4|0;n=n+1|0;if((S|0)>(n|0)){continue}break}}H[l+176>>2]=H[l+176>>2]-j;m=H[l+172>>2];i=H[l+168>>2];k=j&31;if((j&63)>>>0>=32){x=0;i=m>>>k|0}else{x=m>>>k|0;i=((1<>>k}H[l+168>>2]=i;H[l+172>>2]=x;i=1;n=4;if(!(p&1)){continue}break}H[g+4>>2]=H[g+4>>2]|(f>>>27&14|f>>>29|f>>>28)&(H[Z+4>>2]^-1)}p=H[Z>>2]|f;m=p>>>3&286331153;i=m>>>4|m<<4|m;if(q){c=_-4|0;H[c>>2]=H[c>>2]|(H[X-4>>2]^-1)&m<<28}H[_>>2]=H[_>>2]|i&(H[X>>2]^-1);H[_+4>>2]=H[_+4>>2]|(H[X+4>>2]^-1)&p>>>31;g=g+4|0;Z=Z+4|0;_=_+4|0;X=X+4|0;q=q+8|0;if((C|0)>(q|0)){continue}break}}b=b+4|0;if((da|0)>(b|0)){continue}break}}i=1;if((da|0)<=0|(C|0)<=0){break j}m=C&2147483644;k=C&3;p=ea-A>>>0>4294967292;b=0;while(1){i=ja+(N(b,C)<<2)|0;j=0;Oa:{if(!p){while(1){g=H[i>>2];c=g&2147483647;H[i>>2]=(g|0)<0?0-c|0:c;g=H[i+4>>2];c=g&2147483647;H[i+4>>2]=(g|0)<0?0-c|0:c;g=H[i+8>>2];c=g&2147483647;H[i+8>>2]=(g|0)<0?0-c|0:c;g=H[i+12>>2];c=g&2147483647;H[i+12>>2]=(g|0)<0?0-c|0:c;i=i+16|0;j=j+4|0;if((m|0)!=(j|0)){continue}break}if(!k){break Oa}}j=0;while(1){g=H[i>>2];c=g&2147483647;H[i>>2]=(g|0)<0?0-c|0:c;i=i+4|0;j=j+1|0;if((k|0)!=(j|0)){continue}break}}i=1;b=b+1|0;if((da|0)!=(b|0)){continue}break}break j}if(!ca){break w}H[l+52>>2]=H[D+24>>2];H[l+48>>2]=Z;Ba(W,1,9686,l+48|0);break v}H[l+20>>2]=i;H[l+16>>2]=Z;Ba(W,1,9686,l+16|0);i=0;break j}i=0}na=l+304|0;if(i){break i}break b}H[e+108>>2]=(b<<9)+22336;c=0;b=H[e+116>>2];Pa:{Qa:{j=H[D+16>>2]-H[D+8>>2]|0;o=H[D+20>>2]-H[D+12>>2]|0;g=N(j,o);Ra:{Sa:{Ta:{if(g>>>0>K[e+132>>2]){Ca(b);b=Ia(g<<2);H[e+116>>2]=b;if(!b){break Ra}H[e+132>>2]=g;break Ta}if(!b){break Sa}}g=g<<2;if(!g){break Sa}y(b,0,g)}b=H[e+120>>2];h=j+2|0;k=o+3>>>2|0;g=N(h,k+2|0);if(g>>>0<=K[e+136>>2]){U=g<<2;break Qa}Ca(b);U=g<<2;b=Ia(U);H[e+120>>2]=b;if(b){break Qa}}b=0;break Pa}H[e+136>>2]=g;if(U){y(b,0,U)}Ua:{if(!h){break Ua}l=h&7;m=H[e+120>>2];b=m;p=j+1|0;Va:{if(p>>>0>=7){g=h&-8;while(1){H[b+24>>2]=1226833920;H[b+28>>2]=1226833920;H[b+16>>2]=1226833920;H[b+20>>2]=1226833920;H[b+8>>2]=1226833920;H[b+12>>2]=1226833920;H[b>>2]=1226833920;H[b+4>>2]=1226833920;b=b+32|0;c=c+8|0;if((g|0)!=(c|0)){continue}break}if(!l){break Va}}c=0;while(1){H[b>>2]=1226833920;b=b+4|0;c=c+1|0;if((l|0)!=(c|0)){continue}break}}l=h&7;b=m+(N(h,k+1|0)<<2)|0;Wa:{if(p>>>0>=7){g=h&-8;c=0;while(1){H[b+24>>2]=1226833920;H[b+28>>2]=1226833920;H[b+16>>2]=1226833920;H[b+20>>2]=1226833920;H[b+8>>2]=1226833920;H[b+12>>2]=1226833920;H[b>>2]=1226833920;H[b+4>>2]=1226833920;b=b+32|0;c=c+8|0;if((g|0)!=(c|0)){continue}break}if(!l){break Wa}}c=0;while(1){H[b>>2]=1226833920;b=b+4|0;c=c+1|0;if((l|0)!=(c|0)){continue}break}}b=o&3;if(!b){break Ua}l=(b|0)==1?1224736768:(b|0)==2?1207959552:1073741824;g=h&7;b=m+(N(h,k)<<2)|0;if(p>>>0>=7){c=h&-8;U=0;while(1){H[b+28>>2]=l;H[b+24>>2]=l;H[b+20>>2]=l;H[b+16>>2]=l;H[b+12>>2]=l;H[b+8>>2]=l;H[b+4>>2]=l;H[b>>2]=l;b=b+32|0;U=U+8|0;if((c|0)!=(U|0)){continue}break}if(!g){break Ua}}U=0;while(1){H[b>>2]=l;b=b+4|0;U=U+1|0;if((g|0)!=(U|0)){continue}break}}H[e+128>>2]=o;H[e+124>>2]=j;b=1}if(!b){break b}U=t+H[D+28>>2]|0;if((U|0)>=31){if(!ca){break h}H[aa+16>>2]=U;Ba(W,2,8716,aa+16|0);break b}Vb(e);Wa(e,18,46);Wa(e,17,3);Wa(e,0,4);if(H[D+64>>2]){break i}c=H[D+52>>2];Xa:{if(!(c>>>0<=1&(!H[e+144>>2]|(c|0)!=1))){l=c&3;b=H[D+4>>2];g=0;Ya:{if(c-1>>>0>=3){c=c&-4;while(1){p=(r<<3)+b|0;g=H[p+28>>2]+(H[p+20>>2]+(H[p+12>>2]+(H[p+4>>2]+g|0)|0)|0)|0;r=r+4|0;f=f+4|0;if((c|0)!=(f|0)){continue}break}if(!l){break Ya}}while(1){g=H[((r<<3)+b|0)+4>>2]+g|0;r=r+1|0;i=i+1|0;if((l|0)!=(i|0)){continue}break}}ka=H[e+148>>2];c=g+2|0;if(c>>>0>K[e+152>>2]){b=Ga(ka,c);if(!b){break b}H[e+148>>2]=b;b=b+g|0;F[b|0]=0;F[b+1|0]=0;H[e+152>>2]=c;ka=H[e+148>>2];if(!H[D+52>>2]){break Xa}b=H[D+4>>2]}g=0;r=0;while(1){l=r<<3;c=l+b|0;b=H[c+4>>2];if(b){B(g+ka|0,H[c>>2],b)}b=H[D+4>>2];g=H[(l+b|0)+4>>2]+g|0;r=r+1|0;if(r>>>0>2]){continue}break}break Xa}if((c|0)!=1){break i}ka=H[H[D+4>>2]>>2]}b=H[D+60>>2];if(b){S=H[e+116>>2];H[e+116>>2]=b}if(H[D+44>>2]){E=ba&8;ea=e+28|0;z=!(ba&2);_=2;while(1){l=ha+ka|0;la=H[D>>2]+N(R,24)|0;c=H[la>>2];Z=ba&(_>>>0<2&(H[D+28>>2]-4|0)>=(U|0));Za:{if(Z){H[e+20>>2]=l;b=c+l|0;H[e+24>>2]=b;G[e+112>>1]=I[b|0]|I[b+1|0]<<8;F[b|0]=255;F[H[e+24>>2]+1|0]=255;H[e+8>>2]=0;H[e>>2]=0;H[e+16>>2]=l;break Za}H[e+20>>2]=l;b=c+l|0;H[e+24>>2]=b;G[e+112>>1]=I[b|0]|I[b+1|0]<<8;F[b|0]=255;F[H[e+24>>2]+1|0]=255;H[e+104>>2]=e+28;H[e+16>>2]=l;H[e+12>>2]=0;b=c?I[l|0]<<16:16711680;H[e>>2]=b;i=1;c=l+1|0;g=I[l+1|0];_a:{if(I[l|0]==255){if(g>>>0>=144){H[e+12>>2]=1;b=b|65280;break _a}H[e+16>>2]=c;i=0;b=b+(g<<9)|0;break _a}H[e+16>>2]=c;b=b|g<<8}H[e+8>>2]=i;H[e+4>>2]=32768;H[e>>2]=b<<7}ja=H[la>>2];$a:{if(!H[la+8>>2]|(U|0)<=0){break $a}T=z|Z;V=0;while(1){ab:{bb:{cb:{db:{switch(_-1|0){default:if(!Z){break cb}b=1<>>1|b;o=H[e+124>>2];k=o<<2;b=(k+H[e+120>>2]|0)+12|0;g=H[e+116>>2];j=0;c=H[e+128>>2];if(c>>>0>=4){if(!o){break ab}d=N(o,12);m=o<<3;f=0-h|0;while(1){c=0;while(1){l=b;b=H[b>>2];eb:{if(!b){break eb}if(!(!(b&495)|b&2097168)){b=H[e>>2];i=H[e+8>>2];fb:{if(i){break fb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];gb:{if(i){i=8}else{if(b>>>0>143){break gb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break fb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;hb:{if(!(b>>>i&1)){break hb}ib:{if(i){break ib}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];jb:{if(i){i=8}else{if(b>>>0>143){break jb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ib}i=8;b=255}i=i-1|0;H[e+8>>2]=i;p=b>>>i&1;H[g>>2]=p?f:h;i=H[e+124>>2];b=l-4|0;H[b>>2]=H[b>>2]|32;H[l+4>>2]=H[l+4>>2]|8;H[l>>2]=H[l>>2]|p<<19|16;if(E){break hb}b=l+(-2-i<<2)|0;H[b+4>>2]=H[b+4>>2]|32768;H[b>>2]=H[b>>2]|p<<31|65536;b=b-4|0;H[b>>2]=H[b>>2]|131072}b=H[l>>2]|2097152;H[l>>2]=b}if(!(!(b&3960)|b&16777344)){b=H[e>>2];i=H[e+8>>2];kb:{if(i){break kb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];lb:{if(i){i=8}else{if(b>>>0>143){break lb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break kb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){mb:{if(i){break mb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];nb:{if(i){i=8}else{if(b>>>0>143){break nb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break mb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;i=b>>>i&1;H[g+k>>2]=i?f:h;b=l-4|0;H[b>>2]=H[b>>2]|256;H[l+4>>2]=H[l+4>>2]|64;b=H[l>>2]|i<<22|128}else{b=H[l>>2]}b=b|16777216;H[l>>2]=b}if(!(!(b&31680)|b&134218752)){b=H[e>>2];i=H[e+8>>2];ob:{if(i){break ob}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];pb:{if(i){i=8}else{if(b>>>0>143){break pb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ob}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){qb:{if(i){break qb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];rb:{if(i){i=8}else{if(b>>>0>143){break rb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break qb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;i=b>>>i&1;H[g+m>>2]=i?f:h;b=l-4|0;H[b>>2]=H[b>>2]|2048;H[l+4>>2]=H[l+4>>2]|512;b=H[l>>2]|i<<25|1024}else{b=H[l>>2]}b=b|134217728;H[l>>2]=b}if(!(b&253440)|b&1073750016){break eb}b=H[e>>2];i=H[e+8>>2];sb:{if(i){break sb}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];tb:{if(i){i=8}else{if(b>>>0>143){break tb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break sb}i=8;b=255}i=i-1|0;H[e+8>>2]=i;if(b>>>i&1){ub:{if(i){break ub}i=(b|0)!=255;p=H[e+16>>2];b=I[p|0];vb:{if(i){i=8}else{if(b>>>0>143){break vb}i=7}H[e>>2]=b;H[e+16>>2]=p+1;break ub}i=8;b=255}i=i-1|0;H[e+8>>2]=i;p=b>>>i&1;H[d+g>>2]=p?f:h;i=H[e+124>>2];b=l-4|0;H[b>>2]=H[b>>2]|16384;H[l+4>>2]=H[l+4>>2]|4096;H[l>>2]=H[l>>2]|p<<28|8192;b=l+(i<<2)|0;H[b+4>>2]=H[b+4>>2]|4;H[b+12>>2]=H[b+12>>2]|1;H[b+8>>2]=H[b+8>>2]|p<<18|2}H[l>>2]=H[l>>2]|1073741824}g=g+4|0;b=l+4|0;c=c+1|0;if((o|0)!=(c|0)){continue}break}g=d+g|0;b=l+12|0;j=j+4|0;c=H[e+128>>2];if(j>>>0<(c&-4)>>>0){continue}break}}if(!o|c>>>0<=j>>>0){break bb}q=0;m=0-h|0;i=c;while(1){wb:{if((i|0)==(j|0)){i=j;break wb}d=b-4|0;k=H[b>>2];r=0;while(1){t=N(r,3);l=k>>>t|0;if(!(l&2097168|!(l&495))){c=H[e>>2];f=H[e+8>>2];xb:{if(f){break xb}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];yb:{if(i){f=8}else{if(c>>>0>143){break yb}f=7}H[e>>2]=c;H[e+16>>2]=l+1;break xb}f=8;c=255}f=f-1|0;H[e+8>>2]=f;zb:{if(!(c>>>f&1)){break zb}p=(N(o,r)<<2)+g|0;Ab:{if(f){break Ab}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];Bb:{if(i){f=8}else{if(c>>>0>143){break Bb}f=7}H[e>>2]=c;H[e+16>>2]=l+1;break Ab}f=8;c=255}l=f-1|0;H[e+8>>2]=l;i=c>>>l&1;H[p>>2]=i?m:h;l=H[e+124>>2];H[d>>2]=H[d>>2]|32<>2]=H[b>>2]|(i<<19|16)<>2]=H[b+4>>2]|8<>2]=H[c+4>>2]|32768;H[c>>2]=H[c>>2]|i<<31|65536;c=c-4|0;H[c>>2]=H[c>>2]|131072}if((r|0)!=3){break zb}c=(l<<2)+b|0;H[c+4>>2]=H[c+4>>2]|4;H[c+12>>2]=H[c+12>>2]|1;H[c+8>>2]=H[c+8>>2]|i<<18|2}k=H[b>>2]|2097152<>2]=k;c=H[e+128>>2]}i=c;r=r+1|0;if(r>>>0>>0){continue}break}}g=g+4|0;b=b+4|0;q=q+1|0;if((o|0)!=(q|0)){continue}break};break bb;case 1:v=0;t=0;Cb:{Db:{Eb:{Y=H[e+124>>2];if(!((Y|0)!=64|H[e+128>>2]!=64)){b=1<>>1|b;x=0-M|0;p=e+100|0;l=e+96|0;A=e+28|0;g=H[e+120>>2]+268|0;h=H[e+8>>2];b=H[e+4>>2];d=H[e>>2];i=H[e+104>>2];c=H[e+116>>2];if(ba&8){break Eb}while(1){s=0;while(1){k=c;f=g;g=H[g>>2];Fb:{Gb:{Hb:{if(!g){i=H[l>>2];g=H[i>>2];b=b-g|0;Ib:{if(d>>>16>>>0>>0){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?8:12)>>2];while(1){Jb:{if(h){break Jb}i=H[e+16>>2];b=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jb}H[e+16>>2]=b;d=(o<<9)+d|0;h=7;break Jb}H[e+16>>2]=b;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?m:!m;break Ib}d=d-(g<<16)|0;if(!(b&32768)){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?12:8)>>2];while(1){Kb:{if(h){break Kb}i=H[e+16>>2];g=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Kb}H[e+16>>2]=g;d=(o<<9)+d|0;h=7;break Kb}H[e+16>>2]=g;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!m:m;break Ib}c=H[i+4>>2]}if(!c){i=l;break Fb}c=H[p>>2];g=H[c>>2];b=b-g|0;Lb:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=b>>>0>>0;c=H[(i?8:12)+c>>2];H[p>>2]=c;while(1){Mb:{if(h){break Mb}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mb}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Mb}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=i?o:!o;break Lb}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];g=b>>>0>>0;c=H[(g?12:8)+c>>2];H[p>>2]=c;while(1){Nb:{if(h){break Nb}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Nb}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break Nb}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=g?!o:o;break Lb}m=H[c+4>>2]}g=H[c>>2];b=b-g|0;Ob:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?8:12)>>2];while(1){Pb:{if(h){break Pb}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Pb}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Pb}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break Ob}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?12:8)>>2];while(1){Qb:{if(h){break Qb}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Qb}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Qb}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Ob}c=H[c+4>>2]}g=0;i=p;Rb:{Sb:{Tb:{Ub:{Vb:{switch(c|m<<1){case 0:j=f-4|0;i=H[f+4>>2]>>>17&4|H[j>>2]>>>19&1;c=A+(I[i+24384|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;Wb:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){Xb:{if(h){break Xb}m=H[e+16>>2];b=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Xb}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Xb}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=c?o:!o;break Wb}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){Yb:{if(h){break Yb}m=H[e+16>>2];g=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Yb}H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Yb}H[e+16>>2]=g;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!o:o;break Wb}m=H[m+4>>2]}g=I[i+24640|0];H[k>>2]=(m|0)==(g|0)?M:x;H[j>>2]=H[j>>2]|32;H[f+4>>2]=H[f+4>>2]|8;c=f-268|0;H[c>>2]=H[c>>2]|131072;c=f-260|0;H[c>>2]=H[c>>2]|32768;c=f-264|0;i=c;o=H[c>>2];c=g^m;H[i>>2]=o|c<<31|65536;i=c<<19;r=H[e+108>>2];c=A+(I[r+2|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;Zb:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){_b:{if(h){break _b}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break _b}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break _b}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break Zb}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){$b:{if(h){break $b}m=H[e+16>>2];g=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break $b}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break $b}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Zb}c=H[m+4>>2]}g=i|16;if(!c){break Ub}break;case 1:break Vb;case 2:break Tb;case 3:break Rb;default:break Gb}}n=f-4|0;m=H[f+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|g>>>3&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;ac:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){bc:{if(h){break bc}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break bc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break bc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break ac}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){cc:{if(h){break cc}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break cc}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break cc}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break ac}i=H[o+4>>2]}c=I[m+24640|0];H[k+256>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|256;H[f+4>>2]=H[f+4>>2]|64;r=H[e+108>>2];g=(c^i)<<22|g|128}i=A+(I[(g>>>6&495)+r|0]<<2)|0;m=H[i>>2];c=H[m>>2];b=b-c|0;dc:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[m+(i?8:12)>>2];while(1){ec:{if(h){break ec}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ec}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break ec}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=i?o:!o;break dc}d=d-(c<<16)|0;if(!(b&32768)){o=H[m+4>>2];c=b>>>0>>0;H[i>>2]=H[m+(c?12:8)>>2];while(1){fc:{if(h){break fc}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break fc}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break fc}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break dc}c=H[m+4>>2]}if(!c){break Sb}}n=f-4|0;m=H[f+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|g>>>6&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;gc:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){hc:{if(h){break hc}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break hc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break gc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){ic:{if(h){break ic}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ic}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break ic}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break gc}i=H[o+4>>2]}c=I[m+24640|0];H[k+512>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^i)<<25|g|1024;r=H[e+108>>2]}i=A+(I[(g>>>9&495)+r|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;jc:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){kc:{if(h){break kc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break kc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break kc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break jc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){lc:{if(h){break lc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break lc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break jc}c=H[o+4>>2]}if(!c){break Gb}}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|g>>>9&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0;break Hb}mc:{if(g&2097168){break mc}i=A+(I[H[e+108>>2]+(g&495)|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;nc:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){oc:{if(h){break oc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break oc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break oc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break nc}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){pc:{if(h){break pc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break pc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break pc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break nc}c=H[o+4>>2]}if(!c){break mc}q=f-4|0;o=H[f+4>>2]>>>17&4|(H[q>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;qc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){rc:{if(h){break rc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break rc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break rc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;j=m?n:!n;break qc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){sc:{if(h){break sc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break sc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break sc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=c?!n:n;break qc}j=H[j+4>>2]}m=I[o+24640|0];H[k>>2]=(j|0)==(m|0)?M:x;H[q>>2]=H[q>>2]|32;H[f+4>>2]=H[f+4>>2]|8;c=f-268|0;H[c>>2]=H[c>>2]|131072;c=f-260|0;H[c>>2]=H[c>>2]|32768;c=f-264|0;o=c;w=H[c>>2];c=j^m;H[o>>2]=w|c<<31|65536;g=c<<19|g|16}tc:{if(g&16777344){break tc}o=g>>>3|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;uc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){vc:{if(h){break vc}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break vc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break vc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break uc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){wc:{if(h){break wc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break wc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break wc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break uc}c=H[j+4>>2]}if(!c){break tc}q=f-4|0;o=H[f+4>>2]>>>20&4|(H[q>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;xc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){yc:{if(h){break yc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break yc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break yc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break xc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){zc:{if(h){break zc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break zc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break zc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break xc}m=H[j+4>>2]}c=I[o+24640|0];H[k+256>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|256;H[f+4>>2]=H[f+4>>2]|64;g=(c^m)<<22|g|128}Ac:{if(g&134218752){break Ac}o=g>>>6|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Bc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Cc:{if(h){break Cc}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Cc}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Cc}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Bc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Dc:{if(h){break Dc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Dc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Dc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Bc}c=H[j+4>>2]}if(!c){break Ac}q=f-4|0;o=H[f+4>>2]>>>23&4|(H[q>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Ec:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Fc:{if(h){break Fc}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Fc}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Fc}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Ec}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Gc:{if(h){break Gc}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Gc}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break Gc}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Ec}m=H[j+4>>2]}c=I[o+24640|0];H[k+512>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^m)<<25|g|1024}if(g&1073750016){break Gb}o=g>>>9|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Hc:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Ic:{if(h){break Ic}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Ic}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Ic}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Hc}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Jc:{if(h){break Jc}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jc}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Jc}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Hc}c=H[j+4>>2]}if(!c){break Gb}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0}Kc:{if(d>>>16>>>0>>0){j=H[r+4>>2];m=b>>>0>>0;H[i>>2]=H[(m?8:12)+r>>2];while(1){Lc:{if(h){break Lc}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Lc}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Lc}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?j:!j;break Kc}d=d-(c<<16)|0;if(!(b&32768)){j=H[r+4>>2];c=b>>>0>>0;H[i>>2]=H[(c?12:8)+r>>2];while(1){Mc:{if(h){break Mc}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mc}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Mc}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!j:j;break Kc}m=H[r+4>>2]}c=I[q+24640|0];H[k+768>>2]=(m|0)==(c|0)?M:x;H[v>>2]=H[v>>2]|16384;H[f+4>>2]=H[f+4>>2]|4096;H[f+260>>2]=H[f+260>>2]|4;H[f+268>>2]=H[f+268>>2]|1;c=c^m;H[f+264>>2]=H[f+264>>2]|c<<18|2;g=c<<28|g|8192}H[f>>2]=g&-1226833921}g=f+4|0;c=k+4|0;s=s+1|0;if((s|0)!=64){continue}break}g=f+12|0;c=k+772|0;m=t>>>0<60;t=t+4|0;if(m){continue}break}break Db}b=1<>>1|b;l=H[e+120>>2];c=(l+(Y<<2)|0)+12|0;g=H[e+128>>2];h=H[e+8>>2];b=H[e+4>>2];d=H[e>>2];i=H[e+104>>2];o=H[e+116>>2];if(ba&8){Nc:{if(g>>>0<4){break Nc}if(Y){m=e+100|0;p=e+96|0;x=N(Y,12);u=Y<<3;A=0-w|0;P=e+28|0;while(1){M=0;while(1){k=c;c=H[c>>2];Oc:{Pc:{Qc:{if(c){Rc:{if(c&2097168){break Rc}i=P+(I[H[e+108>>2]+(c&495)|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Sc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Sc}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){Tc:{if(h){break Tc}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Tc}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Tc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Sc}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){Uc:{if(h){break Uc}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Uc}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Uc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Rc}q=k-4|0;f=H[k+4>>2]>>>17&4|(H[q>>2]>>>19&1|(c>>>14&16|(c>>>16&64|c&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;Vc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break Vc}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){Wc:{if(h){break Wc}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break Wc}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break Wc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break Vc}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){Xc:{if(h){break Xc}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break Xc}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break Xc}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[o>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|32;H[k+4>>2]=H[k+4>>2]|8;c=(g^l)<<19|c|16}Yc:{if(c&16777344){break Yc}f=c>>>3|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;Zc:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break Zc}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){_c:{if(h){break _c}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break _c}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break _c}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break Zc}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){$c:{if(h){break $c}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break $c}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break $c}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Yc}q=k-4|0;f=H[k+4>>2]>>>20&4|(H[q>>2]>>>22&1|(c>>>15&16|(c>>>19&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=(Y<<2)+o|0;ad:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ad}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){bd:{if(h){break bd}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break bd}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break bd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ad}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){cd:{if(h){break cd}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break cd}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break cd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|256;H[k+4>>2]=H[k+4>>2]|64;c=(g^l)<<22|c|128}dd:{if(c&134218752){break dd}f=c>>>6|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ed:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ed}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){fd:{if(h){break fd}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break fd}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break fd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ed}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){gd:{if(h){break gd}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break gd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break gd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break dd}q=k-4|0;f=H[k+4>>2]>>>23&4|(H[q>>2]>>>25&1|(c>>>18&16|(c>>>22&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=o+u|0;hd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break hd}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){id:{if(h){break id}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break id}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break id}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break hd}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){jd:{if(h){break jd}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break jd}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break jd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024}if(c&1073750016){break Pc}f=c>>>9|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;kd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break kd}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ld:{if(h){break ld}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break ld}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break ld}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break kd}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){md:{if(h){break md}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break md}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break md}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Pc}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|f&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0;break Qc}l=H[p>>2];c=H[l>>2];b=b-c|0;nd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;g=H[l+4>>2];if(b&32768){break nd}i=H[l+4>>2];c=b>>>0>>0;H[p>>2]=H[l+(c?12:8)>>2];while(1){od:{if(h){break od}l=H[e+16>>2];g=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=g;h=8;d=(f<<8)+d|0;break od}if(f>>>0<=143){H[e+16>>2]=g;d=(f<<9)+d|0;h=7;break od}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!i:i;break nd}i=H[l+4>>2];g=b>>>0>>0;H[p>>2]=H[l+(g?8:12)>>2];while(1){pd:{if(h){break pd}l=H[e+16>>2];b=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=b;h=8;d=(f<<8)+d|0;break pd}if(f>>>0<=143){H[e+16>>2]=b;d=(f<<9)+d|0;h=7;break pd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;g=g?i:!i}if(!g){i=p;break Oc}g=H[m>>2];c=H[g>>2];b=b-c|0;qd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;l=H[g+4>>2];if(b&32768){break qd}f=H[g+4>>2];c=b>>>0>>0;g=H[(c?12:8)+g>>2];H[m>>2]=g;while(1){rd:{if(h){break rd}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break rd}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break rd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=c?!f:f;break qd}f=H[g+4>>2];l=b>>>0>>0;g=H[(l?8:12)+g>>2];H[m>>2]=g;while(1){sd:{if(h){break sd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break sd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break sd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;l=l?f:!f}c=H[g>>2];b=b-c|0;td:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;i=H[g+4>>2];if(b&32768){break td}f=H[g+4>>2];c=b>>>0>>0;H[m>>2]=H[(c?12:8)+g>>2];while(1){ud:{if(h){break ud}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break ud}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break ud}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!f:f;break td}f=H[g+4>>2];i=g;g=b>>>0>>0;H[m>>2]=H[i+(g?8:12)>>2];while(1){vd:{if(h){break vd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break vd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break vd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=g?f:!f}g=i;c=0;i=m;wd:{xd:{yd:{zd:{Ad:{switch(g|l<<1){case 0:j=k-4|0;l=H[k+4>>2]>>>17&4|H[j>>2]>>>19&1;g=P+(I[l+24384|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Bd:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Bd}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Cd:{if(h){break Cd}i=H[e+16>>2];g=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(n<<8)+d|0;break Cd}if(n>>>0<=143){H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Cd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Bd}f=H[i+4>>2];q=g;g=b>>>0>>0;H[q>>2]=H[i+(g?8:12)>>2];while(1){Dd:{if(h){break Dd}i=H[e+16>>2];b=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Dd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Dd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=I[l+24640|0];H[o>>2]=(g|0)==(c|0)?w:A;H[j>>2]=H[j>>2]|32;H[k+4>>2]=H[k+4>>2]|8;l=(c^g)<<19;r=H[e+108>>2];g=P+(I[r+2|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ed:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ed}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Fd:{if(h){break Fd}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break Fd}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Fd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ed}f=H[i+4>>2];j=g;g=b>>>0>>0;H[j>>2]=H[i+(g?8:12)>>2];while(1){Gd:{if(h){break Gd}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Gd}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Gd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=l|16;if(!g){break zd}break;case 1:break Ad;case 2:break yd;case 3:break wd;default:break Pc}}n=k-4|0;i=H[k+4>>2]>>>20&4|(H[n>>2]>>>22&1|(c>>>15&16|(c>>>19&64|c>>>3&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=(Y<<2)+o|0;Hd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Hd}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Id:{if(h){break Id}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Id}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Id}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Hd}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Jd:{if(h){break Jd}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Jd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Jd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|256;H[k+4>>2]=H[k+4>>2]|64;r=H[e+108>>2];c=(g^l)<<22|c|128}l=P+(I[(c>>>6&495)+r|0]<<2)|0;i=H[l>>2];g=H[i>>2];b=b-g|0;Kd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;f=H[i+4>>2];if(b&32768){break Kd}f=H[i+4>>2];g=b>>>0>>0;H[l>>2]=H[i+(g?12:8)>>2];while(1){Ld:{if(h){break Ld}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ld}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ld}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=g?!f:f;break Kd}f=H[i+4>>2];j=l;l=b>>>0>>0;H[j>>2]=H[i+(l?8:12)>>2];while(1){Md:{if(h){break Md}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Md}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Md}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;f=l?f:!f}if(!f){break xd}}n=k-4|0;i=H[k+4>>2]>>>23&4|(H[n>>2]>>>25&1|(c>>>18&16|(c>>>22&64|c>>>6&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=o+u|0;Nd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Nd}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Od:{if(h){break Od}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Od}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Od}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Nd}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Pd:{if(h){break Pd}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Pd}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Pd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024;r=H[e+108>>2]}i=P+(I[(c>>>9&495)+r|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Qd:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Qd}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){Rd:{if(h){break Rd}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Rd}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Rd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Qd}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){Sd:{if(h){break Sd}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Sd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Sd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Pc}}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|c>>>9&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0}X=o+x|0;Td:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[r+4>>2];if(b&32768){break Td}j=H[r+4>>2];g=b>>>0>>0;H[i>>2]=H[(g?12:8)+r>>2];while(1){Ud:{if(h){break Ud}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break Ud}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break Ud}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Td}j=H[r+4>>2];l=b>>>0>>0;H[i>>2]=H[(l?8:12)+r>>2];while(1){Vd:{if(h){break Vd}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Vd}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Vd}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}g=I[s+24640|0];H[X>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|16384;H[k+4>>2]=H[k+4>>2]|4096;f=k+(H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|4;H[f+12>>2]=H[f+12>>2]|1;g=g^l;H[f+8>>2]=H[f+8>>2]|g<<18|2;c=g<<28|c|8192}H[k>>2]=c&-1226833921}c=k+4|0;o=o+4|0;M=M+1|0;if((Y|0)!=(M|0)){continue}break}c=k+12|0;o=o+x|0;t=t+4|0;g=H[e+128>>2];if(t>>>0<(g&-4)>>>0){continue}break}break Nc}c=(g&-4)-1|0;t=(c&-4)+4|0;c=(l+(c<<1&-8)|0)+20|0}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i;if(!Y|g>>>0<=t>>>0){break Cb}while(1){h=0;if(H[e+128>>2]!=(t|0)){while(1){Tb(e,c,(N(h,Y)<<2)+o|0,w,h,1);h=h+1|0;if(h>>>0>2]-t>>>0){continue}break}}H[c>>2]=H[c>>2]&-1226833921;o=o+4|0;c=c+4|0;v=v+1|0;if((Y|0)!=(v|0)){continue}break}break Cb}Wd:{if(g>>>0<4){break Wd}if(Y){m=e+100|0;p=e+96|0;x=N(Y,12);u=Y<<3;A=0-w|0;P=e+28|0;while(1){M=0;while(1){k=c;c=H[c>>2];Xd:{Yd:{Zd:{if(c){_d:{if(c&2097168){break _d}i=P+(I[H[e+108>>2]+(c&495)|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;$d:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break $d}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){ae:{if(h){break ae}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break ae}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break ae}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break $d}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){be:{if(h){break be}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break be}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break be}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break _d}q=k-4|0;f=H[k+4>>2]>>>17&4|(H[q>>2]>>>19&1|(c>>>14&16|(c>>>16&64|c&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ce:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ce}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){de:{if(h){break de}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break de}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break de}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ce}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ee:{if(h){break ee}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break ee}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break ee}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[o>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|32;H[k+4>>2]=H[k+4>>2]|8;f=k+(-2-H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|32768;l=g^l;H[f>>2]=H[f>>2]|l<<31|65536;g=f-4|0;H[g>>2]=H[g>>2]|131072;c=l<<19|c|16}fe:{if(c&16777344){break fe}f=c>>>3|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ge:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ge}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){he:{if(h){break he}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break he}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break he}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ge}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ie:{if(h){break ie}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break ie}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break ie}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break fe}q=k-4|0;f=H[k+4>>2]>>>20&4|(H[q>>2]>>>22&1|(c>>>15&16|(c>>>19&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=(Y<<2)+o|0;je:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break je}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ke:{if(h){break ke}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break ke}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break ke}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break je}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){le:{if(h){break le}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break le}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break le}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|256;H[k+4>>2]=H[k+4>>2]|64;c=(g^l)<<22|c|128}me:{if(c&134218752){break me}f=c>>>6|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;ne:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break ne}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){oe:{if(h){break oe}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break oe}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break oe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break ne}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){pe:{if(h){break pe}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break pe}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break pe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break me}q=k-4|0;f=H[k+4>>2]>>>23&4|(H[q>>2]>>>25&1|(c>>>18&16|(c>>>22&64|f&170)));i=P+(I[f+24384|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;r=o+u|0;qe:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break qe}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){re:{if(h){break re}j=H[e+16>>2];l=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(s<<8)+d|0;break re}if(s>>>0<=143){H[e+16>>2]=l;d=(s<<9)+d|0;h=7;break re}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break qe}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){se:{if(h){break se}j=H[e+16>>2];b=j+1|0;s=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(s<<8)+d|0;break se}if(s>>>0<=143){H[e+16>>2]=b;d=(s<<9)+d|0;h=7;break se}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}g=I[f+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024}if(c&1073750016){break Yd}f=c>>>9|0;i=P+(I[H[e+108>>2]+(f&495)|0]<<2)|0;j=H[i>>2];g=H[j>>2];b=b-g|0;te:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[j+4>>2];if(b&32768){break te}n=H[j+4>>2];g=b>>>0>>0;H[i>>2]=H[j+(g?12:8)>>2];while(1){ue:{if(h){break ue}j=H[e+16>>2];l=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break ue}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break ue}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!n:n;break te}n=H[j+4>>2];l=b>>>0>>0;H[i>>2]=H[j+(l?8:12)>>2];while(1){ve:{if(h){break ve}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break ve}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break ve}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?n:!n}if(!l){break Yd}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|f&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0;break Zd}l=H[p>>2];c=H[l>>2];b=b-c|0;we:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;g=H[l+4>>2];if(b&32768){break we}i=H[l+4>>2];c=b>>>0>>0;H[p>>2]=H[l+(c?12:8)>>2];while(1){xe:{if(h){break xe}l=H[e+16>>2];g=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=g;h=8;d=(f<<8)+d|0;break xe}if(f>>>0<=143){H[e+16>>2]=g;d=(f<<9)+d|0;h=7;break xe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!i:i;break we}i=H[l+4>>2];g=b>>>0>>0;H[p>>2]=H[l+(g?8:12)>>2];while(1){ye:{if(h){break ye}l=H[e+16>>2];b=l+1|0;f=I[l+1|0];if(I[l|0]!=255){H[e+16>>2]=b;h=8;d=(f<<8)+d|0;break ye}if(f>>>0<=143){H[e+16>>2]=b;d=(f<<9)+d|0;h=7;break ye}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;g=g?i:!i}if(!g){i=p;break Xd}g=H[m>>2];c=H[g>>2];b=b-c|0;ze:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;l=H[g+4>>2];if(b&32768){break ze}f=H[g+4>>2];c=b>>>0>>0;g=H[(c?12:8)+g>>2];H[m>>2]=g;while(1){Ae:{if(h){break Ae}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ae}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ae}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=c?!f:f;break ze}f=H[g+4>>2];l=b>>>0>>0;g=H[(l?8:12)+g>>2];H[m>>2]=g;while(1){Be:{if(h){break Be}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Be}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Be}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;l=l?f:!f}c=H[g>>2];b=b-c|0;Ce:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;i=H[g+4>>2];if(b&32768){break Ce}f=H[g+4>>2];c=b>>>0>>0;H[m>>2]=H[(c?12:8)+g>>2];while(1){De:{if(h){break De}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break De}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break De}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!f:f;break Ce}f=H[g+4>>2];i=g;g=b>>>0>>0;H[m>>2]=H[i+(g?8:12)>>2];while(1){Ee:{if(h){break Ee}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Ee}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ee}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=g?f:!f}g=i;c=0;i=m;Fe:{Ge:{He:{Ie:{Je:{switch(g|l<<1){case 0:j=k-4|0;l=H[k+4>>2]>>>17&4|H[j>>2]>>>19&1;g=P+(I[l+24384|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ke:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ke}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Le:{if(h){break Le}i=H[e+16>>2];g=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(n<<8)+d|0;break Le}if(n>>>0<=143){H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break Le}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ke}f=H[i+4>>2];q=g;g=b>>>0>>0;H[q>>2]=H[i+(g?8:12)>>2];while(1){Me:{if(h){break Me}i=H[e+16>>2];b=i+1|0;n=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break Me}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Me}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=I[l+24640|0];H[o>>2]=(g|0)==(c|0)?w:A;H[j>>2]=H[j>>2]|32;H[k+4>>2]=H[k+4>>2]|8;l=k+(-2-H[e+124>>2]<<2)|0;H[l+4>>2]=H[l+4>>2]|32768;g=c^g;H[l>>2]=H[l>>2]|g<<31|65536;c=l-4|0;H[c>>2]=H[c>>2]|131072;l=g<<19;r=H[e+108>>2];g=P+(I[r+2|0]<<2)|0;i=H[g>>2];c=H[i>>2];b=b-c|0;Ne:{if(d>>>16>>>0>=c>>>0){d=d-(c<<16)|0;f=H[i+4>>2];if(b&32768){break Ne}f=H[i+4>>2];c=b>>>0>>0;H[g>>2]=H[i+(c?12:8)>>2];while(1){Oe:{if(h){break Oe}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=g;h=8;d=(j<<8)+d|0;break Oe}if(j>>>0<=143){H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Oe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=c?!f:f;break Ne}f=H[i+4>>2];j=g;g=b>>>0>>0;H[j>>2]=H[i+(g?8:12)>>2];while(1){Pe:{if(h){break Pe}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Pe}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Pe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;f=g?f:!f}g=f;c=l|16;if(!g){break Ie}break;case 1:break Je;case 2:break He;case 3:break Fe;default:break Yd}}n=k-4|0;i=H[k+4>>2]>>>20&4|(H[n>>2]>>>22&1|(c>>>15&16|(c>>>19&64|c>>>3&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=(Y<<2)+o|0;Qe:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break Qe}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Re:{if(h){break Re}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Re}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Re}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break Qe}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Se:{if(h){break Se}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Se}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Se}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|256;H[k+4>>2]=H[k+4>>2]|64;r=H[e+108>>2];c=(g^l)<<22|c|128}l=P+(I[(c>>>6&495)+r|0]<<2)|0;i=H[l>>2];g=H[i>>2];b=b-g|0;Te:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;f=H[i+4>>2];if(b&32768){break Te}f=H[i+4>>2];g=b>>>0>>0;H[l>>2]=H[i+(g?12:8)>>2];while(1){Ue:{if(h){break Ue}i=H[e+16>>2];l=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=l;h=8;d=(j<<8)+d|0;break Ue}if(j>>>0<=143){H[e+16>>2]=l;d=(j<<9)+d|0;h=7;break Ue}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}f=g?!f:f;break Te}f=H[i+4>>2];j=l;l=b>>>0>>0;H[j>>2]=H[i+(l?8:12)>>2];while(1){Ve:{if(h){break Ve}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]!=255){H[e+16>>2]=b;h=8;d=(j<<8)+d|0;break Ve}if(j>>>0<=143){H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ve}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;f=l?f:!f}if(!f){break Ge}}n=k-4|0;i=H[k+4>>2]>>>23&4|(H[n>>2]>>>25&1|(c>>>18&16|(c>>>22&64|c>>>6&170)));l=P+(I[i+24384|0]<<2)|0;f=H[l>>2];g=H[f>>2];b=b-g|0;r=o+u|0;We:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;j=H[f+4>>2];if(b&32768){break We}j=H[f+4>>2];g=b>>>0>>0;H[l>>2]=H[f+(g?12:8)>>2];while(1){Xe:{if(h){break Xe}f=H[e+16>>2];l=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(q<<8)+d|0;break Xe}if(q>>>0<=143){H[e+16>>2]=l;d=(q<<9)+d|0;h=7;break Xe}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}j=g?!j:j;break We}j=H[f+4>>2];q=l;l=b>>>0>>0;H[q>>2]=H[f+(l?8:12)>>2];while(1){Ye:{if(h){break Ye}f=H[e+16>>2];b=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(q<<8)+d|0;break Ye}if(q>>>0<=143){H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Ye}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;j=l?j:!j}l=j;g=I[i+24640|0];H[r>>2]=(l|0)==(g|0)?w:A;H[n>>2]=H[n>>2]|2048;H[k+4>>2]=H[k+4>>2]|512;c=(g^l)<<25|c|1024;r=H[e+108>>2]}i=P+(I[(c>>>9&495)+r|0]<<2)|0;f=H[i>>2];g=H[f>>2];b=b-g|0;Ze:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[f+4>>2];if(b&32768){break Ze}j=H[f+4>>2];g=b>>>0>>0;H[i>>2]=H[f+(g?12:8)>>2];while(1){_e:{if(h){break _e}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break _e}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break _e}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break Ze}j=H[f+4>>2];l=b>>>0>>0;H[i>>2]=H[f+(l?8:12)>>2];while(1){$e:{if(h){break $e}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break $e}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break $e}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}if(!l){break Yd}}q=k-4|0;s=H[k+4>>2]>>>26&4|(H[q>>2]>>>28&1|(c>>>21&16|(c>>>25&64|c>>>9&170)));i=P+(I[s+24384|0]<<2)|0;r=H[i>>2];g=H[r>>2];b=b-g|0}X=o+x|0;af:{if(d>>>16>>>0>=g>>>0){d=d-(g<<16)|0;l=H[r+4>>2];if(b&32768){break af}j=H[r+4>>2];g=b>>>0>>0;H[i>>2]=H[(g?12:8)+r>>2];while(1){bf:{if(h){break bf}f=H[e+16>>2];l=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=l;h=8;d=(n<<8)+d|0;break bf}if(n>>>0<=143){H[e+16>>2]=l;d=(n<<9)+d|0;h=7;break bf}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}l=g?!j:j;break af}j=H[r+4>>2];l=b>>>0>>0;H[i>>2]=H[(l?8:12)+r>>2];while(1){cf:{if(h){break cf}f=H[e+16>>2];b=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=b;h=8;d=(n<<8)+d|0;break cf}if(n>>>0<=143){H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break cf}H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;l=l?j:!j}g=I[s+24640|0];H[X>>2]=(l|0)==(g|0)?w:A;H[q>>2]=H[q>>2]|16384;H[k+4>>2]=H[k+4>>2]|4096;f=k+(H[e+124>>2]<<2)|0;H[f+4>>2]=H[f+4>>2]|4;H[f+12>>2]=H[f+12>>2]|1;g=g^l;H[f+8>>2]=H[f+8>>2]|g<<18|2;c=g<<28|c|8192}H[k>>2]=c&-1226833921}c=k+4|0;o=o+4|0;M=M+1|0;if((Y|0)!=(M|0)){continue}break}c=k+12|0;o=o+x|0;t=t+4|0;g=H[e+128>>2];if(t>>>0<(g&-4)>>>0){continue}break}break Wd}c=(g&-4)-1|0;t=(c&-4)+4|0;c=(l+(c<<1&-8)|0)+20|0}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i;if(!Y|g>>>0<=t>>>0){break Cb}while(1){h=0;if(H[e+128>>2]!=(t|0)){while(1){Tb(e,c,(N(h,Y)<<2)+o|0,w,h,0);h=h+1|0;if(h>>>0>2]-t>>>0){continue}break}}H[c>>2]=H[c>>2]&-1226833921;o=o+4|0;c=c+4|0;v=v+1|0;if((Y|0)!=(v|0)){continue}break}break Cb}while(1){s=0;while(1){k=c;f=g;g=H[g>>2];df:{ef:{ff:{if(!g){i=H[l>>2];g=H[i>>2];b=b-g|0;gf:{if(d>>>16>>>0>>0){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?8:12)>>2];while(1){hf:{if(h){break hf}i=H[e+16>>2];b=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hf}H[e+16>>2]=b;d=(o<<9)+d|0;h=7;break hf}H[e+16>>2]=b;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?m:!m;break gf}d=d-(g<<16)|0;if(!(b&32768)){m=H[i+4>>2];c=b>>>0>>0;H[l>>2]=H[i+(c?12:8)>>2];while(1){jf:{if(h){break jf}i=H[e+16>>2];g=i+1|0;o=I[i+1|0];if(I[i|0]==255){if(o>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break jf}H[e+16>>2]=g;d=(o<<9)+d|0;h=7;break jf}H[e+16>>2]=g;h=8;d=(o<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!m:m;break gf}c=H[i+4>>2]}if(!c){i=l;break df}c=H[p>>2];g=H[c>>2];b=b-g|0;kf:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=b>>>0>>0;c=H[(i?8:12)+c>>2];H[p>>2]=c;while(1){lf:{if(h){break lf}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lf}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break lf}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;m=i?o:!o;break kf}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];g=b>>>0>>0;c=H[(g?12:8)+c>>2];H[p>>2]=c;while(1){mf:{if(h){break mf}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break mf}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break mf}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=g?!o:o;break kf}m=H[c+4>>2]}g=H[c>>2];b=b-g|0;nf:{if(d>>>16>>>0>>0){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?8:12)>>2];while(1){of:{if(h){break of}i=H[e+16>>2];b=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break of}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break of}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break nf}d=d-(g<<16)|0;if(!(b&32768)){o=H[c+4>>2];i=c;c=b>>>0>>0;H[p>>2]=H[i+(c?12:8)>>2];while(1){pf:{if(h){break pf}i=H[e+16>>2];g=i+1|0;j=I[i+1|0];if(I[i|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break pf}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break pf}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break nf}c=H[c+4>>2]}g=0;i=p;qf:{rf:{sf:{tf:{uf:{switch(c|m<<1){case 0:j=f-4|0;i=H[f+4>>2]>>>17&4|H[j>>2]>>>19&1;c=A+(I[i+24384|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;vf:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){wf:{if(h){break wf}m=H[e+16>>2];b=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break wf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break wf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;g=c?o:!o;break vf}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){xf:{if(h){break xf}m=H[e+16>>2];g=m+1|0;n=I[m+1|0];if(I[m|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break xf}H[e+16>>2]=g;d=(n<<9)+d|0;h=7;break xf}H[e+16>>2]=g;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}g=c?!o:o;break vf}g=H[m+4>>2]}c=I[i+24640|0];H[k>>2]=(g|0)==(c|0)?M:x;H[j>>2]=H[j>>2]|32;H[f+4>>2]=H[f+4>>2]|8;i=(c^g)<<19;r=H[e+108>>2];c=A+(I[r+2|0]<<2)|0;m=H[c>>2];g=H[m>>2];b=b-g|0;yf:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?8:12)>>2];while(1){zf:{if(h){break zf}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break zf}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break zf}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;g=g<<1;if(g>>>0<32768){continue}break}b=g;c=c?o:!o;break yf}d=d-(g<<16)|0;if(!(b&32768)){o=H[m+4>>2];w=c;c=b>>>0>>0;H[w>>2]=H[m+(c?12:8)>>2];while(1){Af:{if(h){break Af}m=H[e+16>>2];g=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Af}H[e+16>>2]=g;d=(j<<9)+d|0;h=7;break Af}H[e+16>>2]=g;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break yf}c=H[m+4>>2]}g=i|16;if(!c){break tf}break;case 1:break uf;case 2:break sf;case 3:break qf;default:break ef}}n=f-4|0;m=H[f+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|g>>>3&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Bf:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){Cf:{if(h){break Cf}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Cf}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Cf}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break Bf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Df:{if(h){break Df}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Df}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break Df}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break Bf}i=H[o+4>>2]}c=I[m+24640|0];H[k+256>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|256;H[f+4>>2]=H[f+4>>2]|64;r=H[e+108>>2];g=(c^i)<<22|g|128}i=A+(I[(g>>>6&495)+r|0]<<2)|0;m=H[i>>2];c=H[m>>2];b=b-c|0;Ef:{if(d>>>16>>>0>>0){o=H[m+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[m+(i?8:12)>>2];while(1){Ff:{if(h){break Ff}m=H[e+16>>2];b=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Ff}H[e+16>>2]=b;d=(j<<9)+d|0;h=7;break Ff}H[e+16>>2]=b;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=i?o:!o;break Ef}d=d-(c<<16)|0;if(!(b&32768)){o=H[m+4>>2];c=b>>>0>>0;H[i>>2]=H[m+(c?12:8)>>2];while(1){Gf:{if(h){break Gf}m=H[e+16>>2];i=m+1|0;j=I[m+1|0];if(I[m|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Gf}H[e+16>>2]=i;d=(j<<9)+d|0;h=7;break Gf}H[e+16>>2]=i;h=8;d=(j<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!o:o;break Ef}c=H[m+4>>2]}if(!c){break rf}}n=f-4|0;m=H[f+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|g>>>6&170)));i=A+(I[m+24384|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Hf:{if(d>>>16>>>0>>0){j=H[o+4>>2];w=i;i=b>>>0>>0;H[w>>2]=H[o+(i?8:12)>>2];while(1){If:{if(h){break If}o=H[e+16>>2];b=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break If}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break If}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;i=i?j:!j;break Hf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Jf:{if(h){break Jf}o=H[e+16>>2];i=o+1|0;q=I[o+1|0];if(I[o|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Jf}H[e+16>>2]=i;d=(q<<9)+d|0;h=7;break Jf}H[e+16>>2]=i;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}i=c?!j:j;break Hf}i=H[o+4>>2]}c=I[m+24640|0];H[k+512>>2]=(i|0)==(c|0)?M:x;H[n>>2]=H[n>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^i)<<25|g|1024;r=H[e+108>>2]}i=A+(I[(g>>>9&495)+r|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Kf:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){Lf:{if(h){break Lf}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Lf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Lf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break Kf}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Mf:{if(h){break Mf}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Mf}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Mf}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break Kf}c=H[o+4>>2]}if(!c){break ef}}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|g>>>9&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0;break ff}Nf:{if(g&2097168){break Nf}i=A+(I[H[e+108>>2]+(g&495)|0]<<2)|0;o=H[i>>2];c=H[o>>2];b=b-c|0;Of:{if(d>>>16>>>0>>0){j=H[o+4>>2];m=b>>>0>>0;H[i>>2]=H[o+(m?8:12)>>2];while(1){Pf:{if(h){break Pf}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Pf}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break Pf}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?j:!j;break Of}d=d-(c<<16)|0;if(!(b&32768)){j=H[o+4>>2];c=b>>>0>>0;H[i>>2]=H[o+(c?12:8)>>2];while(1){Qf:{if(h){break Qf}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Qf}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break Qf}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!j:j;break Of}c=H[o+4>>2]}if(!c){break Nf}q=f-4|0;o=H[f+4>>2]>>>17&4|(H[q>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Rf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Sf:{if(h){break Sf}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Sf}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Sf}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Rf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Tf:{if(h){break Tf}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Tf}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break Tf}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Rf}m=H[j+4>>2]}c=I[o+24640|0];H[k>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|32;H[f+4>>2]=H[f+4>>2]|8;g=(c^m)<<19|g|16}Uf:{if(g&16777344){break Uf}o=g>>>3|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Vf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Wf:{if(h){break Wf}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Wf}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break Wf}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break Vf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){Xf:{if(h){break Xf}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Xf}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break Xf}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break Vf}c=H[j+4>>2]}if(!c){break Uf}q=f-4|0;o=H[f+4>>2]>>>20&4|(H[q>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;Yf:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){Zf:{if(h){break Zf}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break Zf}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break Zf}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break Yf}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){_f:{if(h){break _f}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break _f}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break _f}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break Yf}m=H[j+4>>2]}c=I[o+24640|0];H[k+256>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|256;H[f+4>>2]=H[f+4>>2]|64;g=(c^m)<<22|g|128}$f:{if(g&134218752){break $f}o=g>>>6|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;ag:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){bg:{if(h){break bg}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break bg}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break bg}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break ag}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){cg:{if(h){break cg}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break cg}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break cg}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break ag}c=H[j+4>>2]}if(!c){break $f}q=f-4|0;o=H[f+4>>2]>>>23&4|(H[q>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));i=A+(I[o+24384|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;dg:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){eg:{if(h){break eg}j=H[e+16>>2];b=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break eg}H[e+16>>2]=b;d=(u<<9)+d|0;h=7;break eg}H[e+16>>2]=b;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?n:!n;break dg}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){fg:{if(h){break fg}j=H[e+16>>2];m=j+1|0;u=I[j+1|0];if(I[j|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break fg}H[e+16>>2]=m;d=(u<<9)+d|0;h=7;break fg}H[e+16>>2]=m;h=8;d=(u<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!n:n;break dg}m=H[j+4>>2]}c=I[o+24640|0];H[k+512>>2]=(m|0)==(c|0)?M:x;H[q>>2]=H[q>>2]|2048;H[f+4>>2]=H[f+4>>2]|512;g=(c^m)<<25|g|1024}if(g&1073750016){break ef}o=g>>>9|0;i=A+(I[H[e+108>>2]+(o&495)|0]<<2)|0;j=H[i>>2];c=H[j>>2];b=b-c|0;gg:{if(d>>>16>>>0>>0){n=H[j+4>>2];m=b>>>0>>0;H[i>>2]=H[j+(m?8:12)>>2];while(1){hg:{if(h){break hg}j=H[e+16>>2];b=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break hg}H[e+16>>2]=b;d=(q<<9)+d|0;h=7;break hg}H[e+16>>2]=b;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;c=m?n:!n;break gg}d=d-(c<<16)|0;if(!(b&32768)){n=H[j+4>>2];c=b>>>0>>0;H[i>>2]=H[j+(c?12:8)>>2];while(1){ig:{if(h){break ig}j=H[e+16>>2];m=j+1|0;q=I[j+1|0];if(I[j|0]==255){if(q>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break ig}H[e+16>>2]=m;d=(q<<9)+d|0;h=7;break ig}H[e+16>>2]=m;h=8;d=(q<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}c=c?!n:n;break gg}c=H[j+4>>2]}if(!c){break ef}v=f-4|0;q=H[f+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));i=A+(I[q+24384|0]<<2)|0;r=H[i>>2];c=H[r>>2];b=b-c|0}jg:{if(d>>>16>>>0>>0){j=H[r+4>>2];m=b>>>0>>0;H[i>>2]=H[(m?8:12)+r>>2];while(1){kg:{if(h){break kg}o=H[e+16>>2];b=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break kg}H[e+16>>2]=b;d=(n<<9)+d|0;h=7;break kg}H[e+16>>2]=b;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;c=c<<1;if(c>>>0<32768){continue}break}b=c;m=m?j:!j;break jg}d=d-(c<<16)|0;if(!(b&32768)){j=H[r+4>>2];c=b>>>0>>0;H[i>>2]=H[(c?12:8)+r>>2];while(1){lg:{if(h){break lg}o=H[e+16>>2];m=o+1|0;n=I[o+1|0];if(I[o|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;d=d+65280|0;h=8;break lg}H[e+16>>2]=m;d=(n<<9)+d|0;h=7;break lg}H[e+16>>2]=m;h=8;d=(n<<8)+d|0}h=h-1|0;d=d<<1;b=b<<1;if(b>>>0<32768){continue}break}m=c?!j:j;break jg}m=H[r+4>>2]}c=I[q+24640|0];H[k+768>>2]=(m|0)==(c|0)?M:x;H[v>>2]=H[v>>2]|16384;H[f+4>>2]=H[f+4>>2]|4096;H[f+260>>2]=H[f+260>>2]|4;H[f+268>>2]=H[f+268>>2]|1;c=c^m;H[f+264>>2]=H[f+264>>2]|c<<18|2;g=c<<28|g|8192}H[f>>2]=g&-1226833921}g=f+4|0;c=k+4|0;s=s+1|0;if((s|0)!=64){continue}break}g=f+12|0;c=k+772|0;m=t>>>0<60;t=t+4|0;if(m){continue}break}}H[e+8>>2]=h;H[e+4>>2]=b;H[e>>2]=d;H[e+104>>2]=i}mg:{if(!(ba&32)){break mg}H[e+104>>2]=e+100;g=H[e+100>>2];b=H[g>>2];d=H[e+4>>2]-b|0;H[e+4>>2]=d;h=H[e>>2];ng:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){og:{if(d){break og}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break og}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break og}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break ng}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break ng}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){pg:{if(b){break pg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break pg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break pg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;qg:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){rg:{if(d){break rg}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break rg}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break rg}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break qg}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break qg}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){sg:{if(b){break sg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break sg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break sg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;tg:{if(h>>>16>>>0>>0){H[e+4>>2]=b;g=H[(b>>>0>d>>>0?8:12)+g>>2];H[e+100>>2]=g;d=H[e+8>>2];while(1){ug:{if(d){break ug}l=H[e+16>>2];c=l+1|0;i=I[l+1|0];if(I[l|0]==255){if(i>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break ug}H[e+16>>2]=c;h=(i<<9)+h|0;d=7;break ug}H[e+16>>2]=c;d=8;h=(i<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}d=b;break tg}h=h-(b<<16)|0;H[e>>2]=h;if(d&32768){break tg}g=H[(b>>>0>d>>>0?12:8)+g>>2];H[e+100>>2]=g;b=H[e+8>>2];while(1){vg:{if(b){break vg}c=H[e+16>>2];b=c+1|0;l=I[c+1|0];if(I[c|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;b=8;break vg}H[e+16>>2]=b;h=(l<<9)+h|0;b=7;break vg}H[e+16>>2]=b;b=8;h=(l<<8)+h|0}b=b-1|0;H[e+8>>2]=b;h=h<<1;H[e>>2]=h;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}b=H[g>>2];d=d-b|0;H[e+4>>2]=d;if(h>>>16>>>0>>0){H[e+4>>2]=b;H[e+100>>2]=H[(b>>>0>d>>>0?8:12)+g>>2];d=H[e+8>>2];while(1){wg:{if(d){break wg}g=H[e+16>>2];c=g+1|0;l=I[g+1|0];if(I[g|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;h=h+65280|0;d=8;break wg}H[e+16>>2]=c;h=(l<<9)+h|0;d=7;break wg}H[e+16>>2]=c;d=8;h=(l<<8)+h|0}d=d-1|0;H[e+8>>2]=d;h=h<<1;H[e>>2]=h;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}break mg}c=h-(b<<16)|0;H[e>>2]=c;if(d&32768){break mg}H[e+100>>2]=H[(b>>>0>d>>>0?12:8)+g>>2];h=H[e+8>>2];while(1){xg:{if(h){break xg}g=H[e+16>>2];b=g+1|0;l=I[g+1|0];if(I[g|0]==255){if(l>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;c=c+65280|0;h=8;break xg}H[e+16>>2]=b;c=(l<<9)+c|0;h=7;break xg}H[e+16>>2]=b;h=8;c=(l<<8)+c|0}h=h-1|0;H[e+8>>2]=h;c=c<<1;H[e>>2]=c;d=d<<1;H[e+4>>2]=d;if(d>>>0<32768){continue}break}}break bb;case 0:break db}}if(Z){u=1<>>1|0;n=H[e+124>>2];d=n<<2;b=(d+H[e+120>>2]|0)+12|0;g=H[e+116>>2];k=0;c=H[e+128>>2];if(c>>>0>=4){if(!n){break ab}t=N(n,12);m=n<<3;o=0-u|0;while(1){c=0;while(1){l=b;b=H[b>>2];yg:{if(!b){break yg}if((b&2097168)==16){b=H[e>>2];h=H[e+8>>2];zg:{if(h){break zg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Ag:{if(p){h=8}else{if(b>>>0>143){break Ag}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break zg}h=8;b=255}i=h-1|0;H[e+8>>2]=i;i=b>>>i&1;b=H[g>>2];H[g>>2]=((i|0)==(b>>>31|0)?o:u)+b;b=H[l>>2]|1048576;H[l>>2]=b}if((b&16777344)==128){b=H[e>>2];h=H[e+8>>2];Bg:{if(h){break Bg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Cg:{if(p){h=8}else{if(b>>>0>143){break Cg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Bg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=d+g|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);b=H[l>>2]|8388608;H[l>>2]=b}if((b&134218752)==1024){b=H[e>>2];h=H[e+8>>2];Dg:{if(h){break Dg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Eg:{if(p){h=8}else{if(b>>>0>143){break Eg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Dg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=g+m|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);b=H[l>>2]|67108864;H[l>>2]=b}if((b&1073750016)!=8192){break yg}b=H[e>>2];h=H[e+8>>2];Fg:{if(h){break Fg}p=(b|0)!=255;i=H[e+16>>2];b=I[i|0];Gg:{if(p){h=8}else{if(b>>>0>143){break Gg}h=7}H[e>>2]=b;H[e+16>>2]=i+1;break Fg}h=8;b=255}p=h-1|0;H[e+8>>2]=p;i=g+t|0;f=H[i>>2];H[i>>2]=f+((b>>>p&1)==(f>>>31|0)?o:u);H[l>>2]=H[l>>2]|536870912}g=g+4|0;b=l+4|0;c=c+1|0;if((n|0)!=(c|0)){continue}break}g=g+t|0;b=l+12|0;k=k+4|0;c=H[e+128>>2];if(k>>>0<(c&-4)>>>0){continue}break}}if(!n|c>>>0<=k>>>0){break bb}q=0;p=0-u|0;d=c;while(1){Hg:{if((d|0)==(k|0)){d=k;break Hg}h=H[b>>2];r=0;while(1){d=N(r,3);if((2097168<>2];j=H[e+8>>2];Ig:{if(j){break Ig}i=(c|0)!=255;l=H[e+16>>2];c=I[l|0];Jg:{if(i){j=8}else{if(c>>>0>143){break Jg}j=7}H[e>>2]=c;H[e+16>>2]=l+1;break Ig}j=8;c=255}l=j-1|0;H[e+8>>2]=l;l=c>>>l&1;c=H[m>>2];H[m>>2]=((l|0)==(c>>>31|0)?p:u)+c;h=H[b>>2]|1048576<>2]=h;c=H[e+128>>2]}r=r+1|0;d=c;if(r>>>0>>0){continue}break}}g=g+4|0;b=b+4|0;q=q+1|0;if((n|0)!=(q|0)){continue}break}break bb}i=H[e+120>>2];d=H[e+116>>2];v=H[e+124>>2];c=H[e+128>>2];if(!((v|0)!=64|(c|0)!=64)){c=i+268|0;t=0;q=1<>>1|0;n=0-q|0;r=H[e+8>>2];g=H[e+4>>2];b=H[e>>2];k=H[e+104>>2];while(1){j=0;while(1){p=d;i=c;d=H[c>>2];if(d){l=c;if((d&2097168)==16){k=ea+((d&1048576?16:d&495?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Kg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Lg:{if(r){break Lg}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Lg}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Lg}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Kg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Mg:{if(r){break Mg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Mg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Mg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Kg}m=H[f+4>>2]}c=H[p>>2];H[p>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|1048576}if((d&16777344)==128){k=ea+((d&8388608?16:d&3960?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Ng:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Og:{if(r){break Og}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Og}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Og}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Ng}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Pg:{if(r){break Pg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Pg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Pg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Ng}m=H[f+4>>2]}c=H[p+256>>2];H[p+256>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|8388608}if((d&134218752)==1024){k=ea+((d&67108864?16:d&31680?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Qg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Rg:{if(r){break Rg}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Rg}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Rg}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Qg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Sg:{if(r){break Sg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Sg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Sg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Qg}m=H[f+4>>2]}c=H[p+512>>2];H[p+512>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|67108864}if((d&1073750016)==8192){k=ea+((d&536870912?16:d&253440?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Tg:{if(b>>>16>>>0>>0){o=H[f+4>>2];m=c>>>0>g>>>0;H[k>>2]=H[f+(m?8:12)>>2];while(1){Ug:{if(r){break Ug}f=H[e+16>>2];g=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Ug}H[e+16>>2]=g;b=(h<<9)+b|0;r=7;break Ug}H[e+16>>2]=g;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;m=m?o:!o;break Tg}b=b-(c<<16)|0;if(!(g&32768)){o=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Vg:{if(r){break Vg}f=H[e+16>>2];m=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8;break Vg}H[e+16>>2]=m;b=(h<<9)+b|0;r=7;break Vg}H[e+16>>2]=m;r=8;b=(h<<8)+b|0}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}m=c?!o:o;break Tg}m=H[f+4>>2]}c=H[p+768>>2];H[p+768>>2]=((m|0)==(c>>>31|0)?n:q)+c;d=d|536870912}H[l>>2]=d}c=i+4|0;d=p+4|0;j=j+1|0;if((j|0)!=64){continue}break}c=i+12|0;d=p+772|0;l=t>>>0<60;t=t+4|0;if(l){continue}break}H[e+8>>2]=r;H[e+4>>2]=g;H[e>>2]=b;H[e+104>>2]=k;break bb}s=1<>>1|0;t=v<<2;f=(t+i|0)+12|0;r=H[e+8>>2];g=H[e+4>>2];b=H[e>>2];k=H[e+104>>2];o=0;Wg:{if(c>>>0<4){break Wg}if(v){n=N(v,12);m=v<<3;u=0-s|0;while(1){j=0;while(1){l=f;i=H[f>>2];if(i){if((i&2097168)==16){k=ea+((i&1048576?16:i&495?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;Xg:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break Xg}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){Yg:{if(r){break Yg}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break Yg}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break Yg}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break Xg}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){Zg:{if(r){break Zg}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break Zg}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break Zg}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}c=H[d>>2];H[d>>2]=((p|0)==(c>>>31|0)?u:s)+c;i=i|1048576}if((i&16777344)==128){k=ea+((i&8388608?16:i&3960?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;_g:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break _g}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){$g:{if(r){break $g}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break $g}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break $g}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break _g}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){ah:{if(r){break ah}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break ah}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break ah}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+t|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|8388608}if((i&134218752)==1024){k=ea+((i&67108864?16:i&31680?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;bh:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break bh}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){ch:{if(r){break ch}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break ch}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break ch}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break bh}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){dh:{if(r){break dh}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break dh}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break dh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+m|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|67108864}if((i&1073750016)==8192){k=ea+((i&536870912?16:i&253440?15:14)<<2)|0;f=H[k>>2];c=H[f>>2];g=g-c|0;eh:{if(b>>>16>>>0>=c>>>0){b=b-(c<<16)|0;p=H[f+4>>2];if(g&32768){break eh}h=H[f+4>>2];c=c>>>0>g>>>0;H[k>>2]=H[f+(c?12:8)>>2];while(1){fh:{if(r){break fh}f=H[e+16>>2];p=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=p;r=8;b=(q<<8)+b|0;break fh}if(q>>>0<=143){H[e+16>>2]=p;b=(q<<9)+b|0;r=7;break fh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;g=g<<1;if(g>>>0<32768){continue}break}p=c?!h:h;break eh}h=H[f+4>>2];p=c>>>0>g>>>0;H[k>>2]=H[f+(p?8:12)>>2];while(1){gh:{if(r){break gh}f=H[e+16>>2];g=f+1|0;q=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=g;r=8;b=(q<<8)+b|0;break gh}if(q>>>0<=143){H[e+16>>2]=g;b=(q<<9)+b|0;r=7;break gh}H[e+12>>2]=H[e+12>>2]+1;b=b+65280|0;r=8}r=r-1|0;b=b<<1;c=c<<1;if(c>>>0<32768){continue}break}g=c;p=p?h:!h}f=p;c=d+n|0;p=H[c>>2];H[c>>2]=p+((f|0)==(p>>>31|0)?u:s);i=i|536870912}H[l>>2]=i}f=l+4|0;d=d+4|0;j=j+1|0;if((v|0)!=(j|0)){continue}break}f=l+12|0;d=d+n|0;o=o+4|0;c=H[e+128>>2];if(o>>>0<(c&-4)>>>0){continue}break}break Wg}l=(c&-4)-1|0;o=(l&-4)+4|0;f=(i+(l<<1&-8)|0)+20|0}H[e+8>>2]=r;H[e+4>>2]=g;H[e>>2]=b;H[e+104>>2]=k;if(!v|c>>>0<=o>>>0){break bb}t=0;l=0-s|0;b=c;while(1){hh:{if((b|0)==(o|0)){b=o;break hh}r=H[f>>2];h=0;while(1){n=N(h,3);if((2097168<>>n|0;i=ea+((b&1048576?16:b&495?15:14)<<2)|0;H[e+104>>2]=i;p=H[i>>2];b=H[p>>2];c=H[e+4>>2]-b|0;H[e+4>>2]=c;g=H[e>>2];ih:{if(g>>>16>>>0>>0){m=H[p+4>>2];H[e+4>>2]=b;c=b>>>0>c>>>0;H[i>>2]=H[p+(c?8:12)>>2];r=H[e+8>>2];while(1){jh:{if(r){break jh}p=H[e+16>>2];i=p+1|0;j=I[p+1|0];if(I[p|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;g=g+65280|0;r=8;break jh}H[e+16>>2]=i;g=(j<<9)+g|0;r=7;break jh}H[e+16>>2]=i;r=8;g=(j<<8)+g|0}r=r-1|0;H[e+8>>2]=r;g=g<<1;H[e>>2]=g;b=b<<1;H[e+4>>2]=b;if(b>>>0<32768){continue}break}c=c?m:!m;break ih}g=g-(b<<16)|0;H[e>>2]=g;if(!(c&32768)){m=H[p+4>>2];b=b>>>0>c>>>0;H[i>>2]=H[p+(b?12:8)>>2];r=H[e+8>>2];while(1){kh:{if(r){break kh}p=H[e+16>>2];i=p+1|0;j=I[p+1|0];if(I[p|0]==255){if(j>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;g=g+65280|0;r=8;break kh}H[e+16>>2]=i;g=(j<<9)+g|0;r=7;break kh}H[e+16>>2]=i;r=8;g=(j<<8)+g|0}r=r-1|0;H[e+8>>2]=r;g=g<<1;H[e>>2]=g;c=c<<1;H[e+4>>2]=c;if(c>>>0<32768){continue}break}c=b?!m:m;break ih}c=H[p+4>>2]}b=H[k>>2];H[k>>2]=((c|0)==(b>>>31|0)?l:s)+b;r=H[f>>2]|1048576<>2]=r;c=H[e+128>>2]}h=h+1|0;b=c;if(h>>>0>>0){continue}break}}f=f+4|0;d=d+4|0;t=t+1|0;if((v|0)!=(t|0)){continue}break}break bb}i=0;t=0;q=0;lh:{mh:{nh:{A=H[e+124>>2];if(!((A|0)!=64|H[e+128>>2]!=64)){b=1<>>1|b;l=0-i|0;s=e+28|0;g=H[e+120>>2]+268|0;f=H[e+8>>2];c=H[e+4>>2];k=H[e>>2];j=H[e+104>>2];b=H[e+116>>2];if(ba&8){break nh}while(1){q=0;while(1){p=b;m=g;g=H[g>>2];if(g){oh:{if(g&2097168){break oh}b=g&495;if(!b){break oh}j=s+(I[b+H[e+108>>2]|0]<<2)|0;h=H[j>>2];b=H[h>>2];c=c-b|0;ph:{if(k>>>16>>>0>>0){o=H[h+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[h+(d?8:12)>>2];while(1){qh:{if(f){break qh}f=H[e+16>>2];c=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break qh}H[e+16>>2]=c;k=(h<<9)+k|0;f=7;break qh}H[e+16>>2]=c;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?o:!o;break ph}k=k-(b<<16)|0;if(!(c&32768)){o=H[h+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[h+(b?12:8)>>2];while(1){rh:{if(f){break rh}f=H[e+16>>2];d=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break rh}H[e+16>>2]=d;k=(h<<9)+k|0;f=7;break rh}H[e+16>>2]=d;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!o:o;break ph}b=H[h+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>17&4|(H[n>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];sh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){th:{if(f){break th}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break th}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break th}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;d=d?h:!h;break sh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){uh:{if(f){break uh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break uh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break uh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}d=b?!h:h;break sh}d=H[u+4>>2]}H[p>>2]=(o|0)==(d|0)?i:l;H[n>>2]=H[n>>2]|32;H[m+4>>2]=H[m+4>>2]|8;b=m-268|0;H[b>>2]=H[b>>2]|131072;b=m-260|0;H[b>>2]=H[b>>2]|32768;b=m-264|0;h=b;x=H[b>>2];b=d^o;H[h>>2]=x|b<<31|65536;g=b<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){o=g>>>3|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;vh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){wh:{if(f){break wh}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break wh}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break wh}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break vh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){xh:{if(f){break xh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break xh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break xh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break vh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];yh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){zh:{if(f){break zh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break zh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break zh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break yh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Ah:{if(f){break Ah}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ah}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Ah}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break yh}b=H[u+4>>2]}H[p+256>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^o)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){o=g>>>6|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Bh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ch:{if(f){break Ch}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ch}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ch}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Bh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Dh:{if(f){break Dh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Dh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Dh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Bh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Eh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Fh:{if(f){break Fh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Fh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Fh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Eh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Gh:{if(f){break Gh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Gh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Gh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Eh}b=H[u+4>>2]}H[p+512>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^o)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){o=g>>>9|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Hh:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ih:{if(f){break Ih}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ih}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ih}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Hh}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Jh:{if(f){break Jh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Jh}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Jh}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Hh}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>26&4|(H[n>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Kh:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Lh:{if(f){break Lh}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Lh}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Lh}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Kh}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Mh:{if(f){break Mh}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Mh}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Mh}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Kh}b=H[u+4>>2]}H[p+768>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;H[m+260>>2]=H[m+260>>2]|4;H[m+268>>2]=H[m+268>>2]|1;b=b^o;H[m+264>>2]=H[m+264>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;b=p+4|0;q=q+1|0;if((q|0)!=64){continue}break}g=m+12|0;b=p+772|0;p=t>>>0<60;t=t+4|0;if(p){continue}break}break mh}b=1<>>1|b;p=H[e+120>>2];g=(p+(A<<2)|0)+12|0;b=H[e+128>>2];f=H[e+8>>2];c=H[e+4>>2];k=H[e>>2];j=H[e+104>>2];o=H[e+116>>2];Nh:{if(ba&8){Oh:{if(b>>>0<4){break Oh}if(A){x=N(A,12);u=A<<3;p=0-l|0;M=e+28|0;while(1){t=0;while(1){m=g;g=H[g>>2];if(g){Ph:{if(g&2097168){break Ph}b=g&495;if(!b){break Ph}j=M+(I[b+H[e+108>>2]|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Qh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;h=H[n+4>>2];if(c&32768){break Qh}h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Rh:{if(f){break Rh}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(n<<8)+k|0;break Rh}if(n>>>0<=143){H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Rh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}h=b?!h:h;break Qh}h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Sh:{if(f){break Sh}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(n<<8)+k|0;break Sh}if(n>>>0<=143){H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Sh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;h=d?h:!h}if(h){s=m-4|0;d=H[m+4>>2]>>>17&4|(H[s>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=M+(I[d+24384|0]<<2)|0;v=H[j>>2];b=H[v>>2];c=c-b|0;h=I[d+24640|0];Th:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[v+4>>2];if(c&32768){break Th}n=H[v+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[v+(b?12:8)>>2];while(1){Uh:{if(f){break Uh}f=H[e+16>>2];d=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(v<<8)+k|0;break Uh}if(v>>>0<=143){H[e+16>>2]=d;k=(v<<9)+k|0;f=7;break Uh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Th}n=H[v+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[v+(d?8:12)>>2];while(1){Vh:{if(f){break Vh}f=H[e+16>>2];c=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(v<<8)+k|0;break Vh}if(v>>>0<=143){H[e+16>>2]=c;k=(v<<9)+k|0;f=7;break Vh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}b=w;H[o>>2]=(h|0)==(b|0)?l:p;H[s>>2]=H[s>>2]|32;H[m+4>>2]=H[m+4>>2]|8;g=(b^h)<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){h=g>>>3|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Wh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Wh}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Xh:{if(f){break Xh}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Xh}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Xh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Wh}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Yh:{if(f){break Yh}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Yh}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Yh}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>20&4|(H[v>>2]>>>22&1|(g>>>15&16|(g>>>19&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=(A<<2)+o|0;h=I[d+24640|0];Zh:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Zh}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){_h:{if(f){break _h}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break _h}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break _h}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Zh}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){$h:{if(f){break $h}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break $h}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break $h}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^h)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){h=g>>>6|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;ai:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break ai}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){bi:{if(f){break bi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break bi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break bi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break ai}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){ci:{if(f){break ci}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break ci}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break ci}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>23&4|(H[v>>2]>>>25&1|(g>>>18&16|(g>>>22&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+u|0;h=I[d+24640|0];di:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break di}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){ei:{if(f){break ei}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break ei}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break ei}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break di}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){fi:{if(f){break fi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break fi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break fi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^h)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){h=g>>>9|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;gi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break gi}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){hi:{if(f){break hi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break hi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break hi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break gi}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){ii:{if(f){break ii}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break ii}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break ii}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+x|0;h=I[d+24640|0];ji:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break ji}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){ki:{if(f){break ki}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break ki}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break ki}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break ji}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){li:{if(f){break li}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break li}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break li}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;d=m+(H[e+124>>2]<<2)|0;H[d+4>>2]=H[d+4>>2]|4;H[d+12>>2]=H[d+12>>2]|1;b=b^h;H[d+8>>2]=H[d+8>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;o=o+4|0;t=t+1|0;if((A|0)!=(t|0)){continue}break}g=m+12|0;o=o+x|0;i=i+4|0;b=H[e+128>>2];if(i>>>0<(b&-4)>>>0){continue}break}break Oh}g=(b&-4)-1|0;i=(g&-4)+4|0;g=(p+(g<<1&-8)|0)+20|0}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j;if(!A|b>>>0<=i>>>0){break Nh}while(1){c=(b|0)==(i|0);f=0;b=i;if(!c){while(1){Ub(e,g,(N(f,A)<<2)+o|0,l,f,H[e+124>>2]+2|0,1);f=f+1|0;b=H[e+128>>2];if(f>>>0>>0){continue}break}}g=g+4|0;o=o+4|0;q=q+1|0;if((A|0)!=(q|0)){continue}break}break Nh}mi:{if(b>>>0<4){break mi}if(A){x=N(A,12);u=A<<3;p=0-l|0;M=e+28|0;while(1){t=0;while(1){m=g;g=H[g>>2];if(g){ni:{if(g&2097168){break ni}b=g&495;if(!b){break ni}j=M+(I[b+H[e+108>>2]|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;oi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;h=H[n+4>>2];if(c&32768){break oi}h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){pi:{if(f){break pi}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(n<<8)+k|0;break pi}if(n>>>0<=143){H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break pi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}h=b?!h:h;break oi}h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){qi:{if(f){break qi}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(n<<8)+k|0;break qi}if(n>>>0<=143){H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break qi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;h=d?h:!h}if(h){s=m-4|0;d=H[m+4>>2]>>>17&4|(H[s>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=M+(I[d+24384|0]<<2)|0;v=H[j>>2];b=H[v>>2];c=c-b|0;h=I[d+24640|0];ri:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;d=H[v+4>>2];if(c&32768){break ri}n=H[v+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[v+(b?12:8)>>2];while(1){si:{if(f){break si}f=H[e+16>>2];d=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(v<<8)+k|0;break si}if(v>>>0<=143){H[e+16>>2]=d;k=(v<<9)+k|0;f=7;break si}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}d=b?!n:n;break ri}n=H[v+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[v+(d?8:12)>>2];while(1){ti:{if(f){break ti}f=H[e+16>>2];c=f+1|0;v=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(v<<8)+k|0;break ti}if(v>>>0<=143){H[e+16>>2]=c;k=(v<<9)+k|0;f=7;break ti}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;d=d?n:!n}H[o>>2]=(h|0)==(d|0)?l:p;H[s>>2]=H[s>>2]|32;H[m+4>>2]=H[m+4>>2]|8;b=m+(-2-H[e+124>>2]<<2)|0;H[b+4>>2]=H[b+4>>2]|32768;d=d^h;H[b>>2]=H[b>>2]|d<<31|65536;b=b-4|0;H[b>>2]=H[b>>2]|131072;g=d<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){h=g>>>3|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;ui:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break ui}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){vi:{if(f){break vi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break vi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break vi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break ui}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){wi:{if(f){break wi}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break wi}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break wi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>20&4|(H[v>>2]>>>22&1|(g>>>15&16|(g>>>19&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=(A<<2)+o|0;h=I[d+24640|0];xi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break xi}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){yi:{if(f){break yi}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break yi}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break yi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break xi}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){zi:{if(f){break zi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break zi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break zi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^h)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){h=g>>>6|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Ai:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Ai}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Bi:{if(f){break Bi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Bi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Bi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Ai}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Ci:{if(f){break Ci}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Ci}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Ci}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>23&4|(H[v>>2]>>>25&1|(g>>>18&16|(g>>>22&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+u|0;h=I[d+24640|0];Di:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Di}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){Ei:{if(f){break Ei}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break Ei}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break Ei}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Di}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){Fi:{if(f){break Fi}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break Fi}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break Fi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^h)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){h=g>>>9|0;j=M+(I[H[e+108>>2]+(h&495)|0]<<2)|0;s=H[j>>2];b=H[s>>2];c=c-b|0;Gi:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[s+4>>2];if(c&32768){break Gi}n=H[s+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[s+(b?12:8)>>2];while(1){Hi:{if(f){break Hi}f=H[e+16>>2];d=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(s<<8)+k|0;break Hi}if(s>>>0<=143){H[e+16>>2]=d;k=(s<<9)+k|0;f=7;break Hi}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!n:n;break Gi}n=H[s+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[s+(d?8:12)>>2];while(1){Ii:{if(f){break Ii}f=H[e+16>>2];c=f+1|0;s=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(s<<8)+k|0;break Ii}if(s>>>0<=143){H[e+16>>2]=c;k=(s<<9)+k|0;f=7;break Ii}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?n:!n}if(w){v=m-4|0;d=H[m+4>>2]>>>26&4|(H[v>>2]>>>28&1|(g>>>21&16|(g>>>25&64|h&170)));j=M+(I[d+24384|0]<<2)|0;r=H[j>>2];b=H[r>>2];c=c-b|0;n=o+x|0;h=I[d+24640|0];Ji:{if(k>>>16>>>0>=b>>>0){k=k-(b<<16)|0;w=H[r+4>>2];if(c&32768){break Ji}s=H[r+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[r+(b?12:8)>>2];while(1){Ki:{if(f){break Ki}f=H[e+16>>2];d=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=d;f=8;k=(r<<8)+k|0;break Ki}if(r>>>0<=143){H[e+16>>2]=d;k=(r<<9)+k|0;f=7;break Ki}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}w=b?!s:s;break Ji}s=H[r+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[r+(d?8:12)>>2];while(1){Li:{if(f){break Li}f=H[e+16>>2];c=f+1|0;r=I[f+1|0];if(I[f|0]!=255){H[e+16>>2]=c;f=8;k=(r<<8)+k|0;break Li}if(r>>>0<=143){H[e+16>>2]=c;k=(r<<9)+k|0;f=7;break Li}H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;w=d?s:!s}b=w;H[n>>2]=(h|0)==(b|0)?l:p;H[v>>2]=H[v>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;d=m+(H[e+124>>2]<<2)|0;H[d+4>>2]=H[d+4>>2]|4;H[d+12>>2]=H[d+12>>2]|1;b=b^h;H[d+8>>2]=H[d+8>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;o=o+4|0;t=t+1|0;if((A|0)!=(t|0)){continue}break}g=m+12|0;o=o+x|0;i=i+4|0;b=H[e+128>>2];if(i>>>0<(b&-4)>>>0){continue}break}break mi}g=(b&-4)-1|0;i=(g&-4)+4|0;g=(p+(g<<1&-8)|0)+20|0}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j;if(!A|b>>>0<=i>>>0){break Nh}while(1){c=(b|0)==(i|0);f=0;b=i;if(!c){while(1){Ub(e,g,(N(f,A)<<2)+o|0,l,f,H[e+124>>2]+2|0,0);f=f+1|0;b=H[e+128>>2];if(f>>>0>>0){continue}break}}g=g+4|0;o=o+4|0;q=q+1|0;if((A|0)!=(q|0)){continue}break}}break lh}while(1){q=0;while(1){p=b;m=g;g=H[g>>2];if(g){Mi:{if(g&2097168){break Mi}b=g&495;if(!b){break Mi}j=s+(I[b+H[e+108>>2]|0]<<2)|0;h=H[j>>2];b=H[h>>2];c=c-b|0;Ni:{if(k>>>16>>>0>>0){o=H[h+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[h+(d?8:12)>>2];while(1){Oi:{if(f){break Oi}f=H[e+16>>2];c=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Oi}H[e+16>>2]=c;k=(h<<9)+k|0;f=7;break Oi}H[e+16>>2]=c;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?o:!o;break Ni}k=k-(b<<16)|0;if(!(c&32768)){o=H[h+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[h+(b?12:8)>>2];while(1){Pi:{if(f){break Pi}f=H[e+16>>2];d=f+1|0;h=I[f+1|0];if(I[f|0]==255){if(h>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Pi}H[e+16>>2]=d;k=(h<<9)+k|0;f=7;break Pi}H[e+16>>2]=d;f=8;k=(h<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!o:o;break Ni}b=H[h+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>17&4|(H[n>>2]>>>19&1|(g>>>14&16|(g>>>16&64|g&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Qi:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Ri:{if(f){break Ri}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ri}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Ri}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Qi}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Si:{if(f){break Si}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Si}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Si}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Qi}b=H[u+4>>2]}H[p>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|32;H[m+4>>2]=H[m+4>>2]|8;g=(b^o)<<19|g|16}g=g|2097152}if(!(!(g&3960)|g&16777344)){o=g>>>3|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Ti:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){Ui:{if(f){break Ui}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Ui}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break Ui}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Ti}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){Vi:{if(f){break Vi}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Vi}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break Vi}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Ti}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>20&4|(H[n>>2]>>>22&1|(g>>>15&16|(g>>>19&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];Wi:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){Xi:{if(f){break Xi}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Xi}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break Xi}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Wi}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){Yi:{if(f){break Yi}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break Yi}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break Yi}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Wi}b=H[u+4>>2]}H[p+256>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|256;H[m+4>>2]=H[m+4>>2]|64;g=(b^o)<<22|g|128}g=g|16777216}if(!(!(g&31680)|g&134218752)){o=g>>>6|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;Zi:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){_i:{if(f){break _i}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break _i}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break _i}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break Zi}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){$i:{if(f){break $i}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break $i}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break $i}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break Zi}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>23&4|(H[n>>2]>>>25&1|(g>>>18&16|(g>>>22&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];aj:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){bj:{if(f){break bj}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break bj}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break bj}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break aj}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){cj:{if(f){break cj}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break cj}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break cj}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break aj}b=H[u+4>>2]}H[p+512>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|2048;H[m+4>>2]=H[m+4>>2]|512;g=(b^o)<<25|g|1024}g=g|134217728}if(!(!(g&253440)|g&1073750016)){o=g>>>9|0;j=s+(I[H[e+108>>2]+(o&495)|0]<<2)|0;n=H[j>>2];b=H[n>>2];c=c-b|0;dj:{if(k>>>16>>>0>>0){h=H[n+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[n+(d?8:12)>>2];while(1){ej:{if(f){break ej}f=H[e+16>>2];c=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break ej}H[e+16>>2]=c;k=(n<<9)+k|0;f=7;break ej}H[e+16>>2]=c;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break dj}k=k-(b<<16)|0;if(!(c&32768)){h=H[n+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[n+(b?12:8)>>2];while(1){fj:{if(f){break fj}f=H[e+16>>2];d=f+1|0;n=I[f+1|0];if(I[f|0]==255){if(n>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break fj}H[e+16>>2]=d;k=(n<<9)+k|0;f=7;break fj}H[e+16>>2]=d;f=8;k=(n<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break dj}b=H[n+4>>2]}if(b){n=m-4|0;d=H[m+4>>2]>>>26&4|(H[n>>2]>>>28&1|(g>>>21&16|(g>>>25&64|o&170)));j=s+(I[d+24384|0]<<2)|0;u=H[j>>2];b=H[u>>2];c=c-b|0;o=I[d+24640|0];gj:{if(k>>>16>>>0>>0){h=H[u+4>>2];d=b>>>0>c>>>0;H[j>>2]=H[u+(d?8:12)>>2];while(1){hj:{if(f){break hj}f=H[e+16>>2];c=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break hj}H[e+16>>2]=c;k=(u<<9)+k|0;f=7;break hj}H[e+16>>2]=c;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;b=b<<1;if(b>>>0<32768){continue}break}c=b;b=d?h:!h;break gj}k=k-(b<<16)|0;if(!(c&32768)){h=H[u+4>>2];b=b>>>0>c>>>0;H[j>>2]=H[u+(b?12:8)>>2];while(1){ij:{if(f){break ij}f=H[e+16>>2];d=f+1|0;u=I[f+1|0];if(I[f|0]==255){if(u>>>0>=144){H[e+12>>2]=H[e+12>>2]+1;k=k+65280|0;f=8;break ij}H[e+16>>2]=d;k=(u<<9)+k|0;f=7;break ij}H[e+16>>2]=d;f=8;k=(u<<8)+k|0}f=f-1|0;k=k<<1;c=c<<1;if(c>>>0<32768){continue}break}b=b?!h:h;break gj}b=H[u+4>>2]}H[p+768>>2]=(o|0)==(b|0)?i:l;H[n>>2]=H[n>>2]|16384;H[m+4>>2]=H[m+4>>2]|4096;H[m+260>>2]=H[m+260>>2]|4;H[m+268>>2]=H[m+268>>2]|1;b=b^o;H[m+264>>2]=H[m+264>>2]|b<<18|2;g=b<<28|g|8192}g=g|1073741824}H[m>>2]=g}g=m+4|0;b=p+4|0;q=q+1|0;if((q|0)!=64){continue}break}g=m+12|0;b=p+772|0;p=t>>>0<60;t=t+4|0;if(p){continue}break}}H[e+8>>2]=f;H[e+4>>2]=c;H[e>>2]=k;H[e+104>>2]=j}}if(T){break ab}Vb(e);Wa(e,18,46);Wa(e,17,3);Wa(e,0,4)}b=_+1|0;c=(b|0)==3;_=c?0:b;U=U-c|0;V=V+1|0;if(V>>>0>=K[la+8>>2]){break $a}if((U|0)>0){continue}break}}ha=ha+ja|0;c=H[e+24>>2];b=J[e+112>>1];F[c|0]=b;F[c+1|0]=b>>>8;R=R+1|0;if(R>>>0>2]){continue}break}}jj:{if(!$){break jj}kj:{c=H[e+24>>2];g=H[e+16>>2];if(c>>>0>g+2>>>0){if(!ca){break kj}g=H[e+16>>2];c=H[e+24>>2];b=H[e+20>>2];H[aa+56>>2]=c-b;H[aa+52>>2]=g-b;H[aa+48>>2]=(c-g|0)-2;Ba(W,2,15235,aa+48|0);break jj}b=H[e+12>>2];if(b>>>0<3){break jj}if(ca){H[aa+80>>2]=H[e+12>>2];Ba(W,2,7107,aa+80|0);break jj}H[aa+64>>2]=b;Ba(W,2,7107,aa- -64|0);break jj}b=H[e+20>>2];H[aa+40>>2]=c-b;H[aa+36>>2]=g-b;H[aa+32>>2]=(c-g|0)-2;Ba(W,2,15235,aa+32|0)}if(!H[D+60>>2]){break i}H[e+116>>2]=S}l=H[pa+4>>2];g=H[D+12>>2];j=H[D+8>>2]-H[pa>>2]|0;c=H[pa+16>>2];if(c&1){b=H[ma+28>>2]+N(ua,152)|0;j=(H[b-144>>2]+j|0)-H[b-152>>2]|0}i=g-l|0;if(c&2){b=H[ma+28>>2]+N(ua,152)|0;i=(H[b-140>>2]+i|0)-H[b-148>>2]|0}k=H[D+60>>2];r=k?k:H[e+116>>2];n=H[e+128>>2];q=H[e+124>>2];m=H[oa+808>>2];lj:{if(!m){break lj}h=0;b=(n|0)!=0&(q|0)!=0;if((m|0)<=30){if(!b){break lj}while(1){l=(N(h,q)<<2)+r|0;b=0;while(1){g=l+(b<<2)|0;p=H[g>>2];c=p>>31;c=(c^p)-c|0;if(c>>>m|0){c=c>>>H[oa+808>>2]|0;H[g>>2]=(p|0)<0?0-c|0:c}b=b+1|0;if((q|0)!=(b|0)){continue}break}h=h+1|0;if((n|0)!=(h|0)){continue}break}break lj}if(!b){break lj}b=N(n,q)<<2;if(!b){break lj}y(r,0,b)}if(k){b=N(n,q);if(H[oa+20>>2]==1){if(!b){break a}e=0;if((b|0)!=1){c=b&1;b=b&-2;g=0;while(1){l=(e<<2)+r|0;H[l>>2]=H[l>>2]/2;H[l+4>>2]=H[l+4>>2]/2;e=e+2|0;g=g+2|0;if((b|0)!=(g|0)){continue}break}if(!c){break a}}b=(e<<2)+r|0;H[b>>2]=H[b>>2]/2;break a}if(!b){break a}ia=O(L[pa+32>>2]*O(.5));g=b&3;if(b>>>0>=4){c=b&-4;b=0;while(1){L[r>>2]=ia*O(H[r>>2]);L[r+4>>2]=ia*O(H[r+4>>2]);L[r+8>>2]=ia*O(H[r+8>>2]);L[r+12>>2]=ia*O(H[r+12>>2]);r=r+16|0;b=b+4|0;if((c|0)!=(b|0)){continue}break}if(!g){break a}}b=0;while(1){L[r>>2]=ia*O(H[r>>2]);r=r+4|0;b=b+1|0;if((g|0)!=(b|0)){continue}break}break a}o=wa-va|0;if(H[oa+20>>2]==1){if(!n){break a}f=(H[ma+36>>2]+(N(i,o)<<2)|0)+(j<<2)|0;d=q&-4;i=0;while(1){b=0;if(d){k=f+(N(i,o)<<2)|0;m=(N(i,q)<<2)+r|0;while(1){p=b<<2;t=p+m|0;l=H[t+4>>2];g=H[t+8>>2];c=H[t+12>>2];p=k+p|0;H[p>>2]=H[t>>2]/2;H[p+12>>2]=(c|0)/2;H[p+8>>2]=(g|0)/2;H[p+4>>2]=(l|0)/2;b=b+4|0;if(d>>>0>b>>>0){continue}break}}mj:{if(b>>>0>=q>>>0){break mj}c=b+1|0;l=f+(N(i,o)<<2)|0;g=(N(i,q)<<2)+r|0;if(q-b&1){b=b<<2;H[b+l>>2]=H[b+g>>2]/2;b=c}if((c|0)==(q|0)){break mj}while(1){c=b<<2;H[c+l>>2]=H[c+g>>2]/2;c=c+4|0;H[c+l>>2]=H[c+g>>2]/2;b=b+2|0;if((q|0)!=(b|0)){continue}break}}i=i+1|0;if((n|0)!=(i|0)){continue}break}break a}if(!n|!q){break a}ia=O(L[pa+32>>2]*O(.5));i=(H[ma+36>>2]+(N(i,o)<<2)|0)+(j<<2)|0;g=q&-4;l=q&3;f=0;c=q>>>0<4;while(1){b=i;e=0;nj:{if(!c){while(1){L[b>>2]=ia*O(H[r>>2]);L[b+4>>2]=ia*O(H[r+4>>2]);L[b+8>>2]=ia*O(H[r+8>>2]);L[b+12>>2]=ia*O(H[r+12>>2]);b=b+16|0;r=r+16|0;e=e+4|0;if((g|0)!=(e|0)){continue}break}if(!l){break nj}}e=0;while(1){L[b>>2]=ia*O(H[r>>2]);b=b+4|0;r=r+4|0;e=e+1|0;if((l|0)!=(e|0)){continue}break}}i=(o<<2)+i|0;f=f+1|0;if((n|0)!=(f|0)){continue}break}break a}H[aa>>2]=U;Ba(W,2,8716,aa)}H[H[d>>2]>>2]=0}Ca(a);na=aa+96|0} +function hb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,k=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,z=0,A=0,C=0,D=0,E=0,J=0,M=0,P=0,Q=0,R=0,S=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=O(0),ea=0,fa=0,ga=0,ha=0,ia=0,ja=0,ka=0,la=0,ma=0,oa=0,pa=0,ra=0,sa=0,ta=0,ua=0,va=0,wa=0,xa=0,ya=0,za=0,Aa=0,Ka=0,La=0,Ma=0,Na=0,Oa=0,Qa=0,Ta=0,Va=0,Wa=0,Xa=O(0),Za=O(0),_a=0,$a=0,ab=0,bb=0,eb=0,fb=0,gb=0,hb=0,jb=0,kb=0,lb=0,ob=O(0),pb=0,qb=0,rb=0,sb=0,tb=0;za=na-16|0;na=za;a:{if(!(I[a+8|0]&128)|H[a+228>>2]!=(b|0)){break a}ya=H[a+180>>2]+N(b,5644)|0;V=H[ya+5596>>2];if(!V){ib(ya);break a}g=H[a+100>>2];if(!g){g=H[a+96>>2]}m=H[g>>2];o=H[g+4>>2];n=H[g+8>>2];l=H[g+12>>2];g=H[a+60>>2];h=H[a+64>>2];j=H[ya+5600>>2];pa=na-16|0;na=pa;D=H[a+232>>2];H[D+36>>2]=b;i=H[H[D+28>>2]+76>>2];H[D+64>>2]=1;H[D+60>>2]=l;H[D+56>>2]=n;H[D+52>>2]=o;H[D+48>>2]=m;H[D+32>>2]=i+N(b,5644);Ca(H[D+68>>2]);o=0;H[D+68>>2]=0;b:{if(g){o=Ea(4,H[H[D+24>>2]+16>>2]);if(!o){break b}m=g&3;c:{if(g>>>0>=4){n=g&-4;b=0;while(1){g=h+(X<<2)|0;H[(H[g>>2]<<2)+o>>2]=1;H[(H[g+4>>2]<<2)+o>>2]=1;H[(H[g+8>>2]<<2)+o>>2]=1;H[(H[g+12>>2]<<2)+o>>2]=1;X=X+4|0;b=b+4|0;if((n|0)!=(b|0)){continue}break}if(!m){break c}}while(1){H[(H[h+(X<<2)>>2]<<2)+o>>2]=1;X=X+1|0;v=v+1|0;if((m|0)!=(v|0)){continue}break}}H[D+68>>2]=o}d:{E=H[D+24>>2];s=H[E+16>>2];e:{if(!s){break e}X=0;f:{while(1){g:{if(H[(X<<2)+o>>2]?0:o){break g}h=H[E+24>>2]+N(X,52)|0;b=H[h+4>>2];n=b-1|0;m=H[D+60>>2];l=n+m|0;g=0-!b|0;w=fe(l,m>>>0>l>>>0?g+1|0:g,b,0);h=H[h>>2];v=h-1|0;l=H[D+56>>2];i=v+l|0;m=0-!h|0;r=fe(i,i>>>0>>0?m+1|0:m,h,0);l=n;n=H[D+52>>2];l=l+n|0;n=fe(l,l>>>0>>0?g+1|0:g,b,0);b=H[H[H[D+20>>2]>>2]+20>>2]+N(X,76)|0;g=H[b+20>>2]-H[b+24>>2]|0;if(g>>>0>31){break g}l=H[b+12>>2];i=H[b+8>>2];u=H[b+4>>2];Y=v;v=H[D+48>>2];t=Y+v|0;h=fe(t,t>>>0>>0?m+1|0:m,h,0);b=h-H[b>>2]|0;h:{if((b>>>0<=h>>>0?b:0)>>>g|0){break h}b=n-u|0;if((b>>>0<=n>>>0?b:0)>>>g|0){break h}b=i-r|0;if((b>>>0<=i>>>0?b:0)>>>g|0){break h}b=l-w|0;if(!((b>>>0<=l>>>0?b:0)>>>g|0)){break g}}H[D+64>>2]=0;break f}X=X+1|0;if((s|0)!=(X|0)){continue}break}if(!H[D+64>>2]){break f}v=0;while(1){b=H[H[H[D+20>>2]>>2]+20>>2]+N(v,76)|0;g=H[b+28>>2]+N(H[b+24>>2],152)|0;h=H[g-148>>2];m=H[g-140>>2];o=H[g-152>>2];g=H[g-144>>2];n=H[D+68>>2];i:{if(H[n+(v<<2)>>2]?0:n){break i}n=m-h|0;g=g-o|0;be(n,0,g);if(!(!qa|(h|0)==(m|0))){X=0;Ba(f,1,2982,0);break b}g=N(g,n);if(g>>>0>=1073741824){X=0;Ba(f,1,2982,0);break b}g=g<<2;H[b+44>>2]=g;j:{k:{l:{h=H[b+36>>2];if(h){if(g>>>0<=K[b+48>>2]){break i}if(H[b+40>>2]){break l}}g=Ia(g);H[b+36>>2]=g;l=g;g=H[b+44>>2];if(!(g?l:1)){break k}H[b+40>>2]=1;H[b+48>>2]=g;break i}Ca(h);g=Ia(H[b+44>>2]);H[b+36>>2]=g;if(g){break j}H[b+48>>2]=0;H[b+40>>2]=0;H[b+44>>2]=0}X=0;Ba(f,1,2982,0);break b}H[b+40>>2]=1;H[b+48>>2]=H[b+44>>2]}v=v+1|0;E=H[D+24>>2];if(v>>>0>2]){continue}break}break e}P=H[E+24>>2];A=H[H[H[D+20>>2]>>2]+20>>2];b=0;while(1){m:{if(H[(b<<2)+o>>2]?0:o){break m}g=A+N(b,76)|0;m=H[g>>2];n=P+N(b,52)|0;h=H[n>>2];v=h-1|0;l=H[D+48>>2];w=v+l|0;i=0-!h|0;l=fe(w,l>>>0>w>>>0?i+1|0:i,h,0);m=m>>>0>l>>>0?m:l;H[g+56>>2]=m;l=H[g+4>>2];n=H[n+4>>2];r=n-1|0;u=H[D+52>>2];t=r+u|0;w=0-!n|0;u=fe(t,t>>>0>>0?w+1|0:w,n,0);l=l>>>0>u>>>0?l:u;H[g+60>>2]=l;u=H[g+8>>2];Y=v;v=H[D+56>>2];t=Y+v|0;h=fe(t,t>>>0>>0?i+1|0:i,h,0);h=h>>>0>u>>>0?u:h;H[g+64>>2]=h;i=H[g+12>>2];v=H[D+60>>2];r=r+v|0;n=fe(r,r>>>0>>0?w+1|0:w,n,0);n=i>>>0>>0?i:n;H[g+68>>2]=n;if(h>>>0>>0|l>>>0>n>>>0){break d}r=H[g+20>>2];if(!r){break m}u=n-1|0;C=0-!n|0;S=h-1|0;J=0-!h|0;t=l-1|0;M=0-!l|0;Z=m-1|0;Q=0-!m|0;R=H[g+28>>2];n=0;h=0;while(1){l=R+N(n,152)|0;m=r+(n^-1)|0;g=m&31;if((m&63)>>>0>=32){i=1<>>32-g}g=u+w|0;v=i+C|0;W=g>>>0>>0?v+1|0:v;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+148>>2]=g;g=i+J|0;v=g+1|0;Y=g;g=w+S|0;W=g>>>0>>0?v:Y;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+144>>2]=g;g=i+M|0;v=g+1|0;Y=g;g=t+w|0;W=g>>>0>>0?v:Y;v=m&31;if((m&63)>>>0>=32){g=W>>>v|0}else{g=((1<>>v}H[l+140>>2]=g;g=i+Q|0;w=w+Z|0;i=w>>>0>>0?g+1|0:g;g=m&31;if((m&63)>>>0>=32){g=i>>>g|0}else{g=((1<>>g}H[l+136>>2]=g;n=n+1|0;h=n?h:h+1|0;if(h|(n|0)!=(r|0)){continue}break}}b=b+1|0;if((s|0)!=(b|0)){continue}break}}X=0;H[pa+8>>2]=0;b=H[D+28>>2];W=Ea(1,8);if(W){H[W+4>>2]=b;H[W>>2]=E}if(!W){break b}S=H[H[D+20>>2]>>2];t=na-144|0;na=t;P=H[D+36>>2];b=N(P,5644);h=H[W+4>>2];J=b+H[h+76>>2]|0;E=H[J+420>>2];n=0;m=0;r=na-32|0;na=r;C=b+H[h+76>>2]|0;Q=H[C+420>>2];Z=H[W>>2];u=H[Z+16>>2];l=Fa(N(u,528));n:{if(!l){break n}b=Fa(u<<2);o:{if(!b){b=l;break o}o=H[h+76>>2]+N(P,5644)|0;w=H[o+420>>2];i=w+1|0;g=Ea(i,240);p:{if(g){q:{if(i){s=H[Z+16>>2];v=g;while(1){H[v+236>>2]=f;i=Ea(s,16);H[v+200>>2]=i;if(!i){break q}A=H[Z+16>>2];H[v+196>>2]=A;s=0;i=0;if(A){while(1){s=H[v+200>>2]+(i<<4)|0;A=H[o+5584>>2]+N(i,1080)|0;M=Ea(H[A+4>>2],16);H[s+12>>2]=M;if(!M){break q}H[s+8>>2]=H[A+4>>2];i=i+1|0;s=H[Z+16>>2];if(i>>>0>>0){continue}break}}v=v+240|0;i=(k|0)==(w|0);k=k+1|0;if(!i){continue}break}}break p}o=H[g+4>>2];if(o){Ca(o);H[g+4>>2]=0}v=g;o=0;while(1){i=H[v+200>>2];if(i){s=0;k=H[v+196>>2];if(k){while(1){A=H[i+12>>2];if(A){Ca(A);H[i+12>>2]=0;k=H[v+196>>2]}i=i+16|0;s=s+1|0;if(s>>>0>>0){continue}break}i=H[v+200>>2]}Ca(i);H[v+200>>2]=0}v=v+240|0;i=(o|0)==(w|0);o=o+1|0;if(!i){continue}break}Ca(g)}g=0}if(g){r:{if(!u){break r}w=u&7;o=l;if(u>>>0>=8){v=u&-8;while(1){i=(q<<2)+b|0;H[i+28>>2]=o+3696;H[i+24>>2]=o+3168;H[i+20>>2]=o+2640;H[i+16>>2]=o+2112;H[i+12>>2]=o+1584;H[i+8>>2]=o+1056;H[i+4>>2]=o+528;H[i>>2]=o;q=q+8|0;o=o+4224|0;n=n+8|0;if((v|0)!=(n|0)){continue}break}if(!w){break r}}while(1){H[(q<<2)+b>>2]=o;q=q+1|0;o=o+528|0;p=p+1|0;if((w|0)!=(p|0)){continue}break}}w=b;s=0;v=H[(H[h+76>>2]+N(P,5644)|0)+5584>>2];o=H[Z+24>>2];b=H[h+24>>2];n=(P>>>0)/(b>>>0)|0;b=H[h+4>>2]+N(H[h+12>>2],P-N(b,n)|0)|0;i=H[Z>>2];H[r+20>>2]=b>>>0>i>>>0?b:i;i=b+H[h+12>>2]|0;b=b>>>0>i>>>0?-1:i;i=H[Z+8>>2];H[r+16>>2]=b>>>0>>0?b:i;b=H[h+8>>2]+N(n,H[h+16>>2])|0;n=H[Z+4>>2];H[r+12>>2]=b>>>0>n>>>0?b:n;h=b+H[h+16>>2]|0;b=b>>>0>h>>>0?-1:h;h=H[Z+12>>2];H[r+8>>2]=b>>>0>>0?b:h;H[r+24>>2]=0;H[r+28>>2]=0;H[r+4>>2]=2147483647;H[r>>2]=2147483647;if(H[Z+16>>2]){while(1){h=w?H[w+(s<<2)>>2]:0;b=H[o+4>>2];P=b-1|0;n=H[r+8>>2];p=P+n|0;i=0-!b|0;p=fe(p,n>>>0>p>>>0?i+1|0:i,b,0);n=H[o>>2];A=n-1|0;k=H[r+16>>2];M=A+k|0;q=0-!n|0;k=fe(M,k>>>0>M>>>0?q+1|0:q,n,0);Y=P;P=H[r+12>>2];M=Y+P|0;i=fe(M,P>>>0>M>>>0?i+1|0:i,b,0);b=H[r+20>>2];P=b+A|0;n=fe(P,b>>>0>P>>>0?q+1|0:q,n,0);b=H[v+4>>2];if(b>>>0>K[r+28>>2]){H[r+28>>2]=b;b=H[v+4>>2]}if(b){$=v+944|0;ba=v+812|0;_=p-1|0;ca=0-!p|0;aa=k-1|0;ia=0-!k|0;ka=i-1|0;fa=0-!i|0;ja=n-1|0;ga=0-!n|0;k=0;while(1){n=k<<2;P=H[n+$>>2];A=H[n+ba>>2];q=0;if(h){H[h+4>>2]=P;H[h>>2]=A;q=h+8|0}b=b-1|0;h=A+b|0;s:{if(h>>>0>31){break s}n=H[o>>2];if(n>>>0>-1>>>h>>>0){break s}i=H[r+4>>2];h=n<>2]=h>>>0>i>>>0?i:h}h=b+P|0;t:{if(h>>>0>31){break t}n=H[o+4>>2];if(n>>>0>-1>>>h>>>0){break t}i=H[r>>2];h=n<>2]=h>>>0>i>>>0?i:h}h=0;n=b&31;if((b&63)>>>0>=32){i=1<>>32-n}R=p;p=_+R|0;n=i;i=ca+i|0;z=p>>>0<_>>>0?i+1|0:i;M=b&31;i=P&31;if((P&63)>>>0>=32){i=1<>>32-i}if((b&63)>>>0>=32){z=z>>>M|0}else{z=((1<>>M}p=Y+z|0;la=p-1|0;p=(p>>>0>>0?i+1|0:i)-!p|0;i=P&31;M=n+fa|0;Y=R+ka|0;M=Y>>>0>>0?M+1|0:M;if((P&63)>>>0>=32){i=p>>>i|0}else{i=((1<>>i}p=i;i=b&31;if((b&63)>>>0>=32){i=M>>>i|0}else{i=((1<>>i}M=(i|0)!=(z|0)?p-(i>>>P|0)&-1>>>P:0;i=n+ia|0;Y=i+1|0;p=i;i=R+aa|0;z=i>>>0>>0?Y:p;p=i;P=b&31;i=A&31;if((A&63)>>>0>=32){i=1<>>32-i}if((b&63)>>>0>=32){z=z>>>P|0}else{z=((1<>>P}p=Y+z|0;la=p-1|0;p=(p>>>0>>0?i+1|0:i)-!p|0;i=A&31;n=n+ga|0;R=R+ja|0;P=R>>>0>>0?n+1|0:n;n=b&31;if((A&63)>>>0>=32){i=p>>>i|0}else{i=((1<>>i}if((b&63)>>>0>=32){n=P>>>n|0}else{n=((1<>>n}n=(n|0)!=(z|0)?i-(n>>>A|0)&-1>>>A:0;if(q){H[q+4>>2]=M;H[q>>2]=n;h=q+8|0}n=N(n,M);if(n>>>0>K[r+24>>2]){H[r+24>>2]=n}k=k+1|0;if(k>>>0>2]){continue}break}}o=o+52|0;v=v+1080|0;s=s+1|0;if(s>>>0>2]){continue}break}}A=Q+1|0;k=H[r+28>>2];s=H[r+24>>2];H[g+4>>2]=0;b=H[C+8>>2]+1|0;M=N(s,u);P=N(M,k);be(b,0,P);u:{if(!qa){b=N(b,P);H[g+8>>2]=b;b=Ea(b,2);H[g+4>>2]=b;if(b){break u}}Ca(l);Ca(w);b=H[g+4>>2];if(b){Ca(b);H[g+4>>2]=0}if(!A){b=g;break o}b=0;n=g;while(1){o=H[n+200>>2];if(o){p=0;q=H[n+196>>2];if(q){while(1){h=H[o+12>>2];if(h){Ca(h);H[o+12>>2]=0;q=H[n+196>>2]}o=o+16|0;p=p+1|0;if(q>>>0>p>>>0){continue}break}o=H[n+200>>2]}Ca(o);H[n+200>>2]=0}n=n+240|0;h=(b|0)==(Q|0);b=b+1|0;if(!h){continue}break}b=g;break o}i=H[Z+24>>2];R=H[r+20>>2];H[g+204>>2]=R;_=H[r+12>>2];H[g+208>>2]=_;aa=H[r+16>>2];H[g+212>>2]=aa;ka=H[r+8>>2];H[g+216>>2]=ka;H[g+12>>2]=P;H[g+16>>2]=M;H[g+20>>2]=s;m=1;H[g+24>>2]=1;if(u){n=H[g+200>>2];v=0;b=i;while(1){o=H[w+(v<<2)>>2];H[n>>2]=H[b>>2];H[n+4>>2]=H[b+4>>2];h=H[n+8>>2];v:{if(!h){break v}p=H[n+12>>2];if((h|0)!=1){ja=h&1;h=h&-2;q=0;while(1){H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2];H[p+16>>2]=H[o+16>>2];H[p+20>>2]=H[o+20>>2];H[p+24>>2]=H[o+24>>2];H[p+28>>2]=H[o+28>>2];p=p+32|0;o=o+32|0;q=q+2|0;if((h|0)!=(q|0)){continue}break}if(!ja){break v}}H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2]}b=b+52|0;n=n+16|0;v=v+1|0;if((u|0)!=(v|0)){continue}break}}if(A>>>0>1){h=g;while(1){H[h+456>>2]=ka;H[h+452>>2]=aa;H[h+448>>2]=_;H[h+444>>2]=R;H[h+264>>2]=1;H[h+260>>2]=s;H[h+256>>2]=M;H[h+252>>2]=P;if(u){n=H[h+440>>2];v=0;b=i;while(1){o=H[w+(v<<2)>>2];H[n>>2]=H[b>>2];H[n+4>>2]=H[b+4>>2];q=H[n+8>>2];w:{if(!q){break w}p=H[n+12>>2];if((q|0)!=1){A=q&1;ja=q&-2;q=0;while(1){H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2];H[p+16>>2]=H[o+16>>2];H[p+20>>2]=H[o+20>>2];H[p+24>>2]=H[o+24>>2];H[p+28>>2]=H[o+28>>2];p=p+32|0;o=o+32|0;q=q+2|0;if((ja|0)!=(q|0)){continue}break}if(!A){break w}}H[p>>2]=H[o>>2];H[p+4>>2]=H[o+4>>2];H[p+8>>2]=H[o+8>>2];H[p+12>>2]=H[o+12>>2]}b=b+52|0;n=n+16|0;v=v+1|0;if((u|0)!=(v|0)){continue}break}}b=H[h+8>>2];H[h+244>>2]=H[h+4>>2];H[h+248>>2]=b;b=(m|0)!=(Q|0);h=h+240|0;m=m+1|0;if(b){continue}break}}Ca(l);Ca(w);b=H[C+420>>2];x:{if(I[C+5640|0]&4){if((b|0)==-1){break x}p=C+424|0;h=H[C+8>>2];q=0;o=g;while(1){m=H[p+36>>2];H[o+44>>2]=1;H[o+84>>2]=m;H[o+48>>2]=H[p>>2];m=H[p+4>>2];H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=m;H[o+60>>2]=H[p+12>>2];H[o+64>>2]=H[p+16>>2];m=H[p+8>>2];H[o+76>>2]=s;H[o+56>>2]=h>>>0>m>>>0?m:h;p=p+148|0;o=o+240|0;m=(b|0)==(q|0);q=q+1|0;if(!m){continue}break}break x}if((b|0)==-1){break x}h=H[C+8>>2];m=H[C+4>>2];o=g;if(b){n=b+1&-2;p=0;while(1){H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=0;H[o+44>>2]=1;H[o+48>>2]=0;H[o+84>>2]=m;H[o+60>>2]=k;H[o+324>>2]=m;H[o+76>>2]=s;H[o+56>>2]=h;H[o+308>>2]=0;H[o+312>>2]=0;H[o+292>>2]=0;H[o+284>>2]=1;H[o+288>>2]=0;H[o+300>>2]=k;H[o+296>>2]=h;H[o+316>>2]=s;H[o+64>>2]=H[o+196>>2];H[o+304>>2]=H[o+436>>2];o=o+480|0;p=p+2|0;if((n|0)!=(p|0)){continue}break}if(b&1){break x}}H[o+68>>2]=0;H[o+72>>2]=0;H[o+52>>2]=0;H[o+44>>2]=1;H[o+48>>2]=0;H[o+84>>2]=m;H[o+60>>2]=k;H[o+76>>2]=s;H[o+56>>2]=h;H[o+64>>2]=H[o+196>>2]}m=g;break n}Ca(l)}Ca(b)}na=r+32|0;g=m;y:{z:{if(!g){break z}A=E+1|0;w=g;v=V;A:{B:{while(1){if(H[w+84>>2]==-1){break A}l=Fa(H[Z+16>>2]<<2);if(!l){break A}b=H[Z+16>>2]<<2;if(b){y(l,1,b)}if(Sb(w)){while(1){n=H[S+20>>2];C:{D:{if(K[w+40>>2]>=K[J+12>>2]){break D}b=H[w+32>>2];h=N(H[w+28>>2],76)+n|0;if(b>>>0>=K[h+24>>2]){break D}h=H[h+28>>2]+N(b,152)|0;if(!H[h+24>>2]){break D}m=h+28|0;i=0;E:{while(1){o=m+N(i,36)|0;b=H[o+20>>2]+N(H[w+36>>2],40)|0;if(!wb(D,H[w+28>>2],H[w+32>>2],H[o+16>>2],H[b>>2],H[b+4>>2],H[b+8>>2],H[b+12>>2])){i=i+1|0;if(i>>>0>2]){continue}break E}break}H[l+(H[w+28>>2]<<2)>>2]=0;H[t+136>>2]=0;if(!Rb(H[W+4>>2],H[S+20>>2],J,w,t+140|0,v,t+136|0,j,f)){break B}i=H[w+32>>2];q=H[w+28>>2];u=H[t+136>>2];if(H[t+140>>2]){H[t+136>>2]=0;C=H[(H[S+20>>2]+N(q,76)|0)+28>>2]+N(i,152)|0;n=H[C+24>>2];if(n){P=j-u|0;M=j+v|0;q=C+28|0;k=0;Q=u+v|0;p=Q;r=0;while(1){F:{if(H[q+8>>2]==H[q>>2]|H[q+12>>2]==H[q+4>>2]){break F}b=H[q+20>>2]+N(H[w+36>>2],40)|0;R=N(H[b+20>>2],H[b+16>>2]);if(!R){break F}n=H[b+24>>2];h=0;while(1){o=H[n+36>>2];if(o){G:{if(k|H[n+64>>2]){H[n+52>>2]=0;i=1;b=64;break G}i=H[n>>2];b=H[n+40>>2];H:{if(b){i=N(b,24)+i|0;if(H[i-20>>2]!=H[i-12>>2]){i=i-24|0;break H}b=b+1|0}else{b=1}H[n+40>>2]=b}b=H[i+20>>2];I:{J:{if(b>>>0>(p^-1)>>>0){break J}m=i+20|0;while(1){if(M>>>0>>0){break J}s=H[n+4>>2];k=H[n+52>>2];if((k|0)!=H[n+56>>2]){m=o}else{b=k<<1|1;s=Ga(s,b<<3);if(!s){Ba(f,1,1024,0);break B}H[n+56>>2]=b;H[n+4>>2]=s;k=H[n+52>>2];b=H[m>>2];m=H[n+36>>2]}o=(k<<3)+s|0;H[o+4>>2]=b;H[o>>2]=p;H[n+52>>2]=k+1;H[i>>2]=H[i>>2]+b;s=H[i+16>>2];k=s+H[i+4>>2]|0;H[i+4>>2]=k;o=m-s|0;H[n+36>>2]=o;H[i+8>>2]=k;p=b+p|0;k=0;if((m|0)==(s|0)){break I}H[n+40>>2]=H[n+40>>2]+1;m=i+44|0;b=H[i+44>>2];i=i+24|0;if((p^-1)>>>0>=b>>>0){continue}break}}m=H[w+28>>2];o=H[w+32>>2];i=H[w+36>>2];if(H[H[W+4>>2]+104>>2]){H[t+120>>2]=m;H[t+116>>2]=o;H[t+112>>2]=r;H[t+108>>2]=i;H[t+104>>2]=h;H[t+100>>2]=P;H[t+96>>2]=b;Ba(f,1,14693,t+96|0);break B}H[t+88>>2]=m;H[t+84>>2]=o;H[t+80>>2]=r;H[t+76>>2]=i;H[t+72>>2]=h;H[t+68>>2]=P;H[t+64>>2]=b;Ba(f,2,14693,t- -64|0);H[n+52>>2]=0;H[n+64>>2]=1;k=1}i=H[n+40>>2];b=44}H[b+n>>2]=i}n=n+68|0;h=h+1|0;if((R|0)!=(h|0)){continue}break}n=H[C+24>>2]}q=q+36|0;r=r+1|0;if(r>>>0>>0){continue}break}i=H[w+32>>2];q=H[w+28>>2];b=k?P:p-Q|0}else{b=0}u=b+u|0}h=H[Z+24>>2]+N(q,52)|0;b=H[h+36>>2];H[h+36>>2]=b>>>0>>0?i:b;break C}n=H[S+20>>2]}H[t+136>>2]=0;if(!Rb(H[W+4>>2],n,J,w,t+140|0,v,t+136|0,j,f)){break B}q=H[w+28>>2];u=H[t+136>>2];if(!H[t+140>>2]){break C}M=H[w+32>>2];b=H[(H[S+20>>2]+N(q,76)|0)+28>>2]+N(M,152)|0;Q=H[b+24>>2];if(!Q){break C}P=j-u|0;s=b+28|0;C=H[w+36>>2];i=0;k=0;K:{L:{while(1){M:{if(H[s+8>>2]==H[s>>2]|H[s+12>>2]==H[s+4>>2]){break M}b=H[s+20>>2]+N(C,40)|0;R=N(H[b+20>>2],H[b+16>>2]);if(!R){break M}o=H[b+24>>2];r=0;while(1){b=H[o+36>>2];if(b){n=H[o>>2];h=H[o+40>>2];N:{if(h){n=N(h,24)+n|0;if(H[n-20>>2]!=H[n-12>>2]){n=n-24|0;break N}h=h+1|0}else{h=1}H[o+40>>2]=h}p=H[n+20>>2];i=p+i|0;if(i>>>0