@svgedit/svgcanvas 7.2.0 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core/utilities.js CHANGED
@@ -7,14 +7,18 @@
7
7
  */
8
8
 
9
9
  import { NS } from './namespaces.js'
10
- import { setUnitAttr, getTypeMap } from './units.js'
10
+ import { setUnitAttr, getTypeMap, shortFloat } from './units.js'
11
11
  import {
12
- hasMatrixTransform, transformListToTransform, transformBox
12
+ hasMatrixTransform,
13
+ transformListToTransform,
14
+ transformBox,
15
+ getTransformList
13
16
  } from './math.js'
14
17
  import { getClosest, mergeDeep } from '../common/util.js'
15
18
 
16
19
  // Much faster than running getBBox() every time
17
- const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use,clipPath'
20
+ const visElems =
21
+ 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use,clipPath'
18
22
  const visElemsArr = visElems.split(',')
19
23
  // const hidElems = 'defs,desc,feGaussianBlur,filter,linearGradient,marker,mask,metadata,pattern,radialGradient,stop,switch,symbol,title,textPath';
20
24
 
@@ -22,15 +26,15 @@ let svgCanvas = null
22
26
  let svgroot_ = null
23
27
 
24
28
  /**
25
- * Object with the following keys/values.
26
- * @typedef {PlainObject} module:utilities.SVGElementJSON
27
- * @property {string} element - Tag name of the SVG element to create
28
- * @property {PlainObject<string, string>} attr - Has key-value attributes to assign to the new element.
29
- * An `id` should be set so that {@link module:utilities.EditorContext#addSVGElementsFromJson} can later re-identify the element for modification or replacement.
30
- * @property {boolean} [curStyles=false] - Indicates whether current style attributes should be applied first
31
- * @property {module:utilities.SVGElementJSON[]} [children] - Data objects to be added recursively as children
32
- * @property {string} [namespace="http://www.w3.org/2000/svg"] - Indicate a (non-SVG) namespace
33
- */
29
+ * Object with the following keys/values.
30
+ * @typedef {PlainObject} module:utilities.SVGElementJSON
31
+ * @property {string} element - Tag name of the SVG element to create
32
+ * @property {PlainObject<string, string>} attr - Has key-value attributes to assign to the new element.
33
+ * An `id` should be set so that {@link module:utilities.EditorContext#addSVGElementsFromJson} can later re-identify the element for modification or replacement.
34
+ * @property {boolean} [curStyles=false] - Indicates whether current style attributes should be applied first
35
+ * @property {module:utilities.SVGElementJSON[]} [children] - Data objects to be added recursively as children
36
+ * @property {string} [namespace="http://www.w3.org/2000/svg"] - Indicate a (non-SVG) namespace
37
+ */
34
38
 
35
39
  /**
36
40
  * An object that creates SVG elements for the canvas.
@@ -49,38 +53,38 @@ let svgroot_ = null
49
53
  * @function module:utilities.EditorContext#addSVGElementsFromJson
50
54
  * @param {module:utilities.SVGElementJSON} data
51
55
  * @returns {Element} The new element
52
- */
56
+ */
53
57
  /**
54
58
  * @function module:utilities.EditorContext#getSelectedElements
55
59
  * @returns {Element[]} the array with selected DOM elements
56
- */
60
+ */
57
61
  /**
58
62
  * @function module:utilities.EditorContext#getDOMDocument
59
63
  * @returns {HTMLDocument}
60
- */
64
+ */
61
65
  /**
62
66
  * @function module:utilities.EditorContext#getDOMContainer
63
67
  * @returns {HTMLElement}
64
- */
68
+ */
65
69
  /**
66
70
  * @function module:utilities.EditorContext#getSvgRoot
67
71
  * @returns {SVGSVGElement}
68
- */
72
+ */
69
73
  /**
70
74
  * @function module:utilities.EditorContext#getBaseUnit
71
75
  * @returns {string}
72
- */
76
+ */
73
77
  /**
74
78
  * @function module:utilities.EditorContext#getSnappingStep
75
79
  * @returns {Float|string}
76
- */
80
+ */
77
81
 
78
82
  /**
79
- * @function module:utilities.init
80
- * @param {module:utilities.EditorContext} canvas
81
- * @returns {void}
82
- */
83
- export const init = (canvas) => {
83
+ * @function module:utilities.init
84
+ * @param {module:utilities.EditorContext} canvas
85
+ * @returns {void}
86
+ */
87
+ export const init = canvas => {
84
88
  svgCanvas = canvas
85
89
  svgroot_ = canvas.getSvgRoot()
86
90
  }
@@ -92,19 +96,19 @@ export const init = (canvas) => {
92
96
  * @returns {string} The string with entity declarations in the internal subset removed
93
97
  * @todo This might be needed in other places `parseFromString` is used even without LGTM flagging
94
98
  */
95
- export const dropXMLInternalSubset = (str) => {
99
+ export const dropXMLInternalSubset = str => {
96
100
  return str.replace(/(<!DOCTYPE\s+\w*\s*\[).*(\?]>)/, '$1$2')
97
101
  // return str.replace(/(?<doctypeOpen><!DOCTYPE\s+\w*\s*\[).*(?<doctypeClose>\?\]>)/, '$<doctypeOpen>$<doctypeClose>');
98
102
  }
99
103
 
100
104
  /**
101
- * Converts characters in a string to XML-friendly entities.
102
- * @function module:utilities.toXml
103
- * @example `&` becomes `&amp;`
104
- * @param {string} str - The string to be converted
105
- * @returns {string} The converted string
106
- */
107
- export const toXml = (str) => {
105
+ * Converts characters in a string to XML-friendly entities.
106
+ * @function module:utilities.toXml
107
+ * @example `&` becomes `&amp;`
108
+ * @param {string} str - The string to be converted
109
+ * @returns {string} The converted string
110
+ */
111
+ export const toXml = str => {
108
112
  // &apos; is ok in XML, but not HTML
109
113
  // &gt; does not normally need escaping, though it can if within a CDATA expression (and preceded by "]]")
110
114
  return str
@@ -123,11 +127,11 @@ export const toXml = (str) => {
123
127
  // also precalculate the size of the array needed.
124
128
 
125
129
  /**
126
- * Converts a string to base64.
127
- * @function module:utilities.encode64
128
- * @param {string} input
129
- * @returns {string} Base64 output
130
- */
130
+ * Converts a string to base64.
131
+ * @function module:utilities.encode64
132
+ * @param {string} input
133
+ * @returns {string} Base64 output
134
+ */
131
135
  export function encode64 (input) {
132
136
  // base64 strings are 4/3 larger than the original string
133
137
  input = encodeUTF8(input) // convert non-ASCII characters
@@ -135,11 +139,11 @@ export function encode64 (input) {
135
139
  }
136
140
 
137
141
  /**
138
- * Converts a string from base64.
139
- * @function module:utilities.decode64
140
- * @param {string} input Base64-encoded input
141
- * @returns {string} Decoded output
142
- */
142
+ * Converts a string from base64.
143
+ * @function module:utilities.decode64
144
+ * @param {string} input Base64-encoded input
145
+ * @returns {string} Decoded output
146
+ */
143
147
  export function decode64 (input) {
144
148
  return decodeUTF8(window.atob(input))
145
149
  }
@@ -155,28 +159,28 @@ export function hashCode (word) {
155
159
  if (word.length === 0) return hash
156
160
  for (let i = 0; i < word.length; i++) {
157
161
  chr = word.charCodeAt(i)
158
- hash = ((hash << 5) - hash) + chr
162
+ hash = (hash << 5) - hash + chr
159
163
  hash |= 0 // Convert to 32bit integer
160
164
  }
161
165
  return hash
162
166
  }
163
167
 
164
168
  /**
165
- * @function module:utilities.decodeUTF8
166
- * @param {string} argString
167
- * @returns {string}
168
- */
169
+ * @function module:utilities.decodeUTF8
170
+ * @param {string} argString
171
+ * @returns {string}
172
+ */
169
173
  export function decodeUTF8 (argString) {
170
174
  return decodeURIComponent(escape(argString))
171
175
  }
172
176
 
173
177
  // codedread:does not seem to work with webkit-based browsers on OSX // Brettz9: please test again as function upgraded
174
178
  /**
175
- * @function module:utilities.encodeUTF8
176
- * @param {string} argString
177
- * @returns {string}
178
- */
179
- export const encodeUTF8 = (argString) => {
179
+ * @function module:utilities.encodeUTF8
180
+ * @param {string} argString
181
+ * @returns {string}
182
+ */
183
+ export const encodeUTF8 = argString => {
180
184
  return unescape(encodeURIComponent(argString))
181
185
  }
182
186
 
@@ -186,8 +190,13 @@ export const encodeUTF8 = (argString) => {
186
190
  * @param {string} dataurl
187
191
  * @returns {string} object URL or empty string
188
192
  */
189
- export const dataURLToObjectURL = (dataurl) => {
190
- if (typeof Uint8Array === 'undefined' || typeof Blob === 'undefined' || typeof URL === 'undefined' || !URL.createObjectURL) {
193
+ export const dataURLToObjectURL = dataurl => {
194
+ if (
195
+ typeof Uint8Array === 'undefined' ||
196
+ typeof Blob === 'undefined' ||
197
+ typeof URL === 'undefined' ||
198
+ !URL.createObjectURL
199
+ ) {
191
200
  return ''
192
201
  }
193
202
  const arr = dataurl.split(',')
@@ -213,7 +222,7 @@ export const dataURLToObjectURL = (dataurl) => {
213
222
  * @param {Blob} blob A Blob object or File object
214
223
  * @returns {string} object URL or empty string
215
224
  */
216
- export const createObjectURL = (blob) => {
225
+ export const createObjectURL = blob => {
217
226
  if (!blob || typeof URL === 'undefined' || !URL.createObjectURL) {
218
227
  return ''
219
228
  }
@@ -227,39 +236,43 @@ export const blankPageObjectURL = (() => {
227
236
  if (typeof Blob === 'undefined') {
228
237
  return ''
229
238
  }
230
- const blob = new Blob(['<html><head><title>SVG-edit</title></head><body>&nbsp;</body></html>'], { type: 'text/html' })
239
+ const blob = new Blob(
240
+ ['<html><head><title>SVG-edit</title></head><body>&nbsp;</body></html>'],
241
+ { type: 'text/html' }
242
+ )
231
243
  return createObjectURL(blob)
232
244
  })()
233
245
 
234
246
  /**
235
- * Converts a string to use XML references (for non-ASCII).
236
- * @function module:utilities.convertToXMLReferences
237
- * @param {string} input
238
- * @returns {string} Decimal numeric character references
239
- */
240
- export const convertToXMLReferences = (input) => {
241
- let output = '';
242
- [...input].forEach((ch) => {
247
+ * Converts a string to use XML references (for non-ASCII).
248
+ * @function module:utilities.convertToXMLReferences
249
+ * @param {string} input
250
+ * @returns {string} Decimal numeric character references
251
+ */
252
+ export const convertToXMLReferences = input => {
253
+ let output = ''
254
+ ;[...input].forEach(ch => {
243
255
  const c = ch.charCodeAt()
244
- output += (c <= 127) ? ch : `&#${c};`
256
+ output += c <= 127 ? ch : `&#${c};`
245
257
  })
246
258
  return output
247
259
  }
248
260
 
249
261
  /**
250
- * Cross-browser compatible method of converting a string to an XML tree.
251
- * Found this function [here]{@link http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f}.
252
- * @function module:utilities.text2xml
253
- * @param {string} sXML
254
- * @throws {Error}
255
- * @returns {XMLDocument}
256
- */
257
- export const text2xml = (sXML) => {
262
+ * Cross-browser compatible method of converting a string to an XML tree.
263
+ * Found this function [here]{@link http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f}.
264
+ * @function module:utilities.text2xml
265
+ * @param {string} sXML
266
+ * @throws {Error}
267
+ * @returns {XMLDocument}
268
+ */
269
+ export const text2xml = sXML => {
258
270
  if (sXML.includes('<svg:svg')) {
259
271
  sXML = sXML.replace(/<(\/?)svg:/g, '<$1').replace('xmlns:svg', 'xmlns')
260
272
  }
261
273
 
262
- let out; let dXML
274
+ let out
275
+ let dXML
263
276
  try {
264
277
  dXML = new DOMParser()
265
278
  dXML.async = false
@@ -268,41 +281,43 @@ export const text2xml = (sXML) => {
268
281
  }
269
282
  try {
270
283
  out = dXML.parseFromString(sXML, 'text/xml')
271
- } catch (e2) { throw new Error('Error parsing XML string') }
284
+ } catch (e2) {
285
+ throw new Error('Error parsing XML string')
286
+ }
272
287
  return out
273
288
  }
274
289
 
275
290
  /**
276
- * @typedef {PlainObject} module:utilities.BBoxObject (like `DOMRect`)
277
- * @property {Float} x
278
- * @property {Float} y
279
- * @property {Float} width
280
- * @property {Float} height
281
- */
291
+ * @typedef {PlainObject} module:utilities.BBoxObject (like `DOMRect`)
292
+ * @property {Float} x
293
+ * @property {Float} y
294
+ * @property {Float} width
295
+ * @property {Float} height
296
+ */
282
297
 
283
298
  /**
284
- * Converts a `SVGRect` into an object.
285
- * @function module:utilities.bboxToObj
286
- * @param {SVGRect} bbox - a SVGRect
287
- * @returns {module:utilities.BBoxObject} An object with properties names x, y, width, height.
288
- */
299
+ * Converts a `SVGRect` into an object.
300
+ * @function module:utilities.bboxToObj
301
+ * @param {SVGRect} bbox - a SVGRect
302
+ * @returns {module:utilities.BBoxObject} An object with properties names x, y, width, height.
303
+ */
289
304
  export const bboxToObj = ({ x, y, width, height }) => {
290
305
  return { x, y, width, height }
291
306
  }
292
307
 
293
308
  /**
294
- * @callback module:utilities.TreeWalker
295
- * @param {Element} elem - DOM element being traversed
296
- * @returns {void}
297
- */
309
+ * @callback module:utilities.TreeWalker
310
+ * @param {Element} elem - DOM element being traversed
311
+ * @returns {void}
312
+ */
298
313
 
299
314
  /**
300
- * Walks the tree and executes the callback on each element in a top-down fashion.
301
- * @function module:utilities.walkTree
302
- * @param {Element} elem - DOM element to traverse
303
- * @param {module:utilities.TreeWalker} cbFn - Callback function to run on each element
304
- * @returns {void}
305
- */
315
+ * Walks the tree and executes the callback on each element in a top-down fashion.
316
+ * @function module:utilities.walkTree
317
+ * @param {Element} elem - DOM element to traverse
318
+ * @param {module:utilities.TreeWalker} cbFn - Callback function to run on each element
319
+ * @returns {void}
320
+ */
306
321
  export const walkTree = (elem, cbFn) => {
307
322
  if (elem?.nodeType === 1) {
308
323
  cbFn(elem)
@@ -314,13 +329,13 @@ export const walkTree = (elem, cbFn) => {
314
329
  }
315
330
 
316
331
  /**
317
- * Walks the tree and executes the callback on each element in a depth-first fashion.
318
- * @function module:utilities.walkTreePost
319
- * @todo Shouldn't this be calling walkTreePost?
320
- * @param {Element} elem - DOM element to traverse
321
- * @param {module:utilities.TreeWalker} cbFn - Callback function to run on each element
322
- * @returns {void}
323
- */
332
+ * Walks the tree and executes the callback on each element in a depth-first fashion.
333
+ * @function module:utilities.walkTreePost
334
+ * @todo Shouldn't this be calling walkTreePost?
335
+ * @param {Element} elem - DOM element to traverse
336
+ * @param {module:utilities.TreeWalker} cbFn - Callback function to run on each element
337
+ * @returns {void}
338
+ */
324
339
  export const walkTreePost = (elem, cbFn) => {
325
340
  if (elem?.nodeType === 1) {
326
341
  let i = elem.childNodes.length
@@ -332,15 +347,15 @@ export const walkTreePost = (elem, cbFn) => {
332
347
  }
333
348
 
334
349
  /**
335
- * Extracts the URL from the `url(...)` syntax of some attributes.
336
- * Three variants:
337
- * - `<circle fill="url(someFile.svg#foo)" />`
338
- * - `<circle fill="url('someFile.svg#foo')" />`
339
- * - `<circle fill='url("someFile.svg#foo")' />`
340
- * @function module:utilities.getUrlFromAttr
341
- * @param {string} attrVal The attribute value as a string
342
- * @returns {string} String with just the URL, like "someFile.svg#foo"
343
- */
350
+ * Extracts the URL from the `url(...)` syntax of some attributes.
351
+ * Three variants:
352
+ * - `<circle fill="url(someFile.svg#foo)" />`
353
+ * - `<circle fill="url('someFile.svg#foo')" />`
354
+ * - `<circle fill='url("someFile.svg#foo")' />`
355
+ * @function module:utilities.getUrlFromAttr
356
+ * @param {string} attrVal The attribute value as a string
357
+ * @returns {string} String with just the URL, like "someFile.svg#foo"
358
+ */
344
359
  export const getUrlFromAttr = function (attrVal) {
345
360
  if (attrVal) {
346
361
  // url('#somegrad')
@@ -359,29 +374,29 @@ export const getUrlFromAttr = function (attrVal) {
359
374
  }
360
375
 
361
376
  /**
362
- * @function module:utilities.getHref
363
- * @param {Element} elem
364
- * @returns {string} The given element's `xlink:href` value
365
- */
377
+ * @function module:utilities.getHref
378
+ * @param {Element} elem
379
+ * @returns {string} The given element's `xlink:href` value
380
+ */
366
381
  export let getHref = function (elem) {
367
382
  return elem.getAttributeNS(NS.XLINK, 'href')
368
383
  }
369
384
 
370
385
  /**
371
- * Sets the given element's `xlink:href` value.
372
- * @function module:utilities.setHref
373
- * @param {Element} elem
374
- * @param {string} val
375
- * @returns {void}
376
- */
386
+ * Sets the given element's `xlink:href` value.
387
+ * @function module:utilities.setHref
388
+ * @param {Element} elem
389
+ * @param {string} val
390
+ * @returns {void}
391
+ */
377
392
  export let setHref = function (elem, val) {
378
393
  elem.setAttributeNS(NS.XLINK, 'xlink:href', val)
379
394
  }
380
395
 
381
396
  /**
382
- * @function module:utilities.findDefs
383
- * @returns {SVGDefsElement} The document's `<defs>` element, creating it first if necessary
384
- */
397
+ * @function module:utilities.findDefs
398
+ * @returns {SVGDefsElement} The document's `<defs>` element, creating it first if necessary
399
+ */
385
400
  export const findDefs = function () {
386
401
  const svgElement = svgCanvas.getSvgContent()
387
402
  let defs = svgElement.getElementsByTagNameNS(NS.SVG, 'defs')
@@ -403,12 +418,12 @@ export const findDefs = function () {
403
418
  // TODO(codedread): Consider moving the next to functions to bbox.js
404
419
 
405
420
  /**
406
- * Get correct BBox for a path in Webkit.
407
- * Converted from code found [here]{@link http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html}.
408
- * @function module:utilities.getPathBBox
409
- * @param {SVGPathElement} path - The path DOM element to get the BBox for
410
- * @returns {module:utilities.BBoxObject} A BBox-like object
411
- */
421
+ * Get correct BBox for a path in Webkit.
422
+ * Converted from code found [here]{@link http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html}.
423
+ * @function module:utilities.getPathBBox
424
+ * @param {SVGPathElement} path - The path DOM element to get the BBox for
425
+ * @returns {module:utilities.BBoxObject} A BBox-like object
426
+ */
412
427
  export const getPathBBox = function (path) {
413
428
  const seglist = path.pathSegList
414
429
  const tot = seglist.numberOfItems
@@ -419,17 +434,23 @@ export const getPathBBox = function (path) {
419
434
 
420
435
  const getCalc = function (j, P1, P2, P3) {
421
436
  return function (t) {
422
- return 1 - t ** 3 * P0[j] +
423
- 3 * 1 - t ** 2 * t * P1[j] +
437
+ return (
438
+ 1 -
439
+ t ** 3 * P0[j] +
440
+ 3 * 1 -
441
+ t ** 2 * t * P1[j] +
424
442
  3 * (1 - t) * t ** 2 * P2[j] +
425
443
  t ** 3 * P3[j]
444
+ )
426
445
  }
427
446
  }
428
447
 
429
448
  for (let i = 0; i < tot; i++) {
430
449
  const seg = seglist.getItem(i)
431
450
 
432
- if (seg.x === undefined) { continue }
451
+ if (seg.x === undefined) {
452
+ continue
453
+ }
433
454
 
434
455
  // Add actual points to limits
435
456
  bounds[0].push(P0[0])
@@ -448,7 +469,9 @@ export const getPathBBox = function (path) {
448
469
  const c = 3 * P1[j] - 3 * P0[j]
449
470
 
450
471
  if (a === 0) {
451
- if (b === 0) { continue }
472
+ if (b === 0) {
473
+ continue
474
+ }
452
475
  const t = -c / b
453
476
  if (t > 0 && t < 1) {
454
477
  bounds[j].push(calc(t))
@@ -456,11 +479,17 @@ export const getPathBBox = function (path) {
456
479
  continue
457
480
  }
458
481
  const b2ac = b ** 2 - 4 * c * a
459
- if (b2ac < 0) { continue }
482
+ if (b2ac < 0) {
483
+ continue
484
+ }
460
485
  const t1 = (-b + Math.sqrt(b2ac)) / (2 * a)
461
- if (t1 > 0 && t1 < 1) { bounds[j].push(calc(t1)) }
486
+ if (t1 > 0 && t1 < 1) {
487
+ bounds[j].push(calc(t1))
488
+ }
462
489
  const t2 = (-b - Math.sqrt(b2ac)) / (2 * a)
463
- if (t2 > 0 && t2 < 1) { bounds[j].push(calc(t2)) }
490
+ if (t2 > 0 && t2 < 1) {
491
+ bounds[j].push(calc(t2))
492
+ }
464
493
  }
465
494
  P0 = P3
466
495
  } else {
@@ -482,15 +511,17 @@ export const getPathBBox = function (path) {
482
511
  }
483
512
 
484
513
  /**
485
- * Get the given/selected element's bounding box object, convert it to be more
486
- * usable when necessary.
487
- * @function module:utilities.getBBox
488
- * @param {Element} elem - Optional DOM element to get the BBox for
489
- * @returns {module:utilities.BBoxObject} Bounding box object
490
- */
514
+ * Get the given/selected element's bounding box object, convert it to be more
515
+ * usable when necessary.
516
+ * @function module:utilities.getBBox
517
+ * @param {Element} elem - Optional DOM element to get the BBox for
518
+ * @returns {module:utilities.BBoxObject} Bounding box object
519
+ */
491
520
  export const getBBox = function (elem) {
492
521
  const selected = elem || svgCanvas.getSelectedElements()[0]
493
- if (elem.nodeType !== 1) { return null }
522
+ if (elem.nodeType !== 1) {
523
+ return null
524
+ }
494
525
  const elname = selected.nodeName
495
526
 
496
527
  let ret = null
@@ -512,7 +543,6 @@ export const getBBox = function (elem) {
512
543
  }
513
544
  break
514
545
  default:
515
-
516
546
  if (elname === 'use') {
517
547
  ret = selected.getBBox() // , true);
518
548
  } else if (visElemsArr.includes(elname)) {
@@ -520,8 +550,8 @@ export const getBBox = function (elem) {
520
550
  try {
521
551
  ret = selected.getBBox()
522
552
  } catch (err) {
523
- // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268
524
- // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835
553
+ // tspan (and textPath apparently) have no `getBBox` in Firefox: https://bugzilla.mozilla.org/show_bug.cgi?id=937268
554
+ // Re: Chrome returning bbox for containing text element, see: https://bugs.chromium.org/p/chromium/issues/detail?id=349835
525
555
  const extent = selected.getExtentOfChar(0) // pos+dimensions of the first glyph
526
556
  const width = selected.getComputedTextLength() // width of the tspan
527
557
  ret = {
@@ -532,7 +562,7 @@ export const getBBox = function (elem) {
532
562
  }
533
563
  }
534
564
  } else {
535
- // Check if element is child of a foreignObject
565
+ // Check if element is child of a foreignObject
536
566
  const fo = getClosest(selected.parentNode, 'foreignObject')
537
567
  if (fo.length && fo[0].getBBox) {
538
568
  ret = fo[0].getBBox()
@@ -549,26 +579,26 @@ export const getBBox = function (elem) {
549
579
  }
550
580
 
551
581
  /**
552
- * @typedef {GenericArray} module:utilities.PathSegmentArray
553
- * @property {Integer} length 2
554
- * @property {"M"|"L"|"C"|"Z"} 0
555
- * @property {Float[]} 1
556
- */
582
+ * @typedef {GenericArray} module:utilities.PathSegmentArray
583
+ * @property {Integer} length 2
584
+ * @property {"M"|"L"|"C"|"Z"} 0
585
+ * @property {Float[]} 1
586
+ */
557
587
 
558
588
  /**
559
- * Create a path 'd' attribute from path segments.
560
- * Each segment is an array of the form: `[singleChar, [x,y, x,y, ...]]`
561
- * @function module:utilities.getPathDFromSegments
562
- * @param {module:utilities.PathSegmentArray[]} pathSegments - An array of path segments to be converted
563
- * @returns {string} The converted path d attribute.
564
- */
589
+ * Create a path 'd' attribute from path segments.
590
+ * Each segment is an array of the form: `[singleChar, [x,y, x,y, ...]]`
591
+ * @function module:utilities.getPathDFromSegments
592
+ * @param {module:utilities.PathSegmentArray[]} pathSegments - An array of path segments to be converted
593
+ * @returns {string} The converted path d attribute.
594
+ */
565
595
  export const getPathDFromSegments = function (pathSegments) {
566
596
  let d = ''
567
597
 
568
598
  pathSegments.forEach(function ([singleChar, pts], _j) {
569
599
  d += singleChar
570
600
  for (let i = 0; i < pts.length; i += 2) {
571
- d += (pts[i] + ',' + pts[i + 1]) + ' '
601
+ d += pts[i] + ',' + pts[i + 1] + ' '
572
602
  }
573
603
  })
574
604
 
@@ -576,15 +606,17 @@ export const getPathDFromSegments = function (pathSegments) {
576
606
  }
577
607
 
578
608
  /**
579
- * Make a path 'd' attribute from a simple SVG element shape.
580
- * @function module:utilities.getPathDFromElement
581
- * @param {Element} elem - The element to be converted
582
- * @returns {string} The path d attribute or `undefined` if the element type is unknown.
583
- */
609
+ * Make a path 'd' attribute from a simple SVG element shape.
610
+ * @function module:utilities.getPathDFromElement
611
+ * @param {Element} elem - The element to be converted
612
+ * @returns {string} The path d attribute or `undefined` if the element type is unknown.
613
+ */
584
614
  export const getPathDFromElement = function (elem) {
585
615
  // Possibly the cubed root of 6, but 1.81 works best
586
616
  let num = 1.81
587
- let d; let rx; let ry
617
+ let d
618
+ let rx
619
+ let ry
588
620
  switch (elem.tagName) {
589
621
  case 'ellipse':
590
622
  case 'circle': {
@@ -597,24 +629,26 @@ export const getPathDFromElement = function (elem) {
597
629
  rx = ry
598
630
  }
599
631
  d = getPathDFromSegments([
600
- ['M', [(cx - rx), (cy)]],
601
- ['C', [(cx - rx), (cy - ry / num), (cx - rx / num), (cy - ry), (cx), (cy - ry)]],
602
- ['C', [(cx + rx / num), (cy - ry), (cx + rx), (cy - ry / num), (cx + rx), (cy)]],
603
- ['C', [(cx + rx), (cy + ry / num), (cx + rx / num), (cy + ry), (cx), (cy + ry)]],
604
- ['C', [(cx - rx / num), (cy + ry), (cx - rx), (cy + ry / num), (cx - rx), (cy)]],
632
+ ['M', [cx - rx, cy]],
633
+ ['C', [cx - rx, cy - ry / num, cx - rx / num, cy - ry, cx, cy - ry]],
634
+ ['C', [cx + rx / num, cy - ry, cx + rx, cy - ry / num, cx + rx, cy]],
635
+ ['C', [cx + rx, cy + ry / num, cx + rx / num, cy + ry, cx, cy + ry]],
636
+ ['C', [cx - rx / num, cy + ry, cx - rx, cy + ry / num, cx - rx, cy]],
605
637
  ['Z', []]
606
638
  ])
607
639
  break
608
- } case 'path':
640
+ }
641
+ case 'path':
609
642
  d = elem.getAttribute('d')
610
643
  break
611
- case 'line': {
612
- const x1 = elem.getAttribute('x1')
613
- const y1 = elem.getAttribute('y1')
614
- const x2 = elem.getAttribute('x2')
615
- const y2 = elem.getAttribute('y2')
616
- d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2
617
- }
644
+ case 'line':
645
+ {
646
+ const x1 = elem.getAttribute('x1')
647
+ const y1 = elem.getAttribute('y1')
648
+ const x2 = elem.getAttribute('x2')
649
+ const y2 = elem.getAttribute('y2')
650
+ d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2
651
+ }
618
652
  break
619
653
  case 'polyline':
620
654
  d = 'M' + elem.getAttribute('points')
@@ -647,14 +681,25 @@ export const getPathDFromElement = function (elem) {
647
681
  ['L', [x + w - rx, y]],
648
682
  ['C', [x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry]],
649
683
  ['L', [x + w, y + h - ry]],
650
- ['C', [x + w, y + h - ry / num, x + w - rx / num, y + h, x + w - rx, y + h]],
684
+ [
685
+ 'C',
686
+ [
687
+ x + w,
688
+ y + h - ry / num,
689
+ x + w - rx / num,
690
+ y + h,
691
+ x + w - rx,
692
+ y + h
693
+ ]
694
+ ],
651
695
  ['L', [x + rx, y + h]],
652
696
  ['C', [x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry]],
653
697
  ['L', [x, y + ry]],
654
698
  ['Z', []]
655
699
  ])
656
700
  break
657
- } default:
701
+ }
702
+ default:
658
703
  break
659
704
  }
660
705
 
@@ -662,33 +707,39 @@ export const getPathDFromElement = function (elem) {
662
707
  }
663
708
 
664
709
  /**
665
- * Get a set of attributes from an element that is useful for convertToPath.
666
- * @function module:utilities.getExtraAttributesForConvertToPath
667
- * @param {Element} elem - The element to be probed
668
- * @returns {PlainObject<"marker-start"|"marker-end"|"marker-mid"|"filter"|"clip-path", string>} An object with attributes.
669
- */
710
+ * Get a set of attributes from an element that is useful for convertToPath.
711
+ * @function module:utilities.getExtraAttributesForConvertToPath
712
+ * @param {Element} elem - The element to be probed
713
+ * @returns {PlainObject<"marker-start"|"marker-end"|"marker-mid"|"filter"|"clip-path", string>} An object with attributes.
714
+ */
670
715
  export const getExtraAttributesForConvertToPath = function (elem) {
671
- const attrs = {};
716
+ const attrs = {}
672
717
  // TODO: make this list global so that we can properly maintain it
673
718
  // TODO: what about @transform, @clip-rule, @fill-rule, etc?
674
- ['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'].forEach(function (item) {
675
- const a = elem.getAttribute(item)
676
- if (a) {
677
- attrs[item] = a
719
+ ;['marker-start', 'marker-end', 'marker-mid', 'filter', 'clip-path'].forEach(
720
+ function (item) {
721
+ const a = elem.getAttribute(item)
722
+ if (a) {
723
+ attrs[item] = a
724
+ }
678
725
  }
679
- })
726
+ )
680
727
  return attrs
681
728
  }
682
729
 
683
730
  /**
684
- * Get the BBox of an element-as-path.
685
- * @function module:utilities.getBBoxOfElementAsPath
686
- * @param {Element} elem - The DOM element to be probed
687
- * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
688
- * @param {module:path.pathActions} pathActions - If a transform exists, `pathActions.resetOrientation()` is used. See: canvas.pathActions.
689
- * @returns {DOMRect|false} The resulting path's bounding box object.
690
- */
691
- export const getBBoxOfElementAsPath = function (elem, addSVGElementsFromJson, pathActions) {
731
+ * Get the BBox of an element-as-path.
732
+ * @function module:utilities.getBBoxOfElementAsPath
733
+ * @param {Element} elem - The DOM element to be probed
734
+ * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
735
+ * @param {module:path.pathActions} pathActions - If a transform exists, `pathActions.resetOrientation()` is used. See: canvas.pathActions.
736
+ * @returns {DOMRect|false} The resulting path's bounding box object.
737
+ */
738
+ export const getBBoxOfElementAsPath = function (
739
+ elem,
740
+ addSVGElementsFromJson,
741
+ pathActions
742
+ ) {
692
743
  const path = addSVGElementsFromJson({
693
744
  element: 'path',
694
745
  attr: getExtraAttributesForConvertToPath(elem)
@@ -726,18 +777,18 @@ export const getBBoxOfElementAsPath = function (elem, addSVGElementsFromJson, pa
726
777
  }
727
778
 
728
779
  /**
729
- * Convert selected element to a path.
730
- * @function module:utilities.convertToPath
731
- * @param {Element} elem - The DOM element to be converted
732
- * @param {module:utilities.SVGElementJSON} attrs - Apply attributes to new path. see canvas.convertToPath
733
- * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
734
- * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
735
- * @param {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} clearSelection - see [canvas.clearSelection]{@link module:svgcanvas.SvgCanvas#clearSelection}
736
- * @param {module:path.EditorContext#addToSelection} addToSelection - see [canvas.addToSelection]{@link module:svgcanvas.SvgCanvas#addToSelection}
737
- * @param {module:history} hstry - see history module
738
- * @param {module:path.EditorContext#addCommandToHistory|module:draw.DrawCanvasInit#addCommandToHistory} addCommandToHistory - see [canvas.addCommandToHistory]{@link module:svgcanvas~addCommandToHistory}
739
- * @returns {SVGPathElement|null} The converted path element or null if the DOM element was not recognized.
740
- */
780
+ * Convert selected element to a path.
781
+ * @function module:utilities.convertToPath
782
+ * @param {Element} elem - The DOM element to be converted
783
+ * @param {module:utilities.SVGElementJSON} attrs - Apply attributes to new path. see canvas.convertToPath
784
+ * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
785
+ * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
786
+ * @param {module:draw.DrawCanvasInit#clearSelection|module:path.EditorContext#clearSelection} clearSelection - see [canvas.clearSelection]{@link module:svgcanvas.SvgCanvas#clearSelection}
787
+ * @param {module:path.EditorContext#addToSelection} addToSelection - see [canvas.addToSelection]{@link module:svgcanvas.SvgCanvas#addToSelection}
788
+ * @param {module:history} hstry - see history module
789
+ * @param {module:path.EditorContext#addCommandToHistory|module:draw.DrawCanvasInit#addCommandToHistory} addCommandToHistory - see [canvas.addCommandToHistory]{@link module:svgcanvas~addCommandToHistory}
790
+ * @returns {SVGPathElement|null} The converted path element or null if the DOM element was not recognized.
791
+ */
741
792
  export const convertToPath = (elem, attrs, svgCanvas) => {
742
793
  const batchCmd = new svgCanvas.history.BatchCommand('Convert element to Path')
743
794
 
@@ -770,14 +821,20 @@ export const convertToPath = (elem, attrs, svgCanvas) => {
770
821
 
771
822
  // Reorient if it has a matrix
772
823
  if (eltrans) {
773
- const tlist = path.transform.baseVal
824
+ const tlist = getTransformList(path)
774
825
  if (hasMatrixTransform(tlist)) {
775
826
  svgCanvas.pathActions.resetOrientation(path)
776
827
  }
777
828
  }
778
829
 
779
830
  const { nextSibling } = elem
780
- batchCmd.addSubCommand(new svgCanvas.history.RemoveElementCommand(elem, nextSibling, elem.parentNode))
831
+ batchCmd.addSubCommand(
832
+ new svgCanvas.history.RemoveElementCommand(
833
+ elem,
834
+ nextSibling,
835
+ elem.parentNode
836
+ )
837
+ )
781
838
  svgCanvas.clearSelection()
782
839
  elem.remove() // We need to remove this element otherwise the nextSibling of 'path' won't be null and an exception will be thrown after subsequent undo and redos.
783
840
 
@@ -796,25 +853,25 @@ export const convertToPath = (elem, attrs, svgCanvas) => {
796
853
  }
797
854
 
798
855
  /**
799
- * Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when
800
- * the rotation angle is a multiple of 90 degrees and there are no complex transforms.
801
- * Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it.
802
- *
803
- * The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated
804
- * about it's center.
805
- *
806
- * The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses
807
- * that width and height, and applies any transforms to get the final bbox. This means the calculated bbox
808
- * is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the
809
- * same bbox.
810
- *
811
- * The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call
812
- * getBBox then apply the angle and any transforms.
813
- *
814
- * @param {Float} angle - The rotation angle in degrees
815
- * @param {boolean} hasAMatrixTransform - True if there is a matrix transform
816
- * @returns {boolean} True if the bbox can be optimized.
817
- */
856
+ * Can the bbox be optimized over the native getBBox? The optimized bbox is the same as the native getBBox when
857
+ * the rotation angle is a multiple of 90 degrees and there are no complex transforms.
858
+ * Getting an optimized bbox can be dramatically slower, so we want to make sure it's worth it.
859
+ *
860
+ * The best example for this is a circle rotate 45 degrees. The circle doesn't get wider or taller when rotated
861
+ * about it's center.
862
+ *
863
+ * The standard, unoptimized technique gets the native bbox of the circle, rotates the box 45 degrees, uses
864
+ * that width and height, and applies any transforms to get the final bbox. This means the calculated bbox
865
+ * is much wider than the original circle. If the angle had been 0, 90, 180, etc. both techniques render the
866
+ * same bbox.
867
+ *
868
+ * The optimization is not needed if the rotation is a multiple 90 degrees. The default technique is to call
869
+ * getBBox then apply the angle and any transforms.
870
+ *
871
+ * @param {Float} angle - The rotation angle in degrees
872
+ * @param {boolean} hasAMatrixTransform - True if there is a matrix transform
873
+ * @returns {boolean} True if the bbox can be optimized.
874
+ */
818
875
  function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasAMatrixTransform) {
819
876
  const angleModulo90 = angle % 90
820
877
  const closeTo90 = angleModulo90 < -89.99 || angleModulo90 > 89.99
@@ -823,14 +880,18 @@ function bBoxCanBeOptimizedOverNativeGetBBox (angle, hasAMatrixTransform) {
823
880
  }
824
881
 
825
882
  /**
826
- * Get bounding box that includes any transforms.
827
- * @function module:utilities.getBBoxWithTransform
828
- * @param {Element} elem - The DOM element to be converted
829
- * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
830
- * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
831
- * @returns {module:utilities.BBoxObject|module:math.TransformedBox|DOMRect} A single bounding box object
832
- */
833
- export const getBBoxWithTransform = function (elem, addSVGElementsFromJson, pathActions) {
883
+ * Get bounding box that includes any transforms.
884
+ * @function module:utilities.getBBoxWithTransform
885
+ * @param {Element} elem - The DOM element to be converted
886
+ * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
887
+ * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
888
+ * @returns {module:utilities.BBoxObject|module:math.TransformedBox|DOMRect} A single bounding box object
889
+ */
890
+ export const getBBoxWithTransform = function (
891
+ elem,
892
+ addSVGElementsFromJson,
893
+ pathActions
894
+ ) {
834
895
  // TODO: Fix issue with rotated groups. Currently they work
835
896
  // fine in FF, but not in other browsers (same problem mentioned
836
897
  // in Issue 339 comment #2).
@@ -841,7 +902,7 @@ export const getBBoxWithTransform = function (elem, addSVGElementsFromJson, path
841
902
  return null
842
903
  }
843
904
 
844
- const tlist = elem.transform.baseVal
905
+ const tlist = getTransformList(elem)
845
906
  const angle = getRotationAngleFromTransformList(tlist)
846
907
  const hasMatrixXForm = hasMatrixTransform(tlist)
847
908
 
@@ -852,14 +913,22 @@ export const getBBoxWithTransform = function (elem, addSVGElementsFromJson, path
852
913
  // TODO: why ellipse and not circle
853
914
  const elemNames = ['ellipse', 'path', 'line', 'polyline', 'polygon']
854
915
  if (elemNames.includes(elem.tagName)) {
855
- goodBb = getBBoxOfElementAsPath(elem, addSVGElementsFromJson, pathActions)
916
+ goodBb = getBBoxOfElementAsPath(
917
+ elem,
918
+ addSVGElementsFromJson,
919
+ pathActions
920
+ )
856
921
  bb = goodBb
857
922
  } else if (elem.tagName === 'rect') {
858
923
  // Look for radius
859
924
  const rx = Number(elem.getAttribute('rx'))
860
925
  const ry = Number(elem.getAttribute('ry'))
861
926
  if (rx || ry) {
862
- goodBb = getBBoxOfElementAsPath(elem, addSVGElementsFromJson, pathActions)
927
+ goodBb = getBBoxOfElementAsPath(
928
+ elem,
929
+ addSVGElementsFromJson,
930
+ pathActions
931
+ )
863
932
  bb = goodBb
864
933
  }
865
934
  }
@@ -879,9 +948,9 @@ export const getBBoxWithTransform = function (elem, addSVGElementsFromJson, path
879
948
  * @todo This is problematic with large stroke-width and, for example, a single
880
949
  * horizontal line. The calculated BBox extends way beyond left and right sides.
881
950
  */
882
- const getStrokeOffsetForBBox = (elem) => {
951
+ const getStrokeOffsetForBBox = elem => {
883
952
  const sw = elem.getAttribute('stroke-width')
884
- return (!isNaN(sw) && elem.getAttribute('stroke') !== 'none') ? sw / 2 : 0
953
+ return !isNaN(sw) && elem.getAttribute('stroke') !== 'none' ? sw / 2 : 0
885
954
  }
886
955
 
887
956
  /**
@@ -893,25 +962,33 @@ const getStrokeOffsetForBBox = (elem) => {
893
962
  */
894
963
 
895
964
  /**
896
- * Get the bounding box for one or more stroked and/or transformed elements.
897
- * @function module:utilities.getStrokedBBox
898
- * @param {Element[]} elems - Array with DOM elements to check
899
- * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
900
- * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
901
- * @returns {module:utilities.BBoxObject|module:math.TransformedBox|DOMRect} A single bounding box object
902
- */
965
+ * Get the bounding box for one or more stroked and/or transformed elements.
966
+ * @function module:utilities.getStrokedBBox
967
+ * @param {Element[]} elems - Array with DOM elements to check
968
+ * @param {module:utilities.EditorContext#addSVGElementsFromJson} addSVGElementsFromJson - Function to add the path element to the current layer. See canvas.addSVGElementsFromJson
969
+ * @param {module:path.pathActions} pathActions - If a transform exists, pathActions.resetOrientation() is used. See: canvas.pathActions.
970
+ * @returns {module:utilities.BBoxObject|module:math.TransformedBox|DOMRect} A single bounding box object
971
+ */
903
972
  export const getStrokedBBox = (elems, addSVGElementsFromJson, pathActions) => {
904
- if (!elems || !elems.length) { return false }
973
+ if (!elems || !elems.length) {
974
+ return false
975
+ }
905
976
 
906
977
  let fullBb
907
- elems.forEach((elem) => {
908
- if (fullBb) { return }
909
- if (!elem.parentNode) { return }
978
+ elems.forEach(elem => {
979
+ if (fullBb) {
980
+ return
981
+ }
982
+ if (!elem.parentNode) {
983
+ return
984
+ }
910
985
  fullBb = getBBoxWithTransform(elem, addSVGElementsFromJson, pathActions)
911
986
  })
912
987
 
913
988
  // This shouldn't ever happen...
914
- if (!fullBb) { return null }
989
+ if (!fullBb) {
990
+ return null
991
+ }
915
992
 
916
993
  // fullBb doesn't include the stoke, so this does no good!
917
994
  // if (elems.length == 1) return fullBb;
@@ -929,8 +1006,12 @@ export const getStrokedBBox = (elems, addSVGElementsFromJson, pathActions) => {
929
1006
  maxX += offset
930
1007
  maxY += offset
931
1008
  } else {
932
- elems.forEach((elem) => {
933
- const curBb = getBBoxWithTransform(elem, addSVGElementsFromJson, pathActions)
1009
+ elems.forEach(elem => {
1010
+ const curBb = getBBoxWithTransform(
1011
+ elem,
1012
+ addSVGElementsFromJson,
1013
+ pathActions
1014
+ )
934
1015
  if (curBb) {
935
1016
  const offset = getStrokeOffsetForBBox(elem)
936
1017
  minX = Math.min(minX, curBb.x - offset)
@@ -944,28 +1025,33 @@ export const getStrokedBBox = (elems, addSVGElementsFromJson, pathActions) => {
944
1025
  })
945
1026
  }
946
1027
 
947
- fullBb.x = minX
948
- fullBb.y = minY
949
- fullBb.width = maxX - minX
950
- fullBb.height = maxY - minY
1028
+ fullBb.x = shortFloat(minX)
1029
+ fullBb.y = shortFloat(minY)
1030
+ fullBb.width = shortFloat(maxX - minX)
1031
+ fullBb.height = shortFloat(maxY - minY)
951
1032
  return fullBb
952
1033
  }
953
1034
 
954
1035
  /**
955
- * Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc).
956
- * Note that 0-opacity, off-screen etc elements are still considered "visible"
957
- * for this function.
958
- * @function module:utilities.getVisibleElements
959
- * @param {Element} parentElement - The parent DOM element to search within
960
- * @returns {Element[]} All "visible" elements.
961
- */
962
- export const getVisibleElements = (parentElement) => {
1036
+ * Get all elements that have a BBox (excludes `<defs>`, `<title>`, etc).
1037
+ * Note that 0-opacity, off-screen etc elements are still considered "visible"
1038
+ * for this function.
1039
+ * @function module:utilities.getVisibleElements
1040
+ * @param {Element} parentElement - The parent DOM element to search within
1041
+ * @returns {Element[]} All "visible" elements.
1042
+ */
1043
+ export const getVisibleElements = parentElement => {
963
1044
  if (!parentElement) {
964
1045
  const svgContent = svgCanvas.getSvgContent()
965
1046
  for (let i = 0; i < svgContent.children.length; i++) {
966
1047
  if (svgContent.children[i].getBBox) {
967
1048
  const bbox = svgContent.children[i].getBBox()
968
- if (bbox.width !== 0 && bbox.height !== 0 && bbox.width !== 0 && bbox.height !== 0) {
1049
+ if (
1050
+ bbox.width !== 0 &&
1051
+ bbox.height !== 0 &&
1052
+ bbox.width !== 0 &&
1053
+ bbox.height !== 0
1054
+ ) {
969
1055
  parentElement = svgContent.children[i]
970
1056
  break
971
1057
  }
@@ -977,7 +1063,7 @@ export const getVisibleElements = (parentElement) => {
977
1063
  if (parentElement) {
978
1064
  const children = parentElement.children
979
1065
  // eslint-disable-next-line array-callback-return
980
- Array.from(children, (elem) => {
1066
+ Array.from(children, elem => {
981
1067
  if (elem.getBBox) {
982
1068
  contentElems.push(elem)
983
1069
  }
@@ -987,13 +1073,15 @@ export const getVisibleElements = (parentElement) => {
987
1073
  }
988
1074
 
989
1075
  /**
990
- * Get the bounding box for one or more stroked and/or transformed elements.
991
- * @function module:utilities.getStrokedBBoxDefaultVisible
992
- * @param {Element[]} elems - Array with DOM elements to check
993
- * @returns {module:utilities.BBoxObject} A single bounding box object
994
- */
995
- export const getStrokedBBoxDefaultVisible = (elems) => {
996
- if (!elems) { elems = getVisibleElements() }
1076
+ * Get the bounding box for one or more stroked and/or transformed elements.
1077
+ * @function module:utilities.getStrokedBBoxDefaultVisible
1078
+ * @param {Element[]} elems - Array with DOM elements to check
1079
+ * @returns {module:utilities.BBoxObject} A single bounding box object
1080
+ */
1081
+ export const getStrokedBBoxDefaultVisible = elems => {
1082
+ if (!elems) {
1083
+ elems = getVisibleElements()
1084
+ }
997
1085
  return getStrokedBBox(
998
1086
  elems,
999
1087
  svgCanvas.addSVGElementsFromJson,
@@ -1002,53 +1090,55 @@ export const getStrokedBBoxDefaultVisible = (elems) => {
1002
1090
  }
1003
1091
 
1004
1092
  /**
1005
- * Get the rotation angle of the given transform list.
1006
- * @function module:utilities.getRotationAngleFromTransformList
1007
- * @param {SVGTransformList} tlist - List of transforms
1008
- * @param {boolean} toRad - When true returns the value in radians rather than degrees
1009
- * @returns {Float} The angle in degrees or radians
1010
- */
1093
+ * Get the rotation angle of the given transform list.
1094
+ * @function module:utilities.getRotationAngleFromTransformList
1095
+ * @param {SVGTransformList} tlist - List of transforms
1096
+ * @param {boolean} toRad - When true returns the value in radians rather than degrees
1097
+ * @returns {Float} The angle in degrees or radians
1098
+ */
1011
1099
  export const getRotationAngleFromTransformList = (tlist, toRad) => {
1012
- if (!tlist) { return 0 } // <svg> element have no tlist
1100
+ if (!tlist) {
1101
+ return 0
1102
+ } // <svg> element have no tlist
1013
1103
  for (let i = 0; i < tlist.numberOfItems; ++i) {
1014
1104
  const xform = tlist.getItem(i)
1015
1105
  if (xform.type === 4) {
1016
- return toRad ? xform.angle * Math.PI / 180.0 : xform.angle
1106
+ return toRad ? (xform.angle * Math.PI) / 180.0 : xform.angle
1017
1107
  }
1018
1108
  }
1019
1109
  return 0.0
1020
1110
  }
1021
1111
 
1022
1112
  /**
1023
- * Get the rotation angle of the given/selected DOM element.
1024
- * @function module:utilities.getRotationAngle
1025
- * @param {Element} [elem] - DOM element to get the angle for. Default to first of selected elements.
1026
- * @param {boolean} [toRad=false] - When true returns the value in radians rather than degrees
1027
- * @returns {Float} The angle in degrees or radians
1028
- */
1113
+ * Get the rotation angle of the given/selected DOM element.
1114
+ * @function module:utilities.getRotationAngle
1115
+ * @param {Element} [elem] - DOM element to get the angle for. Default to first of selected elements.
1116
+ * @param {boolean} [toRad=false] - When true returns the value in radians rather than degrees
1117
+ * @returns {Float} The angle in degrees or radians
1118
+ */
1029
1119
  export let getRotationAngle = (elem, toRad) => {
1030
1120
  const selected = elem || svgCanvas.getSelectedElements()[0]
1031
1121
  // find the rotation transform (if any) and set it
1032
- const tlist = selected.transform?.baseVal
1122
+ const tlist = getTransformList(selected)
1033
1123
  return getRotationAngleFromTransformList(tlist, toRad)
1034
1124
  }
1035
1125
 
1036
1126
  /**
1037
- * Get the reference element associated with the given attribute value.
1038
- * @function module:utilities.getRefElem
1039
- * @param {string} attrVal - The attribute value as a string
1040
- * @returns {Element} Reference element
1041
- */
1042
- export const getRefElem = (attrVal) => {
1127
+ * Get the reference element associated with the given attribute value.
1128
+ * @function module:utilities.getRefElem
1129
+ * @param {string} attrVal - The attribute value as a string
1130
+ * @returns {Element} Reference element
1131
+ */
1132
+ export const getRefElem = attrVal => {
1043
1133
  return getElement(getUrlFromAttr(attrVal).substr(1))
1044
1134
  }
1045
1135
  /**
1046
- * Get the reference element associated with the given attribute value.
1047
- * @function module:utilities.getFeGaussianBlur
1048
- * @param {any} Element
1049
- * @returns {any} Reference element
1050
- */
1051
- export const getFeGaussianBlur = (ele) => {
1136
+ * Get the reference element associated with the given attribute value.
1137
+ * @function module:utilities.getFeGaussianBlur
1138
+ * @param {any} Element
1139
+ * @returns {any} Reference element
1140
+ */
1141
+ export const getFeGaussianBlur = ele => {
1052
1142
  if (ele?.firstChild?.tagName === 'feGaussianBlur') {
1053
1143
  return ele.firstChild
1054
1144
  } else {
@@ -1064,30 +1154,33 @@ export const getFeGaussianBlur = (ele) => {
1064
1154
  }
1065
1155
 
1066
1156
  /**
1067
- * Get a DOM element by ID within the SVG root element.
1068
- * @function module:utilities.getElement
1069
- * @param {string} id - String with the element's new ID
1070
- * @returns {?Element}
1071
- */
1072
- export const getElement = (id) => {
1157
+ * Get a DOM element by ID within the SVG root element.
1158
+ * @function module:utilities.getElement
1159
+ * @param {string} id - String with the element's new ID
1160
+ * @returns {?Element}
1161
+ */
1162
+ export const getElement = id => {
1073
1163
  // querySelector lookup
1074
1164
  return svgroot_.querySelector('#' + id)
1075
1165
  }
1076
1166
 
1077
1167
  /**
1078
- * Assigns multiple attributes to an element.
1079
- * @function module:utilities.assignAttributes
1080
- * @param {Element} elem - DOM element to apply new attribute values to
1081
- * @param {PlainObject<string, string>} attrs - Object with attribute keys/values
1082
- * @param {Integer} [suspendLength] - Milliseconds to suspend redraw
1083
- * @param {boolean} [unitCheck=false] - Boolean to indicate the need to use units.setUnitAttr
1084
- * @returns {void}
1085
- */
1168
+ * Assigns multiple attributes to an element.
1169
+ * @function module:utilities.assignAttributes
1170
+ * @param {Element} elem - DOM element to apply new attribute values to
1171
+ * @param {PlainObject<string, string>} attrs - Object with attribute keys/values
1172
+ * @param {Integer} [suspendLength] - Milliseconds to suspend redraw
1173
+ * @param {boolean} [unitCheck=false] - Boolean to indicate the need to use units.setUnitAttr
1174
+ * @returns {void}
1175
+ */
1086
1176
  export const assignAttributes = (elem, attrs, suspendLength, unitCheck) => {
1087
1177
  for (const [key, value] of Object.entries(attrs)) {
1088
- const ns = (key.substr(0, 4) === 'xml:'
1089
- ? NS.XML
1090
- : key.substr(0, 6) === 'xlink:' ? NS.XLINK : null)
1178
+ const ns =
1179
+ key.substr(0, 4) === 'xml:'
1180
+ ? NS.XML
1181
+ : key.substr(0, 6) === 'xlink:'
1182
+ ? NS.XLINK
1183
+ : null
1091
1184
  if (value === undefined) {
1092
1185
  if (ns) {
1093
1186
  elem.removeAttributeNS(ns, key)
@@ -1107,12 +1200,12 @@ export const assignAttributes = (elem, attrs, suspendLength, unitCheck) => {
1107
1200
  }
1108
1201
 
1109
1202
  /**
1110
- * Remove unneeded (default) attributes, making resulting SVG smaller.
1111
- * @function module:utilities.cleanupElement
1112
- * @param {Element} element - DOM element to clean up
1113
- * @returns {void}
1114
- */
1115
- export const cleanupElement = (element) => {
1203
+ * Remove unneeded (default) attributes, making resulting SVG smaller.
1204
+ * @function module:utilities.cleanupElement
1205
+ * @param {Element} element - DOM element to clean up
1206
+ * @returns {void}
1207
+ */
1208
+ export const cleanupElement = element => {
1116
1209
  const defaults = {
1117
1210
  'fill-opacity': 1,
1118
1211
  'stop-opacity': 1,
@@ -1141,12 +1234,12 @@ export const cleanupElement = (element) => {
1141
1234
  }
1142
1235
 
1143
1236
  /**
1144
- * Round value to for snapping.
1145
- * @function module:utilities.snapToGrid
1146
- * @param {Float} value
1147
- * @returns {Integer}
1148
- */
1149
- export const snapToGrid = (value) => {
1237
+ * Round value to for snapping.
1238
+ * @function module:utilities.snapToGrid
1239
+ * @param {Float} value
1240
+ * @returns {Integer}
1241
+ */
1242
+ export const snapToGrid = value => {
1150
1243
  const unit = svgCanvas.getBaseUnit()
1151
1244
  let stepSize = svgCanvas.getSnappingStep()
1152
1245
  if (unit !== 'px') {
@@ -1162,8 +1255,8 @@ export const snapToGrid = (value) => {
1162
1255
  * @param {Element} img - The DOM element to prevent the click on
1163
1256
  * @returns {void}
1164
1257
  */
1165
- export const preventClickDefault = (img) => {
1166
- $click(img, (e) => {
1258
+ export const preventClickDefault = img => {
1259
+ $click(img, e => {
1167
1260
  e.preventDefault()
1168
1261
  })
1169
1262
  }
@@ -1178,28 +1271,30 @@ export const preventClickDefault = (img) => {
1178
1271
  * @param {any} val
1179
1272
  * @returns {boolean}
1180
1273
  */
1181
- export const isNullish = (val) => {
1274
+ export const isNullish = val => {
1182
1275
  return val === null || val === undefined
1183
1276
  }
1184
1277
 
1185
1278
  /**
1186
- * Overwrite methods for unit testing.
1187
- * @function module:utilities.mock
1188
- * @param {PlainObject} mockMethods
1189
- * @param {module:utilities.getHref} mockMethods.getHref
1190
- * @param {module:utilities.setHref} mockMethods.setHref
1191
- * @param {module:utilities.getRotationAngle} mockMethods.getRotationAngle
1192
- * @returns {void}
1193
- */
1279
+ * Overwrite methods for unit testing.
1280
+ * @function module:utilities.mock
1281
+ * @param {PlainObject} mockMethods
1282
+ * @param {module:utilities.getHref} mockMethods.getHref
1283
+ * @param {module:utilities.setHref} mockMethods.setHref
1284
+ * @param {module:utilities.getRotationAngle} mockMethods.getRotationAngle
1285
+ * @returns {void}
1286
+ */
1194
1287
  export const mock = ({
1195
- getHref: getHrefUser, setHref: setHrefUser, getRotationAngle: getRotationAngleUser
1288
+ getHref: getHrefUser,
1289
+ setHref: setHrefUser,
1290
+ getRotationAngle: getRotationAngleUser
1196
1291
  }) => {
1197
1292
  getHref = getHrefUser
1198
1293
  setHref = setHrefUser
1199
1294
  getRotationAngle = getRotationAngleUser
1200
1295
  }
1201
1296
 
1202
- export const stringToHTML = (str) => {
1297
+ export const stringToHTML = str => {
1203
1298
  const parser = new DOMParser()
1204
1299
  const doc = parser.parseFromString(str, 'text/html')
1205
1300
  return doc.body.firstChild
@@ -1215,9 +1310,9 @@ export const insertChildAtIndex = (parent, child, index = 0) => {
1215
1310
  }
1216
1311
 
1217
1312
  // shortcuts to common DOM functions
1218
- export const $id = (id) => document.getElementById(id)
1219
- export const $qq = (sel) => document.querySelector(sel)
1220
- export const $qa = (sel) => [...document.querySelectorAll(sel)]
1313
+ export const $id = id => document.getElementById(id)
1314
+ export const $qq = sel => document.querySelector(sel)
1315
+ export const $qa = sel => [...document.querySelectorAll(sel)]
1221
1316
  export const $click = (element, handler) => {
1222
1317
  element.addEventListener('click', handler)
1223
1318
  element.addEventListener('touchend', handler)