mxPrintPreview.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. /**
  2. * Copyright (c) 2006-2019, JGraph Ltd
  3. * Copyright (c) 2006-2017, draw.io AG
  4. */
  5. /**
  6. * Class: mxPrintPreview
  7. *
  8. * Implements printing of a diagram across multiple pages. The following opens
  9. * a print preview for an existing graph:
  10. *
  11. * (code)
  12. * var preview = new mxPrintPreview(graph);
  13. * preview.open();
  14. * (end)
  15. *
  16. * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
  17. * across a given number of pages:
  18. *
  19. * (code)
  20. * var pageCount = mxUtils.prompt('Enter page count', '1');
  21. *
  22. * if (pageCount != null)
  23. * {
  24. * var scale = mxUtils.getScaleForPageCount(pageCount, graph);
  25. * var preview = new mxPrintPreview(graph, scale);
  26. * preview.open();
  27. * }
  28. * (end)
  29. *
  30. * Additional pages:
  31. *
  32. * To add additional pages before and after the output, <getCoverPages> and
  33. * <getAppendices> can be used, respectively.
  34. *
  35. * (code)
  36. * var preview = new mxPrintPreview(graph, 1);
  37. *
  38. * preview.getCoverPages = function(w, h)
  39. * {
  40. * return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
  41. * {
  42. * div.innerHTML = '<div style="position:relative;margin:4px;">Cover Page</p>'
  43. * }))];
  44. * };
  45. *
  46. * preview.getAppendices = function(w, h)
  47. * {
  48. * return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
  49. * {
  50. * div.innerHTML = '<div style="position:relative;margin:4px;">Appendix</p>'
  51. * }))];
  52. * };
  53. *
  54. * preview.open();
  55. * (end)
  56. *
  57. * CSS:
  58. *
  59. * The CSS from the original page is not carried over to the print preview.
  60. * To add CSS to the page, use the css argument in the <open> function or
  61. * override <writeHead> to add the respective link tags as follows:
  62. *
  63. * (code)
  64. * var writeHead = preview.writeHead;
  65. * preview.writeHead = function(doc, css)
  66. * {
  67. * writeHead.apply(this, arguments);
  68. * doc.writeln('<link rel="stylesheet" type="text/css" href="style.css">');
  69. * };
  70. * (end)
  71. *
  72. * Padding:
  73. *
  74. * To add a padding to the page in the preview (but not the print output), use
  75. * the following code:
  76. *
  77. * (code)
  78. * preview.writeHead = function(doc)
  79. * {
  80. * writeHead.apply(this, arguments);
  81. *
  82. * doc.writeln('<style type="text/css">');
  83. * doc.writeln('@media screen {');
  84. * doc.writeln(' body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');
  85. * doc.writeln('}');
  86. * doc.writeln('</style>');
  87. * };
  88. * (end)
  89. *
  90. * Headers:
  91. *
  92. * Apart from setting the title argument in the mxPrintPreview constructor you
  93. * can override <renderPage> as follows to add a header to any page:
  94. *
  95. * (code)
  96. * var oldRenderPage = mxPrintPreview.prototype.renderPage;
  97. * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)
  98. * {
  99. * var div = oldRenderPage.apply(this, arguments);
  100. *
  101. * var header = document.createElement('div');
  102. * header.style.position = 'absolute';
  103. * header.style.top = '0px';
  104. * header.style.width = '100%';
  105. * header.style.textAlign = 'right';
  106. * mxUtils.write(header, 'Your header here');
  107. * div.firstChild.appendChild(header);
  108. *
  109. * return div;
  110. * };
  111. * (end)
  112. *
  113. * The pageNumber argument contains the number of the current page, starting at
  114. * 1. To display a header on the first page only, check pageNumber and add a
  115. * vertical offset in the constructor call for the height of the header.
  116. *
  117. * Page Format:
  118. *
  119. * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
  120. * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
  121. * Keep in mind that one can not set the defaults for the print dialog
  122. * of the operating system from JavaScript so the user must manually choose
  123. * a page format that matches this setting.
  124. *
  125. * You can try passing the following CSS directive to <open> to set the
  126. * page format in the print dialog to landscape. However, this CSS
  127. * directive seems to be ignored in most major browsers, including IE.
  128. *
  129. * (code)
  130. * @page {
  131. * size: landscape;
  132. * }
  133. * (end)
  134. *
  135. * Note that the print preview behaves differently in IE when used from the
  136. * filesystem or via HTTP so printing should always be tested via HTTP.
  137. *
  138. * If you are using a DOCTYPE in the source page you can override <getDoctype>
  139. * and provide the same DOCTYPE for the print preview if required. Here is
  140. * an example for IE8 standards mode.
  141. *
  142. * (code)
  143. * var preview = new mxPrintPreview(graph);
  144. * preview.getDoctype = function()
  145. * {
  146. * return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
  147. * };
  148. * preview.open();
  149. * (end)
  150. *
  151. * Constructor: mxPrintPreview
  152. *
  153. * Constructs a new print preview for the given parameters.
  154. *
  155. * Parameters:
  156. *
  157. * graph - <mxGraph> to be previewed.
  158. * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
  159. * pageFormat - <mxRectangle> that specifies the page format (in pixels).
  160. * border - Border in pixels along each side of every page. Note that the
  161. * actual print function in the browser will add another border for
  162. * printing.
  163. * This should match the page format of the printer. Default uses the
  164. * <mxGraph.pageFormat> of the given graph.
  165. * x0 - Optional left offset of the output. Default is 0.
  166. * y0 - Optional top offset of the output. Default is 0.
  167. * borderColor - Optional color of the page border. Default is no border.
  168. * Note that a border is sometimes useful to highlight the printed page
  169. * border in the print preview of the browser.
  170. * title - Optional string that is used for the window title. Default
  171. * is 'Printer-friendly version'.
  172. * pageSelector - Optional boolean that specifies if the page selector
  173. * should appear in the window with the print preview. Default is true.
  174. */
  175. function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
  176. {
  177. this.graph = graph;
  178. this.scale = (scale != null) ? scale : 1 / graph.pageScale;
  179. this.border = (border != null) ? border : 0;
  180. this.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat);
  181. this.title = (title != null) ? title : 'Printer-friendly version';
  182. this.x0 = (x0 != null) ? x0 : 0;
  183. this.y0 = (y0 != null) ? y0 : 0;
  184. this.borderColor = borderColor;
  185. this.pageSelector = (pageSelector != null) ? pageSelector : true;
  186. };
  187. /**
  188. * Variable: graph
  189. *
  190. * Reference to the <mxGraph> that should be previewed.
  191. */
  192. mxPrintPreview.prototype.graph = null;
  193. /**
  194. * Variable: pageFormat
  195. *
  196. * Holds the <mxRectangle> that defines the page format.
  197. */
  198. mxPrintPreview.prototype.pageFormat = null;
  199. /**
  200. * Variable: scale
  201. *
  202. * Holds the scale of the print preview.
  203. */
  204. mxPrintPreview.prototype.scale = null;
  205. /**
  206. * Variable: border
  207. *
  208. * The border inset around each side of every page in the preview. This is set
  209. * to 0 if autoOrigin is false.
  210. */
  211. mxPrintPreview.prototype.border = 0;
  212. /**
  213. * Variable: marginTop
  214. *
  215. * The margin at the top of the page (number). Default is 0.
  216. */
  217. mxPrintPreview.prototype.marginTop = 0;
  218. /**
  219. * Variable: marginBottom
  220. *
  221. * The margin at the bottom of the page (number). Default is 0.
  222. */
  223. mxPrintPreview.prototype.marginBottom = 0;
  224. /**
  225. * Variable: x0
  226. *
  227. * Holds the horizontal offset of the output.
  228. */
  229. mxPrintPreview.prototype.x0 = 0;
  230. /**
  231. * Variable: y0
  232. *
  233. * Holds the vertical offset of the output.
  234. */
  235. mxPrintPreview.prototype.y0 = 0;
  236. /**
  237. * Variable: autoOrigin
  238. *
  239. * Specifies if the origin should be automatically computed based on the top,
  240. * left corner of the actual diagram contents. The required offset will be added
  241. * to <x0> and <y0> in <open>. Default is true.
  242. */
  243. mxPrintPreview.prototype.autoOrigin = true;
  244. /**
  245. * Variable: printOverlays
  246. *
  247. * Specifies if overlays should be printed. Default is false.
  248. */
  249. mxPrintPreview.prototype.printOverlays = false;
  250. /**
  251. * Variable: printControls
  252. *
  253. * Specifies if controls (such as folding icons) should be printed. Default is
  254. * false.
  255. */
  256. mxPrintPreview.prototype.printControls = false;
  257. /**
  258. * Variable: printBackgroundImage
  259. *
  260. * Specifies if the background image should be printed. Default is false.
  261. */
  262. mxPrintPreview.prototype.printBackgroundImage = false;
  263. /**
  264. * Variable: backgroundColor
  265. *
  266. * Holds the color value for the page background color. Default is #ffffff.
  267. */
  268. mxPrintPreview.prototype.backgroundColor = '#ffffff';
  269. /**
  270. * Variable: borderColor
  271. *
  272. * Holds the color value for the page border.
  273. */
  274. mxPrintPreview.prototype.borderColor = null;
  275. /**
  276. * Variable: title
  277. *
  278. * Holds the title of the preview window.
  279. */
  280. mxPrintPreview.prototype.title = null;
  281. /**
  282. * Variable: pageSelector
  283. *
  284. * Boolean that specifies if the page selector should be
  285. * displayed. Default is true.
  286. */
  287. mxPrintPreview.prototype.pageSelector = null;
  288. /**
  289. * Variable: wnd
  290. *
  291. * Reference to the preview window.
  292. */
  293. mxPrintPreview.prototype.wnd = null;
  294. /**
  295. * Variable: targetWindow
  296. *
  297. * Assign any window here to redirect the rendering in <open>.
  298. */
  299. mxPrintPreview.prototype.targetWindow = null;
  300. /**
  301. * Variable: pageCount
  302. *
  303. * Holds the actual number of pages in the preview.
  304. */
  305. mxPrintPreview.prototype.pageCount = 0;
  306. /**
  307. * Variable: clipping
  308. *
  309. * Specifies is clipping should be used to avoid creating too many cell states
  310. * in large diagrams. The bounding box of the cells in the original diagram is
  311. * used if this is enabled. Default is true.
  312. */
  313. mxPrintPreview.prototype.clipping = true;
  314. /**
  315. * Function: getWindow
  316. *
  317. * Returns <wnd>.
  318. */
  319. mxPrintPreview.prototype.getWindow = function()
  320. {
  321. return this.wnd;
  322. };
  323. /**
  324. * Function: getDocType
  325. *
  326. * Returns the string that should go before the HTML tag in the print preview
  327. * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,
  328. * IE8 in IE8 standards mode and edge in IE9 standards mode.
  329. */
  330. mxPrintPreview.prototype.getDoctype = function()
  331. {
  332. var dt = '';
  333. if (document.documentMode == 5)
  334. {
  335. dt = '<meta http-equiv="X-UA-Compatible" content="IE=5">';
  336. }
  337. else if (document.documentMode == 8)
  338. {
  339. dt = '<meta http-equiv="X-UA-Compatible" content="IE=8">';
  340. }
  341. else if (document.documentMode > 8)
  342. {
  343. // Comment needed to make standards doctype apply in IE
  344. dt = '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->';
  345. }
  346. return dt;
  347. };
  348. /**
  349. * Function: appendGraph
  350. *
  351. * Adds the given graph to the existing print preview.
  352. *
  353. * Parameters:
  354. *
  355. * css - Optional CSS string to be used in the head section.
  356. * targetWindow - Optional window that should be used for rendering. If
  357. * this is specified then no HEAD tag, CSS and BODY tag will be written.
  358. */
  359. mxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen)
  360. {
  361. this.graph = graph;
  362. this.scale = (scale != null) ? scale : 1 / graph.pageScale;
  363. this.x0 = x0;
  364. this.y0 = y0;
  365. this.open(null, null, forcePageBreaks, keepOpen);
  366. };
  367. /**
  368. * Function: open
  369. *
  370. * Shows the print preview window. The window is created here if it does
  371. * not exist.
  372. *
  373. * Parameters:
  374. *
  375. * css - Optional CSS string to be used in the head section.
  376. * targetWindow - Optional window that should be used for rendering. If
  377. * this is specified then no HEAD tag, CSS and BODY tag will be written.
  378. */
  379. mxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen)
  380. {
  381. // Closing the window while the page is being rendered may cause an
  382. // exception in IE. This and any other exceptions are simply ignored.
  383. var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
  384. var div = null;
  385. try
  386. {
  387. // Temporarily overrides the method to redirect rendering of overlays
  388. // to the draw pane so that they are visible in the printout
  389. if (this.printOverlays)
  390. {
  391. this.graph.cellRenderer.initializeOverlay = function(state, overlay)
  392. {
  393. overlay.init(state.view.getDrawPane());
  394. };
  395. }
  396. if (this.printControls)
  397. {
  398. this.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler)
  399. {
  400. control.dialect = state.view.graph.dialect;
  401. control.init(state.view.getDrawPane());
  402. };
  403. }
  404. this.wnd = (targetWindow != null) ? targetWindow : this.wnd;
  405. var isNewWindow = false;
  406. if (this.wnd == null)
  407. {
  408. isNewWindow = true;
  409. this.wnd = window.open();
  410. }
  411. var doc = this.wnd.document;
  412. if (isNewWindow)
  413. {
  414. var dt = this.getDoctype();
  415. if (dt != null && dt.length > 0)
  416. {
  417. doc.writeln(dt);
  418. }
  419. if (mxClient.IS_VML)
  420. {
  421. doc.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">');
  422. }
  423. else
  424. {
  425. if (document.compatMode === 'CSS1Compat')
  426. {
  427. doc.writeln('<!DOCTYPE html>');
  428. }
  429. doc.writeln('<html>');
  430. }
  431. doc.writeln('<head>');
  432. this.writeHead(doc, css);
  433. doc.writeln('</head>');
  434. doc.writeln('<body class="mxPage">');
  435. }
  436. // Computes the horizontal and vertical page count
  437. var bounds = this.graph.getGraphBounds().clone();
  438. var currentScale = this.graph.getView().getScale();
  439. var sc = currentScale / this.scale;
  440. var tr = this.graph.getView().getTranslate();
  441. // Uses the absolute origin with no offset for all printing
  442. if (!this.autoOrigin)
  443. {
  444. this.x0 -= tr.x * this.scale;
  445. this.y0 -= tr.y * this.scale;
  446. bounds.width += bounds.x;
  447. bounds.height += bounds.y;
  448. bounds.x = 0;
  449. bounds.y = 0;
  450. this.border = 0;
  451. }
  452. // Store the available page area
  453. var availableWidth = this.pageFormat.width - (this.border * 2);
  454. var availableHeight = this.pageFormat.height - (this.border * 2);
  455. // Adds margins to page format
  456. this.pageFormat.height += this.marginTop + this.marginBottom;
  457. // Compute the unscaled, untranslated bounds to find
  458. // the number of vertical and horizontal pages
  459. bounds.width /= sc;
  460. bounds.height /= sc;
  461. var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
  462. var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
  463. this.pageCount = hpages * vpages;
  464. var writePageSelector = mxUtils.bind(this, function()
  465. {
  466. if (this.pageSelector && (vpages > 1 || hpages > 1))
  467. {
  468. var table = this.createPageSelector(vpages, hpages);
  469. doc.body.appendChild(table);
  470. // Implements position: fixed in IE quirks mode
  471. if (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)
  472. {
  473. table.style.position = 'absolute';
  474. var update = function()
  475. {
  476. table.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px';
  477. };
  478. mxEvent.addListener(this.wnd, 'scroll', function(evt)
  479. {
  480. update();
  481. });
  482. mxEvent.addListener(this.wnd, 'resize', function(evt)
  483. {
  484. update();
  485. });
  486. }
  487. }
  488. });
  489. var addPage = mxUtils.bind(this, function(div, addBreak)
  490. {
  491. // Border of the DIV (aka page) inside the document
  492. if (this.borderColor != null)
  493. {
  494. div.style.borderColor = this.borderColor;
  495. div.style.borderStyle = 'solid';
  496. div.style.borderWidth = '1px';
  497. }
  498. // Needs to be assigned directly because IE doesn't support
  499. // child selectors, eg. body > div { background: white; }
  500. div.style.background = this.backgroundColor;
  501. if (forcePageBreaks || addBreak)
  502. {
  503. div.style.pageBreakAfter = 'always';
  504. }
  505. // NOTE: We are dealing with cross-window DOM here, which
  506. // is a problem in IE, so we copy the HTML markup instead.
  507. // The underlying problem is that the graph display markup
  508. // creation (in mxShape, mxGraphView) is hardwired to using
  509. // document.createElement and hence we must use this document
  510. // to create the complete page and then copy it over to the
  511. // new window.document. This can be fixed later by using the
  512. // ownerDocument of the container in mxShape and mxGraphView.
  513. if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE))
  514. {
  515. // For some obscure reason, removing the DIV from the
  516. // parent before fetching its outerHTML has missing
  517. // fillcolor properties and fill children, so the div
  518. // must be removed afterwards to keep the fillcolors.
  519. doc.writeln(div.outerHTML);
  520. div.parentNode.removeChild(div);
  521. }
  522. else if (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE)
  523. {
  524. var clone = doc.createElement('div');
  525. clone.innerHTML = div.outerHTML;
  526. clone = clone.getElementsByTagName('div')[0];
  527. doc.body.appendChild(clone);
  528. div.parentNode.removeChild(div);
  529. }
  530. else
  531. {
  532. div.parentNode.removeChild(div);
  533. doc.body.appendChild(div);
  534. }
  535. if (forcePageBreaks || addBreak)
  536. {
  537. this.addPageBreak(doc);
  538. }
  539. });
  540. var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);
  541. if (cov != null)
  542. {
  543. for (var i = 0; i < cov.length; i++)
  544. {
  545. addPage(cov[i], true);
  546. }
  547. }
  548. var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);
  549. // Appends each page to the page output for printing, making
  550. // sure there will be a page break after each page (ie. div)
  551. for (var i = 0; i < vpages; i++)
  552. {
  553. var dy = i * availableHeight / this.scale - this.y0 / this.scale +
  554. (bounds.y - tr.y * currentScale) / currentScale;
  555. for (var j = 0; j < hpages; j++)
  556. {
  557. if (this.wnd == null)
  558. {
  559. return null;
  560. }
  561. var dx = j * availableWidth / this.scale - this.x0 / this.scale +
  562. (bounds.x - tr.x * currentScale) / currentScale;
  563. var pageNum = i * hpages + j + 1;
  564. var clip = new mxRectangle(dx, dy, availableWidth, availableHeight);
  565. div = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div)
  566. {
  567. this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);
  568. if (this.printBackgroundImage)
  569. {
  570. this.insertBackgroundImage(div, -dx, -dy);
  571. }
  572. }), pageNum);
  573. // Gives the page a unique ID for later accessing the page
  574. div.setAttribute('id', 'mxPage-'+pageNum);
  575. addPage(div, apx != null || i < vpages - 1 || j < hpages - 1);
  576. }
  577. }
  578. if (apx != null)
  579. {
  580. for (var i = 0; i < apx.length; i++)
  581. {
  582. addPage(apx[i], i < apx.length - 1);
  583. }
  584. }
  585. if (isNewWindow && !keepOpen)
  586. {
  587. this.closeDocument();
  588. writePageSelector();
  589. }
  590. this.wnd.focus();
  591. }
  592. catch (e)
  593. {
  594. // Removes the DIV from the document in case of an error
  595. if (div != null && div.parentNode != null)
  596. {
  597. div.parentNode.removeChild(div);
  598. }
  599. }
  600. finally
  601. {
  602. this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
  603. }
  604. return this.wnd;
  605. };
  606. /**
  607. * Function: addPageBreak
  608. *
  609. * Adds a page break to the given document.
  610. */
  611. mxPrintPreview.prototype.addPageBreak = function(doc)
  612. {
  613. var hr = doc.createElement('hr');
  614. hr.className = 'mxPageBreak';
  615. doc.body.appendChild(hr);
  616. };
  617. /**
  618. * Function: closeDocument
  619. *
  620. * Writes the closing tags for body and page after calling <writePostfix>.
  621. */
  622. mxPrintPreview.prototype.closeDocument = function()
  623. {
  624. try
  625. {
  626. if (this.wnd != null && this.wnd.document != null)
  627. {
  628. var doc = this.wnd.document;
  629. this.writePostfix(doc);
  630. doc.writeln('</body>');
  631. doc.writeln('</html>');
  632. doc.close();
  633. // Removes all event handlers in the print output
  634. mxEvent.release(doc.body);
  635. }
  636. }
  637. catch (e)
  638. {
  639. // ignore any errors resulting from wnd no longer being available
  640. }
  641. };
  642. /**
  643. * Function: writeHead
  644. *
  645. * Writes the HEAD section into the given document, without the opening
  646. * and closing HEAD tags.
  647. */
  648. mxPrintPreview.prototype.writeHead = function(doc, css)
  649. {
  650. if (this.title != null)
  651. {
  652. doc.writeln('<title>' + this.title + '</title>');
  653. }
  654. // Adds required namespaces
  655. if (mxClient.IS_VML)
  656. {
  657. doc.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');
  658. }
  659. // Adds all required stylesheets
  660. mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
  661. // Removes horizontal rules and page selector from print output
  662. doc.writeln('<style type="text/css">');
  663. doc.writeln('@media print {');
  664. doc.writeln(' * { -webkit-print-color-adjust: exact; }');
  665. doc.writeln(' table.mxPageSelector { display: none; }');
  666. doc.writeln(' hr.mxPageBreak { display: none; }');
  667. doc.writeln('}');
  668. doc.writeln('@media screen {');
  669. // NOTE: position: fixed is not supported in IE, so the page selector
  670. // position (absolute) needs to be updated in IE (see below)
  671. doc.writeln(' table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
  672. 'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
  673. 'background: white; border-collapse:collapse; }');
  674. doc.writeln(' table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
  675. doc.writeln(' body.mxPage { background: gray; }');
  676. doc.writeln('}');
  677. if (css != null)
  678. {
  679. doc.writeln(css);
  680. }
  681. doc.writeln('</style>');
  682. };
  683. /**
  684. * Function: writePostfix
  685. *
  686. * Called before closing the body of the page. This implementation is empty.
  687. */
  688. mxPrintPreview.prototype.writePostfix = function(doc)
  689. {
  690. // empty
  691. };
  692. /**
  693. * Function: createPageSelector
  694. *
  695. * Creates the page selector table.
  696. */
  697. mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
  698. {
  699. var doc = this.wnd.document;
  700. var table = doc.createElement('table');
  701. table.className = 'mxPageSelector';
  702. table.setAttribute('border', '0');
  703. var tbody = doc.createElement('tbody');
  704. for (var i = 0; i < vpages; i++)
  705. {
  706. var row = doc.createElement('tr');
  707. for (var j = 0; j < hpages; j++)
  708. {
  709. var pageNum = i * hpages + j + 1;
  710. var cell = doc.createElement('td');
  711. var a = doc.createElement('a');
  712. a.setAttribute('href', '#mxPage-' + pageNum);
  713. // Workaround for FF where the anchor is appended to the URL of the original document
  714. if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
  715. {
  716. var js = 'var page = document.getElementById(\'mxPage-' + pageNum + '\');page.scrollIntoView(true);event.preventDefault();';
  717. a.setAttribute('onclick', js);
  718. }
  719. mxUtils.write(a, pageNum, doc);
  720. cell.appendChild(a);
  721. row.appendChild(cell);
  722. }
  723. tbody.appendChild(row);
  724. }
  725. table.appendChild(tbody);
  726. return table;
  727. };
  728. /**
  729. * Function: renderPage
  730. *
  731. * Creates a DIV that prints a single page of the given
  732. * graph using the given scale and returns the DIV that
  733. * represents the page.
  734. *
  735. * Parameters:
  736. *
  737. * w - Width of the page in pixels.
  738. * h - Height of the page in pixels.
  739. * dx - Optional horizontal page offset in pixels (used internally).
  740. * dy - Optional vertical page offset in pixels (used internally).
  741. * content - Callback that adds the HTML content to the inner div of a page.
  742. * Takes the inner div as the argument.
  743. * pageNumber - Integer representing the page number.
  744. */
  745. mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber)
  746. {
  747. var doc = this.wnd.document;
  748. var div = document.createElement('div');
  749. var arg = null;
  750. try
  751. {
  752. // Workaround for ignored clipping in IE 9 standards
  753. // when printing with page breaks and HTML labels.
  754. if (dx != 0 || dy != 0)
  755. {
  756. div.style.position = 'relative';
  757. div.style.width = w + 'px';
  758. div.style.height = h + 'px';
  759. div.style.pageBreakInside = 'avoid';
  760. var innerDiv = document.createElement('div');
  761. innerDiv.style.position = 'relative';
  762. innerDiv.style.top = this.border + 'px';
  763. innerDiv.style.left = this.border + 'px';
  764. innerDiv.style.width = (w - 2 * this.border) + 'px';
  765. innerDiv.style.height = (h - 2 * this.border) + 'px';
  766. innerDiv.style.overflow = 'hidden';
  767. var viewport = document.createElement('div');
  768. viewport.style.position = 'relative';
  769. viewport.style.marginLeft = dx + 'px';
  770. viewport.style.marginTop = dy + 'px';
  771. // FIXME: IE8 standards output problems
  772. if (doc.documentMode == 8)
  773. {
  774. innerDiv.style.position = 'absolute';
  775. viewport.style.position = 'absolute';
  776. }
  777. if (doc.documentMode == 10)
  778. {
  779. viewport.style.width = '100%';
  780. viewport.style.height = '100%';
  781. }
  782. innerDiv.appendChild(viewport);
  783. div.appendChild(innerDiv);
  784. document.body.appendChild(div);
  785. arg = viewport;
  786. }
  787. // FIXME: IE10/11 too many pages
  788. else
  789. {
  790. div.style.width = w + 'px';
  791. div.style.height = h + 'px';
  792. div.style.overflow = 'hidden';
  793. div.style.pageBreakInside = 'avoid';
  794. // IE8 uses above branch currently
  795. if (doc.documentMode == 8)
  796. {
  797. div.style.position = 'relative';
  798. }
  799. var innerDiv = document.createElement('div');
  800. innerDiv.style.width = (w - 2 * this.border) + 'px';
  801. innerDiv.style.height = (h - 2 * this.border) + 'px';
  802. innerDiv.style.overflow = 'hidden';
  803. if (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 ||
  804. doc.documentMode == 8 || doc.documentMode == 7))
  805. {
  806. innerDiv.style.marginTop = this.border + 'px';
  807. innerDiv.style.marginLeft = this.border + 'px';
  808. }
  809. else
  810. {
  811. innerDiv.style.top = this.border + 'px';
  812. innerDiv.style.left = this.border + 'px';
  813. }
  814. if (this.graph.dialect == mxConstants.DIALECT_VML)
  815. {
  816. innerDiv.style.position = 'absolute';
  817. }
  818. div.appendChild(innerDiv);
  819. document.body.appendChild(div);
  820. arg = innerDiv;
  821. }
  822. }
  823. catch (e)
  824. {
  825. div.parentNode.removeChild(div);
  826. div = null;
  827. throw e;
  828. }
  829. content(arg);
  830. return div;
  831. };
  832. /**
  833. * Function: getRoot
  834. *
  835. * Returns the root cell for painting the graph.
  836. */
  837. mxPrintPreview.prototype.getRoot = function()
  838. {
  839. var root = this.graph.view.currentRoot;
  840. if (root == null)
  841. {
  842. root = this.graph.getModel().getRoot();
  843. }
  844. return root;
  845. };
  846. /**
  847. * Function: useCssTransforms
  848. *
  849. * Returns true if CSS transforms should be used for scaling content.
  850. * This returns true if foreignObject is supported and we're not in Safari
  851. * as it has clipping bugs for transformed CSS content with foreignObjects.
  852. */
  853. mxPrintPreview.prototype.useCssTransforms = function()
  854. {
  855. return !mxClient.NO_FO && !mxClient.IS_SF;
  856. };
  857. /**
  858. * Function: addGraphFragment
  859. *
  860. * Adds a graph fragment to the given div.
  861. *
  862. * Parameters:
  863. *
  864. * dx - Horizontal translation for the diagram.
  865. * dy - Vertical translation for the diagram.
  866. * scale - Scale for the diagram.
  867. * pageNumber - Number of the page to be rendered.
  868. * div - Div that contains the output.
  869. * clip - Contains the clipping rectangle as an <mxRectangle>.
  870. */
  871. mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)
  872. {
  873. var view = this.graph.getView();
  874. var previousContainer = this.graph.container;
  875. this.graph.container = div;
  876. var canvas = view.getCanvas();
  877. var backgroundPane = view.getBackgroundPane();
  878. var drawPane = view.getDrawPane();
  879. var overlayPane = view.getOverlayPane();
  880. var realScale = scale;
  881. if (this.graph.dialect == mxConstants.DIALECT_SVG)
  882. {
  883. view.createSvg();
  884. // Uses CSS transform for scaling
  885. if (this.useCssTransforms())
  886. {
  887. var g = view.getDrawPane().parentNode;
  888. var prev = g.getAttribute('transform');
  889. g.setAttribute('transformOrigin', '0 0');
  890. g.setAttribute('transform', 'scale(' + scale + ',' + scale + ')' +
  891. 'translate(' + dx + ',' + dy + ')');
  892. scale = 1;
  893. dx = 0;
  894. dy = 0;
  895. }
  896. }
  897. else if (this.graph.dialect == mxConstants.DIALECT_VML)
  898. {
  899. view.createVml();
  900. }
  901. else
  902. {
  903. view.createHtml();
  904. }
  905. // Disables events on the view
  906. var eventsEnabled = view.isEventsEnabled();
  907. view.setEventsEnabled(false);
  908. // Disables the graph to avoid cursors
  909. var graphEnabled = this.graph.isEnabled();
  910. this.graph.setEnabled(false);
  911. // Resets the translation
  912. var translate = view.getTranslate();
  913. view.translate = new mxPoint(dx, dy);
  914. // Redraws only states that intersect the clip
  915. var redraw = this.graph.cellRenderer.redraw;
  916. var states = view.states;
  917. var s = view.scale;
  918. // Gets the transformed clip for intersection check below
  919. if (this.clipping)
  920. {
  921. var tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s,
  922. clip.width * s / realScale, clip.height * s / realScale);
  923. // Checks clipping rectangle for speedup
  924. // Must create terminal states for edge clipping even if terminal outside of clip
  925. this.graph.cellRenderer.redraw = function(state, force, rendering)
  926. {
  927. if (state != null)
  928. {
  929. // Gets original state from graph to find bounding box
  930. var orig = states.get(state.cell);
  931. if (orig != null)
  932. {
  933. var bbox = view.getBoundingBox(orig, false);
  934. // Stops rendering if outside clip for speedup but ignores
  935. // edge labels where width and height is set to 0
  936. if (bbox != null && bbox.width > 0 && bbox.height > 0 &&
  937. !mxUtils.intersects(tempClip, bbox))
  938. {
  939. return;
  940. }
  941. }
  942. }
  943. redraw.apply(this, arguments);
  944. };
  945. }
  946. var temp = null;
  947. try
  948. {
  949. // Creates the temporary cell states in the view and
  950. // draws them onto the temporary DOM nodes in the view
  951. var cells = [this.getRoot()];
  952. temp = new mxTemporaryCellStates(view, scale, cells, null, mxUtils.bind(this, function(state)
  953. {
  954. return this.getLinkForCellState(state);
  955. }));
  956. }
  957. finally
  958. {
  959. // Removes overlay pane with selection handles
  960. // controls and icons from the print output
  961. if (mxClient.IS_IE)
  962. {
  963. view.overlayPane.innerHTML = '';
  964. view.canvas.style.overflow = 'hidden';
  965. view.canvas.style.position = 'relative';
  966. view.canvas.style.top = this.marginTop + 'px';
  967. view.canvas.style.width = clip.width + 'px';
  968. view.canvas.style.height = clip.height + 'px';
  969. }
  970. else
  971. {
  972. // Removes everything but the SVG node
  973. var tmp = div.firstChild;
  974. while (tmp != null)
  975. {
  976. var next = tmp.nextSibling;
  977. var name = tmp.nodeName.toLowerCase();
  978. // Note: Width and height are required in FF 11
  979. if (name == 'svg')
  980. {
  981. tmp.style.overflow = 'hidden';
  982. tmp.style.position = 'relative';
  983. tmp.style.top = this.marginTop + 'px';
  984. tmp.setAttribute('width', clip.width);
  985. tmp.setAttribute('height', clip.height);
  986. tmp.style.width = '';
  987. tmp.style.height = '';
  988. }
  989. // Tries to fetch all text labels and only text labels
  990. else if (tmp.style.cursor != 'default' && name != 'div')
  991. {
  992. tmp.parentNode.removeChild(tmp);
  993. }
  994. tmp = next;
  995. }
  996. }
  997. // Puts background image behind SVG output
  998. if (this.printBackgroundImage)
  999. {
  1000. var svgs = div.getElementsByTagName('svg');
  1001. if (svgs.length > 0)
  1002. {
  1003. svgs[0].style.position = 'absolute';
  1004. }
  1005. }
  1006. // Completely removes the overlay pane to remove more handles
  1007. view.overlayPane.parentNode.removeChild(view.overlayPane);
  1008. // Restores the state of the view
  1009. this.graph.setEnabled(graphEnabled);
  1010. this.graph.container = previousContainer;
  1011. this.graph.cellRenderer.redraw = redraw;
  1012. view.canvas = canvas;
  1013. view.backgroundPane = backgroundPane;
  1014. view.drawPane = drawPane;
  1015. view.overlayPane = overlayPane;
  1016. view.translate = translate;
  1017. temp.destroy();
  1018. view.setEventsEnabled(eventsEnabled);
  1019. }
  1020. };
  1021. /**
  1022. * Function: getLinkForCellState
  1023. *
  1024. * Returns the link for the given cell state. This returns null.
  1025. */
  1026. mxPrintPreview.prototype.getLinkForCellState = function(state)
  1027. {
  1028. return this.graph.getLinkForCell(state.cell);
  1029. };
  1030. /**
  1031. * Function: insertBackgroundImage
  1032. *
  1033. * Inserts the background image into the given div.
  1034. */
  1035. mxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy)
  1036. {
  1037. var bg = this.graph.backgroundImage;
  1038. if (bg != null)
  1039. {
  1040. var img = document.createElement('img');
  1041. img.style.position = 'absolute';
  1042. img.style.marginLeft = Math.round(dx * this.scale) + 'px';
  1043. img.style.marginTop = Math.round(dy * this.scale) + 'px';
  1044. img.setAttribute('width', Math.round(this.scale * bg.width));
  1045. img.setAttribute('height', Math.round(this.scale * bg.height));
  1046. img.src = bg.src;
  1047. div.insertBefore(img, div.firstChild);
  1048. }
  1049. };
  1050. /**
  1051. * Function: getCoverPages
  1052. *
  1053. * Returns the pages to be added before the print output. This returns null.
  1054. */
  1055. mxPrintPreview.prototype.getCoverPages = function()
  1056. {
  1057. return null;
  1058. };
  1059. /**
  1060. * Function: getAppendices
  1061. *
  1062. * Returns the pages to be added after the print output. This returns null.
  1063. */
  1064. mxPrintPreview.prototype.getAppendices = function()
  1065. {
  1066. return null;
  1067. };
  1068. /**
  1069. * Function: print
  1070. *
  1071. * Opens the print preview and shows the print dialog.
  1072. *
  1073. * Parameters:
  1074. *
  1075. * css - Optional CSS string to be used in the head section.
  1076. */
  1077. mxPrintPreview.prototype.print = function(css)
  1078. {
  1079. var wnd = this.open(css);
  1080. if (wnd != null)
  1081. {
  1082. wnd.print();
  1083. }
  1084. };
  1085. /**
  1086. * Function: close
  1087. *
  1088. * Closes the print preview window.
  1089. */
  1090. mxPrintPreview.prototype.close = function()
  1091. {
  1092. if (this.wnd != null)
  1093. {
  1094. this.wnd.close();
  1095. this.wnd = null;
  1096. }
  1097. };
  1098. __mxOutput.mxPrintPreview = typeof mxPrintPreview !== 'undefined' ? mxPrintPreview : undefined;