1var mxIsElectron = navigator.userAgent != null &&
2	navigator.userAgent.toLowerCase().indexOf(' electron/') > -1;
3var GOOGLE_APPS_MAX_AREA = 25000000;
4var GOOGLE_SHEET_MAX_AREA = 1048576; //1024x1024
5
6//TODO Add support for loading math from a local folder
7Editor.initMath((remoteMath? 'https://app.diagrams.net/' : '') + 'math/MathJax.js');
8
9function render(data)
10{
11	var autoScale = false;
12
13	if (data.scale == 'auto')
14	{
15		autoScale = true;
16		data.scale = 1;
17	}
18
19	document.body.innerHTML = '';
20	var container = document.createElement('div');
21	container.id = 'graph';
22	container.style.width = '100%';
23	container.style.height = '100%';
24	document.body.appendChild(container);
25
26	var graph = new Graph(container);
27	data.border = parseInt(data.border) || 0;
28	data.w = parseFloat(data.w) || 0;
29	data.h = parseFloat(data.h) || 0;
30	data.scale = parseFloat(data.scale) || 1;
31
32	var extras = null;
33
34	try
35	{
36		extras = JSON.parse(data.extras);
37	}
38	catch (e)
39	{
40		try
41		{
42			extras = JSON.parse(decodeURIComponent(data.extras));
43		}
44		catch (e)
45		{
46			// ignore
47		}
48	}
49
50	var gridColor = null;
51
52	if (extras != null && extras.grid != null)
53	{
54		graph.gridSize = extras.grid.size;
55		graph.view.gridSteps = extras.grid.steps;
56		gridColor = extras.grid.color;
57	}
58
59	if (extras != null && extras.diagramLanguage != null)
60	{
61		Graph.diagramLanguage = extras.diagramLanguage;
62		Graph.translateDiagram = true;
63	}
64
65	//PNG+XML format
66	if (data.xml.substring(0, 5) == 'iVBOR' || (extras != null && extras.isPng))
67	{
68		data.xml = Editor.extractGraphModelFromPng('data:image/png;base64,' + data.xml);
69	}
70
71	//IE11 sends incorrect xml
72	if (data.xml.substring(0, 11) == '<#document>')
73	{
74		data.xml = data.xml.substring(11, data.xml.length - 12);
75	}
76
77	// Parses XML
78	var doc = mxUtils.parseXml(data.xml);
79	var node = Editor.extractGraphModel(doc.documentElement, true);
80
81	if (node == null)
82	{
83		//Electron pdf export
84		try
85		{
86			const { ipcRenderer } = require('electron');
87
88			ipcRenderer.send('render-finished', null);
89		}
90		catch(e)
91		{
92			console.log(e);
93		}
94
95		return graph;
96	}
97
98	var xmlDoc = node.ownerDocument;
99	var diagrams = null;
100	var from = 0;
101
102	if (mxIsElectron && data.format == 'xml')
103	{
104		const { ipcRenderer } = require('electron');
105
106		try
107		{
108			var xml = mxUtils.getXml(xmlDoc);
109			EditorUi.prototype.createUi = function(){};
110			EditorUi.prototype.addTrees = function(){};
111			EditorUi.prototype.updateActionStates = function(){};
112			var editorUi = new EditorUi();
113			var tmpFile = new LocalFile(editorUi, xml);
114			editorUi.setCurrentFile(tmpFile);
115			editorUi.setFileData(xml);
116			ipcRenderer.send('xml-data', mxUtils.getXml(editorUi.getXmlFileData(null, null, data.uncompressed)));
117		}
118		catch(e)
119		{
120			ipcRenderer.send('xml-data-error');
121		}
122
123		return;
124	}
125
126	// Handles mxfile
127	if (xmlDoc.documentElement.nodeName == 'mxfile')
128	{
129		diagrams = xmlDoc.documentElement.getElementsByTagName('diagram');
130	}
131
132	//Add global variables to graph
133	if (extras != null && extras.globalVars != null)
134	{
135		graph.globalVars = extras.globalVars;
136	}
137
138	/**
139	 * Disables custom links on shapes.
140	 */
141	var graphGetLinkForCell = graph.getLinkForCell;
142
143	graph.getLinkForCell = function(cell)
144	{
145		var link = graphGetLinkForCell.apply(this, arguments);
146
147		if (link != null && this.isCustomLink(link))
148		{
149			link = null;
150		}
151
152		return link;
153	};
154
155	/**
156	 * Disables custom links in labels.
157	 */
158	var cellRendererRedrawLabelShape = graph.cellRenderer.redrawLabelShape;
159
160	graph.cellRenderer.redrawLabelShape = function(shape)
161	{
162		cellRendererRedrawLabelShape.apply(this, arguments);
163
164		if (shape.node != null)
165		{
166			var links = shape.node.getElementsByTagName('a');
167
168			for (var i = 0; i < links.length; i++)
169			{
170				var href = links[i].getAttribute('href');
171
172				if (href != null && graph.isCustomLink(href))
173				{
174					links[i].setAttribute('href', '#');
175				}
176			}
177		}
178	};
179
180	var preview = null;
181	var waitCounter = 1;
182	var bounds;
183	var pageId;
184	var expScale;
185	// Waits for all images to finish loading
186	var cache = new Object();
187	var math = false;
188
189	// Decrements waitCounter and invokes callback when finished
190	function decrementWaitCounter()
191	{
192		if (--waitCounter < 1)
193		{
194			//Note: This code targets Chrome as it is the browser used by export server
195			//Ensure that all fonts has been loaded, this promise is never rejected
196			document.fonts.ready.then(function()
197			{
198				var doneDiv = document.createElement("div");
199				var pageCount = diagrams != null? diagrams.length : 1;
200				doneDiv.id = 'LoadingComplete';
201				doneDiv.style.display = 'none';
202				doneDiv.setAttribute('bounds', JSON.stringify(bounds));
203				doneDiv.setAttribute('page-id', pageId);
204				doneDiv.setAttribute('scale', expScale);
205				doneDiv.setAttribute('pageCount', pageCount);
206				document.body.appendChild(doneDiv);
207
208				//Electron pdf export
209				if (mxIsElectron)
210				{
211					try
212					{
213						const { ipcRenderer } = require('electron');
214
215						ipcRenderer.on('get-svg-data', (event, arg) =>
216						{
217							graph.mathEnabled = math; //Enable math such that getSvg works as expected
218							// Returns the exported SVG for the given graph (see EditorUi.exportSvg)
219							var bg = graph.background;
220
221							if (bg == mxConstants.NONE)
222							{
223								bg = null;
224							}
225
226							var svgRoot = graph.getSvg(bg, 1, 0, false, null, true, null, null, null);
227
228							if (graph.shadowVisible)
229							{
230								graph.addSvgShadow(svgRoot);
231							}
232
233							// TODO addFontCss cannot be used as it requires this
234							// Adds CSS
235							//Editor.prototype.addFontCss(svgRoot);
236
237							if (math)
238							{
239								Editor.prototype.addMathCss(svgRoot);
240							}
241
242							ipcRenderer.send('svg-data', '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
243									mxUtils.getXml(svgRoot));
244						});
245
246						//For some reason, Electron 9 doesn't send this object as is without stringifying. Usually when variable is external to function own scope
247						ipcRenderer.send('render-finished', {bounds: JSON.stringify(bounds), pageCount: pageCount});
248					}
249					catch(e)
250					{
251						console.log(e);
252					}
253				}
254			});
255		}
256	};
257
258	function waitForImages(tagName, attributeName)
259	{
260		var imgs = document.body.getElementsByTagName(tagName);
261		waitCounter += imgs.length;
262
263		for (var i = 0; i < imgs.length; i++)
264		{
265			// No load events for image elements in Phantom using indirection instead
266			var src = imgs[i].getAttribute(attributeName);
267
268			if (src != null && src.length > 0 && cache[src] == null)
269			{
270				cache[src] = new Image();
271				cache[src].onload = decrementWaitCounter;
272				cache[src].onerror = decrementWaitCounter;
273				cache[src].src = src;
274			}
275			else
276			{
277				decrementWaitCounter();
278			}
279		}
280	};
281
282	// Waits for MathJax.Hub to become available to register
283	// wait counter callback asynchronously after math render
284	var editorDoMathJaxRender = Editor.doMathJaxRender;
285
286	Editor.doMathJaxRender = function(container)
287	{
288		editorDoMathJaxRender.apply(this, arguments);
289
290		window.setTimeout(function()
291		{
292			window.MathJax.Hub.Queue(function ()
293			{
294				decrementWaitCounter();
295			});
296		}, 0);
297	};
298
299	// Adds async MathJax rendering task
300	function renderMath(elt)
301	{
302		if (math && Editor.MathJaxRender != null)
303		{
304			waitCounter++;
305			Editor.MathJaxRender(elt);
306		}
307	};
308
309	function loadExtFonts(extFonts)
310	{
311		try
312		{
313			extFonts = extFonts.split('|').map(function(ef)
314			{
315				var parts = ef.split('^');
316				return {name: parts[0], url: parts[1]};
317			});
318		}
319		catch(e)
320		{
321			//ignore and return!
322			return;
323		}
324
325		waitCounter += extFonts.length;
326
327		//Note: This code targets Chrome as it is the browser used by export server
328		for (var i = 0; i < extFonts.length; i++)
329		{
330			if (extFonts[i].url.indexOf(Editor.GOOGLE_FONTS) == 0)
331			{
332				var link = document.createElement('link');
333
334				link.setAttribute('rel', 'stylesheet');
335				link.setAttribute('charset', 'UTF-8');
336				link.setAttribute('type', 'text/css');
337
338				link.onload = decrementWaitCounter;
339				link.onerror = decrementWaitCounter;
340
341				link.setAttribute('href', extFonts[i].url);
342				var head = document.getElementsByTagName('head')[0];
343				head.appendChild(link);
344			}
345			else
346			{
347				//Relative urls doesn't work
348				if (extFonts[i].url.indexOf(PROXY_URL) == 0 && PROXY_URL.indexOf('http') == -1)
349				{
350					var href = window.location.href;
351					href = href.substring(0, href.lastIndexOf('/') + 1);
352					extFonts[i].url = href + extFonts[i].url;
353				}
354
355				var font = new FontFace(extFonts[i].name, 'url(' + extFonts[i].url + ')');
356
357				font.load().then(function(loadedFont)
358				{
359					document.fonts.add(loadedFont);
360					decrementWaitCounter();
361				}).catch(decrementWaitCounter);
362			}
363		}
364	};
365
366	function renderGrid()
367	{
368		if (gridColor == null) return;
369
370		var view = graph.view;
371		var gridImage = btoa(unescape(encodeURIComponent(view.createSvgGrid(gridColor))));
372		gridImage = 'url(' + 'data:image/svg+xml;base64,' + gridImage + ')';
373		var phase = graph.gridSize * view.gridSteps * view.scale;
374
375		var x0 = 0;
376		var y0 = 0;
377
378		if (view.backgroundPageShape != null)
379		{
380			var bds = view.getBackgroundPageBounds();
381
382			x0 = 1 + bds.x;
383			y0 = 1 + bds.y;
384		}
385
386		// Computes the offset to maintain origin for grid
387		var position = -Math.round(phase - mxUtils.mod(view.translate.x * view.scale - x0, phase)) + 'px ' +
388			-Math.round(phase - mxUtils.mod(view.translate.y * view.scale - y0, phase)) + 'px';
389
390		var pages = document.querySelectorAll('[id^=mxPage]');
391
392		var cssTxt = 'margin: 0;padding: 0;background-image: ' + gridImage + ';background-position: ' + position;
393		document.body.style.cssText = cssTxt;
394
395		for (var i = 0; i < pages.length; i++)
396		{
397			pages[i].style.cssText = cssTxt;
398		}
399	};
400
401	var origAddFont = Graph.addFont;
402
403	Graph.addFont = function(name, url)
404	{
405		waitCounter++;
406		return origAddFont.call(this, name, url, decrementWaitCounter);
407	};
408
409	function renderPage()
410	{
411		// Enables math typesetting
412		math |= xmlDoc.documentElement.getAttribute('math') == '1';
413
414		//Load external fonts
415		var extFonts = xmlDoc.documentElement.getAttribute('extFonts');
416
417		if (extFonts)
418		{
419			loadExtFonts(extFonts);
420		}
421
422		// Configure graph
423		graph.foldingEnabled = false;
424		graph.setEnabled(false);
425
426		// Sets background image
427		var bgImg = xmlDoc.documentElement.getAttribute('backgroundImage');
428
429		if (bgImg != null)
430		{
431			bgImg = JSON.parse(bgImg);
432			graph.setBackgroundImage(new mxImage(bgImg.src, bgImg.width,
433				bgImg.height, bgImg.x, bgImg.y));
434		}
435
436		// Parses XML into graph
437		var codec = new mxCodec(xmlDoc);
438		var model = graph.getModel();
439		codec.decode(xmlDoc.documentElement, model);
440
441		var bg;
442
443		if (data.format == 'pdf')
444		{
445			if (data.bg == 'none')
446			{
447				bg = null;
448			}
449			else
450			{
451				bg = xmlDoc.documentElement.getAttribute('background');
452
453				if (bg == 'none' || !bg)
454				{
455					bg = '#ffffff';
456				}
457			}
458		}
459		else
460		{
461			// Loads background color
462			bg = (data.bg != null && data.bg.length > 0) ?
463				data.bg : xmlDoc.documentElement.getAttribute('background');
464
465			// Normalizes values for transparent backgrounds
466			if (bg == 'none' || bg == '')
467			{
468				bg = null;
469			}
470
471			// Checks if export format supports transparent backgrounds
472			if (bg == null && data.format != 'gif' && data.format != 'png')
473			{
474				bg = '#ffffff';
475			}
476		}
477
478		// Sets background color on page
479		if (bg != null)
480		{
481			document.body.style.backgroundColor = bg;
482		}
483
484		//handle layers
485		if (extras != null && ((extras.layers != null && extras.layers.length > 0) ||
486							   (extras.layerIds != null && extras.layerIds.length > 0)))
487		{
488			var childCount = model.getChildCount(model.root);
489
490			// Hides all layers
491			for (var i = 0; i < childCount; i++)
492			{
493				model.setVisible(model.getChildAt(model.root, i), false);
494			}
495
496			if (extras.layerIds != null)
497			{
498				for (var i = 0; i < extras.layerIds.length; i++)
499				{
500					model.setVisible(model.getCell(extras.layerIds[i]), true);
501				}
502			}
503			else
504			{
505				for (var i = 0; i < extras.layers.length; i++)
506				{
507					var layer = model.getChildAt(model.root, extras.layers[i]);
508
509					if (layer != null)
510					{
511						model.setVisible(layer, true);
512					}
513				}
514			}
515		}
516
517		// Sets initial value for PDF page background
518		graph.pdfPageVisible = false;
519
520		// Handles PDF output where the output should match the page format if the page is visible
521		if (data.print || (data.format == 'pdf' && xmlDoc.documentElement.getAttribute('page') == '1' && data.w == 0 && data.h == 0 && data.scale == 1))
522		{
523			//Electron printing
524			var printScale = 1;
525
526			if (data.print)
527			{
528				document.title = data.fileTitle;
529
530				var gb = graph.getGraphBounds();
531				printScale = data.pageScale;
532
533				if (isNaN(printScale))
534				{
535					printScale = 1;
536				}
537
538				if (data.fit)
539				{
540					var h = parseInt(data.sheetsAcross);
541					var v = parseInt(data.sheetsDown);
542
543					data.scale = Math.min((data.pageHeight * v) / (gb.height / graph.view.scale),
544							(data.pageWidth * h) / (gb.width / graph.view.scale));
545				}
546				else
547				{
548					data.scale = data.scale / graph.pageScale;
549
550					if (isNaN(data.scale))
551					{
552						printScale = 1 / graph.pageScale;
553					}
554				}
555			}
556
557			var pw = data.pageWidth || xmlDoc.documentElement.getAttribute('pageWidth');
558			var ph = data.pageHeight || xmlDoc.documentElement.getAttribute('pageHeight');
559			graph.pdfPageVisible = true;
560
561			if (pw != null && ph != null)
562			{
563				graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
564			}
565
566			var ps = data.pageScale || xmlDoc.documentElement.getAttribute('pageScale');
567
568			if (ps != null)
569			{
570				graph.pageScale = ps;
571			}
572
573			graph.getPageSize = function()
574			{
575				return new mxRectangle(0, 0, this.pageFormat.width * this.pageScale,
576					this.pageFormat.height * this.pageScale);
577			};
578
579			graph.getPageLayout = function()
580			{
581				var size = this.getPageSize();
582				var bounds = this.getGraphBounds();
583
584				if (bounds.width == 0 || bounds.height == 0)
585				{
586					return new mxRectangle(0, 0, 1, 1);
587				}
588				else
589				{
590					// Computes untransformed graph bounds
591					var x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
592					var y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
593					var w = Math.floor(bounds.width / this.view.scale);
594					var h = Math.floor(bounds.height / this.view.scale);
595
596					var x0 = Math.floor(x / size.width);
597					var y0 = Math.floor(y / size.height);
598					var w0 = Math.ceil((x + w) / size.width) - x0;
599					var h0 = Math.ceil((y + h) / size.height) - y0;
600
601					return new mxRectangle(x0, y0, w0, h0);
602				}
603			};
604
605			// Fits the number of background pages to the graph
606			graph.view.getBackgroundPageBounds = function()
607			{
608				var layout = this.graph.getPageLayout();
609				var page = this.graph.getPageSize();
610
611				return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
612					this.scale * (this.translate.y + layout.y * page.height),
613					this.scale * layout.width * page.width,
614					this.scale * layout.height * page.height);
615			};
616		}
617
618		if (!graph.pdfPageVisible)
619		{
620			var b = graph.getGraphBounds();
621
622			// Floor is needed to keep rendering crisp
623			if (data.w > 0 || data.h > 0)
624			{
625				var s = 1;
626
627				if (data.w > 0 && data.h > 0)
628				{
629					s = Math.min(data.w / b.width, data.h / b.height);
630				}
631				else if (data.w > 0)
632				{
633					s = data.w / b.width;
634				}
635				else
636				{
637					s = data.h / b.height;
638				}
639
640				graph.view.scaleAndTranslate(s,
641					Math.floor(data.border / s - Math.floor(b.x)),
642					Math.floor(data.border / s - Math.floor(b.y)));
643			}
644			else
645			{
646				var s = data.scale;
647
648				if (autoScale)
649				{
650					var pageWidth = (extras != null && extras.pageWidth != null) ? extras.pageWidth : 800;
651
652					if (b.width < pageWidth & b.height < 1.5 * pageWidth)
653					{
654						s = 4;
655					}
656					else if (b.width < 2 * pageWidth & b.height < 3 * pageWidth)
657					{
658						s = 3;
659					}
660					else if (b.width < 4 * pageWidth && b.height < 6 * pageWidth)
661					{
662						s = 2;
663					}
664
665					if (extras != null && extras.isGoogleSheet != null)
666					{
667						GOOGLE_APPS_MAX_AREA = GOOGLE_SHEET_MAX_AREA;
668					}
669
670					//The image cannot exceed 25 MP to be included in Google Apps
671					if (b.width * s * b.height * s > GOOGLE_APPS_MAX_AREA)
672					{
673						//Subtracting 0.01 to prevent any other rounding that can make slightly over 25 MP
674						s = Math.sqrt(GOOGLE_APPS_MAX_AREA / (b.width * b.height)) - 0.01;
675					}
676				}
677
678				graph.view.scaleAndTranslate(s,
679					Math.floor(data.border - Math.floor(b.x)),
680					Math.floor(data.border - Math.floor(b.y)));
681			}
682		}
683		else
684		{
685			// Disables border for PDF page export
686			data.border = 0;
687
688			// Moves to first page in page layout
689			var layout = graph.getPageLayout();
690			var page = graph.getPageSize();
691			var dx = layout.x * page.width;
692			var dy = layout.y * page.height;
693
694			if (dx != 0 || dy != 0)
695			{
696				graph.view.setTranslate(Math.floor(-dx), Math.floor(-dy));
697			}
698		}
699
700		// Gets the diagram bounds and sets the document size
701		bounds = (graph.pdfPageVisible) ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds();
702		bounds.width = Math.ceil(bounds.width + data.border) + 1; //The 1 extra pixels to prevent cutting the cells on the edges when crop is enabled
703		bounds.height = Math.ceil(bounds.height + data.border) + 1; //The 1 extra pixels to prevent starting a new page. TODO Not working in every case
704		expScale = graph.view.scale || 1;
705
706		// Converts the graph to a vertical sequence of pages for PDF export
707		if (graph.pdfPageVisible)
708		{
709			var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
710			var scale = data.print? data.scale : 1 / graph.pageScale;
711			var autoOrigin = (data.print && data.fit != null) ? data.fit : false;
712			var border = 0;
713
714			// Negative coordinates are cropped or shifted if page visible
715			var gb = graph.getGraphBounds();
716			var x0 = 0;
717			var y0 = 0;
718
719			// Applies print scale
720			pf = mxRectangle.fromRectangle(pf);
721			pf.width = Math.ceil(pf.width * printScale) + 1; //The 1 extra pixels to prevent cutting the cells on the right edge of the page
722			pf.height = Math.ceil(pf.height * printScale) + 1; //The 1 extra pixels to prevent starting a new page. TODO Not working in every case
723			scale *= printScale;
724
725			// Starts at first visible page
726			if (!autoOrigin)
727			{
728				var layout = graph.getPageLayout();
729				x0 -= layout.x * pf.width;
730				y0 -= layout.y * pf.height;
731			}
732
733			if (preview == null)
734			{
735				preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
736				preview.printBackgroundImage = true;
737				preview.autoOrigin = autoOrigin;
738				preview.backgroundColor = bg;
739				// Renders print output into this document and removes the graph container
740				preview.open(null, window);
741				graph.container.parentNode.removeChild(graph.container);
742			}
743			else
744			{
745				preview.backgroundColor = bg;
746				preview.autoOrigin = autoOrigin;
747				preview.appendGraph(graph, scale, x0, y0);
748			}
749
750			// Adds shadow
751			// NOTE: Shadow rasterizes output
752			/*if (mxClient.IS_SVG && xmlDoc.documentElement.getAttribute('shadow') == '1')
753			{
754				var svgs = document.getElementsByTagName('svg');
755
756				for (var i = 0; i < svgs.length; i++)
757				{
758					var svg = svgs[i];
759
760					var filter = graph.addSvgShadow(svg, null, true);
761					filter.setAttribute('id', 'shadow-' + i);
762					svg.appendChild(filter);
763					svg.setAttribute('filter', 'url(#' + 'shadow-' + i + ')');
764				}
765
766				border = 7;
767			}*/
768
769			bounds = new mxRectangle(0, 0, pf.width, pf.height);
770		}
771		else
772		{
773			var bgImg = graph.backgroundImage;
774
775			if (bgImg != null)
776			{
777				var t = graph.view.translate;
778				var s = graph.view.scale;
779
780				bounds.add(new mxRectangle(
781					(t.x + bgImg.x) * s, (t.y + bgImg.y) * s,
782					bgImg.width * s, bgImg.height * s));
783			}
784
785			// Adds shadow
786			// NOTE: PDF shadow rasterizes output so it's disabled
787			if (data.format != 'pdf' && mxClient.IS_SVG && xmlDoc.documentElement.getAttribute('shadow') == '1')
788			{
789				graph.addSvgShadow(graph.view.canvas.ownerSVGElement, null, true);
790				graph.setShadowVisible(true);
791				bounds.width += 7;
792				bounds.height += 7;
793			}
794
795			document.body.style.width = Math.ceil(bounds.x + bounds.width) + 'px';
796			document.body.style.height = Math.ceil(bounds.y + bounds.height) + 'px';
797		}
798	};
799
800	if (diagrams != null && diagrams.length > 0)
801	{
802		var to = diagrams.length - 1;
803
804		//Parameters to and all pages should not be sent with formats other than PDF with page view enabled
805		if (!data.allPages)
806		{
807			if (data.pageId != null)
808			{
809				for (var i = 0; i < diagrams.length; i++)
810				{
811					if (data.pageId == diagrams[i].getAttribute('id'))
812					{
813						from = i;
814						to = i;
815						break;
816					}
817				}
818			}
819			else
820			{
821				from = Math.max(0, Math.min(parseInt(data.from) || from, diagrams.length - 1));
822				to = parseInt(data.to);
823				//If to is not defined, use from (so one page), otherwise, to is restricted to the range from "from" to diagrams.length - 1
824				to = isNaN(to)? from : Math.max(from, Math.min(to, diagrams.length - 1));
825			}
826		}
827
828		/**
829		 * Implements %page% and %pagenumber% placeholders
830		 */
831		var graphGetGlobalVariable = graph.getGlobalVariable;
832
833		graph.getGlobalVariable = function(name)
834		{
835			if (name == 'page')
836			{
837				return (diagrams == null) ? 'Page-1' :
838					(diagrams[from].getAttribute('name') || ('Page-' + (from + 1)));
839			}
840			else if (name == 'pagenumber')
841			{
842				return from + 1;
843			}
844
845			return graphGetGlobalVariable.apply(this, arguments);
846		};
847
848		for (var i = from; i <= to; i++)
849		{
850			if (diagrams[i] != null)
851			{
852				if (pageId == null)
853				{
854					pageId = diagrams[i].getAttribute('id')
855				}
856
857				xmlDoc = Editor.parseDiagramNode(diagrams[i]);
858
859				if (xmlDoc != null)
860				{
861					xmlDoc = xmlDoc.ownerDocument;
862				}
863
864				graph.getModel().clear();
865				from = i;
866				renderPage();
867			}
868		}
869	}
870	else
871	{
872		renderPage();
873	}
874
875	if (fallbackFont)
876	{
877		//Add a fallbackFont font to all labels in case the selected font doesn't support the character
878		//Some systems doesn't have a good fallback fomt that supports all languages
879		//Use this with a custom font-face in export-fonts.css file
880		document.querySelectorAll('foreignObject div').forEach(d => d.style.fontFamily = (d.style.fontFamily || '') + ', ' + fallbackFont);
881	}
882
883	renderGrid();
884	// Includes images in SVG and HTML labels
885	waitForImages('image', 'xlink:href');
886	waitForImages('img', 'src');
887	renderMath(document.body);
888	// Immediate return if not waiting for any content
889	decrementWaitCounter();
890
891	return graph;
892};
893
894//Electron pdf export
895if (mxIsElectron)
896{
897	try
898	{
899		const { ipcRenderer } = require('electron');
900
901		ipcRenderer.on('render', (event, arg) =>
902		{
903			render(arg);
904		});
905	}
906	catch(e)
907	{
908		console.log(e);
909	}
910}
911