refactoring van de cmap structuren bijna compleet
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
"use strict";
|
||||
|
||||
const XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
||||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
||||
|
||||
/**
|
||||
* Convert the rendered CMap surface into a standalone SVG snapshot.
|
||||
*
|
||||
* HTML concept cards remain inside an XHTML foreignObject while connector SVG
|
||||
* elements stay vector based. The renderer reads the current DOM only; it has
|
||||
* no knowledge of the editor model or persistence layer.
|
||||
*/
|
||||
export class CmapSnapshotSvgRenderer {
|
||||
constructor({ stylesheetFilter = (sheet) => Boolean(sheet.href?.includes("cmap.css")) } = {}) {
|
||||
this.stylesheetFilter = stylesheetFilter;
|
||||
}
|
||||
|
||||
render(surface, { width = null, height = null, includeStyles = true } = {}) {
|
||||
if (!surface || typeof surface.cloneNode !== "function") {
|
||||
throw new TypeError("A rendered CMap surface is required");
|
||||
}
|
||||
const dimensions = this.dimensions(surface, width, height);
|
||||
const clone = surface.cloneNode(true);
|
||||
this.removeInteractiveElements(clone);
|
||||
clone.removeAttribute("id");
|
||||
clone.style.zoom = "1";
|
||||
clone.style.transform = "none";
|
||||
clone.style.width = `${dimensions.width}px`;
|
||||
clone.style.height = `${dimensions.height}px`;
|
||||
|
||||
const svg = document.createElementNS(SVG_NAMESPACE, "svg");
|
||||
svg.setAttribute("xmlns", SVG_NAMESPACE);
|
||||
svg.setAttribute("xmlns:xhtml", XHTML_NAMESPACE);
|
||||
svg.setAttribute("version", "1.1");
|
||||
svg.setAttribute("width", String(dimensions.width));
|
||||
svg.setAttribute("height", String(dimensions.height));
|
||||
svg.setAttribute("viewBox", `0 0 ${dimensions.width} ${dimensions.height}`);
|
||||
svg.setAttribute("preserveAspectRatio", "xMinYMin meet");
|
||||
|
||||
if (includeStyles) {
|
||||
const style = document.createElementNS(SVG_NAMESPACE, "style");
|
||||
style.textContent = this.stylesheetText();
|
||||
svg.append(style);
|
||||
}
|
||||
|
||||
const foreignObject = document.createElementNS(SVG_NAMESPACE, "foreignObject");
|
||||
foreignObject.setAttribute("x", "0");
|
||||
foreignObject.setAttribute("y", "0");
|
||||
foreignObject.setAttribute("width", String(dimensions.width));
|
||||
foreignObject.setAttribute("height", String(dimensions.height));
|
||||
clone.setAttribute("xmlns", XHTML_NAMESPACE);
|
||||
foreignObject.append(clone);
|
||||
svg.append(foreignObject);
|
||||
|
||||
return new XMLSerializer().serializeToString(svg);
|
||||
}
|
||||
|
||||
dimensions(surface, requestedWidth, requestedHeight) {
|
||||
const width = Number(requestedWidth) || Math.ceil(Math.max(
|
||||
surface.scrollWidth || 0,
|
||||
surface.getBoundingClientRect?.().width || 0,
|
||||
...Array.from(surface.children || []).map((child) =>
|
||||
(Number.parseFloat(child.style.left) || 0) + (child.offsetWidth || 0))
|
||||
));
|
||||
const height = Number(requestedHeight) || Math.ceil(Math.max(
|
||||
surface.scrollHeight || 0,
|
||||
surface.getBoundingClientRect?.().height || 0,
|
||||
...Array.from(surface.children || []).map((child) =>
|
||||
(Number.parseFloat(child.style.top) || 0) + (child.offsetHeight || 0))
|
||||
));
|
||||
return {
|
||||
width: Math.max(1, width || 1),
|
||||
height: Math.max(1, height || 1)
|
||||
};
|
||||
}
|
||||
|
||||
removeInteractiveElements(root) {
|
||||
for (const element of root.querySelectorAll(
|
||||
".rw-cmap-handle, .rw-cmap-resize-handle, .rw-cmap-relation-handle, " +
|
||||
".rw-cmap-edit-handle, .rw-cmap-submap-toggle, .rw-cmap-submap-open")) {
|
||||
element.remove();
|
||||
}
|
||||
for (const element of root.querySelectorAll("[aria-selected], [data-rw-cmap-bound]") ) {
|
||||
element.removeAttribute("aria-selected");
|
||||
element.removeAttribute("data-rw-cmap-bound");
|
||||
}
|
||||
}
|
||||
|
||||
stylesheetText() {
|
||||
if (typeof document === "undefined") return "";
|
||||
const rules = [];
|
||||
for (const sheet of Array.from(document.styleSheets || [])) {
|
||||
if (!this.stylesheetFilter(sheet)) continue;
|
||||
try {
|
||||
rules.push(...Array.from(sheet.cssRules || []).map((rule) => rule.cssText));
|
||||
} catch (_error) {
|
||||
// Cross-origin stylesheets cannot be inspected and are skipped.
|
||||
}
|
||||
}
|
||||
return rules.join("\n");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user