From dc84b46e9868b5aff9bf7254201ea68db0bb0e0f Mon Sep 17 00:00:00 2001 From: Elias Kuiter Date: Wed, 5 Sep 2018 15:15:35 +0200 Subject: [PATCH] PDF export --- client/package.json | 3 +- client/src/components/contextualMenuItems.js | 35 +++++++----------- .../src/components/featureDiagram/export.js | 37 ++++++++++++++++--- .../src/components/overlays/ExportDialog.js | 20 ++++++---- client/src/i18n.js | 18 +++++---- client/src/types.js | 3 +- client/yarn.lock | 26 +++++++++++++ 7 files changed, 98 insertions(+), 44 deletions(-) diff --git a/client/package.json b/client/package.json index d1c158db..0b602b01 100644 --- a/client/package.json +++ b/client/package.json @@ -27,7 +27,8 @@ "reduce-reducers": "^0.4.3", "redux": "^4.0.0", "redux-actions": "^2.6.1", - "reselect": "^3.0.1" + "reselect": "^3.0.1", + "svg2pdf.js": "^1.2.1" }, "devDependencies": { "babel-eslint": "^9.0.0", diff --git a/client/src/components/contextualMenuItems.js b/client/src/components/contextualMenuItems.js index 072b61c9..114e9ff7 100644 --- a/client/src/components/contextualMenuItems.js +++ b/client/src/components/contextualMenuItems.js @@ -6,6 +6,15 @@ import {ContextualMenuItemType} from '../../node_modules/office-ui-fabric-react/ import {getShortcutText} from '../shortcuts'; import {canExport} from './featureDiagram/export'; +const exportFormatItem = (featureDiagramLayout, onShowOverlay, format) => + canExport(featureDiagramLayout, format) + ? [{ + key: format, + text: i18n.t('featureDiagram.commands', format), + onClick: () => onShowOverlay(overlayTypes.exportDialog, {format}) + }] + : []; + const contextualMenuItems = { settings: onShowOverlay => ({ key: 'settings', @@ -27,27 +36,11 @@ const contextualMenuItems = { iconProps: {iconName: 'Share'}, subMenuProps: { items: [ - ...canExport(featureDiagramLayout, formatTypes.png) - ? [{ - key: 'png', - text: i18n.t('featureDiagram.commands.png'), - onClick: () => onShowOverlay(overlayTypes.exportDialog, {format: formatTypes.png}) - }] - : [], - ...canExport(featureDiagramLayout, formatTypes.jpg) - ? [{ - key: 'jpg', - text: i18n.t('featureDiagram.commands.jpg'), - onClick: () => onShowOverlay(overlayTypes.exportDialog, {format: formatTypes.jpg}) - }] - : [], - ...canExport(featureDiagramLayout, formatTypes.svg) - ? [{ - key: 'svg', - text: i18n.t('featureDiagram.commands.svg'), - onClick: () => onShowOverlay(overlayTypes.exportDialog, {format: formatTypes.svg}) - }] - : [] + ...exportFormatItem(featureDiagramLayout, onShowOverlay, formatTypes.png), + ...exportFormatItem(featureDiagramLayout, onShowOverlay, formatTypes.jpg), + {key: 'divider', itemType: ContextualMenuItemType.Divider}, + ...exportFormatItem(featureDiagramLayout, onShowOverlay, formatTypes.svg), + ...exportFormatItem(featureDiagramLayout, onShowOverlay, formatTypes.pdf) ] } }), diff --git a/client/src/components/featureDiagram/export.js b/client/src/components/featureDiagram/export.js index 754d796d..c9cda8a8 100644 --- a/client/src/components/featureDiagram/export.js +++ b/client/src/components/featureDiagram/export.js @@ -13,6 +13,7 @@ function svgData(scale = 1) { svg.querySelector('g').setAttribute('transform', `translate(${-estimatedBbox[0][0] * scale},${-estimatedBbox[0][1] * scale}) scale(${scale})`); return { + svg, string: new XMLSerializer().serializeToString(svg), width: estimatedBboxWidth * scale, height: estimatedBboxHeight * scale @@ -43,16 +44,40 @@ function exportJpg({scale = 1, quality = 0.8}) { .then(() => new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', quality))); } +function exportPdf(_options, fileName) { + const {svg, width, height} = svgData(); + Promise.all([import('svg2pdf.js'), import('jspdf-yworks')]) + .then(([svg2pdf, jsPDF]) => { + const pdf = new jsPDF({ + orientation: 'landscape', + unit: 'pt', + format: [width, height] + }); + try { + svg2pdf(svg, pdf, { + xOffset: 0, + yOffset: 0, + scale: 1 + }); + pdf.save(fileName); + } catch (e) { + console.warn('PDF export failed - choose Arial as font and try again'); + } + }); +} + const exportMap = { [layoutTypes.verticalTree]: { [formatTypes.svg]: exportSvg, [formatTypes.png]: exportPng, - [formatTypes.jpg]: exportJpg + [formatTypes.jpg]: exportJpg, + [formatTypes.pdf]: exportPdf }, [layoutTypes.horizontalTree]: { [formatTypes.svg]: exportSvg, [formatTypes.png]: exportPng, - [formatTypes.jpg]: exportJpg + [formatTypes.jpg]: exportJpg, + [formatTypes.pdf]: exportPdf } }; @@ -60,9 +85,9 @@ export function canExport(featureDiagramLayout, format) { return !!exportMap[featureDiagramLayout][format]; } -export function doExport(featureDiagramLayout, format, ...args) { - const promise = exportMap[featureDiagramLayout][format](...args); +export function doExport(featureDiagramLayout, format, options) { + const fileName = `featureModel-${new Date().toLocaleDateString()}.${format}`, + promise = exportMap[featureDiagramLayout][format](options, fileName); if (promise) - promise.then(blob => - saveAs(blob, `featureModel-${new Date().toLocaleDateString()}.${format}`)); + promise.then(blob => saveAs(blob, fileName)); } \ No newline at end of file diff --git a/client/src/components/overlays/ExportDialog.js b/client/src/components/overlays/ExportDialog.js index d2b8e541..ebdeaeb6 100644 --- a/client/src/components/overlays/ExportDialog.js +++ b/client/src/components/overlays/ExportDialog.js @@ -31,6 +31,16 @@ export default class extends React.Component { this.props.onDismiss(); }; + renderFontComboBox = () => ( + + {i18n.t('dialogs.exportDialog.fontNotice')} + this.props.onSetSetting('featureDiagram.font.family', font)}/> + + ); + renderZoomSpinButton = () => (