UNPKG

svgedit

Version:

Powerful SVG-Editor for your browser

385 lines (343 loc) 19.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: extensions/ext-markers/ext-markers.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: extensions/ext-markers/ext-markers.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/** * @file ext-markers.js * * @license Apache-2.0 * * @copyright 2010 Will Schleter based on ext-arrows.js by Copyright(c) 2010 Alexis Deveria * @copyright 2021 OptimistikSAS * * This extension provides for the addition of markers to the either end * or the middle of a line, polyline, path, polygon. * * Markers are graphics * * to simplify the coding and make the implementation as robust as possible, * markers are not shared - every object has its own set of markers. * this relationship is maintained by a naming convention between the * ids of the markers and the ids of the object * * The following restrictions exist for simplicty of use and programming * objects and their markers to have the same color * marker size is fixed * an application specific attribute - se_type - is added to each marker element * to store the type of marker * * @todo * remove some of the restrictions above * */ export default { name: 'markers', async init () { const svgEditor = this const { svgCanvas } = svgEditor const { BatchCommand, RemoveElementCommand, InsertElementCommand } = svgCanvas.history const { $id, addSVGElementsFromJson: addElem } = svgCanvas const mtypes = ['start', 'mid', 'end'] const markerElems = ['line', 'path', 'polyline', 'polygon'] // note - to add additional marker types add them below with a unique id // and add the associated icon(s) to marker-icons.svg // the geometry is normalized to a 100x100 box with the origin at lower left // Safari did not like negative values for low left of viewBox // remember that the coordinate system has +y downward const markerTypes = { nomarker: {}, leftarrow: { element: 'path', attr: { d: 'M0,50 L100,90 L70,50 L100,10 Z' } }, rightarrow: { element: 'path', attr: { d: 'M100,50 L0,90 L30,50 L0,10 Z' } }, box: { element: 'path', attr: { d: 'M20,20 L20,80 L80,80 L80,20 Z' } }, mcircle: { element: 'circle', attr: { r: 30, cx: 50, cy: 50 } } }; // duplicate shapes to support unfilled (open) marker types with an _o suffix ['leftarrow', 'rightarrow', 'box', 'mcircle'].forEach((v) => { markerTypes[v + '_o'] = markerTypes[v] }) /** * @param {Element} elem - A graphic element will have an attribute like marker-start * @param {"marker-start"|"marker-mid"|"marker-end"} attr * @returns {Element} The marker element that is linked to the graphic element */ const getLinked = (elem, attr) => { const str = elem.getAttribute(attr) if (!str) { return null } const m = str.match(/\(#(.*)\)/) // "url(#mkr_end_svg_1)" would give m[1] = "mkr_end_svg_1" if (!m || m.length !== 2) { return null } return svgCanvas.getElement(m[1]) } /** * Toggles context tool panel off/on. * @param {boolean} on * @returns {void} */ const showPanel = (on, elem) => { $id('marker_panel').style.display = (on) ? 'block' : 'none' if (on &amp;&amp; elem) { mtypes.forEach((pos) => { const marker = getLinked(elem, 'marker-' + pos) if (marker?.attributes?.se_type) { $id(`${pos}_marker_list_opts`).setAttribute('value', marker.attributes.se_type.value) } else { $id(`${pos}_marker_list_opts`).setAttribute('value', 'nomarker') } }) } } /** * @param {string} id * @param {""|"nomarker"|"nomarker"|"leftarrow"|"rightarrow"|"textmarker"|"forwardslash"|"reverseslash"|"verticalslash"|"box"|"star"|"xmark"|"triangle"|"mcircle"} seType * @returns {SVGMarkerElement} */ const addMarker = (id, seType) => { const selElems = svgCanvas.getSelectedElements() let marker = svgCanvas.getElement(id) if (marker) { return undefined } if (seType === '' || seType === 'nomarker') { return undefined } const el = selElems[0] const color = el.getAttribute('stroke') const strokeWidth = 10 const refX = 50 const refY = 50 const viewBox = '0 0 100 100' const markerWidth = 5 const markerHeight = 5 if (!markerTypes[seType]) { console.error(`unknown marker type: ${seType}`) return undefined } // create a generic marker marker = addElem({ element: 'marker', attr: { id, markerUnits: 'strokeWidth', orient: 'auto', style: 'pointer-events:none', se_type: seType } }) const mel = addElem(markerTypes[seType]) const fillcolor = (seType.substr(-2) === '_o') ? 'none' : color mel.setAttribute('fill', fillcolor) mel.setAttribute('stroke', color) mel.setAttribute('stroke-width', strokeWidth) marker.append(mel) marker.setAttribute('viewBox', viewBox) marker.setAttribute('markerWidth', markerWidth) marker.setAttribute('markerHeight', markerHeight) marker.setAttribute('refX', refX) marker.setAttribute('refY', refY) svgCanvas.findDefs().append(marker) return marker } /** * @param {Element} elem * @returns {SVGPolylineElement} */ const convertline = (elem) => { // this routine came from the connectors extension // it is needed because midpoint markers don't work with line elements if (elem.tagName !== 'line') { return elem } // Convert to polyline to accept mid-arrow const x1 = Number(elem.getAttribute('x1')) const x2 = Number(elem.getAttribute('x2')) const y1 = Number(elem.getAttribute('y1')) const y2 = Number(elem.getAttribute('y2')) const { id } = elem const midPt = (' ' + ((x1 + x2) / 2) + ',' + ((y1 + y2) / 2) + ' ') const pline = addElem({ element: 'polyline', attr: { points: (x1 + ',' + y1 + midPt + x2 + ',' + y2), stroke: elem.getAttribute('stroke'), 'stroke-width': elem.getAttribute('stroke-width'), fill: 'none', opacity: elem.getAttribute('opacity') || 1 } }) mtypes.forEach((pos) => { // get any existing marker definitions const nam = 'marker-' + pos const m = elem.getAttribute(nam) if (m) { pline.setAttribute(nam, elem.getAttribute(nam)) } }) const batchCmd = new BatchCommand() batchCmd.addSubCommand(new RemoveElementCommand(elem, elem.parentNode)) batchCmd.addSubCommand(new InsertElementCommand(pline)) elem.insertAdjacentElement('afterend', pline) elem.remove() svgCanvas.clearSelection() pline.id = id svgCanvas.addToSelection([pline]) svgCanvas.addCommandToHistory(batchCmd) return pline } /** * * @returns {void} */ const setMarker = (pos, markerType) => { const selElems = svgCanvas.getSelectedElements() if (selElems.length === 0) return const markerName = 'marker-' + pos const el = selElems[0] const marker = getLinked(el, markerName) if (marker) { marker.remove() } el.removeAttribute(markerName) let val = markerType if (val === '') { val = 'nomarker' } if (val === 'nomarker') { svgCanvas.call('changed', selElems) return } // Set marker on element const id = 'mkr_' + pos + '_' + el.id addMarker(id, val) svgCanvas.changeSelectedAttribute(markerName, 'url(#' + id + ')') if (el.tagName === 'line' &amp;&amp; pos === 'mid') { convertline(el) } svgCanvas.call('changed', selElems) } /** * Called when the main system modifies an object. This routine changes * the associated markers to be the same color. * @param {Element} elem * @returns {void} */ const colorChanged = (elem) => { const color = elem.getAttribute('stroke') mtypes.forEach((pos) => { const marker = getLinked(elem, 'marker-' + pos) if (!marker) { return } if (!marker.attributes.se_type) { return } // not created by this extension const ch = marker.lastElementChild if (!ch) { return } const curfill = ch.getAttribute('fill') const curstroke = ch.getAttribute('stroke') if (curfill &amp;&amp; curfill !== 'none') { ch.setAttribute('fill', color) } if (curstroke &amp;&amp; curstroke !== 'none') { ch.setAttribute('stroke', color) } }) } /** * Called when the main system creates or modifies an object. * Its primary purpose is to create new markers for cloned objects. * @param {Element} el * @returns {void} */ const updateReferences = (el) => { const selElems = svgCanvas.getSelectedElements() mtypes.forEach((pos) => { const markerName = 'marker-' + pos const marker = getLinked(el, markerName) if (!marker || !marker.attributes.se_type) { return } // not created by this extension const url = el.getAttribute(markerName) if (url) { const len = el.id.length const linkid = url.substr(-len - 1, len) if (el.id !== linkid) { const newMarkerId = 'mkr_' + pos + '_' + el.id addMarker(newMarkerId, marker.attributes.se_type.value) svgCanvas.changeSelectedAttribute(markerName, 'url(#' + newMarkerId + ')') svgCanvas.call('changed', selElems) } } }) } return { name: svgEditor.i18next.t(`${name}:name`), // The callback should be used to load the DOM with the appropriate UI items callback () { // Add the context panel and its handler(s) const panelTemplate = document.createElement('template') // create the marker panel let innerHTML = '&lt;div id="marker_panel">' mtypes.forEach((pos) => { innerHTML += `&lt;se-list id="${pos}_marker_list_opts" title="tools.${pos}_marker_list_opts" label="" width="22px" height="22px">` Object.entries(markerTypes).forEach(([marker, _mkr]) => { innerHTML += `&lt;se-list-item id="mkr_${pos}_${marker}" value="${marker}" title="tools.mkr_${marker}" src="${marker}.svg" img-height="22px">&lt;/se-list-item>` }) innerHTML += '&lt;/se-list>' }) innerHTML += '&lt;/div>' panelTemplate.innerHTML = innerHTML $id('tools_top').appendChild(panelTemplate.content.cloneNode(true)) // don't display the panels on start showPanel(false) mtypes.forEach((pos) => { $id(`${pos}_marker_list_opts`).addEventListener('change', (evt) => { setMarker(pos, evt.detail.value) }) }) }, selectedChanged (opts) { // Use this to update the current selected elements if (opts.elems.length === 0) showPanel(false) opts.elems.forEach((elem) => { if (elem &amp;&amp; markerElems.includes(elem.tagName)) { if (opts.selectedElement &amp;&amp; !opts.multiselected) { showPanel(true, elem) } else { showPanel(false) } } else { showPanel(false) } }) }, elementChanged (opts) { const elem = opts.elems[0] if (elem &amp;&amp; ( elem.getAttribute('marker-start') || elem.getAttribute('marker-mid') || elem.getAttribute('marker-end') )) { colorChanged(elem) updateReferences(elem) } } } } } </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="editor_extensions_ext-layer_view_locale_zh-CN.module_js.html">editor/extensions/ext-layer_view/locale/zh-CN.js</a></li><li><a href="module-SVGEditor.html">SVGEditor</a></li><li><a href="module-contextmenu.html">contextmenu</a></li><li><a href="module-jGraduate.html">jGraduate</a></li><li><a href="module-jPicker.html">jPicker</a></li><li><a href="module-locale.html">locale</a></li></ul><h3>Externals</h3><ul><li><a href="external-JamilihArray.html">JamilihArray</a></li><li><a href="external-Math.html">Math</a></li><li><a href="external-Window.html">Window</a></li><li><a href="external-jQuery.html">jQuery</a></li></ul><h3>Namespaces</h3><ul><li><a href="external-jQuery.fn.html">fn</a></li><li><a href="external-jQuery.fn.$.fn.jPicker.defaults.html">defaults</a></li><li><a href="external-jQuery.fn.exports.jPickerMethod.html">exports.jPickerMethod</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.html">jGraduateDefaults</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.images.html">images</a></li><li><a href="external-jQuery.fn.jGraduateDefaults.window.html">window</a></li><li><a href="external-jQuery.jGraduate.html">jGraduate</a></li><li><a href="external-jQuery.jPicker.html">jPicker</a></li><li><a href="external-jQuery.jPicker.ColorMethods.html">ColorMethods</a></li></ul><h3>Classes</h3><ul><li><a href="BottomPanel.html">BottomPanel</a></li><li><a href="Dropdown.html">Dropdown</a></li><li><a href="EditorStartup.html">EditorStartup</a></li><li><a href="ElixMenuButton.html">ElixMenuButton</a></li><li><a href="ElixNumberSpinBox.html">ElixNumberSpinBox</a></li><li><a href="ExplorerButton.html">ExplorerButton</a></li><li><a href="FlyingButton.html">FlyingButton</a></li><li><a href="LayersPanel.html">LayersPanel</a></li><li><a href="LeftPanel.html">LeftPanel</a></li><li><a href="MainMenu.html">MainMenu</a></li><li><a href="NumberSpinBox.html">NumberSpinBox</a></li><li><a href="PaintBox.html">PaintBox</a></li><li><a href="PlainNumberSpinBox.html">PlainNumberSpinBox</a></li><li><a href="Rulers.html">Rulers</a></li><li><a href="SEInput.html">SEInput</a></li><li><a href="SEPalette.html">SEPalette</a></li><li><a href="SESpinInput.html">SESpinInput</a></li><li><a href="SeCMenuDialog.html">SeCMenuDialog</a></li><li><a href="SeCMenuLayerDialog.html">SeCMenuLayerDialog</a></li><li><a href="SeColorPicker.html">SeColorPicker</a></li><li><a href="SeEditPrefsDialog.html">SeEditPrefsDialog</a></li><li><a href="SeExportDialog.html">SeExportDialog</a></li><li><a href="SeImgPropDialog.html">SeImgPropDialog</a></li><li><a href="SeList.html">SeList</a></li><li><a href="SeMenu.html">SeMenu</a></li><li><a href="SeMenuItem.html">SeMenuItem</a></li><li><a href="SePlainAlertDialog.html">SePlainAlertDialog</a></li><li><a href="SePlainBorderButton.html">SePlainBorderButton</a></li><li><a href="SePromptDialog.html">SePromptDialog</a></li><li><a href="SeStorageDialog.html">SeStorageDialog</a></li><li><a href="SeSvgSourceEditorDialog.html">SeSvgSourceEditorDialog</a></li><li><a href="SeText.html">SeText</a></li><li><a href="ToolButton.html">ToolButton</a></li><li><a href="TopPanel.html">TopPanel</a></li><li><a href="configObj.html">configObj</a></li><li><a href="external-jQuery.jGraduate.Paint.html">Paint</a></li><li><a href="external-jQuery.jPicker.Color.html">Color</a></li><li><a href="module.exports.html">exports</a></li><li><a href="module.exports_module.exports.html">exports</a></li><li><a href="module-SVGEditor-Editor.html">Editor</a></li><li><a href="module-jPicker.module.exports.html">module.exports</a></li></ul><h3>Interfaces</h3><ul><li><a href="module-SVGEditor.Config.html">Config</a></li><li><a href="module-SVGEditor.Prefs.html">Prefs</a></li><li><a href="module-SVGthis.CustomHandler.html">CustomHandler</a></li><li><a href="module-locale.LocaleEditorInit.html">LocaleEditorInit</a></li></ul><h3>Events</h3><ul><li><a href="module-SVGEditor.html#event:event:svgEditorReadyEvent">svgEditorReadyEvent</a></li></ul><h3>Tutorials</h3><ul><li><a href="tutorial-CanvasAPI.html">CanvasAPI</a></li><li><a href="tutorial-Editor.html">Editor</a></li><li><a href="tutorial-EditorAPI.html">EditorAPI</a></li><li><a href="tutorial-Events.html">Events</a></li><li><a href="tutorial-FrequentlyAskedQuestions.html">Frequently Asked Questions (FAQ)</a></li></ul><h3>Global</h3><ul><li><a href="global.html#attributeChangedCallback">attributeChangedCallback</a></li><li><a href="global.html#connectedCallback">connectedCallback</a></li><li><a href="global.html#constructor">constructor</a></li><li><a href="global.html#createTemplate">createTemplate</a></li><li><a href="global.html#decrement">decrement</a></li><li><a href="global.html#expireCookie">expireCookie</a></li><li><a href="global.html#formatValueFormatthenumericvalueasastring.Thisisusedafterincrementing/decrementingthevaluetoreformatthevalueasastring.">formatValue Format the numeric value as a string. This is used after incrementing/decrementing the value to reformat the value as a string.</a></li><li><a href="global.html#get">get</a></li><li><a href="global.html#handleClick">handleClick</a></li><li><a href="global.html#handleClose">handleClose</a></li><li><a href="global.html#handleInput">handleInput</a></li><li><a href="global.html#handleKeyDown">handleKeyDown</a></li><li><a href="global.html#handleMouseDown">handleMouseDown</a></li><li><a href="global.html#handleMouseUp">handleMouseUp</a></li><li><a href="global.html#handleOptionsChange">handleOptionsChange</a></li><li><a href="global.html#handleSelect">handleSelect</a></li><li><a href="global.html#handleShow">handleShow</a></li><li><a href="global.html#increment">increment</a></li><li><a href="global.html#init">init</a></li><li><a href="global.html#inputsize">inputsize</a></li><li><a href="global.html#isNullish">isNullish</a></li><li><a href="global.html#loadloadConfig">load load Config</a></li><li><a href="global.html#loadFromURLLoadconfig/datafromURLifgiven">loadFromURL Load config/data from URL if given</a></li><li><a href="global.html#name">name</a></li><li><a href="global.html#observedAttributes">observedAttributes</a></li><li><a href="global.html#parseValue">parseValue</a></li><li><a href="global.html#pref">pref</a></li><li><a href="global.html#readySignal">readySignal</a></li><li><a href="global.html#regexEscape">regexEscape</a></li><li><a href="global.html#removeStoragePrefCookie">removeStoragePrefCookie</a></li><li><a href="global.html#replaceStoragePrompt">replaceStoragePrompt</a></li><li><a href="global.html#set">set</a></li><li><a href="global.html#setupCurConfig">setupCurConfig</a></li><li><a href="global.html#setupCurPrefs">setupCurPrefs</a></li><li><a href="global.html#src">src</a></li><li><a href="global.html#stateEffects">stateEffects</a></li><li><a href="global.html#stepDown">stepDown</a></li><li><a href="global.html#stepUp">stepUp</a></li><li><a href="global.html#triggerInputChanged">triggerInputChanged</a></li><li><a href="global.html#updateLib">updateLib</a></li><li><a href="global.html#value">value</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.5</a> on Sun Dec 07 2025 19:46:40 GMT+0100 (Central European Standard Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>