Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 268 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@
"@codemirror/theme-one-dark": "^6.0.0",
"@xyflow/svelte": "^1.5.0",
"codemirror": "^6.0.0",
"jspdf": "^4.1.0",
"katex": "^0.16.0",
"opentype.js": "^1.3.4",
"pathfinding": "^0.4.18",
"plotly.js-dist-min": "^2.35.0",
"pyodide": "^0.26.0"
"pyodide": "^0.26.0",
"svg2pdf.js": "^2.7.0"
}
}
7 changes: 7 additions & 0 deletions src/app.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/* Bundled fonts — no external CDN dependency */
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/Inter-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/Inter-Medium.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 600; font-style: normal; font-display: swap; src: url('/fonts/Inter-SemiBold.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-style: normal; font-display: swap; src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 500; font-style: normal; font-display: swap; src: url('/fonts/JetBrainsMono-Medium.woff2') format('woff2'); }

/* Modern Design System */
:root {
/* ===== SURFACES (2-tier elevation) ===== */
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/FlowCanvas.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@
edgesFocusable
edgesSelectable
zoomOnDoubleClick={false}
elevateEdgesOnSelect={false}
proOptions={{ hideAttribution: true }}
>
<FlowUpdater pendingUpdates={pendingNodeUpdates} onUpdatesProcessed={clearPendingUpdates} />
Expand Down
15 changes: 13 additions & 2 deletions src/lib/components/contextMenuBuilders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { generateBlockCodeHeader, generateEventCodeHeader } from '$lib/utils/cod
import { exportComponent } from '$lib/schema/componentOps';
import { openImportDialog } from '$lib/schema/fileOps';
import { hasExportableData, exportRecordingData } from '$lib/utils/csvExport';
import { exportToSVG } from '$lib/export/svg';
import { exportToSVG, exportToPDF } from '$lib/export/svg';
import { downloadSvg } from '$lib/utils/download';
import { plotSettingsStore, DEFAULT_BLOCK_SETTINGS } from '$lib/stores/plotSettings';
import { portLabelsStore } from '$lib/stores/portLabels';
Expand Down Expand Up @@ -409,12 +409,23 @@ function buildCanvasMenu(
icon: 'image',
action: async () => {
try {
const svg = await exportToSVG();
const svg = await exportToSVG({ compat: 'inkscape' });
downloadSvg(svg, 'pathview-graph.svg');
} catch (e) {
console.error('SVG export failed:', e);
}
}
},
{
label: 'Export PDF',
icon: 'image',
action: async () => {
try {
await exportToPDF();
} catch (e) {
console.error('PDF export failed:', e);
}
}
}
];

Expand Down
21 changes: 20 additions & 1 deletion src/lib/export/dom2svg/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ interface FontConfig {
}
/** Font mapping: family name → URL string, single config, or array of configs for multiple weights/styles */
type FontMapping = Record<string, string | FontConfig | FontConfig[]>;
/** SVG compatibility configuration flags */
interface SvgCompatConfig {
useClipPathForOverflow: boolean;
stripFilters: boolean;
stripBoxShadows: boolean;
stripMaskImage: boolean;
stripTextShadows: boolean;
avoidStyleAttributes: boolean;
stripXmlSpace: boolean;
stripGroupOpacity: boolean;
inlineClipPathTransforms: boolean;
flattenNestedSvg: boolean;
}
/** SVG compatibility preset */
type SvgCompat = 'full' | 'inkscape' | SvgCompatConfig;
/** Options for domToSvg() */
interface DomToSvgOptions {
/** Map of font-family → URL or FontConfig for text-to-path conversion */
Expand All @@ -26,6 +41,8 @@ interface DomToSvgOptions {
* with nested CSS transforms (e.g. SvelteFlow, React Flow) where
* the default behaviour would double-apply transforms. */
flattenTransforms?: boolean;
/** SVG compatibility preset or custom config (default: 'full') */
compat?: SvgCompat;
}
/** Internal render context passed through the tree */
interface RenderContext {
Expand All @@ -37,6 +54,8 @@ interface RenderContext {
idGenerator: IdGenerator;
/** Options from the caller */
options: DomToSvgOptions;
/** Resolved SVG compatibility config */
compat: SvgCompatConfig;
/** Font cache (available when textToPath is enabled) */
fontCache?: FontCache;
/** Current inherited opacity */
Expand Down Expand Up @@ -72,4 +91,4 @@ interface DomToSvgResult {
*/
declare function domToSvg(element: Element, options?: DomToSvgOptions): Promise<DomToSvgResult>;

export { type DomToSvgOptions, type DomToSvgResult, type FontConfig, type FontMapping, domToSvg };
export { type DomToSvgOptions, type DomToSvgResult, type FontConfig, type FontMapping, type SvgCompat, type SvgCompatConfig, domToSvg };
Loading