import {amendNode} from '../lib/dom.js'; import {a, br, button, canvas, div, img, input} from '../lib/html.js'; import {node} from '../lib/nodes.js'; import {BoolSetting} from '../lib/settings.js'; import {ns as svgNS} from '../lib/svg.js'; import {hiddenLayer, layerGrid, layerLight, settingsTicker} from '../ids.js'; import {registerKeyEvent} from '../keys.js'; import mainLang, {makeLangPack} from '../language.js'; import {mapData, panZoom, root} from '../map.js'; import {definitions} from '../map_tokens.js'; import {addPlugin} from '../plugins.js'; import {labels} from '../shared.js'; import {shell, windows} from '../windows.js'; let defs = ""; const icon = `data:image/svg+xml,%3Csvg xmlns="${svgNS}" viewBox="0 0 100 100"%3E%3Cg stroke="%23000"%3E%3Crect x="2" y="2" width="96" height="96" stroke-width="4" stroke-dasharray="20 8" fill="%2388f" /%3E%3Cpath d="M10,40 v-15 q0,-2 2,-2 h75 q2,0 2,2 v17 z" fill="%23aaa" /%3E%3Cpath d="M10,40 v30 q0,2 2,2 h75 q2,0 2,-2 v-30 z m5,-17 v-3 q0,-2 2,-2 h12 q2,0 2,2 v3 z" fill="%23333" /%3E%3Ccircle cx="50" cy="50" r="18" fill="%23888" /%3E%3Ccircle cx="50" cy="50" r="12" fill="%23111" /%3E%3Crect x="70" y="36" width="15" height="8" rx="1" fill="%23cc0" /%3E%3C/g%3E%3Crect x="86" width="3" y="23.5" height="48" fill="rgba(0, 0, 0, 0.25)" /%3E%3Crect x="82" width="3" y="36" height="8" fill="rgba(0, 0, 0, 0.25)" /%3E%3C/svg%3E`, style = ``, walkElements = (n: Element, ctx: CanvasRenderingContext2D, ctm: DOMMatrix, p: Promise) => { const styles = window.getComputedStyle(n); for (const s of styles) { if (s === "display") { if (styles.getPropertyValue(s) === "none") { return p; } } } switch (n.nodeName) { case "image": return p.then(() => { ctx.setTransform(ctm.multiply((n as SVGImageElement).getCTM()!)); ctx.drawImage(n as SVGImageElement, 0, 0, parseInt(n.getAttribute("width")!), parseInt(n.getAttribute("height")!)); ctx.resetTransform(); }); case "rect": if (n.getAttribute("fill")?.startsWith("url(#Pattern_")) { const pattern = definitions.list.get(n.getAttribute("fill")!.slice(5, -1))?.firstChild; if (pattern instanceof SVGImageElement) { return p.then(() => { const fs = ctx.fillStyle, width = parseInt(pattern.getAttribute("width")!), height = parseInt(pattern.getAttribute("height")!), oc = canvas({width, height}); oc.getContext("2d")!.drawImage(pattern, 0, 0, width, height); ctx.fillStyle = ctx.createPattern(oc, "repeat")!; ctx.setTransform(ctm.multiply((n as SVGRectElement).getCTM()!)); ctx.fillRect(0, 0, parseInt(n.getAttribute("width")!), parseInt(n.getAttribute("height")!)); ctx.resetTransform(); ctx.fillStyle = fs; }); } break; } case "polygon": case "ellipse": return p.then(() => new Promise(sfn => { const {width, height} = mapData; img({"src": `data:image/svg+xml,${encodeURIComponent(`${definitions[node].outerHTML}${n.outerHTML}`)}`, width, height, "onload": function(this: HTMLImageElement) { ctx.drawImage(this, 0, 0); sfn(); }}); })); case "defs": defs = style + n.outerHTML; return p; case "g": const id = n.getAttribute("id"); if (id === layerGrid) { if (hideGrid.value) { return p; } return p.then(() => new Promise(sfn => { const {width, height} = mapData; img({"src": `data:image/svg+xml,${encodeURIComponent(`${definitions.list.get("grid")!.outerHTML}${n.innerHTML}`)}`, width, height, "onload": function(this: HTMLImageElement) { ctx.drawImage(this, 0, 0); sfn(); }}); })); } else if (id === layerLight) { for (const c of n.childNodes as any as SVGElement[]) { p = p.then(() => new Promise(sfn => { const {width, height} = mapData; img({"src": `data:image/svg+xml,${encodeURIComponent(`${defs}${c.outerHTML.replace("mix-blend-mode", "mix-blend-mod")}`)}`, "onload": function(this: HTMLImageElement) { ctx.globalCompositeOperation = ((c as SVGElement).style.getPropertyValue("mix-blend-mode") || "none") as GlobalCompositeOperation; ctx.drawImage(this, 0, 0); ctx.globalCompositeOperation = "source-over"; sfn(); }}); })); } return p; } else if (n.classList.contains(hiddenLayer)) { return p; } } for (const c of n.children) { p = walkElements(c, ctx, ctm, p); } return p; }, lang = makeLangPack({ "ENABLE_GRID": "Show Grid on Screenshot", "ENABLE_PNG": "Automatic PNG creation", "ERROR_GENERATING": "Unknown error while generating PNG", "KEY_SCREENSHOT": "Screenshot Key", "SCREENSHOT_TAKE": "Take Screenshot" }), disablePNG = new BoolSetting("plugin-screenshot-png"), hideGrid = new BoolSetting("plugin-screenshot-grid"), makeScreenshot = () => { const {width, height} = mapData, c = canvas({width, height, "style": "max-width: 100%;max-height: 100%"}), ctx = c.getContext("2d")!, ctm = new DOMMatrix().scaleSelf(panZoom.zoom, panZoom.zoom, 1, width / 2, height / 2).inverse(), now = new Date(), title = `${now.getFullYear()}-${("0" + (now.getMonth()+1)).slice(-2)}-${("0" + now.getDate()).slice(-2)}_${("0" + now.getHours()).slice(-2)}-${("0" + now.getMinutes()).slice(-2)}-${("0" + now.getSeconds()).slice(-2)}`; let p = Promise.resolve(); for (const c of root.children) { p = walkElements(c, ctx, ctm, p); } p.then(() => { const link = a({"download": `${title}.png`}, c), w = windows({"window-icon": icon, "window-title": title}, link); amendNode(shell, w); if (!disablePNG.value) { c.toBlob(b => { if (b) { const href = URL.createObjectURL(b); amendNode(link, {href}); amendNode(w, {"onremove": () => URL.revokeObjectURL(href)}); } else { w.alert(mainLang["ERROR"], lang["ERROR_GENERATING"], icon); } }); } }); }; addPlugin("screenshot", { "settings": { "fn": div([ ([[hideGrid, "ENABLE_GRID"], [disablePNG, "ENABLE_PNG"]] as [BoolSetting, keyof typeof lang][]).map(([s, l]) => [labels(input({"type": "checkbox", "class": settingsTicker, "checked": !s.value, "onchange": function(this: HTMLInputElement) { s.set(!this.checked); }}), [lang[l], ": "]), br()]), button({"onclick": makeScreenshot}, lang["SCREENSHOT_TAKE"]) ]) } }); registerKeyEvent("screenshot-key", lang["KEY_SCREENSHOT"], "PrintScreen", (e: KeyboardEvent) => { makeScreenshot(); e.preventDefault(); })[0]();