Skip to content

Commit 31124c1

Browse files
authored
Merge pull request #259 from pathsim/feature/svg-compat
Feature/svg compat
2 parents 50cc620 + 9127b32 commit 31124c1

21 files changed

+591
-51
lines changed

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/FlowCanvas.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,7 @@
10261026
edgesFocusable
10271027
edgesSelectable
10281028
zoomOnDoubleClick={false}
1029+
elevateEdgesOnSelect={false}
10291030
proOptions={{ hideAttribution: true }}
10301031
>
10311032
<FlowUpdater pendingUpdates={pendingNodeUpdates} onUpdatesProcessed={clearPendingUpdates} />

src/lib/components/contextMenuBuilders.ts

Lines changed: 13 additions & 2 deletions
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';
@@ -409,12 +409,23 @@ function buildCanvasMenu(
409409
icon: 'image',
410410
action: async () => {
411411
try {
412-
const svg = await exportToSVG();
412+
const svg = await exportToSVG({ compat: 'inkscape' });
413413
downloadSvg(svg, 'pathview-graph.svg');
414414
} catch (e) {
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/dom2svg/index.d.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ interface FontConfig {
66
}
77
/** Font mapping: family name → URL string, single config, or array of configs for multiple weights/styles */
88
type FontMapping = Record<string, string | FontConfig | FontConfig[]>;
9+
/** SVG compatibility configuration flags */
10+
interface SvgCompatConfig {
11+
useClipPathForOverflow: boolean;
12+
stripFilters: boolean;
13+
stripBoxShadows: boolean;
14+
stripMaskImage: boolean;
15+
stripTextShadows: boolean;
16+
avoidStyleAttributes: boolean;
17+
stripXmlSpace: boolean;
18+
stripGroupOpacity: boolean;
19+
inlineClipPathTransforms: boolean;
20+
flattenNestedSvg: boolean;
21+
}
22+
/** SVG compatibility preset */
23+
type SvgCompat = 'full' | 'inkscape' | SvgCompatConfig;
924
/** Options for domToSvg() */
1025
interface DomToSvgOptions {
1126
/** Map of font-family → URL or FontConfig for text-to-path conversion */
@@ -26,6 +41,8 @@ interface DomToSvgOptions {
2641
* with nested CSS transforms (e.g. SvelteFlow, React Flow) where
2742
* the default behaviour would double-apply transforms. */
2843
flattenTransforms?: boolean;
44+
/** SVG compatibility preset or custom config (default: 'full') */
45+
compat?: SvgCompat;
2946
}
3047
/** Internal render context passed through the tree */
3148
interface RenderContext {
@@ -37,6 +54,8 @@ interface RenderContext {
3754
idGenerator: IdGenerator;
3855
/** Options from the caller */
3956
options: DomToSvgOptions;
57+
/** Resolved SVG compatibility config */
58+
compat: SvgCompatConfig;
4059
/** Font cache (available when textToPath is enabled) */
4160
fontCache?: FontCache;
4261
/** Current inherited opacity */
@@ -72,4 +91,4 @@ interface DomToSvgResult {
7291
*/
7392
declare function domToSvg(element: Element, options?: DomToSvgOptions): Promise<DomToSvgResult>;
7493

75-
export { type DomToSvgOptions, type DomToSvgResult, type FontConfig, type FontMapping, domToSvg };
94+
export { type DomToSvgOptions, type DomToSvgResult, type FontConfig, type FontMapping, type SvgCompat, type SvgCompatConfig, domToSvg };

0 commit comments

Comments
 (0)