@svgedit/svgcanvas 7.1.4

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.
@@ -0,0 +1,1077 @@
1
+ /**
2
+ * @module elem-get-set get and set methods.
3
+ * @license MIT
4
+ * @copyright 2011 Jeff Schiller
5
+ */
6
+
7
+ import Paint from './paint.js'
8
+ import { NS } from './namespaces.js'
9
+ import {
10
+ getVisibleElements, getStrokedBBoxDefaultVisible, findDefs,
11
+ walkTree, getHref, setHref, getElement
12
+ } from './utilities.js'
13
+ import {
14
+ convertToNum
15
+ } from '../../src/common/units.js'
16
+ import { getParents } from '../../src/common/util.js'
17
+
18
+ let svgCanvas = null
19
+
20
+ /**
21
+ * @function module:elem-get-set.init
22
+ * @param {module:elem-get-set.elemContext} elemContext
23
+ * @returns {void}
24
+ */
25
+ export const init = (canvas) => {
26
+ svgCanvas = canvas
27
+ svgCanvas.getBold = getBoldMethod // Check whether selected element is bold or not.
28
+ svgCanvas.setBold = setBoldMethod // Make the selected element bold or normal.
29
+ svgCanvas.getItalic = getItalicMethod // Check whether selected element is in italics or not.
30
+ svgCanvas.setItalic = setItalicMethod // Make the selected element italic or normal.
31
+ svgCanvas.hasTextDecoration = hasTextDecorationMethod // Check whether the selected element has the given text decoration or not.
32
+ svgCanvas.addTextDecoration = addTextDecorationMethod // Adds the given value to the text decoration
33
+ svgCanvas.removeTextDecoration = removeTextDecorationMethod // Removes the given value from the text decoration
34
+ svgCanvas.setTextAnchor = setTextAnchorMethod // Set the new text anchor.
35
+ svgCanvas.setLetterSpacing = setLetterSpacingMethod // Set the new letter spacing.
36
+ svgCanvas.setWordSpacing = setWordSpacingMethod // Set the new word spacing.
37
+ svgCanvas.setTextLength = setTextLengthMethod // Set the new text length.
38
+ svgCanvas.setLengthAdjust = setLengthAdjustMethod // Set the new length adjust.
39
+ svgCanvas.getFontFamily = getFontFamilyMethod // The current font family
40
+ svgCanvas.setFontFamily = setFontFamilyMethod // Set the new font family.
41
+ svgCanvas.setFontColor = setFontColorMethod // Set the new font color.
42
+ svgCanvas.getFontColor = getFontColorMethod // The current font color
43
+ svgCanvas.getFontSize = getFontSizeMethod // The current font size
44
+ svgCanvas.setFontSize = setFontSizeMethod // Applies the given font size to the selected element.
45
+ svgCanvas.getText = getTextMethod // current text (`textContent`) of the selected element
46
+ svgCanvas.setTextContent = setTextContentMethod // Updates the text element with the given string.
47
+ svgCanvas.setImageURL = setImageURLMethod // Sets the new image URL for the selected image element
48
+ svgCanvas.setLinkURL = setLinkURLMethod // Sets the new link URL for the selected anchor element.
49
+ svgCanvas.setRectRadius = setRectRadiusMethod // Sets the `rx` and `ry` values to the selected `rect` element
50
+ svgCanvas.makeHyperlink = makeHyperlinkMethod // Wraps the selected element(s) in an anchor element or converts group to one.
51
+ svgCanvas.removeHyperlink = removeHyperlinkMethod
52
+ svgCanvas.setSegType = setSegTypeMethod // Sets the new segment type to the selected segment(s).
53
+ svgCanvas.setStrokeWidth = setStrokeWidthMethod // Sets the stroke width for the current selected elements.
54
+ svgCanvas.getResolution = getResolutionMethod // The current dimensions and zoom level in an object
55
+ svgCanvas.getTitle = getTitleMethod // the current group/SVG's title contents or `undefined` if no element
56
+ svgCanvas.setGroupTitle = setGroupTitleMethod // Sets the group/SVG's title content.
57
+ svgCanvas.setStrokeAttr = setStrokeAttrMethod // Set the given stroke-related attribute the given value for selected elements.
58
+ svgCanvas.setBackground = setBackgroundMethod // Set the background of the editor (NOT the actual document).
59
+ svgCanvas.setDocumentTitle = setDocumentTitleMethod // Adds/updates a title element for the document with the given name.
60
+ svgCanvas.getEditorNS = getEditorNSMethod // Returns the editor's namespace URL, optionally adding it to the root element.
61
+ svgCanvas.setResolution = setResolutionMethod // Changes the document's dimensions to the given size.
62
+ svgCanvas.setBBoxZoom = setBBoxZoomMethod // Sets the zoom level on the canvas-side based on the given value.
63
+ svgCanvas.setCurrentZoom = setZoomMethod // Sets the zoom to the given level.
64
+ svgCanvas.setColor = setColorMethod // Change the current stroke/fill color/gradien
65
+ svgCanvas.setGradient = setGradientMethod // Apply the current gradient to selected element's fill or stroke.
66
+ svgCanvas.setPaint = setPaintMethod // Set a color/gradient to a fill/stroke.
67
+ }
68
+
69
+ /**
70
+ * @function module:elem-get-set.SvgCanvas#getResolution
71
+ * @returns {DimensionsAndZoom} The current dimensions and zoom level in an object
72
+ */
73
+ const getResolutionMethod = () => {
74
+ const zoom = svgCanvas.getZoom()
75
+ const w = svgCanvas.getSvgContent().getAttribute('width') / zoom
76
+ const h = svgCanvas.getSvgContent().getAttribute('height') / zoom
77
+
78
+ return {
79
+ w,
80
+ h,
81
+ zoom
82
+ }
83
+ }
84
+
85
+ /**
86
+ * @function module:elem-get-set.SvgCanvas#getTitle
87
+ * @param {Element} [elem]
88
+ * @returns {string|void} the current group/SVG's title contents or
89
+ * `undefined` if no element is passed nd there are no selected elements.
90
+ */
91
+ const getTitleMethod = (elem) => {
92
+ const selectedElements = svgCanvas.getSelectedElements()
93
+ const dataStorage = svgCanvas.getDataStorage()
94
+ elem = elem || selectedElements[0]
95
+ if (!elem) { return undefined }
96
+ if (dataStorage.has(elem, 'gsvg')) {
97
+ elem = dataStorage.get(elem, 'gsvg')
98
+ } else if (dataStorage.has(elem, 'symbol')) {
99
+ elem = dataStorage.get(elem, 'symbol')
100
+ }
101
+ const childs = elem.childNodes
102
+ for (const child of childs) {
103
+ if (child.nodeName === 'title') {
104
+ return child.textContent
105
+ }
106
+ }
107
+ return ''
108
+ }
109
+
110
+ /**
111
+ * Sets the group/SVG's title content.
112
+ * @function module:elem-get-set.SvgCanvas#setGroupTitle
113
+ * @param {string} val
114
+ * @todo Combine this with `setDocumentTitle`
115
+ * @returns {void}
116
+ */
117
+ const setGroupTitleMethod = (val) => {
118
+ const {
119
+ InsertElementCommand, RemoveElementCommand,
120
+ ChangeElementCommand, BatchCommand
121
+ } = svgCanvas.history
122
+ const selectedElements = svgCanvas.getSelectedElements()
123
+ const dataStorage = svgCanvas.getDataStorage()
124
+ let elem = selectedElements[0]
125
+ if (dataStorage.has(elem, 'gsvg')) {
126
+ elem = dataStorage.get(elem, 'gsvg')
127
+ }
128
+
129
+ const ts = elem.querySelectorAll('title')
130
+
131
+ const batchCmd = new BatchCommand('Set Label')
132
+
133
+ let title
134
+ if (val.length === 0) {
135
+ // Remove title element
136
+ const tsNextSibling = ts.nextSibling
137
+ batchCmd.addSubCommand(new RemoveElementCommand(ts[0], tsNextSibling, elem))
138
+ ts.remove()
139
+ } else if (ts.length) {
140
+ // Change title contents
141
+ title = ts[0]
142
+ batchCmd.addSubCommand(new ChangeElementCommand(title, { '#text': title.textContent }))
143
+ title.textContent = val
144
+ } else {
145
+ // Add title element
146
+ title = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'title')
147
+ title.textContent = val
148
+ elem.insertBefore(title, elem.firstChild)
149
+ batchCmd.addSubCommand(new InsertElementCommand(title))
150
+ }
151
+
152
+ svgCanvas.addCommandToHistory(batchCmd)
153
+ }
154
+
155
+ /**
156
+ * Adds/updates a title element for the document with the given name.
157
+ * This is an undoable action.
158
+ * @function module:elem-get-set.SvgCanvas#setDocumentTitle
159
+ * @param {string} newTitle - String with the new title
160
+ * @returns {void}
161
+ */
162
+ const setDocumentTitleMethod = (newTitle) => {
163
+ const { ChangeElementCommand, BatchCommand } = svgCanvas.history
164
+ const childs = svgCanvas.getSvgContent().childNodes
165
+ let docTitle = false; let oldTitle = ''
166
+
167
+ const batchCmd = new BatchCommand('Change Image Title')
168
+
169
+ for (const child of childs) {
170
+ if (child.nodeName === 'title') {
171
+ docTitle = child
172
+ oldTitle = docTitle.textContent
173
+ break
174
+ }
175
+ }
176
+ if (!docTitle) {
177
+ docTitle = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'title')
178
+ svgCanvas.getSvgContent().insertBefore(docTitle, svgCanvas.getSvgContent().firstChild)
179
+ // svgContent.firstChild.before(docTitle); // Ok to replace above with this?
180
+ }
181
+
182
+ if (newTitle.length) {
183
+ docTitle.textContent = newTitle
184
+ } else {
185
+ // No title given, so element is not necessary
186
+ docTitle.remove()
187
+ }
188
+ batchCmd.addSubCommand(new ChangeElementCommand(docTitle, { '#text': oldTitle }))
189
+ svgCanvas.addCommandToHistory(batchCmd)
190
+ }
191
+
192
+ /**
193
+ * Changes the document's dimensions to the given size.
194
+ * @function module:elem-get-set.SvgCanvas#setResolution
195
+ * @param {Float|"fit"} x - Number with the width of the new dimensions in user units.
196
+ * Can also be the string "fit" to indicate "fit to content".
197
+ * @param {Float} y - Number with the height of the new dimensions in user units.
198
+ * @fires module:elem-get-set.SvgCanvas#event:changed
199
+ * @returns {boolean} Indicates if resolution change was successful.
200
+ * It will fail on "fit to content" option with no content to fit to.
201
+ */
202
+ const setResolutionMethod = (x, y) => {
203
+ const { ChangeElementCommand, BatchCommand } = svgCanvas.history
204
+ const zoom = svgCanvas.getZoom()
205
+ const res = svgCanvas.getResolution()
206
+ const { w, h } = res
207
+ let batchCmd
208
+
209
+ if (x === 'fit') {
210
+ // Get bounding box
211
+ const bbox = getStrokedBBoxDefaultVisible()
212
+
213
+ if (bbox) {
214
+ batchCmd = new BatchCommand('Fit Canvas to Content')
215
+ const visEls = getVisibleElements()
216
+ svgCanvas.addToSelection(visEls)
217
+ const dx = []; const dy = []
218
+ visEls.forEach((_item, _i) => {
219
+ dx.push(bbox.x * -1)
220
+ dy.push(bbox.y * -1)
221
+ })
222
+
223
+ const cmd = svgCanvas.moveSelectedElements(dx, dy, true)
224
+ batchCmd.addSubCommand(cmd)
225
+ svgCanvas.clearSelection()
226
+
227
+ x = Math.round(bbox.width)
228
+ y = Math.round(bbox.height)
229
+ } else {
230
+ return false
231
+ }
232
+ }
233
+ if (x !== w || y !== h) {
234
+ if (!batchCmd) {
235
+ batchCmd = new BatchCommand('Change Image Dimensions')
236
+ }
237
+
238
+ x = convertToNum('width', x)
239
+ y = convertToNum('height', y)
240
+
241
+ svgCanvas.getSvgContent().setAttribute('width', x)
242
+ svgCanvas.getSvgContent().setAttribute('height', y)
243
+
244
+ svgCanvas.contentW = x
245
+ svgCanvas.contentH = y
246
+ batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgContent(), { width: w, height: h }))
247
+
248
+ svgCanvas.getSvgContent().setAttribute('viewBox', [0, 0, x / zoom, y / zoom].join(' '))
249
+ batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgContent(), { viewBox: ['0 0', w, h].join(' ') }))
250
+
251
+ svgCanvas.addCommandToHistory(batchCmd)
252
+ svgCanvas.call('changed', [svgCanvas.getSvgContent()])
253
+ }
254
+ return true
255
+ }
256
+
257
+ /**
258
+ * Returns the editor's namespace URL, optionally adding it to the root element.
259
+ * @function module:elem-get-set.SvgCanvas#getEditorNS
260
+ * @param {boolean} [add] - Indicates whether or not to add the namespace value
261
+ * @returns {string} The editor's namespace URL
262
+ */
263
+ const getEditorNSMethod = (add) => {
264
+ if (add) {
265
+ svgCanvas.getSvgContent().setAttribute('xmlns:se', NS.SE)
266
+ }
267
+ return NS.SE
268
+ }
269
+
270
+ /**
271
+ * @typedef {PlainObject} module:elem-get-set.ZoomAndBBox
272
+ * @property {Float} zoom
273
+ * @property {module:utilities.BBoxObject} bbox
274
+ */
275
+ /**
276
+ * Sets the zoom level on the canvas-side based on the given value.
277
+ * @function module:elem-get-set.SvgCanvas#setBBoxZoom
278
+ * @param {"selection"|"canvas"|"content"|"layer"|module:SVGEditor.BBoxObjectWithFactor} val - Bounding box object to zoom to or string indicating zoom option. Note: the object value type is defined in `svg-editor.js`
279
+ * @param {Integer} editorW - The editor's workarea box's width
280
+ * @param {Integer} editorH - The editor's workarea box's height
281
+ * @returns {module:elem-get-set.ZoomAndBBox|void}
282
+ */
283
+ const setBBoxZoomMethod = (val, editorW, editorH) => {
284
+ const zoom = svgCanvas.getZoom()
285
+ const selectedElements = svgCanvas.getSelectedElements()
286
+ let spacer = 0.85
287
+ let bb
288
+ const calcZoom = (bb) => {
289
+ if (!bb) { return false }
290
+ const wZoom = Math.round((editorW / bb.width) * 100 * spacer) / 100
291
+ const hZoom = Math.round((editorH / bb.height) * 100 * spacer) / 100
292
+ const zoom = Math.min(wZoom, hZoom)
293
+ svgCanvas.setZoom(zoom)
294
+ return { zoom, bbox: bb }
295
+ }
296
+
297
+ if (typeof val === 'object') {
298
+ bb = val
299
+ if (bb.width === 0 || bb.height === 0) {
300
+ const newzoom = bb.zoom ? bb.zoom : zoom * bb.factor
301
+ svgCanvas.setZoom(newzoom)
302
+ return { zoom, bbox: bb }
303
+ }
304
+ return calcZoom(bb)
305
+ }
306
+
307
+ switch (val) {
308
+ case 'selection': {
309
+ if (!selectedElements[0]) { return undefined }
310
+ const selectedElems = selectedElements.map((n, _) => {
311
+ if (n) {
312
+ return n
313
+ }
314
+ return undefined
315
+ })
316
+ bb = getStrokedBBoxDefaultVisible(selectedElems)
317
+ break
318
+ } case 'canvas': {
319
+ const res = svgCanvas.getResolution()
320
+ spacer = 0.95
321
+ bb = { width: res.w, height: res.h, x: 0, y: 0 }
322
+ break
323
+ } case 'content':
324
+ bb = getStrokedBBoxDefaultVisible()
325
+ break
326
+ case 'layer':
327
+ bb = getStrokedBBoxDefaultVisible(getVisibleElements(svgCanvas.getCurrentDrawing().getCurrentLayer()))
328
+ break
329
+ default:
330
+ return undefined
331
+ }
332
+ return calcZoom(bb)
333
+ }
334
+
335
+ /**
336
+ * Sets the zoom to the given level.
337
+ * @function module:elem-get-set.SvgCanvas#setZoom
338
+ * @param {Float} zoomLevel - Float indicating the zoom level to change to
339
+ * @fires module:elem-get-set.SvgCanvas#event:ext_zoomChanged
340
+ * @returns {void}
341
+ */
342
+ const setZoomMethod = (zoomLevel) => {
343
+ const selectedElements = svgCanvas.getSelectedElements()
344
+ const res = svgCanvas.getResolution()
345
+ svgCanvas.getSvgContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel)
346
+ svgCanvas.setZoom(zoomLevel)
347
+ selectedElements.forEach((elem) => {
348
+ if (!elem) { return }
349
+ svgCanvas.selectorManager.requestSelector(elem).resize()
350
+ })
351
+ svgCanvas.pathActions.zoomChange()
352
+ svgCanvas.runExtensions('zoomChanged', zoomLevel)
353
+ }
354
+
355
+ /**
356
+ * Change the current stroke/fill color/gradient value.
357
+ * @function module:elem-get-set.SvgCanvas#setColor
358
+ * @param {string} type - String indicating fill or stroke
359
+ * @param {string} val - The value to set the stroke attribute to
360
+ * @param {boolean} preventUndo - Boolean indicating whether or not svgCanvas should be an undoable option
361
+ * @fires module:elem-get-set.SvgCanvas#event:changed
362
+ * @returns {void}
363
+ */
364
+ const setColorMethod = (type, val, preventUndo) => {
365
+ const selectedElements = svgCanvas.getSelectedElements()
366
+ svgCanvas.setCurShape(type, val)
367
+ svgCanvas.setCurProperties(type + '_paint', { type: 'solidColor' })
368
+ const elems = []
369
+ /**
370
+ *
371
+ * @param {Element} e
372
+ * @returns {void}
373
+ */
374
+ const addNonG = (e) => {
375
+ if (e.nodeName !== 'g') {
376
+ elems.push(e)
377
+ }
378
+ }
379
+ let i = selectedElements.length
380
+ while (i--) {
381
+ const elem = selectedElements[i]
382
+ if (elem) {
383
+ if (elem.tagName === 'g') {
384
+ walkTree(elem, addNonG)
385
+ } else if (type === 'fill') {
386
+ if (elem.tagName !== 'polyline' && elem.tagName !== 'line') {
387
+ elems.push(elem)
388
+ }
389
+ } else {
390
+ elems.push(elem)
391
+ }
392
+ }
393
+ }
394
+ if (elems.length > 0) {
395
+ if (!preventUndo) {
396
+ svgCanvas.changeSelectedAttribute(type, val, elems)
397
+ svgCanvas.call('changed', elems)
398
+ } else {
399
+ svgCanvas.changeSelectedAttributeNoUndo(type, val, elems)
400
+ }
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Apply the current gradient to selected element's fill or stroke.
406
+ * @function module:elem-get-set.SvgCanvas#setGradient
407
+ * @param {"fill"|"stroke"} type - String indicating "fill" or "stroke" to apply to an element
408
+ * @returns {void}
409
+ */
410
+ const setGradientMethod = (type) => {
411
+ if (!svgCanvas.getCurProperties(type + '_paint') ||
412
+ svgCanvas.getCurProperties(type + '_paint').type === 'solidColor') { return }
413
+ const canvas = svgCanvas
414
+ let grad = canvas[type + 'Grad']
415
+ // find out if there is a duplicate gradient already in the defs
416
+ const duplicateGrad = findDuplicateGradient(grad)
417
+ const defs = findDefs()
418
+ // no duplicate found, so import gradient into defs
419
+ if (!duplicateGrad) {
420
+ // const origGrad = grad;
421
+ grad = svgCanvas.getDOMDocument().importNode(grad, true)
422
+ defs.append(grad)
423
+ // get next id and set it on the grad
424
+ grad.id = svgCanvas.getNextId()
425
+ } else { // use existing gradient
426
+ grad = duplicateGrad
427
+ }
428
+ svgCanvas.setColor(type, 'url(#' + grad.id + ')')
429
+ }
430
+
431
+ /**
432
+ * Check if exact gradient already exists.
433
+ * @function module:svgcanvas~findDuplicateGradient
434
+ * @param {SVGGradientElement} grad - The gradient DOM element to compare to others
435
+ * @returns {SVGGradientElement} The existing gradient if found, `null` if not
436
+ */
437
+ const findDuplicateGradient = (grad) => {
438
+ const defs = findDefs()
439
+ const existingGrads = defs.querySelectorAll('linearGradient, radialGradient')
440
+ let i = existingGrads.length
441
+ const radAttrs = ['r', 'cx', 'cy', 'fx', 'fy']
442
+ while (i--) {
443
+ const og = existingGrads[i]
444
+ if (grad.tagName === 'linearGradient') {
445
+ if (grad.getAttribute('x1') !== og.getAttribute('x1') ||
446
+ grad.getAttribute('y1') !== og.getAttribute('y1') ||
447
+ grad.getAttribute('x2') !== og.getAttribute('x2') ||
448
+ grad.getAttribute('y2') !== og.getAttribute('y2')
449
+ ) {
450
+ continue
451
+ }
452
+ } else {
453
+ const gradAttrs = {
454
+ r: Number(grad.getAttribute('r')),
455
+ cx: Number(grad.getAttribute('cx')),
456
+ cy: Number(grad.getAttribute('cy')),
457
+ fx: Number(grad.getAttribute('fx')),
458
+ fy: Number(grad.getAttribute('fy'))
459
+ }
460
+ const ogAttrs = {
461
+ r: Number(og.getAttribute('r')),
462
+ cx: Number(og.getAttribute('cx')),
463
+ cy: Number(og.getAttribute('cy')),
464
+ fx: Number(og.getAttribute('fx')),
465
+ fy: Number(og.getAttribute('fy'))
466
+ }
467
+
468
+ let diff = false
469
+ radAttrs.forEach((attr) => {
470
+ if (gradAttrs[attr] !== ogAttrs[attr]) { diff = true }
471
+ })
472
+
473
+ if (diff) { continue }
474
+ }
475
+
476
+ // else could be a duplicate, iterate through stops
477
+ const stops = grad.getElementsByTagNameNS(NS.SVG, 'stop')
478
+ const ostops = og.getElementsByTagNameNS(NS.SVG, 'stop')
479
+
480
+ if (stops.length !== ostops.length) {
481
+ continue
482
+ }
483
+
484
+ let j = stops.length
485
+ while (j--) {
486
+ const stop = stops[j]
487
+ const ostop = ostops[j]
488
+
489
+ if (stop.getAttribute('offset') !== ostop.getAttribute('offset') ||
490
+ stop.getAttribute('stop-opacity') !== ostop.getAttribute('stop-opacity') ||
491
+ stop.getAttribute('stop-color') !== ostop.getAttribute('stop-color')) {
492
+ break
493
+ }
494
+ }
495
+
496
+ if (j === -1) {
497
+ return og
498
+ }
499
+ } // for each gradient in defs
500
+
501
+ return null
502
+ }
503
+
504
+ /**
505
+ * Set a color/gradient to a fill/stroke.
506
+ * @function module:elem-get-set.SvgCanvas#setPaint
507
+ * @param {"fill"|"stroke"} type - String with "fill" or "stroke"
508
+ * @param {} paint - The paint object to apply
509
+ * @returns {void}
510
+ */
511
+ const setPaintMethod = (type, paint) => {
512
+ // make a copy
513
+ const p = new Paint(paint)
514
+ svgCanvas.setPaintOpacity(type, p.alpha / 100, true)
515
+
516
+ // now set the current paint object
517
+ svgCanvas.setCurProperties(type + '_paint', p)
518
+ switch (p.type) {
519
+ case 'solidColor':
520
+ svgCanvas.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none')
521
+ break
522
+ case 'linearGradient':
523
+ case 'radialGradient':
524
+ svgCanvas.setCanvas(type + 'Grad', p[p.type])
525
+ svgCanvas.setGradient(type)
526
+ break
527
+ }
528
+ }
529
+ /**
530
+ * Sets the stroke width for the current selected elements.
531
+ * When attempting to set a line's width to 0, this changes it to 1 instead.
532
+ * @function module:elem-get-set.SvgCanvas#setStrokeWidth
533
+ * @param {Float} val - A Float indicating the new stroke width value
534
+ * @fires module:elem-get-set.SvgCanvas#event:changed
535
+ * @returns {void}
536
+ */
537
+ const setStrokeWidthMethod = (val) => {
538
+ const selectedElements = svgCanvas.getSelectedElements()
539
+ if (val === 0 && ['line', 'path'].includes(svgCanvas.getMode())) {
540
+ svgCanvas.setStrokeWidth(1)
541
+ return
542
+ }
543
+ svgCanvas.setCurProperties('stroke_width', val)
544
+
545
+ const elems = []
546
+ /**
547
+ *
548
+ * @param {Element} e
549
+ * @returns {void}
550
+ */
551
+ const addNonG = (e) => {
552
+ if (e.nodeName !== 'g') {
553
+ elems.push(e)
554
+ }
555
+ }
556
+ let i = selectedElements.length
557
+ while (i--) {
558
+ const elem = selectedElements[i]
559
+ if (elem) {
560
+ if (elem.tagName === 'g') {
561
+ walkTree(elem, addNonG)
562
+ } else {
563
+ elems.push(elem)
564
+ }
565
+ }
566
+ }
567
+ if (elems.length > 0) {
568
+ svgCanvas.changeSelectedAttribute('stroke-width', val, elems)
569
+ svgCanvas.call('changed', selectedElements)
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Set the given stroke-related attribute the given value for selected elements.
575
+ * @function module:elem-get-set.SvgCanvas#setStrokeAttr
576
+ * @param {string} attr - String with the attribute name
577
+ * @param {string|Float} val - String or number with the attribute value
578
+ * @fires module:elem-get-set.SvgCanvas#event:changed
579
+ * @returns {void}
580
+ */
581
+ const setStrokeAttrMethod = (attr, val) => {
582
+ const selectedElements = svgCanvas.getSelectedElements()
583
+ svgCanvas.setCurShape(attr.replace('-', '_'), val)
584
+ const elems = []
585
+
586
+ let i = selectedElements.length
587
+ while (i--) {
588
+ const elem = selectedElements[i]
589
+ if (elem) {
590
+ if (elem.tagName === 'g') {
591
+ walkTree(elem, (e) => { if (e.nodeName !== 'g') { elems.push(e) } })
592
+ } else {
593
+ elems.push(elem)
594
+ }
595
+ }
596
+ }
597
+ if (elems.length > 0) {
598
+ svgCanvas.changeSelectedAttribute(attr, val, elems)
599
+ svgCanvas.call('changed', selectedElements)
600
+ }
601
+ }
602
+ /**
603
+ * Check whether selected element is bold or not.
604
+ * @function module:svgcanvas.SvgCanvas#getBold
605
+ * @returns {boolean} Indicates whether or not element is bold
606
+ */
607
+ const getBoldMethod = () => {
608
+ const selectedElements = svgCanvas.getSelectedElements()
609
+ // should only have one element selected
610
+ const selected = selectedElements[0]
611
+ if (selected?.tagName === 'text' &&
612
+ !selectedElements[1]) {
613
+ return (selected.getAttribute('font-weight') === 'bold')
614
+ }
615
+ return false
616
+ }
617
+
618
+ /**
619
+ * Make the selected element bold or normal.
620
+ * @function module:svgcanvas.SvgCanvas#setBold
621
+ * @param {boolean} b - Indicates bold (`true`) or normal (`false`)
622
+ * @returns {void}
623
+ */
624
+ const setBoldMethod = (b) => {
625
+ const selectedElements = svgCanvas.getSelectedElements()
626
+ const selected = selectedElements[0]
627
+ if (selected?.tagName === 'text' &&
628
+ !selectedElements[1]) {
629
+ svgCanvas.changeSelectedAttribute('font-weight', b ? 'bold' : 'normal')
630
+ }
631
+ if (!selectedElements[0].textContent) {
632
+ svgCanvas.textActions.setCursor()
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Check whether selected element has the given text decoration value or not.
638
+ * @returns {boolean} Indicates whether or not element has the text decoration value
639
+ */
640
+ const hasTextDecorationMethod = (value) => {
641
+ const selectedElements = svgCanvas.getSelectedElements()
642
+ const selected = selectedElements[0]
643
+
644
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
645
+ const attribute = selected.getAttribute('text-decoration') || ''
646
+ return attribute.includes(value)
647
+ }
648
+
649
+ return false
650
+ }
651
+
652
+ /**
653
+ * Adds the given text decoration value
654
+ * @param value The text decoration value
655
+ * @returns {void}
656
+ */
657
+ const addTextDecorationMethod = (value) => {
658
+ const selectedElements = svgCanvas.getSelectedElements()
659
+ const selected = selectedElements[0]
660
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
661
+ const oldValue = selected.getAttribute('text-decoration') || ''
662
+ svgCanvas.changeSelectedAttribute('text-decoration', (oldValue + ' ' + value).trim())
663
+ }
664
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
665
+ svgCanvas.textActions.setCursor()
666
+ }
667
+ }
668
+
669
+ /**
670
+ * Removes the given text decoration value
671
+ * @param value The text decoration value
672
+ * @returns {void}
673
+ */
674
+ const removeTextDecorationMethod = (value) => {
675
+ const selectedElements = svgCanvas.getSelectedElements()
676
+ const selected = selectedElements[0]
677
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
678
+ const actualValues = selected.getAttribute('text-decoration') || ''
679
+ svgCanvas.changeSelectedAttribute('text-decoration', actualValues.replace(value, '').trim())
680
+ }
681
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
682
+ svgCanvas.textActions.setCursor()
683
+ }
684
+ }
685
+
686
+ /**
687
+ * Check whether selected element is in italics or not.
688
+ * @function module:svgcanvas.SvgCanvas#getItalic
689
+ * @returns {boolean} Indicates whether or not element is italic
690
+ */
691
+ const getItalicMethod = () => {
692
+ const selectedElements = svgCanvas.getSelectedElements()
693
+ const selected = selectedElements[0]
694
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
695
+ return (selected.getAttribute('font-style') === 'italic')
696
+ }
697
+ return false
698
+ }
699
+
700
+ /**
701
+ * Make the selected element italic or normal.
702
+ * @function module:svgcanvas.SvgCanvas#setItalic
703
+ * @param {boolean} i - Indicates italic (`true`) or normal (`false`)
704
+ * @returns {void}
705
+ */
706
+ const setItalicMethod = (i) => {
707
+ const selectedElements = svgCanvas.getSelectedElements()
708
+ const selected = selectedElements[0]
709
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
710
+ svgCanvas.changeSelectedAttribute('font-style', i ? 'italic' : 'normal')
711
+ }
712
+ if (!selectedElements[0].textContent) {
713
+ svgCanvas.textActions.setCursor()
714
+ }
715
+ }
716
+
717
+ /**
718
+ * @function module:svgcanvas.SvgCanvas#setTextAnchorMethod Set the new text anchor
719
+ * @param {string} value - The text anchor value (start, middle or end)
720
+ * @returns {void}
721
+ */
722
+ const setTextAnchorMethod = (value) => {
723
+ const selectedElements = svgCanvas.getSelectedElements()
724
+ const selected = selectedElements[0]
725
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
726
+ svgCanvas.changeSelectedAttribute('text-anchor', value)
727
+ }
728
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
729
+ svgCanvas.textActions.setCursor()
730
+ }
731
+ }
732
+
733
+ /**
734
+ * @function module:svgcanvas.SvgCanvas#setLetterSpacingMethod Set the new letter spacing
735
+ * @param {string} value - The letter spacing value
736
+ * @returns {void}
737
+ */
738
+ const setLetterSpacingMethod = (value) => {
739
+ const selectedElements = svgCanvas.getSelectedElements()
740
+ const selected = selectedElements[0]
741
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
742
+ svgCanvas.changeSelectedAttribute('letter-spacing', value)
743
+ }
744
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
745
+ svgCanvas.textActions.setCursor()
746
+ }
747
+ }
748
+
749
+ /**
750
+ * @function module:svgcanvas.SvgCanvas#setWordSpacingMethod Set the new word spacing
751
+ * @param {string} value - The word spacing value
752
+ * @returns {void}
753
+ */
754
+ const setWordSpacingMethod = (value) => {
755
+ const selectedElements = svgCanvas.getSelectedElements()
756
+ const selected = selectedElements[0]
757
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
758
+ svgCanvas.changeSelectedAttribute('word-spacing', value)
759
+ }
760
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
761
+ svgCanvas.textActions.setCursor()
762
+ }
763
+ }
764
+
765
+ /**
766
+ * @function module:svgcanvas.SvgCanvas#setTextLengthMethod Set the new text length
767
+ * @param {string} value - The text length value
768
+ * @returns {void}
769
+ */
770
+ const setTextLengthMethod = (value) => {
771
+ const selectedElements = svgCanvas.getSelectedElements()
772
+ const selected = selectedElements[0]
773
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
774
+ svgCanvas.changeSelectedAttribute('textLength', value)
775
+ }
776
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
777
+ svgCanvas.textActions.setCursor()
778
+ }
779
+ }
780
+
781
+ /**
782
+ * @function module:svgcanvas.SvgCanvas#setLengthAdjustMethod Set the new length adjust
783
+ * @param {string} value - The length adjust value
784
+ * @returns {void}
785
+ */
786
+ const setLengthAdjustMethod = (value) => {
787
+ const selectedElements = svgCanvas.getSelectedElements()
788
+ const selected = selectedElements[0]
789
+ if (selected?.tagName === 'text' && !selectedElements[1]) {
790
+ svgCanvas.changeSelectedAttribute('lengthAdjust', value)
791
+ }
792
+ if (selectedElements.length > 0 && !selectedElements[0].textContent) {
793
+ svgCanvas.textActions.setCursor()
794
+ }
795
+ }
796
+
797
+ /**
798
+ * @function module:svgcanvas.SvgCanvas#getFontFamily
799
+ * @returns {string} The current font family
800
+ */
801
+ const getFontFamilyMethod = () => {
802
+ return svgCanvas.getCurText('font_family')
803
+ }
804
+
805
+ /**
806
+ * Set the new font family.
807
+ * @function module:svgcanvas.SvgCanvas#setFontFamily
808
+ * @param {string} val - String with the new font family
809
+ * @returns {void}
810
+ */
811
+ const setFontFamilyMethod = (val) => {
812
+ const selectedElements = svgCanvas.getSelectedElements()
813
+ svgCanvas.setCurText('font_family', val)
814
+ svgCanvas.changeSelectedAttribute('font-family', val)
815
+ if (!selectedElements[0]?.textContent) {
816
+ svgCanvas.textActions.setCursor()
817
+ }
818
+ }
819
+
820
+ /**
821
+ * Set the new font color.
822
+ * @function module:svgcanvas.SvgCanvas#setFontColor
823
+ * @param {string} val - String with the new font color
824
+ * @returns {void}
825
+ */
826
+ const setFontColorMethod = (val) => {
827
+ svgCanvas.setCurText('fill', val)
828
+ svgCanvas.changeSelectedAttribute('fill', val)
829
+ }
830
+
831
+ /**
832
+ * @function module:svgcanvas.SvgCanvas#getFontColor
833
+ * @returns {string} The current font color
834
+ */
835
+ const getFontColorMethod = () => {
836
+ return svgCanvas.getCurText('fill')
837
+ }
838
+
839
+ /**
840
+ * @function module:svgcanvas.SvgCanvas#getFontSize
841
+ * @returns {Float} The current font size
842
+ */
843
+ const getFontSizeMethod = () => {
844
+ return svgCanvas.getCurText('font_size')
845
+ }
846
+
847
+ /**
848
+ * Applies the given font size to the selected element.
849
+ * @function module:svgcanvas.SvgCanvas#setFontSize
850
+ * @param {Float} val - Float with the new font size
851
+ * @returns {void}
852
+ */
853
+ const setFontSizeMethod = (val) => {
854
+ const selectedElements = svgCanvas.getSelectedElements()
855
+ svgCanvas.setCurText('font_size', val)
856
+ svgCanvas.changeSelectedAttribute('font-size', val)
857
+ if (!selectedElements[0]?.textContent) {
858
+ svgCanvas.textActions.setCursor()
859
+ }
860
+ }
861
+
862
+ /**
863
+ * @function module:svgcanvas.SvgCanvas#getText
864
+ * @returns {string} The current text (`textContent`) of the selected element
865
+ */
866
+ const getTextMethod = () => {
867
+ const selectedElements = svgCanvas.getSelectedElements()
868
+ const selected = selectedElements[0]
869
+ return (selected) ? selected.textContent : ''
870
+ }
871
+
872
+ /**
873
+ * Updates the text element with the given string.
874
+ * @function module:svgcanvas.SvgCanvas#setTextContent
875
+ * @param {string} val - String with the new text
876
+ * @returns {void}
877
+ */
878
+ const setTextContentMethod = (val) => {
879
+ svgCanvas.changeSelectedAttribute('#text', val)
880
+ svgCanvas.textActions.init(val)
881
+ svgCanvas.textActions.setCursor()
882
+ }
883
+
884
+ /**
885
+ * Sets the new image URL for the selected image element. Updates its size if
886
+ * a new URL is given.
887
+ * @function module:svgcanvas.SvgCanvas#setImageURL
888
+ * @param {string} val - String with the image URL/path
889
+ * @fires module:svgcanvas.SvgCanvas#event:changed
890
+ * @returns {void}
891
+ */
892
+ const setImageURLMethod = (val) => {
893
+ const { ChangeElementCommand, BatchCommand } = svgCanvas.history
894
+ const selectedElements = svgCanvas.getSelectedElements()
895
+ const elem = selectedElements[0]
896
+ if (!elem) { return }
897
+
898
+ const attrs = {
899
+ width: elem.getAttribute('width'),
900
+ height: elem.getAttribute('height')
901
+ }
902
+ const setsize = (!attrs.width || !attrs.height)
903
+
904
+ const curHref = getHref(elem)
905
+
906
+ // Do nothing if no URL change or size change
907
+ if (curHref === val && !setsize) {
908
+ return
909
+ }
910
+
911
+ const batchCmd = new BatchCommand('Change Image URL')
912
+
913
+ setHref(elem, val)
914
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, {
915
+ '#href': curHref
916
+ }))
917
+ const img = new Image()
918
+ img.onload = function () {
919
+ const changes = {
920
+ width: elem.getAttribute('width'),
921
+ height: elem.getAttribute('height')
922
+ }
923
+ elem.setAttribute('width', this.width)
924
+ elem.setAttribute('height', this.height)
925
+
926
+ svgCanvas.selectorManager.requestSelector(elem).resize()
927
+
928
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
929
+ svgCanvas.addCommandToHistory(batchCmd)
930
+ svgCanvas.call('changed', [elem])
931
+ }
932
+ img.src = val
933
+ }
934
+
935
+ /**
936
+ * Sets the new link URL for the selected anchor element.
937
+ * @function module:svgcanvas.SvgCanvas#setLinkURL
938
+ * @param {string} val - String with the link URL/path
939
+ * @returns {void}
940
+ */
941
+ const setLinkURLMethod = (val) => {
942
+ const { ChangeElementCommand, BatchCommand } = svgCanvas.history
943
+ const selectedElements = svgCanvas.getSelectedElements()
944
+ let elem = selectedElements[0]
945
+ if (!elem) { return }
946
+ if (elem.tagName !== 'a') {
947
+ // See if parent is an anchor
948
+ const parentsA = getParents(elem.parentNode, 'a')
949
+ if (parentsA?.length) {
950
+ elem = parentsA[0]
951
+ } else {
952
+ return
953
+ }
954
+ }
955
+
956
+ const curHref = getHref(elem)
957
+
958
+ if (curHref === val) { return }
959
+
960
+ const batchCmd = new BatchCommand('Change Link URL')
961
+
962
+ setHref(elem, val)
963
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, {
964
+ '#href': curHref
965
+ }))
966
+
967
+ svgCanvas.addCommandToHistory(batchCmd)
968
+ }
969
+
970
+ /**
971
+ * Sets the `rx` and `ry` values to the selected `rect` element
972
+ * to change its corner radius.
973
+ * @function module:svgcanvas.SvgCanvas#setRectRadius
974
+ * @param {string|Float} val - The new radius
975
+ * @fires module:svgcanvas.SvgCanvas#event:changed
976
+ * @returns {void}
977
+ */
978
+ const setRectRadiusMethod = (val) => {
979
+ const { ChangeElementCommand } = svgCanvas.history
980
+ const selectedElements = svgCanvas.getSelectedElements()
981
+ const selected = selectedElements[0]
982
+ if (selected?.tagName === 'rect') {
983
+ const r = Number(selected.getAttribute('rx'))
984
+ if (r !== val) {
985
+ selected.setAttribute('rx', val)
986
+ selected.setAttribute('ry', val)
987
+ svgCanvas.addCommandToHistory(new ChangeElementCommand(selected, { rx: r, ry: r }, 'Radius'))
988
+ svgCanvas.call('changed', [selected])
989
+ }
990
+ }
991
+ }
992
+
993
+ /**
994
+ * Wraps the selected element(s) in an anchor element or converts group to one.
995
+ * @function module:svgcanvas.SvgCanvas#makeHyperlink
996
+ * @param {string} url
997
+ * @returns {void}
998
+ */
999
+ const makeHyperlinkMethod = (url) => {
1000
+ svgCanvas.groupSelectedElements('a', url)
1001
+ }
1002
+
1003
+ /**
1004
+ * @function module:svgcanvas.SvgCanvas#removeHyperlink
1005
+ * @returns {void}
1006
+ */
1007
+ const removeHyperlinkMethod = () => {
1008
+ svgCanvas.ungroupSelectedElement()
1009
+ }
1010
+
1011
+ /**
1012
+ * Group: Element manipulation.
1013
+ */
1014
+
1015
+ /**
1016
+ * Sets the new segment type to the selected segment(s).
1017
+ * @function module:svgcanvas.SvgCanvas#setSegType
1018
+ * @param {Integer} newType - New segment type. See {@link https://www.w3.org/TR/SVG/paths.html#InterfaceSVGPathSeg} for list
1019
+ * @returns {void}
1020
+ */
1021
+ const setSegTypeMethod = (newType) => {
1022
+ svgCanvas.pathActions.setSegType(newType)
1023
+ }
1024
+
1025
+ /**
1026
+ * Set the background of the editor (NOT the actual document).
1027
+ * @function module:svgcanvas.SvgCanvas#setBackground
1028
+ * @param {string} color - String with fill color to apply
1029
+ * @param {string} url - URL or path to image to use
1030
+ * @returns {void}
1031
+ */
1032
+ const setBackgroundMethod = (color, url) => {
1033
+ const bg = getElement('canvasBackground')
1034
+ const border = bg.querySelector('rect')
1035
+ let bgImg = getElement('background_image')
1036
+ let bgPattern = getElement('background_pattern')
1037
+ border.setAttribute('fill', color === 'chessboard' ? '#fff' : color)
1038
+ if (color === 'chessboard') {
1039
+ if (!bgPattern) {
1040
+ bgPattern = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'foreignObject')
1041
+ svgCanvas.assignAttributes(bgPattern, {
1042
+ id: 'background_pattern',
1043
+ width: '100%',
1044
+ height: '100%',
1045
+ preserveAspectRatio: 'xMinYMin',
1046
+ style: 'pointer-events:none'
1047
+ })
1048
+ const div = document.createElement('div')
1049
+ svgCanvas.assignAttributes(div, {
1050
+ style: 'pointer-events:none;width:100%;height:100%;' +
1051
+ 'background-image:url(data:image/gif;base64,' +
1052
+ 'R0lGODlhEAAQAIAAAP///9bW1iH5BAAAAAAALAAAAAAQABAAAAIfjG+' +
1053
+ 'gq4jM3IFLJgpswNly/XkcBpIiVaInlLJr9FZWAQA7);'
1054
+ })
1055
+ bgPattern.append(div)
1056
+ bg.append(bgPattern)
1057
+ }
1058
+ } else if (bgPattern) {
1059
+ bgPattern.remove()
1060
+ }
1061
+ if (url) {
1062
+ if (!bgImg) {
1063
+ bgImg = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'image')
1064
+ svgCanvas.assignAttributes(bgImg, {
1065
+ id: 'background_image',
1066
+ width: '100%',
1067
+ height: '100%',
1068
+ preserveAspectRatio: 'xMinYMin',
1069
+ style: 'pointer-events:none'
1070
+ })
1071
+ }
1072
+ setHref(bgImg, url)
1073
+ bg.append(bgImg)
1074
+ } else if (bgImg) {
1075
+ bgImg.remove()
1076
+ }
1077
+ }