text: var mxIsElectron = navigator.userAgent != null &&
  	navigator.userAgent.toLowerCase().indexOf(' electron/') > -1;
  var GOOGLE_APPS_MAX_AREA = 25000000;
  var GOOGLE_SHEET_MAX_AREA = 1048576; //1024x1024
  
  //TODO Add support for loading math from a local folder
  Editor.initMath((remoteMath? 'https://app.diagrams.net/' : '') + 'math/MathJax.js');
  
  function render(data)
  {
  	var autoScale = false;
  	
  	if (data.scale == 'auto')
  	{
  		autoScale = true;
  		data.scale = 1;
  	}
  	
  	document.body.innerHTML = '';
  	var container = document.createElement('div');
  	container.id = 'graph';
  	container.style.width = '100%';
  	container.style.height = '100%';
  	document.body.appendChild(container);
  	
  	var graph = new Graph(container);
  	data.border = parseInt(data.border) || 0;
  	data.w = parseFloat(data.w) || 0;
  	data.h = parseFloat(data.h) || 0;
  	data.scale = parseFloat(data.scale) || 1;
  	
  	var extras = null;
  	
  	try
  	{
  		extras = JSON.parse(data.extras);
  	} 
  	catch (e)
  	{
  		try 
  		{
  			extras = JSON.parse(decodeURIComponent(data.extras));
  		}
  		catch (e)
  		{
  			// ignore
  		}
  	}
  
  	var gridColor = null;
  	
  	if (extras != null && extras.grid != null)
  	{
  		graph.gridSize = extras.grid.size;
  		graph.view.gridSteps = extras.grid.steps;
  		gridColor = extras.grid.color;
  	}
  	
  	if (extras != null && extras.diagramLanguage != null)
  	{
  		Graph.diagramLanguage = extras.diagramLanguage;
  		Graph.translateDiagram = true;
  	}
  	
  	//PNG+XML format
  	if (data.xml.substring(0, 5) == 'iVBOR' || (extras != null && extras.isPng))
  	{
  		data.xml = Editor.extractGraphModelFromPng('data:image/png;base64,' + data.xml);
  	}
  	
  	//IE11 sends incorrect xml
  	if (data.xml.substring(0, 11) == '<#document>')
  	{
  		data.xml = data.xml.substring(11, data.xml.length - 12);
  	}
  	
  	// Parses XML
  	var doc = mxUtils.parseXml(data.xml);
  	var node = Editor.extractGraphModel(doc.documentElement, true);
  
  	if (node == null)
  	{
  		//Electron pdf export
  		try 
  		{
  			const { ipcRenderer } = require('electron');
  			
  			ipcRenderer.send('render-finished', null);
  		}
  		catch(e)
  		{
  			console.log(e);
  		}
  		
  		return graph;
  	}
  	
  	var xmlDoc = node.ownerDocument;
  	var diagrams = null;
  	var from = 0;
  
  	if (mxIsElectron && data.format == 'xml')
  	{
  		const { ipcRenderer } = require('electron');
  
  		try
  		{
  			var xml = mxUtils.getXml(xmlDoc);
  			EditorUi.prototype.createUi = function(){};
  			EditorUi.prototype.addTrees = function(){};
  			EditorUi.prototype.updateActionStates = function(){};
  			var editorUi = new EditorUi();
  			var tmpFile = new LocalFile(editorUi, xml);
  			editorUi.setCurrentFile(tmpFile);
  			editorUi.setFileData(xml);
  			ipcRenderer.send('xml-data', mxUtils.getXml(editorUi.getXmlFileData(null, null, data.uncompressed)));
  		}
  		catch(e)
  		{
  			ipcRenderer.send('xml-data-error');
  		}
  		
  		return;
  	}
  
  	// Handles mxfile
  	if (xmlDoc.documentElement.nodeName == 'mxfile')
  	{
  		diagrams = xmlDoc.documentElement.getElementsByTagName('diagram');
  	}
  	
  	//Add global variables to graph
  	if (extras != null && extras.globalVars != null)
  	{
  		graph.globalVars = extras.globalVars;
  	}
  
  	/**
  	 * Disables custom links on shapes.
  	 */
  	var graphGetLinkForCell = graph.getLinkForCell;
  	
  	graph.getLinkForCell = function(cell)
  	{
  		var link = graphGetLinkForCell.apply(this, arguments);
  		
  		if (link != null && this.isCustomLink(link))
  		{
  			link = null;
  		}
  		
  		return link;
  	};
  	
  	/**
  	 * Disables custom links in labels.
  	 */
  	var cellRendererRedrawLabelShape = graph.cellRenderer.redrawLabelShape;
  	
  	graph.cellRenderer.redrawLabelShape = function(shape)
  	{
  		cellRendererRedrawLabelShape.apply(this, arguments);
  		
  		if (shape.node != null)
  		{
  			var links = shape.node.getElementsByTagName('a');
  
  			for (var i = 0; i < links.length; i++)
  			{
  				var href = links[i].getAttribute('href');
  				
  				if (href != null && graph.isCustomLink(href))
  				{
  					links[i].setAttribute('href', '#');
  				}
  			}	
  		}
  	};
  	
  	var preview = null;
  	var waitCounter = 1;
  	var bounds;
  	var pageId;
  	var expScale;
  	// Waits for all images to finish loading
  	var cache = new Object();
  	var math = false;
  	
  	// Decrements waitCounter and invokes callback when finished
  	function decrementWaitCounter()
  	{
  		if (--waitCounter < 1)
  		{
  			//Note: This code targets Chrome as it is the browser used by export server
  			//Ensure that all fonts has been loaded, this promise is never rejected
  			document.fonts.ready.then(function() 
  			{
  				var doneDiv = document.createElement("div");
  				doneDiv.id = 'LoadingComplete';
  				doneDiv.style.display = 'none';
  				doneDiv.setAttribute('bounds', JSON.stringify(bounds));
  				doneDiv.setAttribute('page-id', pageId);
  				doneDiv.setAttribute('scale', expScale);
  				doneDiv.setAttribute('pageCount', diagrams != null? diagrams.length : 1);
  				document.body.appendChild(doneDiv);
  
  				//Electron pdf export
  				if (mxIsElectron)
  				{
  					try 
  					{
  						const { ipcRenderer } = require('electron');
  						
  						ipcRenderer.on('get-svg-data', (event, arg) => 
  						{
  							graph.mathEnabled = math; //Enable math such that getSvg works as expected
  							// Returns the exported SVG for the given graph (see EditorUi.exportSvg)
  							var bg = graph.background;
  								
  							if (bg == mxConstants.NONE)
  							{
  								bg = null;
  							}
  							
  							var svgRoot = graph.getSvg(bg, 1, 0, false, null, true, null, null, null);
  							
  							if (graph.shadowVisible)
  							{
  								graph.addSvgShadow(svgRoot);
  							}
  							
  							// TODO addFontCss cannot be used as it requires this
  							// Adds CSS
  							//Editor.prototype.addFontCss(svgRoot);
  							
  							if (math)
  							{
  								Editor.prototype.addMathCss(svgRoot);
  							}
  							
  							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' +
  									mxUtils.getXml(svgRoot));
  						});
  						
  						//For some reason, Electron 9 doesn't send this object as is without stringifying. Usually when variable is external to function own scope
  						ipcRenderer.send('render-finished', {bounds: JSON.stringify(bounds)});
  					}
  					catch(e)
  					{
  						console.log(e);
  					}
  				}
  			});
  		}
  	};
  	
  	function waitForImages(tagName, attributeName)
  	{
  		var imgs = document.body.getElementsByTagName(tagName);
  		waitCounter += imgs.length;
  
  		for (var i = 0; i < imgs.length; i++)
  		{
  			// No load events for image elements in Phantom using indirection instead
  			var src = imgs[i].getAttribute(attributeName);
  			
  			if (src != null && src.length > 0 && cache[src] == null)
  			{
  				cache[src] = new Image();
  				cache[src].onload = decrementWaitCounter;
  				cache[src].onerror = decrementWaitCounter;
  				cache[src].src = src;
  			}
  			else
  			{
  				decrementWaitCounter();
  			}
  		}
  	};
  	
  	// Waits for MathJax.Hub to become available to register
  	// wait counter callback asynchronously after math render
  	var editorDoMathJaxRender = Editor.doMathJaxRender;
  	
  	Editor.doMathJaxRender = function(container)
  	{
  		editorDoMathJaxRender.apply(this, arguments);
  		
  		window.setTimeout(function()
  		{
  			window.MathJax.Hub.Queue(function ()
  			{
  				decrementWaitCounter();
  			});
  		}, 0);
  	};
  	
  	// Adds async MathJax rendering task
  	function renderMath(elt)
  	{
  		if (math && Editor.MathJaxRender != null)
  		{
  			waitCounter++;
  			Editor.MathJaxRender(elt);
  		}
  	};
  
  	function loadExtFonts(extFonts)
  	{
  		try
  		{
  			extFonts = extFonts.split('|').map(function(ef)
  			{
  				var parts = ef.split('^');
  				return {name: parts[0], url: parts[1]};
  			});
  		}
  		catch(e)
  		{
  			//ignore and return!
  			return;
  		}
  		
  		waitCounter += extFonts.length;
  
  		//Note: This code targets Chrome as it is the browser used by export server
  		for (var i = 0; i < extFonts.length; i++)
  		{
  			if (extFonts[i].url.indexOf(Editor.GOOGLE_FONTS) == 0)
  			{
  				var link = document.createElement('link');
  				
  				link.setAttribute('rel', 'stylesheet');
  				link.setAttribute('charset', 'UTF-8');
  				link.setAttribute('type', 'text/css');
  				
  				link.onload = decrementWaitCounter;
  				link.onerror = decrementWaitCounter;
  			
  				link.setAttribute('href', extFonts[i].url);
  				var head = document.getElementsByTagName('head')[0];
  				head.appendChild(link);
  			}
  			else
  			{
  				//Relative urls doesn't work
  				if (extFonts[i].url.indexOf(PROXY_URL) == 0 && PROXY_URL.indexOf('http') == -1)
  				{
  					var href = window.location.href;
  					href = href.substring(0, href.lastIndexOf('/') + 1);
  					extFonts[i].url = href + extFonts[i].url;
  				}
  				
  				var font = new FontFace(extFonts[i].name, 'url(' + extFonts[i].url + ')');
  				
  				font.load().then(function(loadedFont)
  				{
  					document.fonts.add(loadedFont);
  					decrementWaitCounter();
  				}).catch(decrementWaitCounter);
  			}
  		}
  	};
  
  	function renderGrid()
  	{
  		if (gridColor == null) return;
  			
  		var view = graph.view;
  		var gridImage = btoa(unescape(encodeURIComponent(view.createSvgGrid(gridColor))));
  		gridImage = 'url(' + 'data:image/svg+xml;base64,' + gridImage + ')';
  		var phase = graph.gridSize * view.gridSteps * view.scale;
  		
  		var x0 = 0;
  		var y0 = 0;
  		
  		if (view.backgroundPageShape != null)
  		{
  			var bds = view.getBackgroundPageBounds();
  			
  			x0 = 1 + bds.x;
  			y0 = 1 + bds.y;
  		}
  		
  		// Computes the offset to maintain origin for grid
  		var position = -Math.round(phase - mxUtils.mod(view.translate.x * view.scale - x0, phase)) + 'px ' +
  			-Math.round(phase - mxUtils.mod(view.translate.y * view.scale - y0, phase)) + 'px';
  		
  		var pages = document.querySelectorAll('[id^=mxPage]');
  		
  		var cssTxt = 'margin: 0;padding: 0;background-image: ' + gridImage + ';background-position: ' + position;
  		document.body.style.cssText = cssTxt;
  
  		for (var i = 0; i < pages.length; i++)
  		{
  			pages[i].style.cssText = cssTxt;
  		}
  	};
  	
  	var origAddFont = Graph.addFont;
  	
  	Graph.addFont = function(name, url)
  	{
  		waitCounter++;
  		return origAddFont.call(this, name, url, decrementWaitCounter);	
  	};
  		
  	function renderPage()
  	{
  		// Enables math typesetting
  		math |= xmlDoc.documentElement.getAttribute('math') == '1';
  		
  		//Load external fonts
  		var extFonts = xmlDoc.documentElement.getAttribute('extFonts');
  		
  		if (extFonts)
  		{
  			loadExtFonts(extFonts);
  		}
  		
  		// Configure graph
  		graph.foldingEnabled = false;
  		graph.setEnabled(false);
  		
  		// Sets background image
  		var bgImg = xmlDoc.documentElement.getAttribute('backgroundImage');
  
  		if (bgImg != null)
  		{
  			bgImg = JSON.parse(bgImg);
  			graph.setBackgroundImage(new mxImage(bgImg.src, bgImg.width, bgImg.height));
  		}
  		
  		// Parses XML into graph
  		var codec = new mxCodec(xmlDoc);
  		var model = graph.getModel();
  		codec.decode(xmlDoc.documentElement, model);
  
  		var bg;
  		
  		if (data.format == 'pdf')
  		{
  			if (data.bg == 'none')
  			{
  				bg = null;	
  			}
  			else
  			{
  				bg = xmlDoc.documentElement.getAttribute('background');
  				
  				if (bg == 'none' || !bg)
  				{
  					bg = '#ffffff';
  				}
  			}
  		}
  		else
  		{
  			// Loads background color
  			bg = (data.bg != null && data.bg.length > 0) ?
  				data.bg : xmlDoc.documentElement.getAttribute('background');
  
  			// Normalizes values for transparent backgrounds
  			if (bg == 'none' || bg == '')
  			{
  				bg = null;
  			}
  			
  			// Checks if export format supports transparent backgrounds
  			if (bg == null && data.format != 'gif' && data.format != 'png')
  			{
  				bg = '#ffffff';
  			}	
  		}
  		
  		// Sets background color on page
  		if (bg != null)
  		{
  			document.body.style.backgroundColor = bg;
  		}
  		
  		//handle layers
  		if (extras != null && ((extras.layers != null && extras.layers.length > 0) || 
  							   (extras.layerIds != null && extras.layerIds.length > 0)))
  		{
  			var childCount = model.getChildCount(model.root);
  			
  			// Hides all layers
  			for (var i = 0; i < childCount; i++)
  			{
  				model.setVisible(model.getChildAt(model.root, i), false);
  			}
  
  			if (extras.layerIds != null)
  			{
  				for (var i = 0; i < extras.layerIds.length; i++)
  				{
  					model.setVisible(model.getCell(extras.layerIds[i]), true);
  				}
  			}
  			else
  			{
  				for (var i = 0; i < extras.layers.length; i++)
  				{
  					var layer = model.getChildAt(model.root, extras.layers[i]);
  					
  					if (layer != null)
  					{
  						model.setVisible(layer, true);
  					}
  				}
  			}
  		}
  		
  		// Sets initial value for PDF page background
  		graph.pdfPageVisible = false;
  		
  		// Handles PDF output where the output should match the page format if the page is visible
  		if (data.print || (data.format == 'pdf' && xmlDoc.documentElement.getAttribute('page') == '1' && data.w == 0 && data.h == 0 && data.scale == 1))
  		{
  			//Electron printing
  			var printScale = 1;
  			
  			if (data.print)
  			{
  				document.title = data.fileTitle;
  				
  				var gb = graph.getGraphBounds();
  				printScale = data.pageScale;
  		
  				if (isNaN(printScale))
  				{
  					printScale = 1;
  				}
  				
  				if (data.fit)
  				{
  					var h = parseInt(data.sheetsAcross);
  					var v = parseInt(data.sheetsDown);
  					
  					data.scale = Math.min((data.pageHeight * v) / (gb.height / graph.view.scale),
  							(data.pageWidth * h) / (gb.width / graph.view.scale));
  				}
  				else
  				{
  					data.scale = data.scale / graph.pageScale;
  					
  					if (isNaN(data.scale))
  					{
  						printScale = 1 / graph.pageScale;
  					}
  				}
  			}
  			
  			var pw = data.pageWidth || xmlDoc.documentElement.getAttribute('pageWidth');
  			var ph = data.pageHeight || xmlDoc.documentElement.getAttribute('pageHeight');
  			graph.pdfPageVisible = true;
  			
  			if (pw != null && ph != null)
  			{
  				graph.pageFormat = new mxRectangle(0, 0, parseFloat(pw), parseFloat(ph));
  			}
  			
  			var ps = data.pageScale || xmlDoc.documentElement.getAttribute('pageScale');
  			
  			if (ps != null)
  			{
  				graph.pageScale = ps;
  			}
  			
  			graph.getPageSize = function()
  			{
  				return new mxRectangle(0, 0, this.pageFormat.width * this.pageScale,
  					this.pageFormat.height * this.pageScale);
  			};
  			
  			graph.getPageLayout = function()
  			{
  				var size = this.getPageSize();
  				var bounds = this.getGraphBounds();
  				
  				if (bounds.width == 0 || bounds.height == 0)
  				{
  					return new mxRectangle(0, 0, 1, 1);
  				}
  				else
  				{
  					// Computes untransformed graph bounds
  					var x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
  					var y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
  					var w = Math.floor(bounds.width / this.view.scale);
  					var h = Math.floor(bounds.height / this.view.scale);
  					
  					var x0 = Math.floor(x / size.width);
  					var y0 = Math.floor(y / size.height);
  					var w0 = Math.ceil((x + w) / size.width) - x0;
  					var h0 = Math.ceil((y + h) / size.height) - y0;
  					
  					return new mxRectangle(x0, y0, w0, h0);
  				}
  			};
  
  			// Fits the number of background pages to the graph
  			graph.view.getBackgroundPageBounds = function()
  			{
  				var layout = this.graph.getPageLayout();
  				var page = this.graph.getPageSize();
  				
  				return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
  					this.scale * (this.translate.y + layout.y * page.height),
  					this.scale * layout.width * page.width,
  					this.scale * layout.height * page.height);
  			};
  		}
  
  		if (!graph.pdfPageVisible)
  		{
  			var b = graph.getGraphBounds();
  			
  			// Floor is needed to keep rendering crisp
  			if (data.w > 0 || data.h > 0)
  			{
  				var s = 1;
  				
  				if (data.w > 0 && data.h > 0)
  				{
  					s = Math.min(data.w / b.width, data.h / b.height);
  				}
  				else if (data.w > 0)
  				{
  					s = data.w / b.width;
  				}
  				else
  				{
  					s = data.h / b.height;
  				}
  				
  				graph.view.scaleAndTranslate(s,
  					Math.floor(data.border / s - Math.floor(b.x)),
  					Math.floor(data.border / s - Math.floor(b.y)));
  			}
  			else
  			{
  				var s = data.scale;
  				
  				if (autoScale)
  				{
  					var pageWidth = (extras != null && extras.pageWidth != null) ? extras.pageWidth : 800;
  					
  					if (b.width < pageWidth & b.height < 1.5 * pageWidth)
  					{
  						s = 4;
  					}
  					else if (b.width < 2 * pageWidth & b.height < 3 * pageWidth)
  					{
  						s = 3;
  					}
  					else if (b.width < 4 * pageWidth && b.height < 6 * pageWidth)
  					{
  						s = 2;
  					}
  					
  					if (extras != null && extras.isGoogleSheet != null)
  					{
  						GOOGLE_APPS_MAX_AREA = GOOGLE_SHEET_MAX_AREA;
  					}
  					
  					//The image cannot exceed 25 MP to be included in Google Apps
  					if (b.width * s * b.height * s > GOOGLE_APPS_MAX_AREA)
  					{
  						//Subtracting 0.01 to prevent any other rounding that can make slightly over 25 MP 
  						s = Math.sqrt(GOOGLE_APPS_MAX_AREA / (b.width * b.height)) - 0.01;
  					}
  				}
  				
  				graph.view.scaleAndTranslate(s,
  					Math.floor(data.border - Math.floor(b.x)),
  					Math.floor(data.border - Math.floor(b.y)));
  			}
  		}
  		else
  		{
  			// Disables border for PDF page export
  			data.border = 0;
  			
  			// Moves to first page in page layout
  			var layout = graph.getPageLayout();
  			var page = graph.getPageSize();
  			var dx = layout.x * page.width;
  			var dy = layout.y * page.height;
  			
  			if (dx != 0 || dy != 0)
  			{
  				graph.view.setTranslate(Math.floor(-dx), Math.floor(-dy));
  			}
  		}
  		
  		// Gets the diagram bounds and sets the document size
  		bounds = (graph.pdfPageVisible) ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds();
  		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
  		bounds.height = Math.ceil(bounds.height + data.border);
  		expScale = graph.view.scale || 1;
  		
  		// Converts the graph to a vertical sequence of pages for PDF export
  		if (graph.pdfPageVisible)
  		{
  			var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
  			var scale = data.print? data.scale : 1 / graph.pageScale;
  			var autoOrigin = (data.print && data.fit != null) ? data.fit : false;
  			var border = 0;
  
  			// Negative coordinates are cropped or shifted if page visible
  			var gb = graph.getGraphBounds();
  			var x0 = 0;
  			var y0 = 0;
  	
  			// Applies print scale
  			pf = mxRectangle.fromRectangle(pf);
  			pf.width = Math.ceil(pf.width * printScale) + 1; //The 1 extra pixels to prevent cutting the cells on the right edge of the page
  			pf.height = Math.ceil(pf.height * printScale);
  			scale *= printScale;
  			
  			// Starts at first visible page
  			if (!autoOrigin)
  			{
  				var layout = graph.getPageLayout();
  				x0 -= layout.x * pf.width;
  				y0 -= layout.y * pf.height;
  			}
  			
  			if (preview == null)
  			{
  				preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
  				preview.printBackgroundImage = true;
  				preview.autoOrigin = autoOrigin;
  				preview.backgroundColor = bg;
  				// Renders print output into this document and removes the graph container
  				preview.open(null, window);
  				graph.container.parentNode.removeChild(graph.container);
  			}
  			else
  			{
  				preview.backgroundColor = bg;
  				preview.autoOrigin = autoOrigin; 
  				preview.appendGraph(graph, scale, x0, y0);
  			}
  			// Adds shadow
  			// NOTE: Shadow rasterizes output
  			/*if (mxClient.IS_SVG && xmlDoc.documentElement.getAttribute('shadow') == '1')
  			{
  				var svgs = document.getElementsByTagName('svg');
  				
  				for (var i = 0; i < svgs.length; i++)
  				{
  					var svg = svgs[i];
  
  					var filter = graph.addSvgShadow(svg, null, true);
  					filter.setAttribute('id', 'shadow-' + i);
  					svg.appendChild(filter);
  					svg.setAttribute('filter', 'url(#' + 'shadow-' + i + ')');
  				}
  				
  				border = 7;
  			}*/
  			
  			bounds = new mxRectangle(0, 0, pf.width, pf.height);
  		}
  		else
  		{
  			// Adds shadow
  			// NOTE: PDF shadow rasterizes output so it's disabled
  			if (data.format != 'pdf' && mxClient.IS_SVG && xmlDoc.documentElement.getAttribute('shadow') == '1')
  			{
  				graph.addSvgShadow(graph.view.canvas.ownerSVGElement, null, true);
  				graph.setShadowVisible(true);
  				bounds.width += 7;
  				bounds.height += 7;
  			}
  			
  			document.body.style.width = Math.ceil(bounds.x + bounds.width) + 'px';
  			document.body.style.height = Math.ceil(bounds.y + bounds.height) + 'px';
  		}
  	}
  	
  	if (diagrams != null && diagrams.length > 0)
  	{
  		var to = diagrams.length - 1;
  		
  		//Parameters to and all pages should not be sent with formats other than PDF with page view enabled
  		if (!data.allPages)
  		{
  			if (data.pageId != null)
  			{
  				for (var i = 0; i < diagrams.length; i++)
  				{
  					if (data.pageId == diagrams[i].getAttribute('id'))
  					{
  						from = i;
  						to = i;
  						break;
  					}
  				}
  			}
  			else
  			{
  				from = Math.max(0, Math.min(parseInt(data.from) || from, diagrams.length - 1));
  				to = parseInt(data.to);
  				//If to is not defined, use from (so one page), otherwise, to is restricted to the range from "from" to diagrams.length - 1
  				to = isNaN(to)? from : Math.max(from, Math.min(to, diagrams.length - 1));
  			}
  		}
  		
  		/**
  		 * Implements %page% and %pagenumber% placeholders
  		 */
  		var graphGetGlobalVariable = graph.getGlobalVariable;
  		
  		graph.getGlobalVariable = function(name)
  		{
  			if (name == 'page')
  			{
  				return (diagrams == null) ? 'Page-1' :
  					(diagrams[from].getAttribute('name') || ('Page-' + (from + 1)));
  			}
  			else if (name == 'pagenumber')
  			{
  				return from + 1;
  			}
  			
  			return graphGetGlobalVariable.apply(this, arguments);
  		};
  			
  		for (var i = from; i <= to; i++) 
  		{
  			if (diagrams[i] != null)
  			{
  				if (pageId == null)
  				{
  					pageId = diagrams[i].getAttribute('id')
  				}
  				
  				xmlDoc = Editor.parseDiagramNode(diagrams[i]);
  				
  				if (xmlDoc != null)
  				{
  					xmlDoc = xmlDoc.ownerDocument;
  				}
  
  				graph.getModel().clear();
  				from = i;
  				renderPage();
  			}
  		}
  	}
  	else
  	{
  		renderPage();
  	}
  	
  	if (fallbackFont)
  	{
  		//Add a fallbackFont font to all labels in case the selected font doesn't support the character
  		//Some systems doesn't have a good fallback fomt that supports all languages
  		//Use this with a custom font-face in export-fonts.css file
  		document.querySelectorAll('foreignObject div').forEach(d => d.style.fontFamily = (d.style.fontFamily || '') + ', ' + fallbackFont);
  	}
  	
  	renderGrid();
  	// Includes images in SVG and HTML labels
  	waitForImages('image', 'xlink:href');
  	waitForImages('img', 'src');
  	renderMath(document.body);
  	// Immediate return if not waiting for any content
  	decrementWaitCounter();
  	
  	return graph;
  };
  
  //Electron pdf export
  if (mxIsElectron)
  {
  	try 
  	{
  		const { ipcRenderer } = require('electron');
  		
  		ipcRenderer.on('render', (event, arg) => 
  		{
  			render(arg);
  		});
  	}
  	catch(e)
  	{
  		console.log(e);
  	}
  }
  