xyc
2024-05-17 49b00a322eae2b9b95f04e41c174ef3b4940017c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/**
* Handles underlying communication between the embedding window and the editor frame
* @module EmbeddedSVGEdit
*/
 
let cbid = 0;
 
/**
* @callback module:EmbeddedSVGEdit.CallbackSetter
* @param {GenericCallback} newCallback Callback to be stored (signature dependent on function)
* @returns {undefined}
*/
/**
* @callback module:EmbeddedSVGEdit.CallbackSetGetter
* @param {...*} args Signature dependent on the function
* @returns {module:EmbeddedSVGEdit.CallbackSetter}
*/
 
/**
* @param {string} funcName
* @returns {module:EmbeddedSVGEdit.CallbackSetGetter}
*/
function getCallbackSetter (funcName) {
  return function (...args) {
    const t = this, // New callback
      cbid = t.send(funcName, args, function () {}); // The callback (currently it's nothing, but will be set later)
 
    return function (newCallback) {
      t.callbacks[cbid] = newCallback; // Set callback
    };
  };
}
 
/**
* Having this separate from messageListener allows us to
* avoid using JSON parsing (and its limitations) in the case
* of same domain control.
* @param {module:EmbeddedSVGEdit.EmbeddedSVGEdit} t The `this` value
* @param {JSON} data
* @returns {undefined}
*/
function addCallback (t, {result, error, id: cbid}) {
  if (typeof cbid === 'number' && t.callbacks[cbid]) {
    // These should be safe both because we check `cbid` is numeric and
    //   because the calls are from trusted origins
    if (result) {
      t.callbacks[cbid](result); // lgtm [js/remote-property-injection]
    } else {
      t.callbacks[cbid](error, 'error'); // lgtm [js/remote-property-injection]
    }
  }
}
 
/**
* @param {Event} e
* @returns {undefined}
*/
function messageListener (e) {
  // We accept and post strings as opposed to objects for the sake of IE9 support; this
  //   will most likely be changed in the future
  if (!e.data || !['string', 'object'].includes(typeof e.data)) {
    return;
  }
  const {allowedOrigins} = this,
    data = typeof e.data === 'object' ? e.data : JSON.parse(e.data);
  if (!data || typeof data !== 'object' || data.namespace !== 'svg-edit' ||
    e.source !== this.frame.contentWindow ||
    (!allowedOrigins.includes('*') && !allowedOrigins.includes(e.origin))
  ) {
    console.log(`The origin ${e.origin} was not whitelisted as an origin from which responses may be received by this ${window.origin} script.`);
    return;
  }
  addCallback(this, data);
}
 
/**
* @callback module:EmbeddedSVGEdit.MessageListener
* @param {MessageEvent} e
* @returns {undefined}
*/
/**
* @param {module:EmbeddedSVGEdit.EmbeddedSVGEdit} t The `this` value
* @returns {module:EmbeddedSVGEdit.MessageListener} Event listener
*/
function getMessageListener (t) {
  return function (e) {
    messageListener.call(t, e);
  };
}
 
/**
* Embedded SVG-edit API.
* General usage:
* - Have an iframe somewhere pointing to a version of svg-edit > r1000.
* @example
 
// Initialize the magic with:
const svgCanvas = new EmbeddedSVGEdit(window.frames.svgedit);
 
// Pass functions in this format:
svgCanvas.setSvgString('string');
 
// Or if a callback is needed:
svgCanvas.setSvgString('string')(function(data, error){
  if (error){
  // There was an error
  } else{
  // Handle data
  }
});
 
// Everything is done with the same API as the real svg-edit,
// and all documentation is unchanged.
 
// However, this file depends on the postMessage API which
// can only support JSON-serializable arguments and
// return values, so, for example, arguments whose value is
// 'undefined', a function, a non-finite number, or a built-in
// object like Date(), RegExp(), etc. will most likely not behave
// as expected. In such a case one may need to host
// the SVG editor on the same domain and reference the
// JavaScript methods on the frame itself.
 
// The only other difference is
// when handling returns: the callback notation is used instead.
const blah = new EmbeddedSVGEdit(window.frames.svgedit);
blah.clearSelection('woot', 'blah', 1337, [1, 2, 3, 4, 5, 'moo'], -42, {a: 'tree',b:6, c: 9})(function(){console.log('GET DATA',arguments)})
*
* @memberof module:EmbeddedSVGEdit
*/
class EmbeddedSVGEdit {
  /**
  * @param {HTMLIFrameElement} frame
  * @param {string[]} [allowedOrigins=[]] Array of origins from which incoming
  *   messages will be allowed when same origin is not used; defaults to none.
  *   If supplied, it should probably be the same as svgEditor's allowedOrigins
  */
  constructor (frame, allowedOrigins) {
    const t = this;
    this.allowedOrigins = allowedOrigins || [];
    // Initialize communication
    this.frame = frame;
    this.callbacks = {};
    // List of functions extracted with this:
    // Run in firebug on http://svg-edit.googlecode.com/svn/trunk/docs/files/svgcanvas-js.html
 
    // for (const i=0,q=[],f = document.querySelectorAll('div.CFunction h3.CTitle a'); i < f.length; i++) { q.push(f[i].name); }; q
    // const functions = ['clearSelection', 'addToSelection', 'removeFromSelection', 'open', 'save', 'getSvgString', 'setSvgString',
    // 'createLayer', 'deleteCurrentLayer', 'setCurrentLayer', 'renameCurrentLayer', 'setCurrentLayerPosition', 'setLayerVisibility',
    // 'moveSelectedToLayer', 'clear'];
 
    // Newer, well, it extracts things that aren't documented as well. All functions accessible through the normal thingy can now be accessed though the API
    // const {svgCanvas} = frame.contentWindow;
    // const l = [];
    // for (const i in svgCanvas) { if (typeof svgCanvas[i] === 'function') { l.push(i);} };
    // alert("['" + l.join("', '") + "']");
    // Run in svgedit itself
    const functions = [
      'addExtension',
      'addSVGElementFromJson',
      'addToSelection',
      'alignSelectedElements',
      'assignAttributes',
      'bind',
      'call',
      'changeSelectedAttribute',
      'cleanupElement',
      'clear',
      'clearSelection',
      'clearSvgContentElement',
      'cloneLayer',
      'cloneSelectedElements',
      'convertGradients',
      'convertToGroup',
      'convertToNum',
      'convertToPath',
      'copySelectedElements',
      'createLayer',
      'cutSelectedElements',
      'cycleElement',
      'deleteCurrentLayer',
      'deleteSelectedElements',
      'embedImage',
      'exportPDF',
      'findDefs',
      'getBBox',
      'getBlur',
      'getBold',
      'getColor',
      'getContentElem',
      'getCurrentDrawing',
      'getDocumentTitle',
      'getEditorNS',
      'getElem',
      'getFillOpacity',
      'getFontColor',
      'getFontFamily',
      'getFontSize',
      'getHref',
      'getId',
      'getIntersectionList',
      'getItalic',
      'getMode',
      'getMouseTarget',
      'getNextId',
      'getOffset',
      'getOpacity',
      'getPaintOpacity',
      'getPrivateMethods',
      'getRefElem',
      'getResolution',
      'getRootElem',
      'getRotationAngle',
      'getSelectedElems',
      'getStrokeOpacity',
      'getStrokeWidth',
      'getStrokedBBox',
      'getStyle',
      'getSvgString',
      'getText',
      'getTitle',
      'getTransformList',
      'getUIStrings',
      'getUrlFromAttr',
      'getVersion',
      'getVisibleElements',
      'getVisibleElementsAndBBoxes',
      'getZoom',
      'groupSelectedElements',
      'groupSvgElem',
      'hasMatrixTransform',
      'identifyLayers',
      'importSvgString',
      'leaveContext',
      'linkControlPoints',
      'makeHyperlink',
      'matrixMultiply',
      'mergeAllLayers',
      'mergeLayer',
      'moveSelectedElements',
      'moveSelectedToLayer',
      'moveToBottomSelectedElement',
      'moveToTopSelectedElement',
      'moveUpDownSelected',
      'open',
      'pasteElements',
      'prepareSvg',
      'pushGroupProperties',
      'randomizeIds',
      'rasterExport',
      'ready',
      'recalculateAllSelectedDimensions',
      'recalculateDimensions',
      'remapElement',
      'removeFromSelection',
      'removeHyperlink',
      'removeUnusedDefElems',
      'renameCurrentLayer',
      'round',
      'runExtensions',
      'sanitizeSvg',
      'save',
      'selectAllInCurrentLayer',
      'selectOnly',
      'setBBoxZoom',
      'setBackground',
      'setBlur',
      'setBlurNoUndo',
      'setBlurOffsets',
      'setBold',
      'setColor',
      'setConfig',
      'setContext',
      'setCurrentLayer',
      'setCurrentLayerPosition',
      'setDocumentTitle',
      'setFillPaint',
      'setFontColor',
      'setFontFamily',
      'setFontSize',
      'setGoodImage',
      'setGradient',
      'setGroupTitle',
      'setHref',
      'setIdPrefix',
      'setImageURL',
      'setItalic',
      'setLayerVisibility',
      'setLinkURL',
      'setMode',
      'setOpacity',
      'setPaint',
      'setPaintOpacity',
      'setRectRadius',
      'setResolution',
      'setRotationAngle',
      'setSegType',
      'setStrokeAttr',
      'setStrokePaint',
      'setStrokeWidth',
      'setSvgString',
      'setTextContent',
      'setUiStrings',
      'setUseData',
      'setZoom',
      'svgCanvasToString',
      'svgToString',
      'transformListToTransform',
      'ungroupSelectedElement',
      'uniquifyElems',
      'updateCanvas',
      'zoomChanged'
    ];
 
    // TODO: rewrite the following, it's pretty scary.
    for (let i = 0; i < functions.length; i++) {
      this[functions[i]] = getCallbackSetter(functions[i]);
    }
 
    // Older IE may need a polyfill for addEventListener, but so it would for SVG
    window.addEventListener('message', getMessageListener(this));
    window.addEventListener('keydown', (e) => {
      const {key, keyCode, charCode, which} = e;
      if (e.key === 'Backspace') {
        e.preventDefault();
        const keyboardEvent = new KeyboardEvent(e.type, {
          key, keyCode, charCode, which
        });
        t.frame.contentDocument.dispatchEvent(keyboardEvent);
      }
    });
  }
 
  /**
  * @param {string} name
  * @param {ArgumentsArray} args Signature dependent on function
  * @param {GenericCallback} callback
  * @returns {Integer}
  */
  send (name, args, callback) {
    const t = this;
    cbid++;
 
    this.callbacks[cbid] = callback;
    setTimeout((function (cbid) {
      return function () { // Delay for the callback to be set in case its synchronous
        /*
        * Todo: Handle non-JSON arguments and return values (undefined,
        *   nonfinite numbers, functions, and built-in objects like Date,
        *   RegExp), etc.? Allow promises instead of callbacks? Review
        *   SVG-Edit functions for whether JSON-able parameters can be
        *   made compatile with all API functionality
        */
        // We accept and post strings for the sake of IE9 support
        let sameOriginWithGlobal = false;
        try {
          sameOriginWithGlobal = window.location.origin === t.frame.contentWindow.location.origin &&
            t.frame.contentWindow.svgEditor.canvas;
        } catch (err) {}
 
        if (sameOriginWithGlobal) {
          // Although we do not really need this API if we are working same
          //  domain, it could allow us to write in a way that would work
          //  cross-domain as well, assuming we stick to the argument limitations
          //  of the current JSON-based communication API (e.g., not passing
          //  callbacks). We might be able to address these shortcomings; see
          //  the todo elsewhere in this file.
          const message = {id: cbid},
            {svgEditor: {canvas: svgCanvas}} = t.frame.contentWindow;
          try {
            message.result = svgCanvas[name].apply(svgCanvas, args);
          } catch (err) {
            message.error = err.message;
          }
          addCallback(t, message);
        } else { // Requires the ext-xdomain-messaging.js extension
          t.frame.contentWindow.postMessage(JSON.stringify({
            namespace: 'svgCanvas', id: cbid, name, args
          }), '*');
        }
      };
    }(cbid)), 0);
 
    return cbid;
  }
}
 
export default EmbeddedSVGEdit;