aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/frontend/src')
-rw-r--r--subprojects/frontend/src/RootStore.ts2
-rw-r--r--subprojects/frontend/src/graph/GraphArea.tsx2
-rw-r--r--subprojects/frontend/src/graph/export/ExportPanel.tsx (renamed from subprojects/frontend/src/graph/ExportPanel.tsx)26
-rw-r--r--subprojects/frontend/src/graph/export/ExportSettingsStore.tsx (renamed from subprojects/frontend/src/graph/ExportSettingsStore.tsx)25
-rw-r--r--subprojects/frontend/src/graph/export/exportDiagram.tsx (renamed from subprojects/frontend/src/graph/exportDiagram.tsx)31
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-bold.ttfbin0 -> 33444 bytes
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license8
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-italic.ttfbin0 -> 36192 bytes
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license8
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-regular.ttfbin0 -> 33508 bytes
-rw-r--r--subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license8
-rw-r--r--subprojects/frontend/src/graph/export/serializePDF.ts37
12 files changed, 130 insertions, 17 deletions
diff --git a/subprojects/frontend/src/RootStore.ts b/subprojects/frontend/src/RootStore.ts
index 27bff0de..8a17d2de 100644
--- a/subprojects/frontend/src/RootStore.ts
+++ b/subprojects/frontend/src/RootStore.ts
@@ -9,7 +9,7 @@ import { makeAutoObservable, runInAction } from 'mobx';
9 9
10import PWAStore from './PWAStore'; 10import PWAStore from './PWAStore';
11import type EditorStore from './editor/EditorStore'; 11import type EditorStore from './editor/EditorStore';
12import ExportSettingsScotre from './graph/ExportSettingsStore'; 12import ExportSettingsScotre from './graph/export/ExportSettingsStore';
13import Compressor from './persistence/Compressor'; 13import Compressor from './persistence/Compressor';
14import ThemeStore from './theme/ThemeStore'; 14import ThemeStore from './theme/ThemeStore';
15 15
diff --git a/subprojects/frontend/src/graph/GraphArea.tsx b/subprojects/frontend/src/graph/GraphArea.tsx
index 1416a259..b5d93aef 100644
--- a/subprojects/frontend/src/graph/GraphArea.tsx
+++ b/subprojects/frontend/src/graph/GraphArea.tsx
@@ -11,10 +11,10 @@ import { useState } from 'react';
11import { useResizeDetector } from 'react-resize-detector'; 11import { useResizeDetector } from 'react-resize-detector';
12 12
13import DotGraphVisualizer from './DotGraphVisualizer'; 13import DotGraphVisualizer from './DotGraphVisualizer';
14import ExportPanel from './ExportPanel';
15import type GraphStore from './GraphStore'; 14import type GraphStore from './GraphStore';
16import VisibilityPanel from './VisibilityPanel'; 15import VisibilityPanel from './VisibilityPanel';
17import ZoomCanvas from './ZoomCanvas'; 16import ZoomCanvas from './ZoomCanvas';
17import ExportPanel from './export/ExportPanel';
18 18
19function GraphArea({ graph }: { graph: GraphStore }): JSX.Element { 19function GraphArea({ graph }: { graph: GraphStore }): JSX.Element {
20 const { breakpoints } = useTheme(); 20 const { breakpoints } = useTheme();
diff --git a/subprojects/frontend/src/graph/ExportPanel.tsx b/subprojects/frontend/src/graph/export/ExportPanel.tsx
index 2621deff..c93fa837 100644
--- a/subprojects/frontend/src/graph/ExportPanel.tsx
+++ b/subprojects/frontend/src/graph/export/ExportPanel.tsx
@@ -8,6 +8,7 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
8import ContentCopyIcon from '@mui/icons-material/ContentCopy'; 8import ContentCopyIcon from '@mui/icons-material/ContentCopy';
9import DarkModeIcon from '@mui/icons-material/DarkMode'; 9import DarkModeIcon from '@mui/icons-material/DarkMode';
10import ImageIcon from '@mui/icons-material/Image'; 10import ImageIcon from '@mui/icons-material/Image';
11import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
11import LightModeIcon from '@mui/icons-material/LightMode'; 12import LightModeIcon from '@mui/icons-material/LightMode';
12import SaveAltIcon from '@mui/icons-material/SaveAlt'; 13import SaveAltIcon from '@mui/icons-material/SaveAlt';
13import ShapeLineIcon from '@mui/icons-material/ShapeLine'; 14import ShapeLineIcon from '@mui/icons-material/ShapeLine';
@@ -24,11 +25,11 @@ import { styled } from '@mui/material/styles';
24import { observer } from 'mobx-react-lite'; 25import { observer } from 'mobx-react-lite';
25import { useCallback } from 'react'; 26import { useCallback } from 'react';
26 27
27import { useRootStore } from '../RootStoreProvider'; 28import { useRootStore } from '../../RootStoreProvider';
28import getLogger from '../utils/getLogger'; 29import getLogger from '../../utils/getLogger';
30import type GraphStore from '../GraphStore';
31import SlideInPanel from '../SlideInPanel';
29 32
30import type GraphStore from './GraphStore';
31import SlideInPanel from './SlideInPanel';
32import exportDiagram from './exportDiagram'; 33import exportDiagram from './exportDiagram';
33 34
34const log = getLogger('graph.ExportPanel'); 35const log = getLogger('graph.ExportPanel');
@@ -138,6 +139,13 @@ function ExportPanel({
138 <ShapeLineIcon fontSize="small" /> SVG 139 <ShapeLineIcon fontSize="small" /> SVG
139 </ToggleButton> 140 </ToggleButton>
140 <ToggleButton 141 <ToggleButton
142 value="pdf"
143 selected={exportSettingsStore.format === 'pdf'}
144 onClick={() => exportSettingsStore.setFormat('pdf')}
145 >
146 <InsertDriveFileOutlinedIcon fontSize="small" /> PDF
147 </ToggleButton>
148 <ToggleButton
141 value="png" 149 value="png"
142 selected={exportSettingsStore.format === 'png'} 150 selected={exportSettingsStore.format === 'png'}
143 onClick={() => exportSettingsStore.setFormat('png')} 151 onClick={() => exportSettingsStore.setFormat('png')}
@@ -170,7 +178,7 @@ function ExportPanel({
170 } 178 }
171 label="Transparent background" 179 label="Transparent background"
172 /> 180 />
173 {exportSettingsStore.format === 'svg' && ( 181 {exportSettingsStore.canEmbedFonts && (
174 <FormControlLabel 182 <FormControlLabel
175 control={ 183 control={
176 <Switch 184 <Switch
@@ -182,13 +190,17 @@ function ExportPanel({
182 <Stack direction="column"> 190 <Stack direction="column">
183 <Typography>Embed fonts</Typography> 191 <Typography>Embed fonts</Typography>
184 <Typography variant="caption"> 192 <Typography variant="caption">
185 +75&thinsp;kB, only supported in browsers 193 {exportSettingsStore.format === 'pdf' ? (
194 <>+20&thinsp;kB fully embedded</>
195 ) : (
196 <>+75&thinsp;kB, only supported in browsers</>
197 )}
186 </Typography> 198 </Typography>
187 </Stack> 199 </Stack>
188 } 200 }
189 /> 201 />
190 )} 202 )}
191 {exportSettingsStore.format === 'png' && ( 203 {exportSettingsStore.canScale && (
192 <Box mx={4} mt={1} mb={2}> 204 <Box mx={4} mt={1} mb={2}>
193 <Slider 205 <Slider
194 aria-label="Image scale" 206 aria-label="Image scale"
diff --git a/subprojects/frontend/src/graph/ExportSettingsStore.tsx b/subprojects/frontend/src/graph/export/ExportSettingsStore.tsx
index 8ee91b73..53a161ab 100644
--- a/subprojects/frontend/src/graph/ExportSettingsStore.tsx
+++ b/subprojects/frontend/src/graph/export/ExportSettingsStore.tsx
@@ -6,7 +6,7 @@
6 6
7import { makeAutoObservable } from 'mobx'; 7import { makeAutoObservable } from 'mobx';
8 8
9export type ExportFormat = 'svg' | 'png'; 9export type ExportFormat = 'svg' | 'pdf' | 'png';
10export type ExportTheme = 'light' | 'dark'; 10export type ExportTheme = 'light' | 'dark';
11 11
12export default class ExportSettingsStore { 12export default class ExportSettingsStore {
@@ -16,7 +16,9 @@ export default class ExportSettingsStore {
16 16
17 transparent = true; 17 transparent = true;
18 18
19 embedFonts = false; 19 embedSVGFonts = false;
20
21 embedPDFFonts = true;
20 22
21 scale = 100; 23 scale = 100;
22 24
@@ -43,4 +45,23 @@ export default class ExportSettingsStore {
43 setScale(scale: number): void { 45 setScale(scale: number): void {
44 this.scale = scale; 46 this.scale = scale;
45 } 47 }
48
49 get embedFonts(): boolean {
50 return this.format === 'pdf' ? this.embedPDFFonts : this.embedSVGFonts;
51 }
52
53 private set embedFonts(embedFonts: boolean) {
54 if (this.format === 'pdf') {
55 this.embedPDFFonts = embedFonts;
56 }
57 this.embedSVGFonts = embedFonts;
58 }
59
60 get canEmbedFonts(): boolean {
61 return this.format === 'svg' || this.format === 'pdf';
62 }
63
64 get canScale(): boolean {
65 return this.format === 'png';
66 }
46} 67}
diff --git a/subprojects/frontend/src/graph/exportDiagram.tsx b/subprojects/frontend/src/graph/export/exportDiagram.tsx
index 3ba278f9..685b6ace 100644
--- a/subprojects/frontend/src/graph/exportDiagram.tsx
+++ b/subprojects/frontend/src/graph/export/exportDiagram.tsx
@@ -17,13 +17,13 @@ import labelSVG from '@material-icons/svg/svg/label/baseline.svg?raw';
17import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw'; 17import labelOutlinedSVG from '@material-icons/svg/svg/label/outline.svg?raw';
18import type { Theme } from '@mui/material/styles'; 18import type { Theme } from '@mui/material/styles';
19 19
20import { darkTheme, lightTheme } from '../theme/ThemeProvider'; 20import { darkTheme, lightTheme } from '../../theme/ThemeProvider';
21import { copyBlob, saveBlob } from '../utils/fileIO'; 21import { copyBlob, saveBlob } from '../../utils/fileIO';
22import type GraphStore from '../GraphStore';
23import { createGraphTheme } from '../GraphTheme';
24import { SVG_NS } from '../postProcessSVG';
22 25
23import type ExportSettingsStore from './ExportSettingsStore'; 26import type ExportSettingsStore from './ExportSettingsStore';
24import type GraphStore from './GraphStore';
25import { createGraphTheme } from './GraphTheme';
26import { SVG_NS } from './postProcessSVG';
27 27
28const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>'; 28const PROLOG = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';
29const PNG_CONTENT_TYPE = 'image/png'; 29const PNG_CONTENT_TYPE = 'image/png';
@@ -283,6 +283,20 @@ async function serializePNG(
283 }); 283 });
284} 284}
285 285
286let serializePDFCached:
287 | ((svg: SVGSVGElement, embedFonts: boolean) => Promise<Blob>)
288 | undefined;
289
290async function serializePDF(
291 svg: SVGSVGElement,
292 settings: ExportSettingsStore,
293): Promise<Blob> {
294 if (serializePDFCached === undefined) {
295 serializePDFCached = (await import('./serializePDF')).default;
296 }
297 return serializePDFCached(svg, settings.embedFonts);
298}
299
286export default async function exportDiagram( 300export default async function exportDiagram(
287 svgContainer: HTMLElement | undefined, 301 svgContainer: HTMLElement | undefined,
288 graph: GraphStore, 302 graph: GraphStore,
@@ -319,11 +333,16 @@ export default async function exportDiagram(
319 // If we are creating a PNG, font file size doesn't matter, 333 // If we are creating a PNG, font file size doesn't matter,
320 // and we can reuse fonts the browser has already downloaded. 334 // and we can reuse fonts the browser has already downloaded.
321 fontsCSS = await fetchVariableFontCSS(); 335 fontsCSS = await fetchVariableFontCSS();
322 } else if (settings.embedFonts) { 336 } else if (settings.format === 'svg' && settings.embedFonts) {
323 fontsCSS = await fetchFontCSS(); 337 fontsCSS = await fetchFontCSS();
324 } 338 }
325 appendStyles(svgDocument, copyOfSVG, theme, colorNodes, fontsCSS); 339 appendStyles(svgDocument, copyOfSVG, theme, colorNodes, fontsCSS);
326 340
341 if (settings.format === 'pdf') {
342 const pdf = await serializePDF(copyOfSVG, settings);
343 await saveBlob(pdf, 'graph.pdf', 'application/pdf', EXPORT_ID);
344 return;
345 }
327 const serializedSVG = serializeSVG(svgDocument); 346 const serializedSVG = serializeSVG(svgDocument);
328 if (settings.format === 'png') { 347 if (settings.format === 'png') {
329 const png = await serializePNG(serializedSVG, svg, settings, theme); 348 const png = await serializePNG(serializedSVG, svg, settings, theme);
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf
new file mode 100644
index 00000000..7472d192
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf
Binary files differ
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license
new file mode 100644
index 00000000..442f1821
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-bold.ttf.license
@@ -0,0 +1,8 @@
1Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
2
3SPDX-License-Identifier: OFL-1.1
4
5This file was derived from Open Sans v3.3
6(https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Bold.ttf)
7using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator)
8with the Basic Subsetting setting to reduce the file size by retaining latin characters only.
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf
new file mode 100644
index 00000000..cecb0ce1
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf
Binary files differ
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license
new file mode 100644
index 00000000..0657164a
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-italic.ttf.license
@@ -0,0 +1,8 @@
1Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
2
3SPDX-License-Identifier: OFL-1.1
4
5This file was derived from Open Sans v3.3
6(https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Italic.ttf)
7using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator)
8with the Basic Subsetting setting to reduce the file size by retaining latin characters only.
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf
new file mode 100644
index 00000000..46c0f716
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf
Binary files differ
diff --git a/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license
new file mode 100644
index 00000000..8bc20e51
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/open-sans-latin-regular.ttf.license
@@ -0,0 +1,8 @@
1Copyright 2020 The Open Sans Project Authors (https://github.com/googlefonts/opensans)
2
3SPDX-License-Identifier: OFL-1.1
4
5This file was derived from Open Sans v3.3
6(https://github.com/googlefonts/opensans/blob/bd7e37632246368c60fdcbd374dbf9bad11969b6/fonts/ttf/OpenSans-Regular.ttf)
7using the Font Squirrel Web Font Generator (https://www.fontsquirrel.com/tools/webfont-generator)
8with the Basic Subsetting setting to reduce the file size by retaining latin characters only.
diff --git a/subprojects/frontend/src/graph/export/serializePDF.ts b/subprojects/frontend/src/graph/export/serializePDF.ts
new file mode 100644
index 00000000..75d1a4f4
--- /dev/null
+++ b/subprojects/frontend/src/graph/export/serializePDF.ts
@@ -0,0 +1,37 @@
1/*
2 * SPDX-FileCopyrightText: 2024 The Refinery Authors <https://refinery.tools/>
3 *
4 * SPDX-License-Identifier: EPL-2.0
5 */
6
7import { jsPDF } from 'jspdf';
8import { svg2pdf } from 'svg2pdf.js';
9
10import boldFontURL from './open-sans-latin-bold.ttf?url';
11import italicFontURL from './open-sans-latin-italic.ttf?url';
12import normalFontURL from './open-sans-latin-regular.ttf?url';
13
14export default async function serializePDF(
15 svg: SVGSVGElement,
16 embedFonts: boolean,
17): Promise<Blob> {
18 const width = svg.width.baseVal.value;
19 const height = svg.height.baseVal.value;
20 // eslint-disable-next-line new-cap -- jsPDF uses a lowercase constructor.
21 const document = new jsPDF({
22 orientation: width > height ? 'l' : 'p',
23 unit: 'px',
24 format: [width, height],
25 compress: true,
26 });
27 if (embedFonts) {
28 document.addFont(normalFontURL, 'Open Sans', 'normal', 400);
29 document.addFont(italicFontURL, 'Open Sans', 'italic', 400);
30 document.addFont(boldFontURL, 'Open Sans', 'normal', 700);
31 }
32 const result = await svg2pdf(svg, document, {
33 width,
34 height,
35 });
36 return result.output('blob');
37}