Skip to content

Commit fb6db69

Browse files
committed
Bundle Inter and JetBrains Mono fonts, add PDF export, remove Google Fonts CDN
1 parent e2c241d commit fb6db69

13 files changed

Lines changed: 329 additions & 12 deletions

package-lock.json

Lines changed: 268 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@
4444
"@codemirror/theme-one-dark": "^6.0.0",
4545
"@xyflow/svelte": "^1.5.0",
4646
"codemirror": "^6.0.0",
47+
"jspdf": "^4.1.0",
4748
"katex": "^0.16.0",
4849
"opentype.js": "^1.3.4",
4950
"pathfinding": "^0.4.18",
5051
"plotly.js-dist-min": "^2.35.0",
51-
"pyodide": "^0.26.0"
52+
"pyodide": "^0.26.0",
53+
"svg2pdf.js": "^2.7.0"
5254
}
5355
}

src/app.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
/* Bundled fonts — no external CDN dependency */
2+
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/Inter-Regular.woff2') format('woff2'); }
3+
@font-face { font-family: 'Inter'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/Inter-Medium.woff2') format('woff2'); }
4+
@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap; src: url('/fonts/Inter-SemiBold.woff2') format('woff2'); }
5+
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2'); }
6+
@font-face { font-family: 'JetBrains Mono'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/JetBrainsMono-Medium.woff2') format('woff2'); }
7+
18
/* Modern Design System */
29
:root {
310
/* ===== SURFACES (2-tier elevation) ===== */

src/lib/components/contextMenuBuilders.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { generateBlockCodeHeader, generateEventCodeHeader } from '$lib/utils/cod
2222
import { exportComponent } from '$lib/schema/componentOps';
2323
import { openImportDialog } from '$lib/schema/fileOps';
2424
import { hasExportableData, exportRecordingData } from '$lib/utils/csvExport';
25-
import { exportToSVG } from '$lib/export/svg';
25+
import { exportToSVG, exportToPDF } from '$lib/export/svg';
2626
import { downloadSvg } from '$lib/utils/download';
2727
import { plotSettingsStore, DEFAULT_BLOCK_SETTINGS } from '$lib/stores/plotSettings';
2828
import { portLabelsStore } from '$lib/stores/portLabels';
@@ -415,6 +415,17 @@ function buildCanvasMenu(
415415
console.error('SVG export failed:', e);
416416
}
417417
}
418+
},
419+
{
420+
label: 'Export PDF',
421+
icon: 'image',
422+
action: async () => {
423+
try {
424+
await exportToPDF();
425+
} catch (e) {
426+
console.error('PDF export failed:', e);
427+
}
428+
}
418429
}
419430
];
420431

src/lib/export/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
* Export module
33
*/
44

5-
export { exportToSVG } from './svg';
5+
export { exportToSVG, exportToPDF } from './svg';
66
export type { ExportOptions } from './svg';

src/lib/export/svg/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
* excluding UI elements (background, controls, minimap).
66
*/
77

8-
export { exportToSVG } from './renderer';
8+
export { exportToSVG, exportToPDF } from './renderer';
99
export type { ExportOptions } from './types';
1010
export { DEFAULT_OPTIONS } from './types';

src/lib/export/svg/renderer.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* is self-contained and renders correctly in any viewer without fonts.
1111
*/
1212

13+
import { jsPDF } from 'jspdf';
14+
import 'svg2pdf.js';
1315
import { domToSvg } from '../dom2svg/index.js';
1416
import type { FontMapping } from '../dom2svg/index.js';
1517
import type { ExportOptions } from './types';
@@ -29,8 +31,19 @@ const EXCLUDE_SELECTORS = [
2931
/** KaTeX font CDN base URL */
3032
const KATEX_CDN = 'https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/fonts';
3133

32-
/** KaTeX font mapping for dom2svg textToPath conversion */
33-
const KATEX_FONTS: FontMapping = {
34+
/** Font mapping for dom2svg textToPath conversion */
35+
const EXPORT_FONTS: FontMapping = {
36+
// UI fonts (bundled)
37+
Inter: [
38+
{ url: '/fonts/Inter-Regular.woff2', weight: 400, style: 'normal' },
39+
{ url: '/fonts/Inter-Medium.woff2', weight: 500, style: 'normal' },
40+
{ url: '/fonts/Inter-SemiBold.woff2', weight: 600, style: 'normal' }
41+
],
42+
'JetBrains Mono': [
43+
{ url: '/fonts/JetBrainsMono-Regular.woff2', weight: 400, style: 'normal' },
44+
{ url: '/fonts/JetBrainsMono-Medium.woff2', weight: 500, style: 'normal' }
45+
],
46+
// KaTeX math fonts (CDN)
3447
KaTeX_Main: [
3548
{ url: `${KATEX_CDN}/KaTeX_Main-Regular.woff2`, weight: 'normal', style: 'normal' },
3649
{ url: `${KATEX_CDN}/KaTeX_Main-Bold.woff2`, weight: 'bold', style: 'normal' },
@@ -173,7 +186,7 @@ export async function exportToSVG(options: ExportOptions = {}): Promise<string>
173186
exclude: EXCLUDE_SELECTORS,
174187
flattenTransforms: true,
175188
textToPath: true,
176-
fonts: KATEX_FONTS,
189+
fonts: EXPORT_FONTS,
177190
compat: opts.compat
178191
});
179192

@@ -194,3 +207,24 @@ export async function exportToSVG(options: ExportOptions = {}): Promise<string>
194207
element.style.overflow = origOverflow;
195208
}
196209
}
210+
211+
export async function exportToPDF(options: ExportOptions = {}): Promise<void> {
212+
const svgString = await exportToSVG(options);
213+
214+
// Parse SVG string into a DOM element
215+
const parser = new DOMParser();
216+
const doc = parser.parseFromString(svgString, 'image/svg+xml');
217+
const svgEl = doc.documentElement;
218+
219+
const width = parseFloat(svgEl.getAttribute('width') || '800');
220+
const height = parseFloat(svgEl.getAttribute('height') || '600');
221+
222+
const pdf = new jsPDF({
223+
orientation: width > height ? 'landscape' : 'portrait',
224+
unit: 'pt',
225+
format: [width, height]
226+
});
227+
228+
await (pdf as any).svg(svgEl, { x: 0, y: 0, width, height });
229+
pdf.save('pathview-graph.pdf');
230+
}

src/routes/+page.svelte

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -951,9 +951,6 @@
951951
<svelte:head>
952952
<title>PathView</title>
953953
<link rel="icon" type="image/png" href="{base}/favicon.png">
954-
<link rel="preconnect" href="https://fonts.googleapis.com">
955-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
956-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
957954
</svelte:head>
958955

959956
<div class="app">

static/fonts/Inter-Medium.woff2

112 KB
Binary file not shown.

static/fonts/Inter-Regular.woff2

109 KB
Binary file not shown.

0 commit comments

Comments
 (0)