Skip to content

Commit

Permalink
PDF export
Browse files Browse the repository at this point in the history
  • Loading branch information
ekuiter committed Sep 5, 2018
1 parent 6c4a553 commit dc84b46
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 44 deletions.
3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 14 additions & 21 deletions client/src/components/contextualMenuItems.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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)
]
}
}),
Expand Down
37 changes: 31 additions & 6 deletions client/src/components/featureDiagram/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -43,26 +44,50 @@ 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
}
};

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));
}
20 changes: 12 additions & 8 deletions client/src/components/overlays/ExportDialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ export default class extends React.Component {
this.props.onDismiss();
};

renderFontComboBox = () => (
<React.Fragment>
{i18n.t('dialogs.exportDialog.fontNotice')}
<FontComboBox
label={i18n.t('panels.settingsPanel.labels.featureDiagram.font.family')}
selectedFont={getSetting(this.props.settings, 'featureDiagram.font.family')}
onChange={font => this.props.onSetSetting('featureDiagram.font.family', font)}/>
</React.Fragment>
);

renderZoomSpinButton = () => (
<SpinButton
className="setting"
Expand All @@ -46,14 +56,7 @@ export default class extends React.Component {
hidden={!this.props.isOpen}
onDismiss={this.props.onDismiss}
dialogContentProps={{title: i18n.t('dialogs.exportDialog', this.props.format, 'title')}}>
{this.props.format === formatTypes.svg &&
<React.Fragment>
{i18n.t('dialogs.exportDialog.svg.content')}
<FontComboBox
label={i18n.t('panels.settingsPanel.labels.featureDiagram.font.family')}
selectedFont={getSetting(this.props.settings, 'featureDiagram.font.family')}
onChange={font => this.props.onSetSetting('featureDiagram.font.family', font)}/>
</React.Fragment>}
{this.props.format === formatTypes.svg && this.renderFontComboBox()}
{this.props.format === formatTypes.png && this.renderZoomSpinButton()}
{this.props.format === formatTypes.jpg &&
<React.Fragment>
Expand All @@ -65,6 +68,7 @@ export default class extends React.Component {
value={this.state.quality}
min={10} max={100} suffix=" %"/>
</React.Fragment>}
{this.props.format === formatTypes.pdf && this.renderFontComboBox()}
<DialogFooter>
<PrimaryButton onClick={this.onSubmit} text={i18n.t('dialogs.exportDialog.export')}/>
</DialogFooter>
Expand Down
18 changes: 11 additions & 7 deletions client/src/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,24 @@ export const strings = {
exportDialog: {
export: 'Export',
zoom: 'Zoom',
fontNotice: (
<p>
Note that the font will <strong>not</strong> be embedded.
Please make sure to choose a font that is commonly available.
</p>
),
svg: {
title: 'Export as SVG',
content: (
<p>
Note that the font will <strong>not</strong> be embedded into the SVG file.
Please make sure to choose a font that is commonly available.
</p>
)
title: 'Export as SVG'
},
png: {
title: 'Export as PNG'
},
jpg: {
title: 'Export as JPEG',
quality: 'Quality'
},
pdf: {
title: 'Export as PDF'
}
}
},
Expand All @@ -125,6 +128,7 @@ export const strings = {
svg: 'SVG…',
png: 'PNG…',
jpg: 'JPEG…',
pdf: 'PDF…',
undo: 'Undo',
redo: 'Redo',
setLayout: 'Layout',
Expand Down
3 changes: 2 additions & 1 deletion client/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const
formatTypes = {
svg: 'svg',
png: 'png',
jpg: 'jpg'
jpg: 'jpg',
pdf: 'pdf'
},
FormatType = PropTypes.oneOf(Object.values(formatTypes)),

Expand Down
26 changes: 26 additions & 0 deletions client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ [email protected], address@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9"

"adler32cs@github:chick307/adler32cs.js":
version "0.0.2"
resolved "https://codeload.github.com/chick307/adler32cs.js/tar.gz/7fd00ffa24bf173a1eeb987ce7a21ae214eff658"

ajv-keywords@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
Expand Down Expand Up @@ -1587,6 +1591,10 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"

[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/cf-blob.js/-/cf-blob.js-0.0.1.tgz#f5ab7e12e798caf08ccf828c69aba0f063d83f99"

[email protected], chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
Expand Down Expand Up @@ -3191,6 +3199,10 @@ filename-regex@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"

"filesaver.js@github:andyinabox/FileSaver.js":
version "1.0.0"
resolved "https://codeload.github.com/andyinabox/FileSaver.js/tar.gz/973b433b8fbaee9a11d53b9ae423b046742cfe32"

fileset@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0"
Expand Down Expand Up @@ -4677,6 +4689,14 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"

jspdf-yworks@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/jspdf-yworks/-/jspdf-yworks-1.3.2.tgz#4ac18d845438a8a062b21b6f612229688fbd5eea"
dependencies:
adler32cs "github:chick307/adler32cs.js"
cf-blob.js "0.0.1"
filesaver.js "github:andyinabox/FileSaver.js"

jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
Expand Down Expand Up @@ -7289,6 +7309,12 @@ supports-color@^5.3.0, supports-color@^5.4.0:
dependencies:
has-flag "^3.0.0"

svg2pdf.js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/svg2pdf.js/-/svg2pdf.js-1.2.1.tgz#9bad9124362c1673b8abf6a441245db8631b7909"
dependencies:
jspdf-yworks "^1.3.2"

svgo@^0.7.0:
version "0.7.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5"
Expand Down

0 comments on commit dc84b46

Please sign in to comment.