aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-23 22:28:20 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2024-02-23 22:42:04 +0100
commit3a4e1f0115705c96b1f4712d72998eda085f426f (patch)
tree2de53642c5bb2315838537ebbc55b2134fb1901c
parentrefactor(web): use filesystem access API when available (diff)
downloadrefinery-3a4e1f0115705c96b1f4712d72998eda085f426f.tar.gz
refinery-3a4e1f0115705c96b1f4712d72998eda085f426f.tar.zst
refinery-3a4e1f0115705c96b1f4712d72998eda085f426f.zip
feat(web): pdf export
-rw-r--r--LICENSES/OFL-1.1.txt43
-rw-r--r--subprojects/frontend/package.json3
-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
-rw-r--r--yarn.lock235
15 files changed, 410 insertions, 18 deletions
diff --git a/LICENSES/OFL-1.1.txt b/LICENSES/OFL-1.1.txt
new file mode 100644
index 00000000..6fe84ee2
--- /dev/null
+++ b/LICENSES/OFL-1.1.txt
@@ -0,0 +1,43 @@
1SIL OPEN FONT LICENSE
2
3Version 1.1 - 26 February 2007
4
5PREAMBLE
6
7The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
8
9The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
10
11DEFINITIONS
12
13"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
14
15"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
16
17"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
18
19"Modified Version" refers to any derivative made by adding to, deleting, or substituting — in part or in whole — any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
20
21"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
22
23PERMISSION & CONDITIONS
24
25Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
26
271) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
28
292) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
30
313) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
32
334) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
34
355) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
36
37TERMINATION
38
39This license becomes null and void if any of the above conditions are not met.
40
41DISCLAIMER
42
43THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json
index 7d663754..033b55c3 100644
--- a/subprojects/frontend/package.json
+++ b/subprojects/frontend/package.json
@@ -59,6 +59,7 @@
59 "d3-selection": "^3.0.0", 59 "d3-selection": "^3.0.0",
60 "d3-zoom": "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch", 60 "d3-zoom": "patch:d3-zoom@npm%3A3.0.0#~/.yarn/patches/d3-zoom-npm-3.0.0-18f706a421.patch",
61 "escape-string-regexp": "^5.0.0", 61 "escape-string-regexp": "^5.0.0",
62 "jspdf": "^2.5.1",
62 "lodash-es": "^4.17.21", 63 "lodash-es": "^4.17.21",
63 "loglevel": "^1.9.1", 64 "loglevel": "^1.9.1",
64 "loglevel-plugin-prefix": "^0.8.4", 65 "loglevel-plugin-prefix": "^0.8.4",
@@ -70,6 +71,7 @@
70 "react": "^18.2.0", 71 "react": "^18.2.0",
71 "react-dom": "^18.2.0", 72 "react-dom": "^18.2.0",
72 "react-resize-detector": "^10.0.1", 73 "react-resize-detector": "^10.0.1",
74 "svg2pdf.js": "^2.2.3",
73 "xstate": "^4.38.3", 75 "xstate": "^4.38.3",
74 "zod": "^3.22.4" 76 "zod": "^3.22.4"
75 }, 77 },
@@ -81,6 +83,7 @@
81 "@types/d3-zoom": "^3.0.8", 83 "@types/d3-zoom": "^3.0.8",
82 "@types/eslint": "^8.56.3", 84 "@types/eslint": "^8.56.3",
83 "@types/html-minifier-terser": "^7.0.2", 85 "@types/html-minifier-terser": "^7.0.2",
86 "@types/jspdf": "^2.0.0",
84 "@types/lodash-es": "^4.17.12", 87 "@types/lodash-es": "^4.17.12",
85 "@types/micromatch": "^4.0.6", 88 "@types/micromatch": "^4.0.6",
86 "@types/ms": "^0.7.34", 89 "@types/ms": "^0.7.34",
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}
diff --git a/yarn.lock b/yarn.lock
index edfddec8..38b62b85 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1234,7 +1234,7 @@ __metadata:
1234 languageName: node 1234 languageName: node
1235 linkType: hard 1235 linkType: hard
1236 1236
1237"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": 1237"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7":
1238 version: 7.23.9 1238 version: 7.23.9
1239 resolution: "@babel/runtime@npm:7.23.9" 1239 resolution: "@babel/runtime@npm:7.23.9"
1240 dependencies: 1240 dependencies:
@@ -2197,6 +2197,7 @@ __metadata:
2197 "@types/d3-zoom": "npm:^3.0.8" 2197 "@types/d3-zoom": "npm:^3.0.8"
2198 "@types/eslint": "npm:^8.56.3" 2198 "@types/eslint": "npm:^8.56.3"
2199 "@types/html-minifier-terser": "npm:^7.0.2" 2199 "@types/html-minifier-terser": "npm:^7.0.2"
2200 "@types/jspdf": "npm:^2.0.0"
2200 "@types/lodash-es": "npm:^4.17.12" 2201 "@types/lodash-es": "npm:^4.17.12"
2201 "@types/micromatch": "npm:^4.0.6" 2202 "@types/micromatch": "npm:^4.0.6"
2202 "@types/ms": "npm:^0.7.34" 2203 "@types/ms": "npm:^0.7.34"
@@ -2228,6 +2229,7 @@ __metadata:
2228 eslint-plugin-react: "npm:^7.33.2" 2229 eslint-plugin-react: "npm:^7.33.2"
2229 eslint-plugin-react-hooks: "npm:^4.6.0" 2230 eslint-plugin-react-hooks: "npm:^4.6.0"
2230 html-minifier-terser: "npm:^7.2.0" 2231 html-minifier-terser: "npm:^7.2.0"
2232 jspdf: "npm:^2.5.1"
2231 lodash-es: "npm:^4.17.21" 2233 lodash-es: "npm:^4.17.21"
2232 loglevel: "npm:^1.9.1" 2234 loglevel: "npm:^1.9.1"
2233 loglevel-plugin-prefix: "npm:^0.8.4" 2235 loglevel-plugin-prefix: "npm:^0.8.4"
@@ -2242,6 +2244,7 @@ __metadata:
2242 react: "npm:^18.2.0" 2244 react: "npm:^18.2.0"
2243 react-dom: "npm:^18.2.0" 2245 react-dom: "npm:^18.2.0"
2244 react-resize-detector: "npm:^10.0.1" 2246 react-resize-detector: "npm:^10.0.1"
2247 svg2pdf.js: "npm:^2.2.3"
2245 typescript: "npm:5.3.3" 2248 typescript: "npm:5.3.3"
2246 vite: "npm:^5.1.4" 2249 vite: "npm:^5.1.4"
2247 vite-plugin-pwa: "npm:^0.19.0" 2250 vite-plugin-pwa: "npm:^0.19.0"
@@ -2935,6 +2938,15 @@ __metadata:
2935 languageName: node 2938 languageName: node
2936 linkType: hard 2939 linkType: hard
2937 2940
2941"@types/jspdf@npm:^2.0.0":
2942 version: 2.0.0
2943 resolution: "@types/jspdf@npm:2.0.0"
2944 dependencies:
2945 jspdf: "npm:*"
2946 checksum: 10c0/69bed9c099c9a0d369c2734fa97862cb67c4d1151ce45b7f9f3c6dfedf7b1bb612cf0561aa5768b7adf5cec207adf2fa82ffbcb7907c716b33b1f5a8f75fe5fc
2947 languageName: node
2948 linkType: hard
2949
2938"@types/lodash-es@npm:^4.17.12": 2950"@types/lodash-es@npm:^4.17.12":
2939 version: 4.17.12 2951 version: 4.17.12
2940 resolution: "@types/lodash-es@npm:4.17.12" 2952 resolution: "@types/lodash-es@npm:4.17.12"
@@ -2997,6 +3009,13 @@ __metadata:
2997 languageName: node 3009 languageName: node
2998 linkType: hard 3010 linkType: hard
2999 3011
3012"@types/raf@npm:^3.4.0":
3013 version: 3.4.3
3014 resolution: "@types/raf@npm:3.4.3"
3015 checksum: 10c0/dea835f0daa399c51db9137f5337dc08a2b4a5f61f645658966ecabaebbbd0fd59551f384a1141e14e22a1cc5a591da7d4d88c60a525ad1399108b6dd2641d75
3016 languageName: node
3017 linkType: hard
3018
3000"@types/react-dom@npm:^18.2.19": 3019"@types/react-dom@npm:^18.2.19":
3001 version: 18.2.19 3020 version: 18.2.19
3002 resolution: "@types/react-dom@npm:18.2.19" 3021 resolution: "@types/react-dom@npm:18.2.19"
@@ -3548,6 +3567,15 @@ __metadata:
3548 languageName: node 3567 languageName: node
3549 linkType: hard 3568 linkType: hard
3550 3569
3570"atob@npm:^2.1.2":
3571 version: 2.1.2
3572 resolution: "atob@npm:2.1.2"
3573 bin:
3574 atob: bin/atob.js
3575 checksum: 10c0/ada635b519dc0c576bb0b3ca63a73b50eefacf390abb3f062558342a8d68f2db91d0c8db54ce81b0d89de3b0f000de71f3ae7d761fd7d8cc624278fe443d6c7e
3576 languageName: node
3577 linkType: hard
3578
3551"available-typed-arrays@npm:^1.0.5": 3579"available-typed-arrays@npm:^1.0.5":
3552 version: 1.0.5 3580 version: 1.0.5
3553 resolution: "available-typed-arrays@npm:1.0.5" 3581 resolution: "available-typed-arrays@npm:1.0.5"
@@ -3634,6 +3662,13 @@ __metadata:
3634 languageName: node 3662 languageName: node
3635 linkType: hard 3663 linkType: hard
3636 3664
3665"base64-arraybuffer@npm:^1.0.2":
3666 version: 1.0.2
3667 resolution: "base64-arraybuffer@npm:1.0.2"
3668 checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f
3669 languageName: node
3670 linkType: hard
3671
3637"binary-extensions@npm:^2.0.0": 3672"binary-extensions@npm:^2.0.0":
3638 version: 2.2.0 3673 version: 2.2.0
3639 resolution: "binary-extensions@npm:2.2.0" 3674 resolution: "binary-extensions@npm:2.2.0"
@@ -3683,6 +3718,15 @@ __metadata:
3683 languageName: node 3718 languageName: node
3684 linkType: hard 3719 linkType: hard
3685 3720
3721"btoa@npm:^1.2.1":
3722 version: 1.2.1
3723 resolution: "btoa@npm:1.2.1"
3724 bin:
3725 btoa: bin/btoa.js
3726 checksum: 10c0/557b9682e40a68ae057af1b377e28884e6ff756ba0f499fe0f8c7b725a5bfb5c0d891604ac09944dbe330c9d43fb3976fef734f9372608d0d8e78a30eda292ae
3727 languageName: node
3728 linkType: hard
3729
3686"buffer-from@npm:^1.0.0": 3730"buffer-from@npm:^1.0.0":
3687 version: 1.1.2 3731 version: 1.1.2
3688 resolution: "buffer-from@npm:1.1.2" 3732 resolution: "buffer-from@npm:1.1.2"
@@ -3752,6 +3796,22 @@ __metadata:
3752 languageName: node 3796 languageName: node
3753 linkType: hard 3797 linkType: hard
3754 3798
3799"canvg@npm:^3.0.6":
3800 version: 3.0.10
3801 resolution: "canvg@npm:3.0.10"
3802 dependencies:
3803 "@babel/runtime": "npm:^7.12.5"
3804 "@types/raf": "npm:^3.4.0"
3805 core-js: "npm:^3.8.3"
3806 raf: "npm:^3.4.1"
3807 regenerator-runtime: "npm:^0.13.7"
3808 rgbcolor: "npm:^1.0.1"
3809 stackblur-canvas: "npm:^2.0.0"
3810 svg-pathdata: "npm:^6.0.3"
3811 checksum: 10c0/b6bcd95d60c923c6a4e2be49e1fc1d395790577913a5a68439a2bb5a784ee75533ed7720bef69f2d9d0404203b4d61e89fdf1346f829e5da71e54cc57614153f
3812 languageName: node
3813 linkType: hard
3814
3755"chalk@npm:^2.4.2": 3815"chalk@npm:^2.4.2":
3756 version: 2.4.2 3816 version: 2.4.2
3757 resolution: "chalk@npm:2.4.2" 3817 resolution: "chalk@npm:2.4.2"
@@ -3953,6 +4013,13 @@ __metadata:
3953 languageName: node 4013 languageName: node
3954 linkType: hard 4014 linkType: hard
3955 4015
4016"core-js@npm:^3.6.0, core-js@npm:^3.8.3":
4017 version: 3.36.0
4018 resolution: "core-js@npm:3.36.0"
4019 checksum: 10c0/62dcb41ba79ead581e4c5b2740ae18bfe6ee230e853893736d16edb01b580574d8645ff6c5513d1c75d59620f8451aee45c119d3c4f5ebc66cff5f003a816864
4020 languageName: node
4021 linkType: hard
4022
3956"cosmiconfig@npm:^7.0.0": 4023"cosmiconfig@npm:^7.0.0":
3957 version: 7.1.0 4024 version: 7.1.0
3958 resolution: "cosmiconfig@npm:7.1.0" 4025 resolution: "cosmiconfig@npm:7.1.0"
@@ -4003,6 +4070,24 @@ __metadata:
4003 languageName: node 4070 languageName: node
4004 linkType: hard 4071 linkType: hard
4005 4072
4073"css-line-break@npm:^2.1.0":
4074 version: 2.1.0
4075 resolution: "css-line-break@npm:2.1.0"
4076 dependencies:
4077 utrie: "npm:^1.0.2"
4078 checksum: 10c0/b2222d99d5daf7861ecddc050244fdce296fad74b000dcff6bdfb1eb16dc2ef0b9ffe2c1c965e3239bd05ebe9eadb6d5438a91592fa8648d27a338e827cf9048
4079 languageName: node
4080 linkType: hard
4081
4082"cssesc@npm:^3.0.0":
4083 version: 3.0.0
4084 resolution: "cssesc@npm:3.0.0"
4085 bin:
4086 cssesc: bin/cssesc
4087 checksum: 10c0/6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7
4088 languageName: node
4089 linkType: hard
4090
4006"csstype@npm:^3.0.2, csstype@npm:^3.1.3": 4091"csstype@npm:^3.0.2, csstype@npm:^3.1.3":
4007 version: 3.1.3 4092 version: 3.1.3
4008 resolution: "csstype@npm:3.1.3" 4093 resolution: "csstype@npm:3.1.3"
@@ -4501,6 +4586,13 @@ __metadata:
4501 languageName: node 4586 languageName: node
4502 linkType: hard 4587 linkType: hard
4503 4588
4589"dompurify@npm:^2.2.0":
4590 version: 2.4.7
4591 resolution: "dompurify@npm:2.4.7"
4592 checksum: 10c0/c04fa6a7c7276d0bc80e6330f697cfecd96ec1d3964b17de916f26cb0b5b2c9a98ba343d84e759f2b8339e577e619ef3749f3d128ef18ddb8230b09bd2ff3f29
4593 languageName: node
4594 linkType: hard
4595
4504"dot-case@npm:^3.0.4": 4596"dot-case@npm:^3.0.4":
4505 version: 3.0.4 4597 version: 3.0.4
4506 resolution: "dot-case@npm:3.0.4" 4598 resolution: "dot-case@npm:3.0.4"
@@ -5215,6 +5307,13 @@ __metadata:
5215 languageName: node 5307 languageName: node
5216 linkType: hard 5308 linkType: hard
5217 5309
5310"fflate@npm:^0.4.8":
5311 version: 0.4.8
5312 resolution: "fflate@npm:0.4.8"
5313 checksum: 10c0/29d1eddaaa5deab61b1c6b0d21282adacadbc4d2c01e94d8b1ee784398151673b9c563e53f97a801bc410a1ae55e8de5378114a743430e643e7a0644ba8e5a42
5314 languageName: node
5315 linkType: hard
5316
5218"file-entry-cache@npm:^6.0.1": 5317"file-entry-cache@npm:^6.0.1":
5219 version: 6.0.1 5318 version: 6.0.1
5220 resolution: "file-entry-cache@npm:6.0.1" 5319 resolution: "file-entry-cache@npm:6.0.1"
@@ -5276,6 +5375,13 @@ __metadata:
5276 languageName: node 5375 languageName: node
5277 linkType: hard 5376 linkType: hard
5278 5377
5378"font-family-papandreou@npm:^0.2.0-patch1":
5379 version: 0.2.0-patch2
5380 resolution: "font-family-papandreou@npm:0.2.0-patch2"
5381 checksum: 10c0/f2028070906b71a648b3aba63e22b4077fa3adf5949749f27fc7b44a41ffd1f017221e0d9bd550b8a1d8fcef7cfc3fc22cf6e3d99c5cc8a00ebfee29a3f26841
5382 languageName: node
5383 linkType: hard
5384
5279"for-each@npm:^0.3.3": 5385"for-each@npm:^0.3.3":
5280 version: 0.3.3 5386 version: 0.3.3
5281 resolution: "for-each@npm:0.3.3" 5387 resolution: "for-each@npm:0.3.3"
@@ -5667,6 +5773,16 @@ __metadata:
5667 languageName: node 5773 languageName: node
5668 linkType: hard 5774 linkType: hard
5669 5775
5776"html2canvas@npm:^1.0.0-rc.5":
5777 version: 1.4.1
5778 resolution: "html2canvas@npm:1.4.1"
5779 dependencies:
5780 css-line-break: "npm:^2.1.0"
5781 text-segmentation: "npm:^1.0.3"
5782 checksum: 10c0/6de86f75762b00948edf2ea559f16da0a1ec3facc4a8a7d3f35fcec59bb0c5970463478988ae3d9082152e0173690d46ebf4082e7ac803dd4817bae1d355c0db
5783 languageName: node
5784 linkType: hard
5785
5670"http-cache-semantics@npm:^4.1.1": 5786"http-cache-semantics@npm:^4.1.1":
5671 version: 4.1.1 5787 version: 4.1.1
5672 resolution: "http-cache-semantics@npm:4.1.1" 5788 resolution: "http-cache-semantics@npm:4.1.1"
@@ -6271,6 +6387,31 @@ __metadata:
6271 languageName: node 6387 languageName: node
6272 linkType: hard 6388 linkType: hard
6273 6389
6390"jspdf@npm:*, jspdf@npm:^2.5.1":
6391 version: 2.5.1
6392 resolution: "jspdf@npm:2.5.1"
6393 dependencies:
6394 "@babel/runtime": "npm:^7.14.0"
6395 atob: "npm:^2.1.2"
6396 btoa: "npm:^1.2.1"
6397 canvg: "npm:^3.0.6"
6398 core-js: "npm:^3.6.0"
6399 dompurify: "npm:^2.2.0"
6400 fflate: "npm:^0.4.8"
6401 html2canvas: "npm:^1.0.0-rc.5"
6402 dependenciesMeta:
6403 canvg:
6404 optional: true
6405 core-js:
6406 optional: true
6407 dompurify:
6408 optional: true
6409 html2canvas:
6410 optional: true
6411 checksum: 10c0/dad15d4f53ead1d2e9d5f6fd9b6e72c7233ba5cbc30d98461eb0ef609aa908b28fd5eaaf2b763b55df945c7ecca2323097d9331f09fee1d6c23c06785520ab5f
6412 languageName: node
6413 linkType: hard
6414
6274"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5": 6415"jsx-ast-utils@npm:^2.4.1 || ^3.0.0, jsx-ast-utils@npm:^3.3.5":
6275 version: 3.3.5 6416 version: 3.3.5
6276 resolution: "jsx-ast-utils@npm:3.3.5" 6417 resolution: "jsx-ast-utils@npm:3.3.5"
@@ -7015,6 +7156,13 @@ __metadata:
7015 languageName: node 7156 languageName: node
7016 linkType: hard 7157 linkType: hard
7017 7158
7159"performance-now@npm:^2.1.0":
7160 version: 2.1.0
7161 resolution: "performance-now@npm:2.1.0"
7162 checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9
7163 languageName: node
7164 linkType: hard
7165
7018"picocolors@npm:^1.0.0": 7166"picocolors@npm:^1.0.0":
7019 version: 1.0.0 7167 version: 1.0.0
7020 resolution: "picocolors@npm:1.0.0" 7168 resolution: "picocolors@npm:1.0.0"
@@ -7130,6 +7278,15 @@ __metadata:
7130 languageName: node 7278 languageName: node
7131 linkType: hard 7279 linkType: hard
7132 7280
7281"raf@npm:^3.4.1":
7282 version: 3.4.1
7283 resolution: "raf@npm:3.4.1"
7284 dependencies:
7285 performance-now: "npm:^2.1.0"
7286 checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e
7287 languageName: node
7288 linkType: hard
7289
7133"randombytes@npm:^2.1.0": 7290"randombytes@npm:^2.1.0":
7134 version: 2.1.0 7291 version: 2.1.0
7135 resolution: "randombytes@npm:2.1.0" 7292 resolution: "randombytes@npm:2.1.0"
@@ -7264,6 +7421,13 @@ __metadata:
7264 languageName: node 7421 languageName: node
7265 linkType: hard 7422 linkType: hard
7266 7423
7424"regenerator-runtime@npm:^0.13.7":
7425 version: 0.13.11
7426 resolution: "regenerator-runtime@npm:0.13.11"
7427 checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24
7428 languageName: node
7429 linkType: hard
7430
7267"regenerator-runtime@npm:^0.14.0": 7431"regenerator-runtime@npm:^0.14.0":
7268 version: 0.14.0 7432 version: 0.14.0
7269 resolution: "regenerator-runtime@npm:0.14.0" 7433 resolution: "regenerator-runtime@npm:0.14.0"
@@ -7424,6 +7588,13 @@ __metadata:
7424 languageName: node 7588 languageName: node
7425 linkType: hard 7589 linkType: hard
7426 7590
7591"rgbcolor@npm:^1.0.1":
7592 version: 1.0.1
7593 resolution: "rgbcolor@npm:1.0.1"
7594 checksum: 10c0/13af06c523351bac2854b85a22d1dfafd9310efd898e9bd96c8706f9aa09a3ddc8392ab00ae03d12950782164a97677f21834ffd84ffebf76ae106add319f956
7595 languageName: node
7596 linkType: hard
7597
7427"rimraf@npm:^3.0.2": 7598"rimraf@npm:^3.0.2":
7428 version: 3.0.2 7599 version: 3.0.2
7429 resolution: "rimraf@npm:3.0.2" 7600 resolution: "rimraf@npm:3.0.2"
@@ -7764,6 +7935,15 @@ __metadata:
7764 languageName: node 7935 languageName: node
7765 linkType: hard 7936 linkType: hard
7766 7937
7938"specificity@npm:^0.4.1":
7939 version: 0.4.1
7940 resolution: "specificity@npm:0.4.1"
7941 bin:
7942 specificity: ./bin/specificity
7943 checksum: 10c0/5da85a05052b55e344cb0f5bce5d07cbabbbe8945da176a481589db5a13e9fbcfa879ceb075cf564b94e680fae0a2ab14ea55cc87496b86a6d5122545946d7c2
7944 languageName: node
7945 linkType: hard
7946
7767"ssri@npm:^10.0.0": 7947"ssri@npm:^10.0.0":
7768 version: 10.0.5 7948 version: 10.0.5
7769 resolution: "ssri@npm:10.0.5" 7949 resolution: "ssri@npm:10.0.5"
@@ -7773,6 +7953,13 @@ __metadata:
7773 languageName: node 7953 languageName: node
7774 linkType: hard 7954 linkType: hard
7775 7955
7956"stackblur-canvas@npm:^2.0.0":
7957 version: 2.7.0
7958 resolution: "stackblur-canvas@npm:2.7.0"
7959 checksum: 10c0/df290d0629056d5bb43d37548d0b24cb8593c79d742650e68489abf61013db578c9980724c2508bb738d107204f2e2494ab94c3cf69d6b725caa9c63b8c7e272
7960 languageName: node
7961 linkType: hard
7962
7776"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": 7963"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3":
7777 version: 4.2.3 7964 version: 4.2.3
7778 resolution: "string-width@npm:4.2.3" 7965 resolution: "string-width@npm:4.2.3"
@@ -7942,6 +8129,34 @@ __metadata:
7942 languageName: node 8129 languageName: node
7943 linkType: hard 8130 linkType: hard
7944 8131
8132"svg-pathdata@npm:^6.0.3":
8133 version: 6.0.3
8134 resolution: "svg-pathdata@npm:6.0.3"
8135 checksum: 10c0/1ba4ad2fa81e86df37d6e78d3be9e664bbedf97773b725a863a85db384285be32dc37d9c0d61e477d89594ee95b967d2c53d6bee2d76420aab670ab4124a38b9
8136 languageName: node
8137 linkType: hard
8138
8139"svg2pdf.js@npm:^2.2.3":
8140 version: 2.2.3
8141 resolution: "svg2pdf.js@npm:2.2.3"
8142 dependencies:
8143 cssesc: "npm:^3.0.0"
8144 font-family-papandreou: "npm:^0.2.0-patch1"
8145 specificity: "npm:^0.4.1"
8146 svgpath: "npm:^2.3.0"
8147 peerDependencies:
8148 jspdf: ^2.0.0
8149 checksum: 10c0/9845b837152b255c94ac6ba12e6376e771b18cb7bbfab6b11b56a19f437bca2e077386e58b5aa3f57ada021797ab784c6379d5be8d1df911eb705402f45d0cb2
8150 languageName: node
8151 linkType: hard
8152
8153"svgpath@npm:^2.3.0":
8154 version: 2.6.0
8155 resolution: "svgpath@npm:2.6.0"
8156 checksum: 10c0/aed042d7cc0e2f3b55d618af2bee58faf8820d53f5d45490194e9bd1cd9ae7d57be641fd3692429a133be2ffb75ff43aad2e6b1c297359ffbc808016a106f199
8157 languageName: node
8158 linkType: hard
8159
7945"synckit@npm:^0.8.6": 8160"synckit@npm:^0.8.6":
7946 version: 0.8.8 8161 version: 0.8.8
7947 resolution: "synckit@npm:0.8.8" 8162 resolution: "synckit@npm:0.8.8"
@@ -8006,6 +8221,15 @@ __metadata:
8006 languageName: node 8221 languageName: node
8007 linkType: hard 8222 linkType: hard
8008 8223
8224"text-segmentation@npm:^1.0.3":
8225 version: 1.0.3
8226 resolution: "text-segmentation@npm:1.0.3"
8227 dependencies:
8228 utrie: "npm:^1.0.2"
8229 checksum: 10c0/8b9ae8524e3a332371060d0ca62f10ad49a13e954719ea689a6c3a8b8c15c8a56365ede2bb91c322fb0d44b6533785f0da603e066b7554d052999967fb72d600
8230 languageName: node
8231 linkType: hard
8232
8009"text-table@npm:^0.2.0": 8233"text-table@npm:^0.2.0":
8010 version: 0.2.0 8234 version: 0.2.0
8011 resolution: "text-table@npm:0.2.0" 8235 resolution: "text-table@npm:0.2.0"
@@ -8306,6 +8530,15 @@ __metadata:
8306 languageName: node 8530 languageName: node
8307 linkType: hard 8531 linkType: hard
8308 8532
8533"utrie@npm:^1.0.2":
8534 version: 1.0.2
8535 resolution: "utrie@npm:1.0.2"
8536 dependencies:
8537 base64-arraybuffer: "npm:^1.0.2"
8538 checksum: 10c0/eaffe645bd81a39e4bc3abb23df5895e9961dbdd49748ef3b173529e8b06ce9dd1163e9705d5309a1c61ee41ffcb825e2043bc0fd1659845ffbdf4b1515dfdb4
8539 languageName: node
8540 linkType: hard
8541
8309"vite-plugin-pwa@npm:^0.19.0": 8542"vite-plugin-pwa@npm:^0.19.0":
8310 version: 0.19.0 8543 version: 0.19.0
8311 resolution: "vite-plugin-pwa@npm:0.19.0" 8544 resolution: "vite-plugin-pwa@npm:0.19.0"