@bendyline/squisq-formats 2.7.1 → 2.7.2
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/dist/{chunk-BHWIHYUJ.js → chunk-52RXDH24.js} +118 -46
- package/dist/{chunk-3NKXJF4C.js → chunk-ETK2SKVQ.js} +55 -7
- package/dist/{chunk-TX2HE34S.js → chunk-PKFWF4FX.js} +12 -9
- package/dist/chunk-VCZUXQUL.js +90 -0
- package/dist/{chunk-A6N6IN3I.js → chunk-XVBQB2SL.js} +9 -0
- package/dist/{chunk-5ACTORJI.js → chunk-YITBSNPM.js} +82 -15
- package/dist/{chunk-ERT4EVR2.js → chunk-Z3QORA5G.js} +1 -1
- package/dist/diagrams/index.d.ts +15 -4
- package/dist/diagrams/index.js +33 -6
- package/dist/docx/index.d.ts +11 -0
- package/dist/docx/index.js +3 -2
- package/dist/epub/index.d.ts +1 -1
- package/dist/epub/index.js +2 -1
- package/dist/html/index.js +2 -1
- package/dist/index.js +8 -7
- package/dist/outside-in/index.js +2 -2
- package/dist/pptx/index.js +3 -2
- package/dist/registry/index.js +1 -1
- package/dist/svgIslandPoster-RYA6PDX7.js +73 -0
- package/package.json +2 -2
- /package/dist/{chunk-HNGS7A5Z.js → chunk-XCHIYF3A.js} +0 -0
|
@@ -3,11 +3,16 @@ import {
|
|
|
3
3
|
} from "./chunk-YVCUNZB5.js";
|
|
4
4
|
import {
|
|
5
5
|
RelIdAllocator,
|
|
6
|
+
blipXml,
|
|
6
7
|
inlineNodeToRuns,
|
|
7
8
|
inlineNodesToRuns,
|
|
8
9
|
sanitizeOfficeHyperlink,
|
|
9
10
|
toOoxmlHex
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XVBQB2SL.js";
|
|
12
|
+
import {
|
|
13
|
+
svgImageFromBytes,
|
|
14
|
+
svgIslandImage
|
|
15
|
+
} from "./chunk-VCZUXQUL.js";
|
|
11
16
|
import {
|
|
12
17
|
extractPlainText,
|
|
13
18
|
stripHtmlTags
|
|
@@ -74,7 +79,87 @@ import {
|
|
|
74
79
|
resolveThemeForDoc
|
|
75
80
|
} from "@bendyline/squisq/doc";
|
|
76
81
|
import { splitIconMarkers } from "@bendyline/squisq/icon-marker";
|
|
77
|
-
import {
|
|
82
|
+
import {
|
|
83
|
+
isSvgImageSource,
|
|
84
|
+
parseVideoEmbedUrl,
|
|
85
|
+
readFrontmatterThemeId
|
|
86
|
+
} from "@bendyline/squisq/markdown";
|
|
87
|
+
|
|
88
|
+
// src/pptx/pictureLayers.ts
|
|
89
|
+
import { svgSourceFromBytes } from "@bendyline/squisq/markdown";
|
|
90
|
+
function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId, svgRelId) {
|
|
91
|
+
const name = escapeXml(img.alt || "Picture");
|
|
92
|
+
const descr = img.alt ? ` descr="${escapeXml(img.alt)}"` : "";
|
|
93
|
+
const link = hyperlinkRelId ? `<a:hlinkClick r:id="${hyperlinkRelId}"/>` : "";
|
|
94
|
+
return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill>` + blipXml(img.relId, svgRelId) + `<a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
95
|
+
}
|
|
96
|
+
function fitCentered(area, width, height) {
|
|
97
|
+
const scale = Math.min(area.width / width, area.height / height);
|
|
98
|
+
const fitted = { width: Math.round(width * scale), height: Math.round(height * scale) };
|
|
99
|
+
return {
|
|
100
|
+
x: area.x + Math.round((area.width - fitted.width) / 2),
|
|
101
|
+
y: area.y + Math.round((area.height - fitted.height) / 2),
|
|
102
|
+
...fitted
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function arrayBufferOf(data) {
|
|
106
|
+
return data instanceof Uint8Array ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
|
|
107
|
+
}
|
|
108
|
+
function usablePicture(picture) {
|
|
109
|
+
return !!picture && picture.width > 0 && picture.height > 0;
|
|
110
|
+
}
|
|
111
|
+
function mermaidTitle(source) {
|
|
112
|
+
return /^\s*accTitle\s*:\s*(.+?)\s*$/mu.exec(source)?.[1] ?? "Diagram";
|
|
113
|
+
}
|
|
114
|
+
function mermaidPictureShapes(layer, area, ctx) {
|
|
115
|
+
const picture = ctx.diagramImages?.get(layer.content.source.trim());
|
|
116
|
+
if (!usablePicture(picture)) return null;
|
|
117
|
+
const alt = mermaidTitle(layer.content.source);
|
|
118
|
+
const relId = ctx.addImage("diagram.png", arrayBufferOf(picture.data), alt);
|
|
119
|
+
const rect = fitCentered(area, picture.width, picture.height);
|
|
120
|
+
return [
|
|
121
|
+
buildImageShape({ relId, alt }, ctx.allocShapeId(), rect.x, rect.y, rect.width, rect.height)
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
function svgIslandPictureShapes(layer, area, ctx) {
|
|
125
|
+
const image = svgIslandImage(layer.content.source, { color: layer.content.color });
|
|
126
|
+
if (!image) return null;
|
|
127
|
+
return svgPictureShapes(image, layer.content.source, area, ctx, layer.content.alt ?? image.alt);
|
|
128
|
+
}
|
|
129
|
+
function svgImagePictureShapes(layer, area, ctx) {
|
|
130
|
+
const data = ctx.images?.get(layer.content.src);
|
|
131
|
+
const image = data ? svgImageFromBytes(data) : null;
|
|
132
|
+
if (!data || !image) return null;
|
|
133
|
+
const alt = layer.content.alt || image.alt;
|
|
134
|
+
return svgPictureShapes(
|
|
135
|
+
image,
|
|
136
|
+
svgSourceFromBytes(data),
|
|
137
|
+
area,
|
|
138
|
+
ctx,
|
|
139
|
+
alt,
|
|
140
|
+
layer.content.fit === "fill"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
function svgPictureShapes(image, source, area, ctx, alt, stretch = false) {
|
|
144
|
+
const hostPicture = ctx.diagramImages?.get(source.trim());
|
|
145
|
+
const fallback = usablePicture(hostPicture) ? arrayBufferOf(hostPicture.data) : ctx.svgIslandPosters?.svgIslandPosterPng(image.width / image.height)?.data;
|
|
146
|
+
if (!fallback) return null;
|
|
147
|
+
const relId = ctx.addImage("svg-island.png", fallback, alt);
|
|
148
|
+
const svgRelId = ctx.addImage("svg-island.svg", image.data, alt);
|
|
149
|
+
const rect = stretch ? area : fitCentered(area, image.width, image.height);
|
|
150
|
+
return [
|
|
151
|
+
buildImageShape(
|
|
152
|
+
{ relId, alt },
|
|
153
|
+
ctx.allocShapeId(),
|
|
154
|
+
rect.x,
|
|
155
|
+
rect.y,
|
|
156
|
+
rect.width,
|
|
157
|
+
rect.height,
|
|
158
|
+
null,
|
|
159
|
+
svgRelId
|
|
160
|
+
)
|
|
161
|
+
];
|
|
162
|
+
}
|
|
78
163
|
|
|
79
164
|
// src/pptx/styles.ts
|
|
80
165
|
var SLIDE_WIDTH = 12192e3;
|
|
@@ -202,9 +287,12 @@ async function docToPptx(doc, options = {}) {
|
|
|
202
287
|
if (slideBlocks.length === 0) {
|
|
203
288
|
slideBlocks.push({ id: "empty-slide", startTime: 0, duration: 0, audioSegment: 0, layers: [] });
|
|
204
289
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
290
|
+
const hasLayer = (type) => slideBlocks.some((b) => b.layers?.some((l) => l.type === type));
|
|
291
|
+
const videoPosters = hasLayer("videoEmbed") ? await import("./videoPoster-D7DTZC2L.js") : null;
|
|
292
|
+
const hasSvgImage = slideBlocks.some(
|
|
293
|
+
(b) => b.layers?.some((l) => l.type === "image" && isSvgImageSource(l.content.src))
|
|
294
|
+
);
|
|
295
|
+
const svgIslandPosters = hasLayer("svg") || hasSvgImage ? await import("./svgIslandPoster-RYA6PDX7.js") : null;
|
|
208
296
|
const slideXmls = [];
|
|
209
297
|
const slideContexts = [];
|
|
210
298
|
for (let index = 0; index < slideBlocks.length; index++) {
|
|
@@ -218,6 +306,7 @@ async function docToPptx(doc, options = {}) {
|
|
|
218
306
|
);
|
|
219
307
|
ctx.diagramImages = effectiveOptions.diagramImages;
|
|
220
308
|
ctx.videoPosters = videoPosters;
|
|
309
|
+
ctx.svgIslandPosters = svgIslandPosters;
|
|
221
310
|
slideXmls.push(buildLayerSlideXml(slideBlocks[index], ctx));
|
|
222
311
|
slideContexts.push(ctx);
|
|
223
312
|
}
|
|
@@ -321,6 +410,8 @@ var SlideContext = class {
|
|
|
321
410
|
this.relationships = [];
|
|
322
411
|
/** Poster helpers for hosted videos, loaded only when the deck has one. */
|
|
323
412
|
this.videoPosters = null;
|
|
413
|
+
/** Stand-in PNGs for SVG islands, loaded only when the deck has one. */
|
|
414
|
+
this.svgIslandPosters = null;
|
|
324
415
|
this.embeddedImages = [];
|
|
325
416
|
this.nextShapeId = 4;
|
|
326
417
|
/** Font Awesome families referenced by semantic or materialized text runs. */
|
|
@@ -472,7 +563,11 @@ function convertLayerToShapes(layer, ctx, textBottom) {
|
|
|
472
563
|
case "shape":
|
|
473
564
|
return [buildLayerShape(layer, ctx.allocShapeId())];
|
|
474
565
|
case "image": {
|
|
475
|
-
const
|
|
566
|
+
const svg = isSvgImageSource(layer.content.src);
|
|
567
|
+
const area = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
568
|
+
const picture = svg ? svgImagePictureShapes(layer, area, ctx) : null;
|
|
569
|
+
if (picture) return picture;
|
|
570
|
+
const data = svg ? void 0 : ctx.images?.get(layer.content.src);
|
|
476
571
|
if (data) {
|
|
477
572
|
const image = ctx.addImage(layer.content.src, data, layer.content.alt);
|
|
478
573
|
const rect = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
@@ -539,7 +634,7 @@ function convertLayerToShapes(layer, ctx, textBottom) {
|
|
|
539
634
|
}
|
|
540
635
|
case "video": {
|
|
541
636
|
const poster = layer.content.posterSrc;
|
|
542
|
-
const data = poster ? ctx.images?.get(poster) : void 0;
|
|
637
|
+
const data = poster && !isSvgImageSource(poster) ? ctx.images?.get(poster) : void 0;
|
|
543
638
|
if (poster && data) {
|
|
544
639
|
const relId = ctx.addImage(poster, data, layer.content.alt);
|
|
545
640
|
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === relId);
|
|
@@ -598,45 +693,28 @@ function convertLayerToShapes(layer, ctx, textBottom) {
|
|
|
598
693
|
];
|
|
599
694
|
}
|
|
600
695
|
case "mermaid": {
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
picture.data.byteOffset,
|
|
605
|
-
picture.data.byteOffset + picture.data.byteLength
|
|
606
|
-
) : picture.data;
|
|
607
|
-
const relId = ctx.addImage(
|
|
608
|
-
`diagram-${ctx.embeddedImages.length + 1}.png`,
|
|
609
|
-
data,
|
|
610
|
-
mermaidTitle(layer.content.source)
|
|
611
|
-
);
|
|
612
|
-
const embedded = ctx.embeddedImages.find((candidate) => candidate.relId === relId);
|
|
613
|
-
const area = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
614
|
-
const scale = Math.min(area.width / picture.width, area.height / picture.height);
|
|
615
|
-
const width = Math.round(picture.width * scale);
|
|
616
|
-
const height = Math.round(picture.height * scale);
|
|
617
|
-
return [
|
|
618
|
-
buildImageShape(
|
|
619
|
-
embedded,
|
|
620
|
-
ctx.allocShapeId(),
|
|
621
|
-
area.x + Math.round((area.width - width) / 2),
|
|
622
|
-
area.y + Math.round((area.height - height) / 2),
|
|
623
|
-
width,
|
|
624
|
-
height
|
|
625
|
-
)
|
|
626
|
-
];
|
|
627
|
-
}
|
|
628
|
-
return [
|
|
696
|
+
const area = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
697
|
+
const picture = mermaidPictureShapes(layer, area, ctx);
|
|
698
|
+
return picture ?? [
|
|
629
699
|
buildLayerTextShape(
|
|
630
700
|
textLayerForPlaceholder(layer.position, layer.content.source, ctx),
|
|
631
701
|
ctx.allocShapeId()
|
|
632
702
|
)
|
|
633
703
|
];
|
|
634
704
|
}
|
|
705
|
+
case "svg": {
|
|
706
|
+
const area = toEmuRect(resolveLayerRect(layer.position, 1920, 1080));
|
|
707
|
+
const picture = svgIslandPictureShapes(layer, area, ctx);
|
|
708
|
+
const label = layer.content.alt ?? "SVG illustration";
|
|
709
|
+
return picture ?? [
|
|
710
|
+
buildLayerTextShape(
|
|
711
|
+
textLayerForPlaceholder(layer.position, `[${label}]`, ctx),
|
|
712
|
+
ctx.allocShapeId()
|
|
713
|
+
)
|
|
714
|
+
];
|
|
715
|
+
}
|
|
635
716
|
}
|
|
636
717
|
}
|
|
637
|
-
function mermaidTitle(source) {
|
|
638
|
-
return /^\s*accTitle\s*:\s*(.+?)\s*$/mu.exec(source)?.[1] ?? "Diagram";
|
|
639
|
-
}
|
|
640
718
|
function resolvePositionValue(value, dimension) {
|
|
641
719
|
if (typeof value === "number") return value;
|
|
642
720
|
if (typeof value === "string") {
|
|
@@ -1157,12 +1235,6 @@ function buildBodyShapeCustom(bodyContent, top, height, style) {
|
|
|
1157
1235
|
const defRPr = style.hasTheme ? `<a:defRPr><a:solidFill><a:srgbClr val="${style.text}"/></a:solidFill><a:latin typeface="${escapeXml(style.bodyFont)}"/></a:defRPr>` : "";
|
|
1158
1236
|
return `<p:sp><p:nvSpPr><p:cNvPr id="3" name="Content"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph idx="1"/></p:nvPr></p:nvSpPr><p:spPr><a:xfrm><a:off x="${BODY_LEFT}" y="${top}"/><a:ext cx="${BODY_WIDTH}" cy="${height}"/></a:xfrm></p:spPr><p:txBody><a:bodyPr/><a:lstStyle><a:lvl1pPr>${defRPr}</a:lvl1pPr></a:lstStyle>` + bodyContent + `</p:txBody></p:sp>`;
|
|
1159
1237
|
}
|
|
1160
|
-
function buildImageShape(img, shapeId, left, top, maxWidth, maxHeight, hyperlinkRelId) {
|
|
1161
|
-
const name = escapeXml(img.alt || "Picture");
|
|
1162
|
-
const descr = img.alt ? ` descr="${escapeXml(img.alt)}"` : "";
|
|
1163
|
-
const link = hyperlinkRelId ? `<a:hlinkClick r:id="${hyperlinkRelId}"/>` : "";
|
|
1164
|
-
return `<p:pic><p:nvPicPr>` + (link ? `<p:cNvPr id="${shapeId}" name="${name}"${descr}>${link}</p:cNvPr>` : `<p:cNvPr id="${shapeId}" name="${name}"${descr}/>`) + `<p:cNvPicPr><a:picLocks noChangeAspect="1"/></p:cNvPicPr><p:nvPr/></p:nvPicPr><p:blipFill><a:blip r:embed="${img.relId}"/><a:stretch><a:fillRect/></a:stretch></p:blipFill><p:spPr><a:xfrm><a:off x="${left}" y="${top}"/><a:ext cx="${maxWidth}" cy="${maxHeight}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></p:spPr></p:pic>`;
|
|
1165
|
-
}
|
|
1166
1238
|
function convertBodyBlocks(nodes, ctx) {
|
|
1167
1239
|
const parts = [];
|
|
1168
1240
|
for (const node of nodes) {
|
|
@@ -1212,7 +1284,7 @@ function isImageOnlyParagraph(node) {
|
|
|
1212
1284
|
function convertParagraph(node, ctx) {
|
|
1213
1285
|
const soloImage = isImageOnlyParagraph(node);
|
|
1214
1286
|
if (soloImage && ctx.images) {
|
|
1215
|
-
const data = ctx.images.get(soloImage.url);
|
|
1287
|
+
const data = isSvgImageSource(soloImage.url) ? void 0 : ctx.images.get(soloImage.url);
|
|
1216
1288
|
if (data) {
|
|
1217
1289
|
ctx.addImage(soloImage.url, data, soloImage.alt || "");
|
|
1218
1290
|
return "";
|
|
@@ -1364,7 +1436,7 @@ function makeHyperlinkRun(text, rId, format) {
|
|
|
1364
1436
|
}
|
|
1365
1437
|
function convertImage(node, format, ctx) {
|
|
1366
1438
|
if (ctx.images) {
|
|
1367
|
-
const data = ctx.images.get(node.url);
|
|
1439
|
+
const data = isSvgImageSource(node.url) ? void 0 : ctx.images.get(node.url);
|
|
1368
1440
|
if (data) {
|
|
1369
1441
|
ctx.addImage(node.url, data, node.alt || "");
|
|
1370
1442
|
return "";
|
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
extractFilename,
|
|
4
4
|
inferMimeType
|
|
5
5
|
} from "./chunk-YVCUNZB5.js";
|
|
6
|
+
import {
|
|
7
|
+
exportImageBytes
|
|
8
|
+
} from "./chunk-VCZUXQUL.js";
|
|
6
9
|
import {
|
|
7
10
|
FootnoteIndex,
|
|
8
11
|
footnoteIds
|
|
@@ -137,7 +140,8 @@ function buildInlineImageMaps(images) {
|
|
|
137
140
|
continue;
|
|
138
141
|
}
|
|
139
142
|
canonicalKeyFor.set(buffer, path);
|
|
140
|
-
|
|
143
|
+
const bytes = exportImageBytes(path, buffer);
|
|
144
|
+
if (bytes) imageMap[path] = arrayBufferToBase64DataUrl(bytes, inferMimeType(path));
|
|
141
145
|
}
|
|
142
146
|
return { imageMap, imageAliases };
|
|
143
147
|
}
|
|
@@ -286,7 +290,11 @@ function escapeHtml(str) {
|
|
|
286
290
|
// src/html/plainHtml.ts
|
|
287
291
|
import {
|
|
288
292
|
findBlockVideoEmbed,
|
|
293
|
+
isSvgIslandLang,
|
|
294
|
+
parseSvgIsland,
|
|
289
295
|
sanitizeHtmlNodes,
|
|
296
|
+
svgIslandDataUrl,
|
|
297
|
+
svgSourceFromDataUrl,
|
|
290
298
|
sanitizeUrl,
|
|
291
299
|
videoEmbedFromIframe,
|
|
292
300
|
videoEmbedIframeAttributes,
|
|
@@ -308,7 +316,14 @@ function markdownDocToPlainHtml(doc, options = {}) {
|
|
|
308
316
|
const resolveId = themeId ?? readFrontmatterThemeId2(doc.frontmatter);
|
|
309
317
|
const theme = options.theme ?? (resolveId ? resolveThemeForDoc(doc, resolveId, options.themeRegistry) : void 0);
|
|
310
318
|
const footnotes = new FootnoteIndex(doc);
|
|
311
|
-
const ctx = {
|
|
319
|
+
const ctx = {
|
|
320
|
+
images,
|
|
321
|
+
links,
|
|
322
|
+
htmlPolicy,
|
|
323
|
+
footnotes,
|
|
324
|
+
videoEmbeds,
|
|
325
|
+
textColor: theme?.colors.text
|
|
326
|
+
};
|
|
312
327
|
const body = renderTopLevel(doc.children, ctx) + renderFootnotesSection(footnotes, ctx);
|
|
313
328
|
const fontsLink = theme && externalResources === "allow" ? renderFontsLink(theme) : "";
|
|
314
329
|
const usesIcons = docUsesIcons(doc);
|
|
@@ -322,8 +337,10 @@ ${escapeStyleText(iconsCss)}
|
|
|
322
337
|
const themedCss = theme ? renderThemedCss(theme) : DEFAULT_CSS;
|
|
323
338
|
const videoCss = docUsesVideoEmbeds(doc, htmlPolicy) ? `
|
|
324
339
|
${VIDEO_EMBED_CSS}` : "";
|
|
340
|
+
const svgCss = docUsesSvgIslands(doc) ? `
|
|
341
|
+
${SVG_ISLAND_CSS}` : "";
|
|
325
342
|
const safeCss = escapeStyleText(`${themedCss}
|
|
326
|
-
${FEATURE_CSS}${videoCss}`);
|
|
343
|
+
${FEATURE_CSS}${videoCss}${svgCss}`);
|
|
327
344
|
return `<!DOCTYPE html>
|
|
328
345
|
<html lang="en">
|
|
329
346
|
<head>
|
|
@@ -372,6 +389,15 @@ function docUsesVideoEmbeds(doc, htmlPolicy) {
|
|
|
372
389
|
(node) => node.type === "paragraph" && findBlockVideoEmbed(node) !== null || visit(node)
|
|
373
390
|
);
|
|
374
391
|
}
|
|
392
|
+
function docUsesSvgIslands(doc) {
|
|
393
|
+
const visit = (node) => {
|
|
394
|
+
if (!node || typeof node !== "object") return false;
|
|
395
|
+
const n = node;
|
|
396
|
+
if (n.type === "code") return isSvgIslandLang(n.lang);
|
|
397
|
+
return Array.isArray(n.children) && n.children.some(visit);
|
|
398
|
+
};
|
|
399
|
+
return visit(doc);
|
|
400
|
+
}
|
|
375
401
|
function renderTopLevel(children, ctx) {
|
|
376
402
|
const out = [];
|
|
377
403
|
for (let i = 0; i < children.length; i++) {
|
|
@@ -726,6 +752,10 @@ function nodeToHtml(node, ctx) {
|
|
|
726
752
|
case "inlineCode":
|
|
727
753
|
return `<code>${escapeHtml2(node.value ?? "")}</code>`;
|
|
728
754
|
case "code": {
|
|
755
|
+
if (isSvgIslandLang(node.lang)) {
|
|
756
|
+
const island = svgIslandToHtml(node.value ?? "", ctx);
|
|
757
|
+
if (island) return island;
|
|
758
|
+
}
|
|
729
759
|
const lang = node.lang ? ` class="language-${escapeAttr(node.lang)}"` : "";
|
|
730
760
|
return `<pre class="squisq-code-block"><code${lang}>${escapeHtml2(node.value ?? "")}</code></pre>`;
|
|
731
761
|
}
|
|
@@ -748,7 +778,7 @@ function nodeToHtml(node, ctx) {
|
|
|
748
778
|
case "image": {
|
|
749
779
|
const original = node.url ?? "";
|
|
750
780
|
const resolved = ctx?.images?.get(original) ?? original;
|
|
751
|
-
const safeSrc = sanitizeUrl(resolved, "media");
|
|
781
|
+
const safeSrc = sanitizeUrl(resolved, "media") ?? svgDataImageToHtmlSrc(resolved);
|
|
752
782
|
if (!safeSrc) return escapeHtml2(node.alt ?? "");
|
|
753
783
|
return `<img src="${escapeAttr(safeSrc)}" alt="${escapeAttr(node.alt ?? "")}" />`;
|
|
754
784
|
}
|
|
@@ -896,6 +926,20 @@ var VIDEO_EMBED_CSS = ` .squisq-video-embed { display: block; max-width: 720px;
|
|
|
896
926
|
.squisq-video-embed-play::after { content: ''; position: absolute; left: 26px; top: 12px; border-style: solid; border-width: 10px 0 10px 16px; border-color: transparent transparent transparent #fff; }
|
|
897
927
|
.squisq-video-embed-provider { position: absolute; left: 12px; bottom: 10px; color: #fff; font-size: 0.8em; font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); }
|
|
898
928
|
.squisq-video-embed-caption { margin-top: 0.4em; font-size: 0.875em; opacity: 0.8; }`;
|
|
929
|
+
function svgDataImageToHtmlSrc(url) {
|
|
930
|
+
const source = svgSourceFromDataUrl(url);
|
|
931
|
+
const result = source === null ? null : parseSvgIsland(source);
|
|
932
|
+
return result?.ok ? svgIslandDataUrl(result.island) : null;
|
|
933
|
+
}
|
|
934
|
+
function svgIslandToHtml(source, ctx) {
|
|
935
|
+
const result = parseSvgIsland(source);
|
|
936
|
+
if (!result.ok) return null;
|
|
937
|
+
const { island } = result;
|
|
938
|
+
const src = svgIslandDataUrl(island, { color: ctx?.textColor });
|
|
939
|
+
return `<figure class="squisq-svg-island"><img src="${escapeAttr(src)}" alt="${escapeAttr(island.title ?? "Illustration")}" width="${island.width}" height="${island.height}"></figure>`;
|
|
940
|
+
}
|
|
941
|
+
var SVG_ISLAND_CSS = ` .squisq-svg-island { margin: 1.25em 0; }
|
|
942
|
+
.squisq-svg-island img { display: block; max-width: 100%; height: auto; }`;
|
|
899
943
|
function escapeHtml2(s) {
|
|
900
944
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
901
945
|
}
|
|
@@ -1069,7 +1113,8 @@ async function readImagesForDoc(mdDoc, docDir, readBinary) {
|
|
|
1069
1113
|
const cleanAuthored = authored.replace(/[#?].*$/, "");
|
|
1070
1114
|
const resolved = resolveRelative(docDir, cleanAuthored);
|
|
1071
1115
|
if (resolved === null) continue;
|
|
1072
|
-
const
|
|
1116
|
+
const read = await readBinary(resolved);
|
|
1117
|
+
const data = read && exportImageBytes(resolved, read);
|
|
1073
1118
|
if (!data) continue;
|
|
1074
1119
|
out.set(authored, { data, zipPath: resolved });
|
|
1075
1120
|
}
|
|
@@ -1194,7 +1239,8 @@ async function markdownDocsToHtmlBundle(options) {
|
|
|
1194
1239
|
const clean = authored.replace(/[#?].*$/, "");
|
|
1195
1240
|
const resolvedImg = resolveRelative(docDir, clean);
|
|
1196
1241
|
if (resolvedImg === null) continue;
|
|
1197
|
-
const
|
|
1242
|
+
const read = await readBinary(resolvedImg);
|
|
1243
|
+
const data = read && exportImageBytes(resolvedImg, read);
|
|
1198
1244
|
if (!data) continue;
|
|
1199
1245
|
const safe = sanitizeZipPath2(resolvedImg);
|
|
1200
1246
|
if (!safe) continue;
|
|
@@ -2020,7 +2066,9 @@ async function docToHtmlZip(doc, options) {
|
|
|
2020
2066
|
);
|
|
2021
2067
|
continue;
|
|
2022
2068
|
}
|
|
2023
|
-
|
|
2069
|
+
const bytes = exportImageBytes(originalPath, buffer);
|
|
2070
|
+
if (!bytes) continue;
|
|
2071
|
+
zip.file(zipPath, bytes);
|
|
2024
2072
|
imagePathMap[originalPath] = zipPath;
|
|
2025
2073
|
}
|
|
2026
2074
|
}
|
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
extractFilename,
|
|
3
3
|
inferMimeType
|
|
4
4
|
} from "./chunk-YVCUNZB5.js";
|
|
5
|
+
import {
|
|
6
|
+
exportImageBytes,
|
|
7
|
+
svgIslandsAsImages
|
|
8
|
+
} from "./chunk-VCZUXQUL.js";
|
|
5
9
|
import {
|
|
6
10
|
extractPlainText
|
|
7
11
|
} from "./chunk-AVOZAKGP.js";
|
|
@@ -23,8 +27,11 @@ import JSZip from "jszip";
|
|
|
23
27
|
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
24
28
|
import { resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
25
29
|
import { readFrontmatterThemeId, sanitizeUrl } from "@bendyline/squisq/markdown";
|
|
26
|
-
async function markdownDocToEpub(
|
|
30
|
+
async function markdownDocToEpub(sourceDoc, options = {}) {
|
|
27
31
|
options.signal?.throwIfAborted();
|
|
32
|
+
const themeId = options.themeId ?? readFrontmatterThemeId(sourceDoc.frontmatter);
|
|
33
|
+
const islandColor = themeId ? resolveThemeForDoc(sourceDoc, themeId, options.themeRegistry).colors.text : void 0;
|
|
34
|
+
const { doc, images: islandImages } = svgIslandsAsImages(sourceDoc, { color: islandColor });
|
|
28
35
|
const fmTitle = doc.frontmatter?.title;
|
|
29
36
|
const fmAuthor = doc.frontmatter?.author;
|
|
30
37
|
const title = options.title ?? (typeof fmTitle === "string" ? fmTitle : "Untitled");
|
|
@@ -42,11 +49,12 @@ async function markdownDocToEpub(doc, options = {}) {
|
|
|
42
49
|
const imageEntries = collectDocImages(doc.children);
|
|
43
50
|
const resolvedImages = /* @__PURE__ */ new Map();
|
|
44
51
|
const usedImageNames = /* @__PURE__ */ new Set();
|
|
45
|
-
if (options.images) {
|
|
52
|
+
if (options.images || islandImages.size > 0) {
|
|
46
53
|
let imageIndex = 0;
|
|
47
54
|
for (const src of imageEntries) {
|
|
48
55
|
if ((imageIndex++ & 63) === 0) options.signal?.throwIfAborted();
|
|
49
|
-
const
|
|
56
|
+
const attached = options.images?.get(src);
|
|
57
|
+
const data = islandImages.get(src)?.data ?? (attached && exportImageBytes(src, attached));
|
|
50
58
|
if (data) {
|
|
51
59
|
const filename = uniqueFilename(safeArchiveBasename(src, "image"), usedImageNames);
|
|
52
60
|
usedImageNames.add(filename);
|
|
@@ -54,12 +62,7 @@ async function markdownDocToEpub(doc, options = {}) {
|
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
|
-
const css = generateStylesheet(
|
|
58
|
-
options.themeId ?? readFrontmatterThemeId(doc.frontmatter),
|
|
59
|
-
doc,
|
|
60
|
-
options.themeRegistry,
|
|
61
|
-
embeddedIconFonts
|
|
62
|
-
);
|
|
65
|
+
const css = generateStylesheet(themeId, doc, options.themeRegistry, embeddedIconFonts);
|
|
63
66
|
const zip = new JSZip();
|
|
64
67
|
zip.file("mimetype", "application/epub+zip", { compression: "STORE" });
|
|
65
68
|
zip.file("META-INF/container.xml", CONTAINER_XML);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/shared/svgIslands.ts
|
|
2
|
+
import {
|
|
3
|
+
isSvgImageSource,
|
|
4
|
+
isSvgIslandLang,
|
|
5
|
+
parseSvgIsland,
|
|
6
|
+
svgIslandMarkup,
|
|
7
|
+
svgSourceFromBytes
|
|
8
|
+
} from "@bendyline/squisq/markdown";
|
|
9
|
+
var SVG_ISLAND_FALLBACK_ALT = "Illustration";
|
|
10
|
+
function ownBuffer(encoded) {
|
|
11
|
+
const bytes = new Uint8Array(encoded.length);
|
|
12
|
+
bytes.set(encoded);
|
|
13
|
+
return bytes.buffer;
|
|
14
|
+
}
|
|
15
|
+
function svgIslandImage(source, options = {}) {
|
|
16
|
+
const result = parseSvgIsland(source);
|
|
17
|
+
if (!result.ok) return null;
|
|
18
|
+
const { island } = result;
|
|
19
|
+
const data = ownBuffer(
|
|
20
|
+
new TextEncoder().encode(
|
|
21
|
+
svgIslandMarkup(island, { color: options.color, replaceCurrentColor: true })
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
return {
|
|
25
|
+
data,
|
|
26
|
+
contentType: "image/svg+xml",
|
|
27
|
+
width: island.width,
|
|
28
|
+
height: island.height,
|
|
29
|
+
alt: island.title ?? SVG_ISLAND_FALLBACK_ALT
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function docHasSvgIslands(doc) {
|
|
33
|
+
const visit = (node) => {
|
|
34
|
+
if (node.type === "code") return isSvgIslandLang(node.lang);
|
|
35
|
+
const nested = node.children;
|
|
36
|
+
return Array.isArray(nested) && nested.some(visit);
|
|
37
|
+
};
|
|
38
|
+
return visit(doc);
|
|
39
|
+
}
|
|
40
|
+
function svgIslandsAsImages(doc, options = {}) {
|
|
41
|
+
const prefix = options.prefix ?? "squisq-svg-island";
|
|
42
|
+
const images = /* @__PURE__ */ new Map();
|
|
43
|
+
const replace = (nodes) => {
|
|
44
|
+
let changed = false;
|
|
45
|
+
const next = nodes.map((node) => {
|
|
46
|
+
if (node.type === "code" && isSvgIslandLang(node.lang)) {
|
|
47
|
+
const image = svgIslandImage(node.value, options);
|
|
48
|
+
if (!image) return node;
|
|
49
|
+
const url = `${prefix}-${images.size + 1}.svg`;
|
|
50
|
+
images.set(url, image);
|
|
51
|
+
changed = true;
|
|
52
|
+
const picture = { type: "image", url, alt: image.alt, title: null };
|
|
53
|
+
const paragraph = { type: "paragraph", children: [picture] };
|
|
54
|
+
return paragraph;
|
|
55
|
+
}
|
|
56
|
+
const nested = node.children;
|
|
57
|
+
if (Array.isArray(nested)) {
|
|
58
|
+
const replaced = replace(nested);
|
|
59
|
+
if (replaced !== nested) {
|
|
60
|
+
changed = true;
|
|
61
|
+
return { ...node, children: replaced };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return node;
|
|
65
|
+
});
|
|
66
|
+
return changed ? next : nodes;
|
|
67
|
+
};
|
|
68
|
+
if (!docHasSvgIslands(doc)) return { doc, images };
|
|
69
|
+
const children = replace(doc.children);
|
|
70
|
+
return { doc: children === doc.children ? doc : { ...doc, children }, images };
|
|
71
|
+
}
|
|
72
|
+
function isSvgImageEntry(path, contentType) {
|
|
73
|
+
return isSvgImageSource(path) || /^image\/svg\+xml\b/i.test(contentType ?? "");
|
|
74
|
+
}
|
|
75
|
+
function svgImageFromBytes(data) {
|
|
76
|
+
return svgIslandImage(svgSourceFromBytes(data));
|
|
77
|
+
}
|
|
78
|
+
function exportImageBytes(path, data, contentType) {
|
|
79
|
+
if (!isSvgImageEntry(path, contentType)) return data;
|
|
80
|
+
return svgImageFromBytes(data)?.data ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
svgIslandImage,
|
|
85
|
+
docHasSvgIslands,
|
|
86
|
+
svgIslandsAsImages,
|
|
87
|
+
isSvgImageEntry,
|
|
88
|
+
svgImageFromBytes,
|
|
89
|
+
exportImageBytes
|
|
90
|
+
};
|
|
@@ -117,11 +117,20 @@ function inlineNodeToRuns(node, handlers, format) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// src/shared/officeSvgBlip.ts
|
|
121
|
+
var SVG_BLIP_EXT_URI = "{96DAC541-7B7A-43D3-8B79-37D633B846F1}";
|
|
122
|
+
var NS_ASVG = "http://schemas.microsoft.com/office/drawing/2016/SVG/main";
|
|
123
|
+
function blipXml(rasterRelId, svgRelId) {
|
|
124
|
+
if (!svgRelId) return `<a:blip r:embed="${rasterRelId}"/>`;
|
|
125
|
+
return `<a:blip r:embed="${rasterRelId}"><a:extLst><a:ext uri="${SVG_BLIP_EXT_URI}"><asvg:svgBlip xmlns:asvg="${NS_ASVG}" r:embed="${svgRelId}"/></a:ext></a:extLst></a:blip>`;
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
export {
|
|
121
129
|
RelIdAllocator,
|
|
122
130
|
toOoxmlHex,
|
|
123
131
|
normalizeOoxmlHex,
|
|
124
132
|
sanitizeOfficeHyperlink,
|
|
133
|
+
blipXml,
|
|
125
134
|
inlineNodesToRuns,
|
|
126
135
|
inlineNodeToRuns
|
|
127
136
|
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RelIdAllocator,
|
|
3
|
+
blipXml,
|
|
3
4
|
inlineNodeToRuns,
|
|
4
5
|
inlineNodesToRuns,
|
|
5
6
|
normalizeOoxmlHex,
|
|
6
7
|
sanitizeOfficeHyperlink
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-XVBQB2SL.js";
|
|
9
|
+
import {
|
|
10
|
+
docHasSvgIslands,
|
|
11
|
+
isSvgImageEntry,
|
|
12
|
+
svgImageFromBytes,
|
|
13
|
+
svgIslandImage
|
|
14
|
+
} from "./chunk-VCZUXQUL.js";
|
|
8
15
|
import {
|
|
9
16
|
stripHtmlTags
|
|
10
17
|
} from "./chunk-AVOZAKGP.js";
|
|
@@ -70,7 +77,9 @@ import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
|
70
77
|
import { docToMarkdown, resolveThemeForDoc } from "@bendyline/squisq/doc";
|
|
71
78
|
import {
|
|
72
79
|
findBlockVideoEmbed,
|
|
73
|
-
|
|
80
|
+
isSvgIslandLang,
|
|
81
|
+
readFrontmatterThemeId,
|
|
82
|
+
svgSourceFromBytes
|
|
74
83
|
} from "@bendyline/squisq/markdown";
|
|
75
84
|
|
|
76
85
|
// src/docx/videoEmbed.ts
|
|
@@ -133,6 +142,22 @@ function readWordVideo(drawing, resolveHyperlink) {
|
|
|
133
142
|
return { embed, title };
|
|
134
143
|
}
|
|
135
144
|
|
|
145
|
+
// src/docx/svgIsland.ts
|
|
146
|
+
var NS_A2 = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
147
|
+
var NS_PIC2 = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
148
|
+
var EMU_PER_INCH2 = 914400;
|
|
149
|
+
var MAX_WIDTH_EMU = 6 * EMU_PER_INCH2;
|
|
150
|
+
var MAX_HEIGHT_EMU = 8 * EMU_PER_INCH2;
|
|
151
|
+
function buildWordSvgIslandRun(parts) {
|
|
152
|
+
let cx = parts.width / 96 * EMU_PER_INCH2;
|
|
153
|
+
let cy = parts.height / 96 * EMU_PER_INCH2;
|
|
154
|
+
const scale = Math.min(1, MAX_WIDTH_EMU / cx, MAX_HEIGHT_EMU / cy);
|
|
155
|
+
cx = Math.max(1, Math.round(cx * scale));
|
|
156
|
+
cy = Math.max(1, Math.round(cy * scale));
|
|
157
|
+
const alt = escapeXml(parts.alt);
|
|
158
|
+
return `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${parts.docPrId}" name="${alt}" descr="${alt}"/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${NS_A2}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${NS_A2}"><a:graphicData uri="${NS_PIC2}"><pic:pic xmlns:pic="${NS_PIC2}"><pic:nvPicPr><pic:cNvPr id="0" name="${alt}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill>${blipXml(parts.pngRelId, parts.svgRelId)}<a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
159
|
+
}
|
|
160
|
+
|
|
136
161
|
// src/docx/styles.ts
|
|
137
162
|
var HEADING_STYLE_MAP = {
|
|
138
163
|
Heading1: 1,
|
|
@@ -196,6 +221,12 @@ async function markdownDocToDocx(doc, options = {}) {
|
|
|
196
221
|
if ([...stories.body, ...stories.header, ...stories.footer].some(isVideoBlock)) {
|
|
197
222
|
ctx.videoPosters = await import("./videoPoster-D7DTZC2L.js");
|
|
198
223
|
}
|
|
224
|
+
const hasSvgImages = [...options.images ?? []].some(
|
|
225
|
+
([url, image]) => isSvgImageEntry(url, image.contentType)
|
|
226
|
+
);
|
|
227
|
+
if (docHasSvgIslands(doc) || hasSvgImages) {
|
|
228
|
+
ctx.svgIslandPosters = await import("./svgIslandPoster-RYA6PDX7.js");
|
|
229
|
+
}
|
|
199
230
|
ctx.setRelationshipSource("document");
|
|
200
231
|
const bodyXml = convertBlocks(stories.body, ctx);
|
|
201
232
|
ctx.setRelationshipSource("header");
|
|
@@ -254,6 +285,8 @@ var ExportContext = class {
|
|
|
254
285
|
this.iconFamilies = /* @__PURE__ */ new Set();
|
|
255
286
|
/** Poster + embed-code helpers, loaded only when the document has a video. */
|
|
256
287
|
this.videoPosters = null;
|
|
288
|
+
/** Stand-in PNGs for SVG islands, loaded only when the document has one. */
|
|
289
|
+
this.svgIslandPosters = null;
|
|
257
290
|
this.nextDocPrId = 1;
|
|
258
291
|
let themeFont;
|
|
259
292
|
let themeTitleFont;
|
|
@@ -261,6 +294,7 @@ var ExportContext = class {
|
|
|
261
294
|
let themeBodyColor;
|
|
262
295
|
let themeMutedColor;
|
|
263
296
|
let themeBackgroundColor;
|
|
297
|
+
let themeTextColor;
|
|
264
298
|
if (options.themeId) {
|
|
265
299
|
const theme = resolveThemeForDoc(doc, options.themeId, options.themeRegistry);
|
|
266
300
|
themeFont = firstFontFromStack(resolveFontFamily(theme.typography?.bodyFont, ""));
|
|
@@ -269,6 +303,7 @@ var ExportContext = class {
|
|
|
269
303
|
themeBodyColor = normalizeOoxmlHex(theme.colors?.text);
|
|
270
304
|
themeMutedColor = normalizeOoxmlHex(theme.colors?.textMuted);
|
|
271
305
|
themeBackgroundColor = normalizeOoxmlHex(theme.colors?.background);
|
|
306
|
+
themeTextColor = theme.colors?.text;
|
|
272
307
|
}
|
|
273
308
|
this.font = options.defaultFont ?? themeFont ?? DEFAULT_FONT;
|
|
274
309
|
this.headingFont = themeTitleFont ?? this.font;
|
|
@@ -278,6 +313,8 @@ var ExportContext = class {
|
|
|
278
313
|
this.mutedColor = themeMutedColor;
|
|
279
314
|
this.backgroundColor = themeBackgroundColor;
|
|
280
315
|
this.resolvedImages = options.images ?? /* @__PURE__ */ new Map();
|
|
316
|
+
this.svgIslandFallbacks = options.svgIslandFallbacks;
|
|
317
|
+
this.islandColor = themeTextColor;
|
|
281
318
|
this.allowRelativeHyperlinks = options.allowRelativeHyperlinks ?? false;
|
|
282
319
|
this.signal = options.signal;
|
|
283
320
|
}
|
|
@@ -429,7 +466,7 @@ function convertBlock(node, ctx, listDepth) {
|
|
|
429
466
|
case "list":
|
|
430
467
|
return convertList(node, ctx, listDepth);
|
|
431
468
|
case "code":
|
|
432
|
-
return convertCodeBlock(node);
|
|
469
|
+
return isSvgIslandLang(node.lang) && convertSvgIsland(node, ctx) || convertCodeBlock(node);
|
|
433
470
|
case "table":
|
|
434
471
|
return convertTable(node, ctx);
|
|
435
472
|
case "thematicBreak":
|
|
@@ -497,6 +534,27 @@ function convertListItem(item, ctx, numId, depth) {
|
|
|
497
534
|
}
|
|
498
535
|
return parts.join("");
|
|
499
536
|
}
|
|
537
|
+
function convertSvgIsland(node, ctx) {
|
|
538
|
+
const image = svgIslandImage(node.value, { color: ctx.islandColor });
|
|
539
|
+
const run = image ? svgPictureRun(image, node.value, ctx) : "";
|
|
540
|
+
return run ? `<w:p>${run}</w:p>` : "";
|
|
541
|
+
}
|
|
542
|
+
function svgPictureRun(image, source, ctx, display = {}) {
|
|
543
|
+
const host = ctx.svgIslandFallbacks?.get(source.trim());
|
|
544
|
+
const fallback = host && host.width > 0 && host.height > 0 ? host.data : ctx.svgIslandPosters?.svgIslandPosterPng(image.width / image.height)?.data;
|
|
545
|
+
if (!fallback) return "";
|
|
546
|
+
const png = ctx.addImage(fallback, "image/png", `image${ctx.images.length + 1}.png`);
|
|
547
|
+
const svg = ctx.addImage(image.data, "image/svg+xml", `image${ctx.images.length + 1}.svg`);
|
|
548
|
+
const sized = Boolean(display.width && display.height);
|
|
549
|
+
return buildWordSvgIslandRun({
|
|
550
|
+
pngRelId: png.relId,
|
|
551
|
+
svgRelId: svg.relId,
|
|
552
|
+
docPrId: png.docPrId,
|
|
553
|
+
width: sized ? display.width : image.width,
|
|
554
|
+
height: sized ? display.height : image.height,
|
|
555
|
+
alt: display.alt || image.alt
|
|
556
|
+
});
|
|
557
|
+
}
|
|
500
558
|
function convertCodeBlock(node) {
|
|
501
559
|
const lines = node.value.split("\n");
|
|
502
560
|
const parts = [];
|
|
@@ -632,34 +690,43 @@ function convertImage(node, ctx) {
|
|
|
632
690
|
const alt = node.alt || node.url;
|
|
633
691
|
return makeRun(`[Image: ${alt}]`, { italic: true });
|
|
634
692
|
}
|
|
693
|
+
if (isSvgImageEntry(node.url, imageEntry.contentType)) {
|
|
694
|
+
const image = svgImageFromBytes(imageEntry.data);
|
|
695
|
+
const run = image && svgPictureRun(image, svgSourceFromBytes(imageEntry.data), ctx, {
|
|
696
|
+
width: imageEntry.width,
|
|
697
|
+
height: imageEntry.height,
|
|
698
|
+
alt: node.alt
|
|
699
|
+
});
|
|
700
|
+
return run || makeRun(`[Image: ${node.alt || node.url}]`, { italic: true });
|
|
701
|
+
}
|
|
635
702
|
const { data, contentType } = imageEntry;
|
|
636
703
|
const ext = contentType.split("/")[1]?.replace("jpeg", "jpg") || "png";
|
|
637
704
|
const filename = `image${ctx.images.length + 1}.${ext}`;
|
|
638
705
|
const { relId, docPrId } = ctx.addImage(data, contentType, filename);
|
|
639
706
|
const dims = imageEntry.width && imageEntry.height ? { width: imageEntry.width, height: imageEntry.height } : readImageDimensions(data);
|
|
640
|
-
const
|
|
641
|
-
const
|
|
642
|
-
const
|
|
707
|
+
const EMU_PER_INCH3 = 914400;
|
|
708
|
+
const MAX_WIDTH_EMU2 = 6 * EMU_PER_INCH3;
|
|
709
|
+
const MAX_HEIGHT_EMU2 = 8 * EMU_PER_INCH3;
|
|
643
710
|
let cx;
|
|
644
711
|
let cy;
|
|
645
712
|
if (dims) {
|
|
646
713
|
const fitted = fitWithin(
|
|
647
|
-
dims.width / 96 *
|
|
648
|
-
dims.height / 96 *
|
|
649
|
-
|
|
650
|
-
|
|
714
|
+
dims.width / 96 * EMU_PER_INCH3,
|
|
715
|
+
dims.height / 96 * EMU_PER_INCH3,
|
|
716
|
+
MAX_WIDTH_EMU2,
|
|
717
|
+
MAX_HEIGHT_EMU2
|
|
651
718
|
);
|
|
652
719
|
cx = Math.round(fitted.width);
|
|
653
720
|
cy = Math.round(fitted.height);
|
|
654
721
|
} else {
|
|
655
|
-
cx = 5 *
|
|
656
|
-
cy = 3 *
|
|
722
|
+
cx = 5 * EMU_PER_INCH3;
|
|
723
|
+
cy = 3 * EMU_PER_INCH3;
|
|
657
724
|
}
|
|
658
725
|
const name = escapeXml(node.alt || filename);
|
|
659
726
|
const description = node.alt ? ` descr="${escapeXml(node.alt)}"` : "";
|
|
660
|
-
const
|
|
661
|
-
const
|
|
662
|
-
return `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${docPrId}" name="${name}"${description}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${
|
|
727
|
+
const NS_A3 = "http://schemas.openxmlformats.org/drawingml/2006/main";
|
|
728
|
+
const NS_PIC3 = "http://schemas.openxmlformats.org/drawingml/2006/picture";
|
|
729
|
+
return `<w:r><w:drawing><wp:inline distT="0" distB="0" distL="0" distR="0"><wp:extent cx="${cx}" cy="${cy}"/><wp:docPr id="${docPrId}" name="${name}"${description}/><wp:cNvGraphicFramePr><a:graphicFrameLocks xmlns:a="${NS_A3}" noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic xmlns:a="${NS_A3}"><a:graphicData uri="${NS_PIC3}"><pic:pic xmlns:pic="${NS_PIC3}"><pic:nvPicPr><pic:cNvPr id="0" name="${name}"/><pic:cNvPicPr/></pic:nvPicPr><pic:blipFill><a:blip r:embed="${relId}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic></a:graphicData></a:graphic></wp:inline></w:drawing></w:r>`;
|
|
663
730
|
}
|
|
664
731
|
function convertFootnoteRef(node, ctx) {
|
|
665
732
|
const fnId = ctx.getFootnoteId(node.identifier);
|
package/dist/diagrams/index.d.ts
CHANGED
|
@@ -16,14 +16,20 @@ import { MarkdownDocument, MarkdownCodeBlock } from '@bendyline/squisq/markdown'
|
|
|
16
16
|
* - ASCII-art ```diagram, ```tree, and ```timeline fences (also unlabeled or
|
|
17
17
|
* `text` fences whose content the detectors accept);
|
|
18
18
|
* - heading blocks with a container template — `{[drawing]}`, `{[layout]}`,
|
|
19
|
-
* `{[diagram]}` — together with every child heading they own
|
|
19
|
+
* `{[diagram]}` — together with every child heading they own;
|
|
20
|
+
* - ```svg fences (SVG islands) — but only when `kinds` asks for `svg`: DOCX
|
|
21
|
+
* and PPTX embed an island natively as vector art, so by default it is
|
|
22
|
+
* left in place. Ask for it to picture islands for PDF, or to get the
|
|
23
|
+
* `svg` map of PNG fallbacks the Office exporters place beside the vector.
|
|
20
24
|
*
|
|
21
25
|
* `docToPptx` keeps drawings, timelines and ASCII diagrams as native shapes;
|
|
22
26
|
* for it, use the returned `mermaid` map as its `diagramImages` option.
|
|
23
27
|
*/
|
|
24
28
|
|
|
25
29
|
/** What kind of diagram a picture is wanted for. */
|
|
26
|
-
type ExportDiagramKind = 'mermaid' | 'diagram' | 'timeline' | 'tree' | 'container';
|
|
30
|
+
type ExportDiagramKind = 'mermaid' | 'diagram' | 'timeline' | 'tree' | 'container' | 'svg';
|
|
31
|
+
/** Every kind, `svg` included — for exporters with no vector picture of their own (PDF). */
|
|
32
|
+
declare const EXPORT_DIAGRAM_KINDS: readonly ExportDiagramKind[];
|
|
27
33
|
interface ExportDiagram {
|
|
28
34
|
readonly kind: ExportDiagramKind;
|
|
29
35
|
/**
|
|
@@ -49,7 +55,7 @@ interface DiagramPicture {
|
|
|
49
55
|
type DiagramRasterizer = (diagram: ExportDiagram) => Promise<DiagramPicture | null>;
|
|
50
56
|
interface RasterizeDiagramsOptions {
|
|
51
57
|
signal?: AbortSignal;
|
|
52
|
-
/**
|
|
58
|
+
/** The kinds to picture. Default: all of them except `svg` (see above). */
|
|
53
59
|
kinds?: readonly ExportDiagramKind[];
|
|
54
60
|
/** Prefix for the image URLs written into the document. Default: `diagram`. */
|
|
55
61
|
urlPrefix?: string;
|
|
@@ -71,6 +77,11 @@ interface RasterizedDiagrams {
|
|
|
71
77
|
}>;
|
|
72
78
|
/** Pictures of Mermaid diagrams keyed by trimmed source, for `docToPptx`'s `diagramImages`. */
|
|
73
79
|
readonly mermaid: Map<string, DiagramPicture>;
|
|
80
|
+
/**
|
|
81
|
+
* Pictures of SVG islands keyed by trimmed source: the PNG fallbacks for
|
|
82
|
+
* DOCX's `svgIslandFallbacks` and PPTX's `diagramImages`.
|
|
83
|
+
*/
|
|
84
|
+
readonly svg: Map<string, DiagramPicture>;
|
|
74
85
|
}
|
|
75
86
|
/** The diagram kind of a fence, or null for ordinary code. */
|
|
76
87
|
declare function fenceDiagramKind(node: MarkdownCodeBlock): ExportDiagramKind | null;
|
|
@@ -83,4 +94,4 @@ declare function findDiagrams(doc: MarkdownDocument): ExportDiagram[];
|
|
|
83
94
|
*/
|
|
84
95
|
declare function rasterizeDiagrams(doc: MarkdownDocument, render: DiagramRasterizer, options?: RasterizeDiagramsOptions): Promise<RasterizedDiagrams>;
|
|
85
96
|
|
|
86
|
-
export { type DiagramPicture, type DiagramRasterizer, type ExportDiagram, type ExportDiagramKind, type RasterizeDiagramsOptions, type RasterizedDiagrams, fenceDiagramKind, findDiagrams, rasterizeDiagrams };
|
|
97
|
+
export { type DiagramPicture, type DiagramRasterizer, EXPORT_DIAGRAM_KINDS, type ExportDiagram, type ExportDiagramKind, type RasterizeDiagramsOptions, type RasterizedDiagrams, fenceDiagramKind, findDiagrams, rasterizeDiagrams };
|
package/dist/diagrams/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
// src/diagrams/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
extractPlainText,
|
|
4
|
+
isSvgIslandLang,
|
|
5
|
+
parseSvgIsland,
|
|
6
|
+
stringifyMarkdown
|
|
7
|
+
} from "@bendyline/squisq/markdown";
|
|
3
8
|
import {
|
|
4
9
|
detectAsciiDiagram,
|
|
5
10
|
detectAsciiTimeline,
|
|
@@ -13,16 +18,26 @@ import {
|
|
|
13
18
|
isExplicitTreeLang,
|
|
14
19
|
resolveTemplateName
|
|
15
20
|
} from "@bendyline/squisq/doc";
|
|
21
|
+
var DEFAULT_KINDS = [
|
|
22
|
+
"mermaid",
|
|
23
|
+
"diagram",
|
|
24
|
+
"timeline",
|
|
25
|
+
"tree",
|
|
26
|
+
"container"
|
|
27
|
+
];
|
|
28
|
+
var EXPORT_DIAGRAM_KINDS = [...DEFAULT_KINDS, "svg"];
|
|
16
29
|
var KIND_NAMES = {
|
|
17
30
|
mermaid: "Diagram",
|
|
18
31
|
diagram: "Diagram",
|
|
19
32
|
timeline: "Timeline",
|
|
20
33
|
tree: "Tree",
|
|
21
|
-
container: "Diagram"
|
|
34
|
+
container: "Diagram",
|
|
35
|
+
svg: "Illustration"
|
|
22
36
|
};
|
|
23
37
|
function fenceDiagramKind(node) {
|
|
24
38
|
const lang = node.lang ?? null;
|
|
25
39
|
if ((lang ?? "").trim().toLowerCase() === "mermaid") return "mermaid";
|
|
40
|
+
if (isSvgIslandLang(lang)) return parseSvgIsland(node.value).ok ? "svg" : null;
|
|
26
41
|
if (isEligibleAsciiFenceLang(lang)) {
|
|
27
42
|
const detection = detectAsciiDiagram(node.value, { explicit: isExplicitDiagramLang(lang) });
|
|
28
43
|
if (detection.isDiagram) return "diagram";
|
|
@@ -47,6 +62,14 @@ function mermaidAlt(source) {
|
|
|
47
62
|
const description = /^\s*accDescr\s*:\s*(.+?)\s*$/mu.exec(source)?.[1] ?? "";
|
|
48
63
|
return [title, description].filter(Boolean).join(". ");
|
|
49
64
|
}
|
|
65
|
+
function fenceAlt(kind, source) {
|
|
66
|
+
if (kind === "mermaid") return mermaidAlt(source);
|
|
67
|
+
if (kind === "svg") {
|
|
68
|
+
const result = parseSvgIsland(source);
|
|
69
|
+
return result.ok && result.island.title || "";
|
|
70
|
+
}
|
|
71
|
+
return "";
|
|
72
|
+
}
|
|
50
73
|
function fenceMarkdown(node) {
|
|
51
74
|
return stringifyMarkdown({ type: "document", children: [node] }).trim();
|
|
52
75
|
}
|
|
@@ -66,7 +89,7 @@ function findInDocument(doc) {
|
|
|
66
89
|
markdown: fenceMarkdown(fence),
|
|
67
90
|
source: fence.value,
|
|
68
91
|
template: "",
|
|
69
|
-
alt: (kind
|
|
92
|
+
alt: fenceAlt(kind, fence.value) || KIND_NAMES[kind]
|
|
70
93
|
}
|
|
71
94
|
});
|
|
72
95
|
}
|
|
@@ -147,7 +170,7 @@ function replaceFences(nodes, replacements) {
|
|
|
147
170
|
return changed ? next : nodes;
|
|
148
171
|
}
|
|
149
172
|
async function rasterizeDiagrams(doc, render, options = {}) {
|
|
150
|
-
const kinds =
|
|
173
|
+
const kinds = new Set(options.kinds ?? DEFAULT_KINDS);
|
|
151
174
|
const prefix = options.urlPrefix ?? "diagram";
|
|
152
175
|
const taken = imageUrls(doc.children, /* @__PURE__ */ new Set());
|
|
153
176
|
let counter = 0;
|
|
@@ -161,11 +184,12 @@ async function rasterizeDiagrams(doc, render, options = {}) {
|
|
|
161
184
|
};
|
|
162
185
|
const images = /* @__PURE__ */ new Map();
|
|
163
186
|
const mermaid = /* @__PURE__ */ new Map();
|
|
187
|
+
const svg = /* @__PURE__ */ new Map();
|
|
164
188
|
const fenceReplacements = /* @__PURE__ */ new Map();
|
|
165
189
|
const containerReplacements = [];
|
|
166
190
|
for (const entry of findInDocument(doc)) {
|
|
167
191
|
options.signal?.throwIfAborted();
|
|
168
|
-
if (
|
|
192
|
+
if (!kinds.has(entry.diagram.kind)) continue;
|
|
169
193
|
let picture = null;
|
|
170
194
|
try {
|
|
171
195
|
picture = await render(entry.diagram);
|
|
@@ -184,6 +208,7 @@ async function rasterizeDiagrams(doc, render, options = {}) {
|
|
|
184
208
|
height: picture.height
|
|
185
209
|
});
|
|
186
210
|
if (entry.diagram.kind === "mermaid") mermaid.set(entry.diagram.source.trim(), picture);
|
|
211
|
+
if (entry.diagram.kind === "svg") svg.set(entry.diagram.source.trim(), picture);
|
|
187
212
|
const paragraph = imageParagraph(url, entry.diagram.alt);
|
|
188
213
|
if (entry.fence) {
|
|
189
214
|
fenceReplacements.set(entry.fence, paragraph);
|
|
@@ -212,10 +237,12 @@ async function rasterizeDiagrams(doc, render, options = {}) {
|
|
|
212
237
|
return {
|
|
213
238
|
markdownDoc: children === doc.children ? doc : { ...doc, children },
|
|
214
239
|
images,
|
|
215
|
-
mermaid
|
|
240
|
+
mermaid,
|
|
241
|
+
svg
|
|
216
242
|
};
|
|
217
243
|
}
|
|
218
244
|
export {
|
|
245
|
+
EXPORT_DIAGRAM_KINDS,
|
|
219
246
|
fenceDiagramKind,
|
|
220
247
|
findDiagrams,
|
|
221
248
|
rasterizeDiagrams
|
package/dist/docx/index.d.ts
CHANGED
|
@@ -65,6 +65,17 @@ interface DocxExportOptions {
|
|
|
65
65
|
}>;
|
|
66
66
|
/** Permit carefully validated relative hyperlink targets. Default: false. */
|
|
67
67
|
allowRelativeHyperlinks?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* PNG renderings of SVG islands (```svg fences), keyed by the fence source
|
|
70
|
+
* as written (trimmed) — the `svg` map from `rasterizeDiagrams` with
|
|
71
|
+
* `kinds: ['svg']`. Word draws an island's SVG; the PNG is what older
|
|
72
|
+
* readers show. Without one, a generated stand-in is used.
|
|
73
|
+
*/
|
|
74
|
+
svgIslandFallbacks?: Map<string, {
|
|
75
|
+
data: ArrayBuffer | Uint8Array;
|
|
76
|
+
width: number;
|
|
77
|
+
height: number;
|
|
78
|
+
}>;
|
|
68
79
|
}
|
|
69
80
|
/**
|
|
70
81
|
* Convert a MarkdownDocument to a .docx Blob.
|
package/dist/docx/index.js
CHANGED
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
docxToDoc,
|
|
5
5
|
docxToMarkdownDoc,
|
|
6
6
|
markdownDocToDocx
|
|
7
|
-
} from "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
} from "../chunk-YITBSNPM.js";
|
|
8
|
+
import "../chunk-XVBQB2SL.js";
|
|
9
|
+
import "../chunk-VCZUXQUL.js";
|
|
9
10
|
import "../chunk-AVOZAKGP.js";
|
|
10
11
|
import "../chunk-ILCJ3WFD.js";
|
|
11
12
|
import "../chunk-JU2RHXUB.js";
|
package/dist/epub/index.d.ts
CHANGED
|
@@ -66,7 +66,7 @@ interface EpubExportOptions {
|
|
|
66
66
|
* Chapters are split at H1/H2 heading boundaries. All referenced images
|
|
67
67
|
* (provided via `options.images`) are embedded in the archive.
|
|
68
68
|
*/
|
|
69
|
-
declare function markdownDocToEpub(
|
|
69
|
+
declare function markdownDocToEpub(sourceDoc: MarkdownDocument, options?: EpubExportOptions): Promise<ArrayBuffer>;
|
|
70
70
|
/**
|
|
71
71
|
* Convert a squisq Doc to an EPUB 3 file.
|
|
72
72
|
*
|
package/dist/epub/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
docToEpub,
|
|
3
3
|
markdownDocToEpub
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-PKFWF4FX.js";
|
|
5
5
|
import "../chunk-YVCUNZB5.js";
|
|
6
|
+
import "../chunk-VCZUXQUL.js";
|
|
6
7
|
import "../chunk-AVOZAKGP.js";
|
|
7
8
|
import "../chunk-JU2RHXUB.js";
|
|
8
9
|
import "../chunk-BXWNU4T5.js";
|
package/dist/html/index.js
CHANGED
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
markdownDocToPlainHtml,
|
|
11
11
|
markdownDocsToHtmlBundle,
|
|
12
12
|
markdownDocsToPlainHtmlBundle
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-ETK2SKVQ.js";
|
|
14
14
|
import {
|
|
15
15
|
arrayBufferToBase64DataUrl,
|
|
16
16
|
extractFilename,
|
|
17
17
|
inferMimeType
|
|
18
18
|
} from "../chunk-YVCUNZB5.js";
|
|
19
|
+
import "../chunk-VCZUXQUL.js";
|
|
19
20
|
import "../chunk-BXWNU4T5.js";
|
|
20
21
|
import "../chunk-RSU3V5IQ.js";
|
|
21
22
|
export {
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
htmlToMarkdown,
|
|
6
6
|
htmlToMarkdownDoc,
|
|
7
7
|
htmlToMarkdownDocSync
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ETK2SKVQ.js";
|
|
9
9
|
import {
|
|
10
10
|
docToEpub,
|
|
11
11
|
markdownDocToEpub
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PKFWF4FX.js";
|
|
13
13
|
import {
|
|
14
14
|
BUILTIN_FORMAT_IDS
|
|
15
15
|
} from "./chunk-2P5HJAQL.js";
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
renderOutsideInDocument,
|
|
29
29
|
withOutsideInMarkdownEditing,
|
|
30
30
|
withOutsideInMetadata
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-Z3QORA5G.js";
|
|
32
32
|
import {
|
|
33
33
|
DEFAULT_CONVERSION_LIMITS,
|
|
34
34
|
convert,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
defaultRegistry,
|
|
38
38
|
prepareConversion,
|
|
39
39
|
resolveConversionLimits
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-XCHIYF3A.js";
|
|
41
41
|
import {
|
|
42
42
|
ConversionError
|
|
43
43
|
} from "./chunk-KXOZMWBS.js";
|
|
@@ -52,16 +52,17 @@ import {
|
|
|
52
52
|
docxToDoc,
|
|
53
53
|
docxToMarkdownDoc,
|
|
54
54
|
markdownDocToDocx
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-YITBSNPM.js";
|
|
56
56
|
import {
|
|
57
57
|
docToPptx,
|
|
58
58
|
markdownDocToPptx,
|
|
59
59
|
pptxToDoc,
|
|
60
60
|
pptxToMarkdownDoc
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-52RXDH24.js";
|
|
62
62
|
import "./chunk-6N2J7C2B.js";
|
|
63
63
|
import "./chunk-YVCUNZB5.js";
|
|
64
|
-
import "./chunk-
|
|
64
|
+
import "./chunk-XVBQB2SL.js";
|
|
65
|
+
import "./chunk-VCZUXQUL.js";
|
|
65
66
|
import {
|
|
66
67
|
docToXlsx,
|
|
67
68
|
markdownDocToXlsx,
|
package/dist/outside-in/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
renderOutsideInDocument,
|
|
7
7
|
withOutsideInMarkdownEditing,
|
|
8
8
|
withOutsideInMetadata
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-Z3QORA5G.js";
|
|
10
|
+
import "../chunk-XCHIYF3A.js";
|
|
11
11
|
import "../chunk-KXOZMWBS.js";
|
|
12
12
|
import {
|
|
13
13
|
OUTSIDE_IN_FORMAT_IDS,
|
package/dist/pptx/index.js
CHANGED
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
pptxToContainer,
|
|
5
5
|
pptxToDoc,
|
|
6
6
|
pptxToMarkdownDoc
|
|
7
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-52RXDH24.js";
|
|
8
8
|
import {
|
|
9
9
|
analyzePptxLayouts,
|
|
10
10
|
inspectPptxLayouts
|
|
11
11
|
} from "../chunk-6N2J7C2B.js";
|
|
12
12
|
import "../chunk-YVCUNZB5.js";
|
|
13
|
-
import "../chunk-
|
|
13
|
+
import "../chunk-XVBQB2SL.js";
|
|
14
|
+
import "../chunk-VCZUXQUL.js";
|
|
14
15
|
import "../chunk-AVOZAKGP.js";
|
|
15
16
|
import "../chunk-ILCJ3WFD.js";
|
|
16
17
|
import "../chunk-JU2RHXUB.js";
|
package/dist/registry/index.js
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
upngEncoder
|
|
3
|
+
} from "./chunk-34KECNZO.js";
|
|
4
|
+
|
|
5
|
+
// src/shared/svgIslandPoster.ts
|
|
6
|
+
var LONG_EDGE = 480;
|
|
7
|
+
var posterCache = /* @__PURE__ */ new Map();
|
|
8
|
+
function svgIslandPosterPng(aspectRatio) {
|
|
9
|
+
const ratio = Number.isFinite(aspectRatio) && aspectRatio > 0 ? aspectRatio : 4 / 3;
|
|
10
|
+
const width = Math.max(48, Math.round(ratio >= 1 ? LONG_EDGE : LONG_EDGE * ratio));
|
|
11
|
+
const height = Math.max(48, Math.round(ratio >= 1 ? LONG_EDGE / ratio : LONG_EDGE));
|
|
12
|
+
const key = `${width}x${height}`;
|
|
13
|
+
if (posterCache.has(key)) return posterCache.get(key) ?? null;
|
|
14
|
+
if (!upngEncoder) {
|
|
15
|
+
posterCache.set(key, null);
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const panel = [243, 244, 246];
|
|
19
|
+
const ink = [156, 163, 175];
|
|
20
|
+
const rgba = new Uint8Array(width * height * 4);
|
|
21
|
+
for (let i = 0; i < width * height; i++) {
|
|
22
|
+
rgba[i * 4] = panel[0];
|
|
23
|
+
rgba[i * 4 + 1] = panel[1];
|
|
24
|
+
rgba[i * 4 + 2] = panel[2];
|
|
25
|
+
rgba[i * 4 + 3] = 255;
|
|
26
|
+
}
|
|
27
|
+
const size = Math.round(Math.min(width, height) * 0.28);
|
|
28
|
+
const left = (width - size) / 2;
|
|
29
|
+
const top = (height - size * 0.8) / 2;
|
|
30
|
+
const glyphH = size * 0.8;
|
|
31
|
+
const stroke = Math.max(2, size * 0.06);
|
|
32
|
+
const inFrame = (x, y) => {
|
|
33
|
+
const inside = x >= left && x <= left + size && y >= top && y <= top + glyphH;
|
|
34
|
+
const inner = x >= left + stroke && x <= left + size - stroke && y >= top + stroke && y <= top + glyphH - stroke;
|
|
35
|
+
return inside && !inner;
|
|
36
|
+
};
|
|
37
|
+
const sunX = left + size * 0.32;
|
|
38
|
+
const sunY = top + glyphH * 0.34;
|
|
39
|
+
const sunR = size * 0.09;
|
|
40
|
+
const inSun = (x, y) => (x - sunX) ** 2 + (y - sunY) ** 2 <= sunR ** 2;
|
|
41
|
+
const base = top + glyphH - stroke;
|
|
42
|
+
const inMountain = (x, y) => {
|
|
43
|
+
if (y > base || x < left + stroke || x > left + size - stroke) return false;
|
|
44
|
+
const peakX = left + size * 0.62;
|
|
45
|
+
const peakY = top + glyphH * 0.42;
|
|
46
|
+
const halfW = size * 0.38;
|
|
47
|
+
return y >= peakY + (base - peakY) * Math.abs(x - peakX) / halfW;
|
|
48
|
+
};
|
|
49
|
+
const SAMPLES = 4;
|
|
50
|
+
for (let y = Math.floor(top); y < Math.ceil(top + glyphH); y++) {
|
|
51
|
+
for (let x = Math.floor(left); x < Math.ceil(left + size); x++) {
|
|
52
|
+
let hits = 0;
|
|
53
|
+
for (let sy = 0; sy < SAMPLES; sy++) {
|
|
54
|
+
for (let sx = 0; sx < SAMPLES; sx++) {
|
|
55
|
+
const px = x + (sx + 0.5) / SAMPLES;
|
|
56
|
+
const py = y + (sy + 0.5) / SAMPLES;
|
|
57
|
+
if (inFrame(px, py) || inSun(px, py) || inMountain(px, py)) hits++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!hits) continue;
|
|
61
|
+
const t = hits / SAMPLES ** 2;
|
|
62
|
+
const at = (y * width + x) * 4;
|
|
63
|
+
for (let c = 0; c < 3; c++) rgba[at + c] = Math.round(panel[c] + (ink[c] - panel[c]) * t);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const data = upngEncoder.encode([rgba.buffer], width, height, 0);
|
|
67
|
+
const poster = { data, contentType: "image/png", width, height };
|
|
68
|
+
posterCache.set(key, poster);
|
|
69
|
+
return poster;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
svgIslandPosterPng
|
|
73
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-formats",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "Document format converters — DOCX, PDF, OOXML import/export",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"typecheck": "tsc --noEmit"
|
|
126
126
|
},
|
|
127
127
|
"dependencies": {
|
|
128
|
-
"@bendyline/squisq": "2.12.
|
|
128
|
+
"@bendyline/squisq": "2.12.2",
|
|
129
129
|
"@pdf-lib/fontkit": "1.1.1",
|
|
130
130
|
"@pdf-lib/upng": "1.0.1",
|
|
131
131
|
"@xmldom/xmldom": "0.9.12",
|
|
File without changes
|