drawio-offline
Version:
diagrams.net desktop
897 lines (756 loc) • 22.5 kB
JavaScript
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");
var pageCount = diagrams != null? diagrams.length : 1;
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', pageCount);
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), pageCount: pageCount});
}
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);
}
}