@bendyline/squisq-react 2.11.15 → 2.11.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/{chunk-6PUMLLJH.js → chunk-2FZUFQO3.js} +2 -2
- package/dist/{chunk-P3FJ3YKS.js → chunk-2VSD55NT.js} +123 -415
- package/dist/chunk-5FJZDX7H.js +298 -0
- package/dist/{chunk-VUMNXXK4.js → chunk-622IA3KB.js} +12 -9
- package/dist/chunk-63G5OJWY.js +123 -0
- package/dist/{chunk-KDTKUE53.js → chunk-L4LIIOU2.js} +1 -1
- package/dist/{chunk-7T26IKW2.js → chunk-PSAO2CTF.js} +1 -1
- package/dist/{chunk-WHVSQ42V.js → chunk-PZCWGBRQ.js} +2 -2
- package/dist/{chunk-WLUZTUNZ.js → chunk-QXZXQPL3.js} +6 -50
- package/dist/{chunk-U2FIZWAL.js → chunk-SVVPYDV3.js} +160 -16
- package/dist/diagram-pictures/index.d.ts +61 -0
- package/dist/diagram-pictures/index.js +213 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -2
- package/dist/index.js +24 -22
- package/dist/json-view/index.js +4 -4
- package/dist/layers/index.js +3 -3
- package/dist/markdown/index.js +3 -3
- package/dist/page/index.js +7 -6
- package/dist/player/index.js +10 -9
- package/dist/squisq-player.full.global.js +379 -378
- package/dist/squisq-player.global.js +71 -70
- package/dist/standalone-player-full.d.ts +1 -0
- package/dist/standalone-player-full.js +4 -0
- package/dist/standalone-source.js +1 -1
- package/package.json +10 -2
- package/dist/chunk-LR3AIGDD.js +0 -58
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
renderMermaidSvg,
|
|
3
|
+
useMediaProvider,
|
|
2
4
|
useResourcePolicy
|
|
3
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-63G5OJWY.js";
|
|
4
6
|
|
|
5
7
|
// src/hooks/useMediaSchedule.ts
|
|
6
8
|
import { useMemo } from "react";
|
|
@@ -475,7 +477,7 @@ function useAudioSync(audioRef, track, basePath = "", enabled = true, mode = "me
|
|
|
475
477
|
}
|
|
476
478
|
|
|
477
479
|
// src/hooks/useDocPlayback.ts
|
|
478
|
-
import { useMemo as
|
|
480
|
+
import { useMemo as useMemo4, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
479
481
|
import { resolveMotionForDoc } from "@bendyline/squisq/schemas";
|
|
480
482
|
import {
|
|
481
483
|
DEFAULT_THEME,
|
|
@@ -485,11 +487,145 @@ import {
|
|
|
485
487
|
} from "@bendyline/squisq/schemas";
|
|
486
488
|
import {
|
|
487
489
|
expandDocBlocks,
|
|
488
|
-
flattenRenderableBlocks,
|
|
490
|
+
flattenRenderableBlocks as flattenRenderableBlocks2,
|
|
489
491
|
isTemplateBlock,
|
|
490
492
|
resolvePersistentLayers,
|
|
491
493
|
VIEWPORT_PRESETS
|
|
492
494
|
} from "@bendyline/squisq/doc";
|
|
495
|
+
|
|
496
|
+
// src/hooks/useFeatureMediaAspectRatios.ts
|
|
497
|
+
import { useEffect as useEffect2, useMemo as useMemo3, useState as useState2 } from "react";
|
|
498
|
+
import {
|
|
499
|
+
extractImages,
|
|
500
|
+
extractEmbeddedVideos,
|
|
501
|
+
flattenRenderableBlocks
|
|
502
|
+
} from "@bendyline/squisq/doc";
|
|
503
|
+
import { isResourceUrlAllowed as isResourceUrlAllowed2 } from "@bendyline/squisq/markdown";
|
|
504
|
+
var EMPTY_RATIOS = {};
|
|
505
|
+
var ratioCache = /* @__PURE__ */ new Map();
|
|
506
|
+
var diagramSequence = 0;
|
|
507
|
+
function probeAspectRatio(kind, url, report) {
|
|
508
|
+
let element;
|
|
509
|
+
let size;
|
|
510
|
+
if (kind === "image") {
|
|
511
|
+
const image = new Image();
|
|
512
|
+
element = image;
|
|
513
|
+
size = () => [image.naturalWidth, image.naturalHeight];
|
|
514
|
+
} else {
|
|
515
|
+
const video = document.createElement("video");
|
|
516
|
+
video.preload = "metadata";
|
|
517
|
+
video.muted = true;
|
|
518
|
+
element = video;
|
|
519
|
+
size = () => [video.videoWidth, video.videoHeight];
|
|
520
|
+
}
|
|
521
|
+
const event = kind === "image" ? "load" : "loadedmetadata";
|
|
522
|
+
let released = false;
|
|
523
|
+
const cleanup = () => {
|
|
524
|
+
if (released) return;
|
|
525
|
+
released = true;
|
|
526
|
+
clearTimeout(timeout);
|
|
527
|
+
element.removeEventListener(event, loaded);
|
|
528
|
+
element.removeEventListener("error", cleanup);
|
|
529
|
+
element.removeAttribute("src");
|
|
530
|
+
if (element instanceof HTMLVideoElement) element.load();
|
|
531
|
+
};
|
|
532
|
+
const loaded = () => {
|
|
533
|
+
const [width, height] = size();
|
|
534
|
+
if (width > 0 && height > 0) report(width / height);
|
|
535
|
+
cleanup();
|
|
536
|
+
};
|
|
537
|
+
const timeout = setTimeout(cleanup, 8e3);
|
|
538
|
+
element.addEventListener(event, loaded);
|
|
539
|
+
element.addEventListener("error", cleanup);
|
|
540
|
+
element.src = url;
|
|
541
|
+
return cleanup;
|
|
542
|
+
}
|
|
543
|
+
function useFeatureMediaAspectRatios(blocks, basePath, theme) {
|
|
544
|
+
const provider = useMediaProvider();
|
|
545
|
+
const policy = useResourcePolicy();
|
|
546
|
+
const sourceKey = useMemo3(() => {
|
|
547
|
+
const sources = /* @__PURE__ */ new Map();
|
|
548
|
+
for (const block of flattenRenderableBlocks(blocks ?? [])) {
|
|
549
|
+
if (block.summaryLayout !== "feature") continue;
|
|
550
|
+
for (const image of extractImages(block.contents))
|
|
551
|
+
sources.set(image.src, { kind: "image", source: image.src });
|
|
552
|
+
for (const video of extractEmbeddedVideos(block.contents))
|
|
553
|
+
sources.set(video.src, { kind: "video", source: video.src });
|
|
554
|
+
for (const node of block.contents ?? []) {
|
|
555
|
+
if (node.type === "code" && node.lang?.trim().toLowerCase() === "mermaid")
|
|
556
|
+
sources.set(node.value, { kind: "mermaid", source: node.value });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return JSON.stringify([...sources.values()]);
|
|
560
|
+
}, [blocks]);
|
|
561
|
+
const scope = useMemo3(
|
|
562
|
+
() => ({ sourceKey, provider, policy, basePath, theme }),
|
|
563
|
+
[sourceKey, provider, policy, basePath, theme]
|
|
564
|
+
);
|
|
565
|
+
const [result, setResult] = useState2();
|
|
566
|
+
useEffect2(() => {
|
|
567
|
+
if (typeof document === "undefined") return;
|
|
568
|
+
let current = true;
|
|
569
|
+
const cleanups = [];
|
|
570
|
+
const ratios = {};
|
|
571
|
+
const report = (source, ratio) => {
|
|
572
|
+
if (!current || !Number.isFinite(ratio) || ratio <= 0) return;
|
|
573
|
+
ratios[source] = ratio;
|
|
574
|
+
setResult({ scope, ratios: { ...ratios } });
|
|
575
|
+
};
|
|
576
|
+
const sources = JSON.parse(sourceKey);
|
|
577
|
+
for (const { kind, source } of sources) {
|
|
578
|
+
if (kind === "mermaid") {
|
|
579
|
+
void renderMermaidSvg(
|
|
580
|
+
`squisq-feature-measure-${++diagramSequence}`,
|
|
581
|
+
source,
|
|
582
|
+
void 0,
|
|
583
|
+
theme
|
|
584
|
+
).then(({ svg }) => {
|
|
585
|
+
if (!current) return;
|
|
586
|
+
const viewBox = new DOMParser().parseFromString(svg, "image/svg+xml").documentElement.getAttribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
587
|
+
if (viewBox?.length === 4 && viewBox[2] > 0 && viewBox[3] > 0)
|
|
588
|
+
report(source, viewBox[2] / viewBox[3]);
|
|
589
|
+
}).catch(() => {
|
|
590
|
+
});
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const resolve = async () => {
|
|
594
|
+
const absolute = /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(source);
|
|
595
|
+
const fallback = absolute ? source : `${basePath.replace(/\/$/, "")}/${source}`;
|
|
596
|
+
let url = fallback;
|
|
597
|
+
if (!absolute && provider) {
|
|
598
|
+
try {
|
|
599
|
+
url = await provider.resolveUrl(source);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (!current || !isResourceUrlAllowed2(url, policy)) return;
|
|
604
|
+
const cacheKey = `${kind}:${url}`;
|
|
605
|
+
const cached = ratioCache.get(cacheKey);
|
|
606
|
+
if (cached !== void 0) {
|
|
607
|
+
report(source, cached);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
cleanups.push(
|
|
611
|
+
probeAspectRatio(kind, url, (ratio) => {
|
|
612
|
+
if (!current) return;
|
|
613
|
+
ratioCache.set(cacheKey, ratio);
|
|
614
|
+
report(source, ratio);
|
|
615
|
+
})
|
|
616
|
+
);
|
|
617
|
+
};
|
|
618
|
+
void resolve();
|
|
619
|
+
}
|
|
620
|
+
return () => {
|
|
621
|
+
current = false;
|
|
622
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
623
|
+
};
|
|
624
|
+
}, [sourceKey, provider, policy, basePath, theme, scope]);
|
|
625
|
+
return result?.scope === scope ? result.ratios : EMPTY_RATIOS;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/hooks/useDocPlayback.ts
|
|
493
629
|
function useDocPlayback(script, currentTime, options = {}) {
|
|
494
630
|
const {
|
|
495
631
|
viewport = VIEWPORT_PRESETS.landscape,
|
|
@@ -497,14 +633,20 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
497
633
|
onSeek,
|
|
498
634
|
useAudioSegmentTiming = true,
|
|
499
635
|
deterministicTransitions = false,
|
|
500
|
-
motion: motionOverride
|
|
636
|
+
motion: motionOverride,
|
|
637
|
+
basePath = "."
|
|
501
638
|
} = options;
|
|
502
|
-
const
|
|
639
|
+
const mediaAspectRatios = useFeatureMediaAspectRatios(
|
|
640
|
+
script?.blocks,
|
|
641
|
+
basePath,
|
|
642
|
+
theme ?? DEFAULT_THEME
|
|
643
|
+
);
|
|
644
|
+
const blocks = useMemo4(() => {
|
|
503
645
|
if (!script?.blocks) {
|
|
504
646
|
return [];
|
|
505
647
|
}
|
|
506
648
|
const hasChildren = script.blocks.some((b) => b.children && b.children.length > 0);
|
|
507
|
-
const flatBlocks = hasChildren ?
|
|
649
|
+
const flatBlocks = hasChildren ? flattenRenderableBlocks2(script.blocks) : script.blocks;
|
|
508
650
|
const hasTemplates = flatBlocks.some(isTemplateBlock);
|
|
509
651
|
const resolvedTheme = theme ?? DEFAULT_THEME;
|
|
510
652
|
const persistentLayers = resolvePersistentLayers(
|
|
@@ -519,6 +661,7 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
519
661
|
const expanded = expandDocBlocks(flatBlocks, {
|
|
520
662
|
audioSegments,
|
|
521
663
|
viewport,
|
|
664
|
+
mediaAspectRatios,
|
|
522
665
|
persistentLayers,
|
|
523
666
|
theme,
|
|
524
667
|
motion: resolveMotionForDoc(
|
|
@@ -546,24 +689,25 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
546
689
|
script?.motion,
|
|
547
690
|
script?.frontmatter,
|
|
548
691
|
motionOverride,
|
|
692
|
+
mediaAspectRatios,
|
|
549
693
|
viewport,
|
|
550
694
|
theme,
|
|
551
695
|
useAudioSegmentTiming
|
|
552
696
|
]);
|
|
553
|
-
const currentBlock =
|
|
554
|
-
const currentBlockIndex =
|
|
697
|
+
const currentBlock = useMemo4(() => getBlockAtTime(blocks, currentTime), [blocks, currentTime]);
|
|
698
|
+
const currentBlockIndex = useMemo4(
|
|
555
699
|
() => currentBlock ? blocks.indexOf(currentBlock) : -1,
|
|
556
700
|
[blocks, currentBlock]
|
|
557
701
|
);
|
|
558
|
-
const blockTime =
|
|
702
|
+
const blockTime = useMemo4(() => {
|
|
559
703
|
if (!currentBlock) return 0;
|
|
560
704
|
return Math.max(0, currentTime - currentBlock.startTime);
|
|
561
705
|
}, [currentBlock, currentTime]);
|
|
562
|
-
const blockProgress =
|
|
706
|
+
const blockProgress = useMemo4(() => {
|
|
563
707
|
if (!currentBlock || currentBlock.duration === 0) return 0;
|
|
564
708
|
return Math.min(1, blockTime / currentBlock.duration);
|
|
565
709
|
}, [currentBlock, blockTime]);
|
|
566
|
-
const docProgress =
|
|
710
|
+
const docProgress = useMemo4(() => {
|
|
567
711
|
if (!script || script.duration === 0) return 0;
|
|
568
712
|
return Math.min(1, currentTime / script.duration);
|
|
569
713
|
}, [script, currentTime]);
|
|
@@ -630,7 +774,7 @@ function useDocPlayback(script, currentTime, options = {}) {
|
|
|
630
774
|
}
|
|
631
775
|
|
|
632
776
|
// src/hooks/useViewportOrientation.ts
|
|
633
|
-
import { useState as
|
|
777
|
+
import { useState as useState3, useEffect as useEffect3, useMemo as useMemo5 } from "react";
|
|
634
778
|
import {
|
|
635
779
|
VIEWPORT_PRESETS as VIEWPORT_PRESETS2
|
|
636
780
|
} from "@bendyline/squisq/doc";
|
|
@@ -656,11 +800,11 @@ function getViewportForOrientation(orientation) {
|
|
|
656
800
|
}
|
|
657
801
|
}
|
|
658
802
|
function useViewportOrientation() {
|
|
659
|
-
const [windowSize, setWindowSize] =
|
|
803
|
+
const [windowSize, setWindowSize] = useState3(() => ({
|
|
660
804
|
width: typeof window !== "undefined" ? window.innerWidth : 1920,
|
|
661
805
|
height: typeof window !== "undefined" ? window.innerHeight : 1080
|
|
662
806
|
}));
|
|
663
|
-
|
|
807
|
+
useEffect3(() => {
|
|
664
808
|
if (typeof window === "undefined") return;
|
|
665
809
|
const handleResize = () => {
|
|
666
810
|
setWindowSize({
|
|
@@ -679,11 +823,11 @@ function useViewportOrientation() {
|
|
|
679
823
|
clearTimeout(timeoutId);
|
|
680
824
|
};
|
|
681
825
|
}, []);
|
|
682
|
-
const orientation =
|
|
826
|
+
const orientation = useMemo5(
|
|
683
827
|
() => getOrientationFromWindow(windowSize.width, windowSize.height),
|
|
684
828
|
[windowSize.width, windowSize.height]
|
|
685
829
|
);
|
|
686
|
-
const viewport =
|
|
830
|
+
const viewport = useMemo5(() => getViewportForOrientation(orientation), [orientation]);
|
|
687
831
|
return {
|
|
688
832
|
viewport,
|
|
689
833
|
orientation,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Theme } from '@bendyline/squisq/schemas';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PNG pictures of diagrams, for document exports (DOCX, EPUB, PDF, PPTX).
|
|
5
|
+
*
|
|
6
|
+
* Browser-only. A diagram is drawn as SVG, the SVG is loaded as an image,
|
|
7
|
+
* and the image is painted onto a canvas and read back as PNG. That last
|
|
8
|
+
* step fails in some browsers once the SVG holds `<foreignObject>`, so every
|
|
9
|
+
* path here produces plain SVG: Mermaid with SVG-text labels, Squisq
|
|
10
|
+
* diagrams through `BlockRenderer` without their slide chrome, and file
|
|
11
|
+
* trees as monospace text. A picture also cannot load the page's web fonts,
|
|
12
|
+
* so text is set in a system font stack.
|
|
13
|
+
*
|
|
14
|
+
* `createDiagramPictureRenderer` matches the `DiagramRasterizer` that
|
|
15
|
+
* `@bendyline/squisq-formats/diagrams` takes.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** A PNG and the size it should display at, in CSS pixels. */
|
|
19
|
+
interface DiagramPicturePng {
|
|
20
|
+
data: Uint8Array;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
}
|
|
24
|
+
interface DiagramPictureOptions {
|
|
25
|
+
/** Colors for the diagram. Default: Squisq's default (light) theme. */
|
|
26
|
+
theme?: Theme;
|
|
27
|
+
/** Pixel density of the PNG relative to its display size. Default: 2. */
|
|
28
|
+
scale?: number;
|
|
29
|
+
/** Widest display size in CSS pixels. Default: 680. */
|
|
30
|
+
maxWidth?: number;
|
|
31
|
+
/** Background painted behind the diagram. Default: white. */
|
|
32
|
+
background?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The diagram description `@bendyline/squisq-formats/diagrams` passes. */
|
|
35
|
+
interface DiagramToPicture {
|
|
36
|
+
kind: 'mermaid' | 'diagram' | 'timeline' | 'tree' | 'container';
|
|
37
|
+
markdown: string;
|
|
38
|
+
source: string;
|
|
39
|
+
}
|
|
40
|
+
/** System fonts every platform has, since a picture can't load web fonts. */
|
|
41
|
+
declare const EXPORT_FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
42
|
+
/** Paint `svg` (viewBox already set) at `width` × `height` CSS pixels and read back PNG bytes. */
|
|
43
|
+
declare function svgToPng(svg: SVGSVGElement, width: number, height: number, options?: DiagramPictureOptions): Promise<DiagramPicturePng>;
|
|
44
|
+
/** A Mermaid diagram as a PNG at its natural size (capped at `maxWidth`). */
|
|
45
|
+
declare function renderMermaidPicture(source: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng>;
|
|
46
|
+
/** A file-tree fence as monospace text lines, exactly as written. */
|
|
47
|
+
declare function renderTreePicture(source: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng | null>;
|
|
48
|
+
/**
|
|
49
|
+
* A Squisq-rendered diagram — an ASCII diagram or timeline fence, or a
|
|
50
|
+
* drawing, layout, or diagram heading block — as a PNG cropped to its
|
|
51
|
+
* content, or null when it has nothing to draw.
|
|
52
|
+
*/
|
|
53
|
+
declare function renderSquisqDiagramPicture(markdown: string, options?: DiagramPictureOptions): Promise<DiagramPicturePng | null>;
|
|
54
|
+
/**
|
|
55
|
+
* A renderer for `rasterizeDiagrams` from `@bendyline/squisq-formats/diagrams`:
|
|
56
|
+
* Mermaid, ASCII diagrams and timelines, file trees, and drawing, layout,
|
|
57
|
+
* and diagram blocks.
|
|
58
|
+
*/
|
|
59
|
+
declare function createDiagramPictureRenderer(options?: DiagramPictureOptions): (diagram: DiagramToPicture) => Promise<DiagramPicturePng | null>;
|
|
60
|
+
|
|
61
|
+
export { type DiagramPictureOptions, type DiagramPicturePng, type DiagramToPicture, EXPORT_FONT_STACK, createDiagramPictureRenderer, renderMermaidPicture, renderSquisqDiagramPicture, renderTreePicture, svgToPng };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BlockRenderer
|
|
3
|
+
} from "../chunk-5FJZDX7H.js";
|
|
4
|
+
import "../chunk-PZCWGBRQ.js";
|
|
5
|
+
import "../chunk-QXZXQPL3.js";
|
|
6
|
+
import {
|
|
7
|
+
renderMermaidSvgForExport
|
|
8
|
+
} from "../chunk-63G5OJWY.js";
|
|
9
|
+
|
|
10
|
+
// src/export/diagramPictures.ts
|
|
11
|
+
import { createElement } from "react";
|
|
12
|
+
import { createRoot } from "react-dom/client";
|
|
13
|
+
import { VIEWPORT_PRESETS } from "@bendyline/squisq/schemas";
|
|
14
|
+
import {
|
|
15
|
+
DEFAULT_THEME,
|
|
16
|
+
flattenRenderableBlocks,
|
|
17
|
+
markdownToDoc,
|
|
18
|
+
materializeBlockLayers
|
|
19
|
+
} from "@bendyline/squisq/doc";
|
|
20
|
+
import { parseMarkdown } from "@bendyline/squisq/markdown";
|
|
21
|
+
var EXPORT_FONT_STACK = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif";
|
|
22
|
+
var EXPORT_MONO_STACK = "ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace";
|
|
23
|
+
var CANVAS_UNITS_PER_CSS_PIXEL = 2.5;
|
|
24
|
+
var PADDING = 16;
|
|
25
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
26
|
+
var pictureCount = 0;
|
|
27
|
+
function settings(options) {
|
|
28
|
+
return {
|
|
29
|
+
theme: options.theme ?? DEFAULT_THEME,
|
|
30
|
+
scale: options.scale ?? 2,
|
|
31
|
+
maxWidth: options.maxWidth ?? 680,
|
|
32
|
+
background: options.background ?? "#ffffff"
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function svgToPng(svg, width, height, options = {}) {
|
|
36
|
+
const { scale, background } = settings(options);
|
|
37
|
+
const pixelWidth = Math.max(1, Math.round(width * scale));
|
|
38
|
+
const pixelHeight = Math.max(1, Math.round(height * scale));
|
|
39
|
+
const clone = svg.cloneNode(true);
|
|
40
|
+
clone.setAttribute("xmlns", SVG_NS);
|
|
41
|
+
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
42
|
+
clone.setAttribute("width", String(pixelWidth));
|
|
43
|
+
clone.setAttribute("height", String(pixelHeight));
|
|
44
|
+
clone.removeAttribute("style");
|
|
45
|
+
const xml = new XMLSerializer().serializeToString(clone);
|
|
46
|
+
const image = new Image();
|
|
47
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xml)}`;
|
|
48
|
+
await image.decode();
|
|
49
|
+
const canvas = document.createElement("canvas");
|
|
50
|
+
canvas.width = pixelWidth;
|
|
51
|
+
canvas.height = pixelHeight;
|
|
52
|
+
const context = canvas.getContext("2d");
|
|
53
|
+
if (!context) throw new Error("No 2D canvas is available to draw the diagram.");
|
|
54
|
+
context.fillStyle = background;
|
|
55
|
+
context.fillRect(0, 0, pixelWidth, pixelHeight);
|
|
56
|
+
context.drawImage(image, 0, 0, pixelWidth, pixelHeight);
|
|
57
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
58
|
+
if (!blob) throw new Error("The diagram could not be encoded as PNG.");
|
|
59
|
+
return {
|
|
60
|
+
data: new Uint8Array(await blob.arrayBuffer()),
|
|
61
|
+
width: Math.round(width),
|
|
62
|
+
height: Math.round(height)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function parseViewBox(svg) {
|
|
66
|
+
const values = (svg.getAttribute("viewBox") ?? "").trim().split(/[\s,]+/u).map(Number);
|
|
67
|
+
if (values.length !== 4 || values.some((value) => !Number.isFinite(value))) return null;
|
|
68
|
+
const [x = 0, y = 0, width = 0, height = 0] = values;
|
|
69
|
+
return width > 0 && height > 0 ? { x, y, width, height } : null;
|
|
70
|
+
}
|
|
71
|
+
async function renderMermaidPicture(source, options = {}) {
|
|
72
|
+
const { theme, maxWidth } = settings(options);
|
|
73
|
+
const markup = await renderMermaidSvgForExport(
|
|
74
|
+
`squisq-export-mermaid-${String(++pictureCount)}`,
|
|
75
|
+
source,
|
|
76
|
+
theme,
|
|
77
|
+
EXPORT_FONT_STACK
|
|
78
|
+
);
|
|
79
|
+
const template = document.createElement("template");
|
|
80
|
+
template.innerHTML = markup;
|
|
81
|
+
const svg = template.content.querySelector("svg");
|
|
82
|
+
if (!svg) throw new Error("Mermaid produced no SVG.");
|
|
83
|
+
const box = parseViewBox(svg);
|
|
84
|
+
if (!box) throw new Error("The Mermaid SVG has no size.");
|
|
85
|
+
const style = document.createElementNS(SVG_NS, "style");
|
|
86
|
+
style.textContent = `.edgeLabel text, .edgeLabel tspan { fill: ${theme.colors.text} !important; }`;
|
|
87
|
+
svg.append(style);
|
|
88
|
+
const width = Math.min(maxWidth, box.width);
|
|
89
|
+
return svgToPng(svg, width, box.height * width / box.width, options);
|
|
90
|
+
}
|
|
91
|
+
async function renderTreePicture(source, options = {}) {
|
|
92
|
+
const { theme, maxWidth } = settings(options);
|
|
93
|
+
const lines = source.replace(/\s+$/u, "").split("\n");
|
|
94
|
+
if (lines.length === 0) return null;
|
|
95
|
+
const fontSize = 14;
|
|
96
|
+
const lineHeight = 20;
|
|
97
|
+
const longest = Math.max(...lines.map((line) => [...line].length));
|
|
98
|
+
const width = Math.min(maxWidth, Math.ceil(longest * fontSize * 0.62) + 2 * PADDING);
|
|
99
|
+
const height = lines.length * lineHeight + 2 * PADDING;
|
|
100
|
+
const svg = document.createElementNS(SVG_NS, "svg");
|
|
101
|
+
svg.setAttribute("viewBox", `0 0 ${String(width)} ${String(height)}`);
|
|
102
|
+
const text = document.createElementNS(SVG_NS, "text");
|
|
103
|
+
text.setAttribute("font-family", EXPORT_MONO_STACK);
|
|
104
|
+
text.setAttribute("font-size", String(fontSize));
|
|
105
|
+
text.setAttribute("fill", theme.colors.text);
|
|
106
|
+
text.setAttribute("xml:space", "preserve");
|
|
107
|
+
lines.forEach((line, index) => {
|
|
108
|
+
const span = document.createElementNS(SVG_NS, "tspan");
|
|
109
|
+
span.setAttribute("x", String(PADDING));
|
|
110
|
+
span.setAttribute("y", String(PADDING + (index + 0.75) * lineHeight));
|
|
111
|
+
span.textContent = line;
|
|
112
|
+
text.append(span);
|
|
113
|
+
});
|
|
114
|
+
svg.append(text);
|
|
115
|
+
return svgToPng(svg, width, height, options);
|
|
116
|
+
}
|
|
117
|
+
function isChrome(layer) {
|
|
118
|
+
const id = layer.id;
|
|
119
|
+
if (id === "bg" || id === "title" || id.endsWith("-title") || id.endsWith("-description")) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (id.endsWith("-empty")) return true;
|
|
123
|
+
return layer.type === "shape" && typeof layer.content.fill === "string" && layer.content.fill.includes("gradient(");
|
|
124
|
+
}
|
|
125
|
+
function withExportFont(layer) {
|
|
126
|
+
if (layer.type !== "text") return layer;
|
|
127
|
+
return {
|
|
128
|
+
...layer,
|
|
129
|
+
content: { ...layer.content, style: { ...layer.content.style, fontFamily: EXPORT_FONT_STACK } }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function settle() {
|
|
133
|
+
await document.fonts?.ready;
|
|
134
|
+
for (let frame = 0; frame < 2; frame++) {
|
|
135
|
+
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function renderSquisqDiagramPicture(markdown, options = {}) {
|
|
139
|
+
const { theme, maxWidth } = settings(options);
|
|
140
|
+
const doc = markdownToDoc(parseMarkdown(markdown), { generateCoverBlock: false });
|
|
141
|
+
const block = flattenRenderableBlocks(doc.blocks)[0];
|
|
142
|
+
if (!block) return null;
|
|
143
|
+
const viewport = VIEWPORT_PRESETS.landscape;
|
|
144
|
+
const { layers } = materializeBlockLayers(block, { theme, viewport, persistentLayers: false });
|
|
145
|
+
const kept = layers.filter((layer) => !isChrome(layer)).map(withExportFont);
|
|
146
|
+
if (kept.length === 0) return null;
|
|
147
|
+
const host = document.createElement("div");
|
|
148
|
+
host.setAttribute("aria-hidden", "true");
|
|
149
|
+
host.style.cssText = `position:fixed;left:-${String(viewport.width * 2)}px;top:0;width:${String(
|
|
150
|
+
viewport.width
|
|
151
|
+
)}px;height:${String(viewport.height)}px;pointer-events:none;`;
|
|
152
|
+
document.body.append(host);
|
|
153
|
+
const root = createRoot(host);
|
|
154
|
+
try {
|
|
155
|
+
const pictureBlock = {
|
|
156
|
+
id: `squisq-export-${String(++pictureCount)}`,
|
|
157
|
+
startTime: 0,
|
|
158
|
+
duration: 0,
|
|
159
|
+
audioSegment: 0,
|
|
160
|
+
layers: kept
|
|
161
|
+
};
|
|
162
|
+
root.render(
|
|
163
|
+
createElement(BlockRenderer, {
|
|
164
|
+
block: pictureBlock,
|
|
165
|
+
blockTime: 0,
|
|
166
|
+
basePath: "",
|
|
167
|
+
viewport,
|
|
168
|
+
animationsEnabled: false,
|
|
169
|
+
theme
|
|
170
|
+
})
|
|
171
|
+
);
|
|
172
|
+
await settle();
|
|
173
|
+
const svg = host.querySelector("svg.block-svg");
|
|
174
|
+
if (!svg) return null;
|
|
175
|
+
const box = svg.getBBox();
|
|
176
|
+
if (box.width <= 0 || box.height <= 0) return null;
|
|
177
|
+
const x = box.x - PADDING;
|
|
178
|
+
const y = box.y - PADDING;
|
|
179
|
+
const contentWidth = box.width + 2 * PADDING;
|
|
180
|
+
const contentHeight = box.height + 2 * PADDING;
|
|
181
|
+
const cropped = svg.cloneNode(true);
|
|
182
|
+
cropped.setAttribute(
|
|
183
|
+
"viewBox",
|
|
184
|
+
`${String(x)} ${String(y)} ${String(contentWidth)} ${String(contentHeight)}`
|
|
185
|
+
);
|
|
186
|
+
cropped.removeAttribute("clip-path");
|
|
187
|
+
const width = Math.min(maxWidth, contentWidth / CANVAS_UNITS_PER_CSS_PIXEL);
|
|
188
|
+
return await svgToPng(cropped, width, contentHeight * width / contentWidth, options);
|
|
189
|
+
} finally {
|
|
190
|
+
root.unmount();
|
|
191
|
+
host.remove();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function createDiagramPictureRenderer(options = {}) {
|
|
195
|
+
return async (diagram) => {
|
|
196
|
+
switch (diagram.kind) {
|
|
197
|
+
case "mermaid":
|
|
198
|
+
return renderMermaidPicture(diagram.source, options);
|
|
199
|
+
case "tree":
|
|
200
|
+
return renderTreePicture(diagram.source, options);
|
|
201
|
+
default:
|
|
202
|
+
return renderSquisqDiagramPicture(diagram.markdown, options);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
EXPORT_FONT_STACK,
|
|
208
|
+
createDiagramPictureRenderer,
|
|
209
|
+
renderMermaidPicture,
|
|
210
|
+
renderSquisqDiagramPicture,
|
|
211
|
+
renderTreePicture,
|
|
212
|
+
svgToPng
|
|
213
|
+
};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -114,6 +114,8 @@ interface PlaybackActions {
|
|
|
114
114
|
suppressOutgoingForNextBlock: (blockId: string) => void;
|
|
115
115
|
}
|
|
116
116
|
interface UseDocPlaybackOptions {
|
|
117
|
+
/** Fallback media root for intrinsic feature metadata. */
|
|
118
|
+
basePath?: string;
|
|
117
119
|
/** Target viewport used to materialize template blocks. */
|
|
118
120
|
viewport?: ViewportConfig;
|
|
119
121
|
/** Active theme used for materialization and transition defaults. */
|
package/dist/hooks/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useDocPlayback,
|
|
7
7
|
useMediaSchedule,
|
|
8
8
|
useViewportOrientation
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-SVVPYDV3.js";
|
|
10
10
|
import {
|
|
11
11
|
useAutoSurface
|
|
12
12
|
} from "../chunk-TT6ENR6T.js";
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
useMediaProvider,
|
|
17
17
|
useMediaUrl,
|
|
18
18
|
useResourcePolicy
|
|
19
|
-
} from "../chunk-
|
|
19
|
+
} from "../chunk-63G5OJWY.js";
|
|
20
20
|
export {
|
|
21
21
|
MediaContext,
|
|
22
22
|
ResourcePolicyContext,
|
package/dist/index.js
CHANGED
|
@@ -15,31 +15,17 @@ import {
|
|
|
15
15
|
formatTime,
|
|
16
16
|
resolveDocPlayerAppearance,
|
|
17
17
|
useMediaClipDurations
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-622IA3KB.js";
|
|
19
19
|
import {
|
|
20
20
|
PaginatedDocView
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-PSAO2CTF.js";
|
|
22
22
|
import {
|
|
23
|
-
BlockRenderer,
|
|
24
23
|
CanvasSection,
|
|
25
24
|
LinearDocView,
|
|
26
25
|
PageSectionView,
|
|
27
26
|
PageViewContext,
|
|
28
27
|
usePageView
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import {
|
|
31
|
-
ImageLayer,
|
|
32
|
-
MapLayer,
|
|
33
|
-
MermaidLayer,
|
|
34
|
-
PathLayer,
|
|
35
|
-
ShapeLayer,
|
|
36
|
-
TableLayer,
|
|
37
|
-
TextLayer,
|
|
38
|
-
TreeLayer,
|
|
39
|
-
VideoLayer,
|
|
40
|
-
getAnimationStyle,
|
|
41
|
-
getTransitionClass
|
|
42
|
-
} from "./chunk-WHVSQ42V.js";
|
|
28
|
+
} from "./chunk-2VSD55NT.js";
|
|
43
29
|
import {
|
|
44
30
|
useModalDialog
|
|
45
31
|
} from "./chunk-65POMKHR.js";
|
|
@@ -48,11 +34,11 @@ import {
|
|
|
48
34
|
useDocPlayback,
|
|
49
35
|
useMediaSchedule,
|
|
50
36
|
useViewportOrientation
|
|
51
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-SVVPYDV3.js";
|
|
52
38
|
import {
|
|
53
39
|
JsonView,
|
|
54
40
|
createJsonFormFenceRenderer
|
|
55
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-L4LIIOU2.js";
|
|
56
42
|
import {
|
|
57
43
|
useAutoSurface
|
|
58
44
|
} from "./chunk-TT6ENR6T.js";
|
|
@@ -64,17 +50,33 @@ import {
|
|
|
64
50
|
MarkdownRenderer,
|
|
65
51
|
useAnimatedImage,
|
|
66
52
|
useFenceRenderers
|
|
67
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-2FZUFQO3.js";
|
|
54
|
+
import {
|
|
55
|
+
BlockRenderer
|
|
56
|
+
} from "./chunk-5FJZDX7H.js";
|
|
57
|
+
import {
|
|
58
|
+
ImageLayer,
|
|
59
|
+
MapLayer,
|
|
60
|
+
MermaidLayer,
|
|
61
|
+
PathLayer,
|
|
62
|
+
ShapeLayer,
|
|
63
|
+
TableLayer,
|
|
64
|
+
TextLayer,
|
|
65
|
+
TreeLayer,
|
|
66
|
+
VideoLayer,
|
|
67
|
+
getAnimationStyle,
|
|
68
|
+
getTransitionClass
|
|
69
|
+
} from "./chunk-PZCWGBRQ.js";
|
|
68
70
|
import {
|
|
69
71
|
MermaidDiagram
|
|
70
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-QXZXQPL3.js";
|
|
71
73
|
import {
|
|
72
74
|
MediaContext,
|
|
73
75
|
ResourcePolicyContext,
|
|
74
76
|
useMediaProvider,
|
|
75
77
|
useMediaUrl,
|
|
76
78
|
useResourcePolicy
|
|
77
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-63G5OJWY.js";
|
|
78
80
|
export {
|
|
79
81
|
AnimatedImageControls,
|
|
80
82
|
BlockRenderer,
|
package/dist/json-view/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
JsonView,
|
|
3
3
|
createJsonFormFenceRenderer
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-L4LIIOU2.js";
|
|
5
5
|
import "../chunk-TT6ENR6T.js";
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-2FZUFQO3.js";
|
|
7
|
+
import "../chunk-QXZXQPL3.js";
|
|
8
|
+
import "../chunk-63G5OJWY.js";
|
|
9
9
|
export {
|
|
10
10
|
JsonView,
|
|
11
11
|
createJsonFormFenceRenderer
|
package/dist/layers/index.js
CHANGED
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
TextLayer,
|
|
9
9
|
TreeLayer,
|
|
10
10
|
VideoLayer
|
|
11
|
-
} from "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
11
|
+
} from "../chunk-PZCWGBRQ.js";
|
|
12
|
+
import "../chunk-QXZXQPL3.js";
|
|
13
|
+
import "../chunk-63G5OJWY.js";
|
|
14
14
|
export {
|
|
15
15
|
ImageLayer,
|
|
16
16
|
MapLayer,
|