UNPKG

g2o-svg

Version:
377 lines (369 loc) 14.7 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('g2o')) : typeof define === 'function' && define.amd ? define(['exports', 'g2o'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.G2OCANVAS = {}, global.g2o)); })(this, (function (exports, g2o) { 'use strict'; class HTMLElementDOM { doc; constructor(doc) { this.doc = doc; } addEventListener(target, name, callback) { target.addEventListener(name, callback); } appendChild(parent, child) { parent.appendChild(child); } getAttribute(element, qualifiedName) { return element.getAttribute(qualifiedName); } getBoundingClientRect(element) { return element.getBoundingClientRect(); } getElementById(elementId) { return document.getElementById(elementId); } isDocumentBody(element) { return element === document.body; } removeEventListener(target, name, callback) { target.removeEventListener(name, callback); } } function mod(v, l) { while (v < 0) { v += l; } return v % l; } const floor = Math.floor; /** * @param {Number} v - Any float * @returns {Number} That float trimmed to the third decimal place. * @description A pretty fast toFixed(3) alternative. * @see {@link http://jsperf.com/parsefloat-tofixed-vs-math-round/18} */ function toFixed(v) { return floor(v * 1000000) / 1000000; } const ns = 'http://www.w3.org/2000/svg'; const xlink = 'http://www.w3.org/1999/xlink'; function createSVGElement(qualifiedName, attrs = {}) { const elem = document.createElementNS(ns, qualifiedName); if (attrs && Object.keys(attrs).length > 0) { setAttributes(elem, attrs); } return elem; } function setAttributes(elem, attrs) { // SVGAttributes does not have an index signature. const styles = attrs; const keys = Object.keys(attrs); for (let i = 0; i < keys.length; i++) { const qualifiedName = keys[i]; const value = styles[qualifiedName]; if (/href/.test(keys[i])) { elem.setAttributeNS(xlink, qualifiedName, value); } else { elem.setAttribute(qualifiedName, value); } } } class SVGViewDOM { downcast(element) { if (element instanceof SVGElement) { return element; } else { throw new Error("element is not an SVGElement"); } } createSVGElement(qualifiedName, attributes = {}) { return createSVGElement(qualifiedName, attributes); } setAttribute(element, qualifiedName, value) { element.setAttribute(qualifiedName, value); } setAttributes(element, attributes) { setAttributes(element, attributes); } removeAttribute(element, qualifiedName) { element.removeAttribute(qualifiedName); } removeAttributes(element, attributes) { svg.removeAttributes(element, attributes); } appendChild(parent, child) { parent.appendChild(child); } removeChild(parent, child) { parent.removeChild(child); } setTextContent(element, textContent) { element.textContent = textContent; } getParentNode(element) { return element.parentNode; } getLastChild(element) { return element.lastChild; } getElementDefs(svg) { return get_svg_element_defs(svg); } setStyle(element, name, value) { element.style[name] = value; } } /** * Finds the SVGDefsElement from the children of the SVGElement. */ function get_svg_element_defs(svg) { const children = svg.children; const N = children.length; for (let i = 0; i < N; i++) { const child = children.item(i); if (child instanceof SVGDefsElement) { return child; } } throw new Error(); } /** * sets the "hidden" _flagUpdate property. */ function set_defs_dirty_flag(defs, dirtyFlag) { // eslint-disable-next-line @typescript-eslint/no-explicit-any defs._flagUpdate = dirtyFlag; } /** * gets the "hidden" _flagUpdate property. */ function get_defs_dirty_flag(defs) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return defs._flagUpdate; } const svg = { ns: 'http://www.w3.org/2000/svg', xlink: 'http://www.w3.org/1999/xlink', // Create an svg namespaced element. createElement: function (qualifiedName, attrs = {}) { const elem = document.createElementNS(svg.ns, qualifiedName); if (attrs && Object.keys(attrs).length > 0) { svg.setAttributes(elem, attrs); } return elem; }, // Add attributes from an svg element. setAttributes: function (elem, attrs) { // SVGAttributes does not have an index signature. const styles = attrs; const keys = Object.keys(attrs); for (let i = 0; i < keys.length; i++) { const qualifiedName = keys[i]; const value = styles[qualifiedName]; if (/href/.test(keys[i])) { elem.setAttributeNS(svg.xlink, qualifiedName, value); } else { elem.setAttribute(qualifiedName, value); } } return this; }, // Remove attributes from an svg element. removeAttributes: function (elem, attrs) { if (elem) { for (const key in attrs) { elem.removeAttribute(key); } return this; } else { throw new Error("elem MUST be defined."); } }, path_from_anchors: function (board, position, attitude, anchors, closed) { // The anchors are user coordinates and don't include the position and attitude of the body. // By switching x amd y here we handle a 90 degree coordinate rotation. // We are not completely done because Text and Images are rotated. const [screenX, screenY] = screen_functions(board); const l = anchors.length; const last = l - 1; let d; // The elusive last Commands.move point let string = ''; for (let i = 0; i < l; i++) { const b = anchors[i]; const prev = closed ? mod(i - 1, l) : Math.max(i - 1, 0); const a = anchors[prev]; let command; let c; let vx, vy, ux, uy, ar, bl, br, cl; let rx, ry, xAxisRotation, largeArcFlag, sweepFlag; let x = toFixed(screenX(b.x, b.y)); let y = toFixed(screenY(b.x, b.y)); switch (b.command) { case g2o.Commands.close: command = g2o.Commands.close; break; case g2o.Commands.arc: rx = b.rx; ry = b.ry; xAxisRotation = b.xAxisRotation; largeArcFlag = b.largeArcFlag; sweepFlag = b.sweepFlag; command = g2o.Commands.arc + ' ' + rx + ' ' + ry + ' ' + xAxisRotation + ' ' + largeArcFlag + ' ' + sweepFlag + ' ' + x + ' ' + y; break; case g2o.Commands.curve: ar = (a.controls && a.controls.b) || g2o.G20.zero; bl = (b.controls && b.controls.a) || g2o.G20.zero; if (a.relative) { vx = toFixed(screenX(ar.x + a.x, ar.y + a.y)); vy = toFixed(screenY(ar.x + a.x, ar.y + a.y)); } else { vx = toFixed(screenX(ar.x, ar.y)); vy = toFixed(screenY(ar.x, ar.y)); } if (b.relative) { ux = toFixed(screenX(bl.x + b.x, bl.y + b.y)); uy = toFixed(screenY(bl.x + b.x, bl.y + b.y)); } else { ux = toFixed(screenX(bl.x, bl.y)); uy = toFixed(screenY(bl.x, bl.y)); } command = ((i === 0) ? g2o.Commands.move : g2o.Commands.curve) + ' ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; break; case g2o.Commands.move: { d = b; command = g2o.Commands.move + ' ' + x + ' ' + y; break; } default: { command = b.command + ' ' + x + ' ' + y; } } // Add a final point and close it off if (i >= last && closed) { if (b.command === g2o.Commands.curve) { // Make sure we close to the most previous Commands.move c = d; br = (b.controls && b.controls.b) || b; cl = (c.controls && c.controls.a) || c; if (b.relative) { vx = toFixed(screenX(br.x + b.x, br.y + b.y)); vy = toFixed(screenY(br.x + b.x, br.y + b.y)); } else { vx = toFixed(screenX(br.x, br.y)); vy = toFixed(screenY(br.x, br.y)); } if (c.relative) { ux = toFixed(screenX(cl.x + c.x, cl.y + c.y)); uy = toFixed(screenY(cl.x + c.x, cl.y + c.y)); } else { ux = toFixed(screenX(cl.x, cl.y)); uy = toFixed(screenY(cl.x, cl.y)); } x = toFixed(screenX(c.x, c.y)); y = toFixed(screenY(c.x, c.y)); command += ' C ' + vx + ' ' + vy + ' ' + ux + ' ' + uy + ' ' + x + ' ' + y; } if (b.command !== g2o.Commands.close) { command += ' Z'; } } string += command + ' '; } return string; }, /** * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/clipPath * @param shape * @param svgElement * @returns */ getClip: function (viewDOM, shape, svgElement) { let clipPath = shape.zzz.svgClipPathElement; if (!clipPath) { clipPath = shape.zzz.svgClipPathElement = viewDOM.createSVGElement('clipPath', { 'clip-rule': 'nonzero' }); } if (viewDOM.getParentNode(clipPath) === null) { viewDOM.appendChild(viewDOM.getElementDefs(svgElement), clipPath); } return clipPath; }, defs: { update: function (svgElement, defs) { if (get_defs_dirty_flag(defs)) { const children = Array.prototype.slice.call(defs.children, 0); for (let i = 0; i < children.length; i++) { const child = children[i]; const id = child.id; const selector = `[fill="url(#${id})"],[stroke="url(#${id})"],[clip-path="url(#${id})"]`; const exists = svgElement.querySelector(selector); if (!exists) { defs.removeChild(child); } } set_defs_dirty_flag(defs, false); } } }, }; /** * If the bounding box is oriented such that y increases in the upwards direction, * exchange the x and y coordinates because we will be applying a 90 degree rotation. */ function screen_functions(board) { if (board.goofy) { if (board.crazy) { // eslint-disable-next-line @typescript-eslint/no-unused-vars return [(x, y) => y, (x, y) => x]; } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars return [(x, y) => x, (x, y) => y]; } } else { if (board.crazy) { // eslint-disable-next-line @typescript-eslint/no-unused-vars return [(x, y) => x, (x, y) => y]; } else { // eslint-disable-next-line @typescript-eslint/no-unused-vars return [(x, y) => y, (x, y) => x]; } } } class SVGViewFactory { constructor() { // Nothing to see here. } createView(viewBox, containerId) { const viewDOM = new SVGViewDOM(); return new g2o.TreeView(viewDOM, viewBox, containerId); } } /** * A convenience function for initializing a new GraphicsBoard using SVG. * @param elementOrId HTML identifier (id) of element in which the board is rendered. * @param options An object that sets some of the board properties. */ function initBoard(elementOrId, options = {}) { const elementDOM = new HTMLElementDOM(window.document); const viewDOM = new SVGViewDOM(); return new g2o.GraphicsBoard(elementOrId, elementDOM, viewDOM, new SVGViewFactory(), options); } exports.SVGViewDOM = SVGViewDOM; exports.SVGViewFactory = SVGViewFactory; exports.initBoard = initBoard; })); //# sourceMappingURL=index.js.map