@cyber-dash-tech/revela 0.19.6 → 0.19.8
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 +14 -14
- package/README.zh-CN.md +14 -14
- package/designs/lucent/DESIGN.md +1 -1
- package/designs/lucent/design.css +15 -0
- package/designs/lucent-dark/DESIGN.md +1 -1
- package/designs/lucent-dark/design.css +15 -0
- package/designs/monet/DESIGN.md +40 -127
- package/designs/monet/design.css +15 -0
- package/designs/starter/DESIGN.md +16 -24
- package/designs/starter/design.css +15 -0
- package/designs/summit/DESIGN.md +46 -113
- package/designs/summit/design.css +15 -0
- package/lib/design/designs.ts +1 -1
- package/lib/export/html.ts +14 -0
- package/lib/page-templates/built-in-preview.html +3 -2
- package/lib/page-templates/render.ts +54 -4
- package/lib/page-templates/vocabulary.ts +6 -2
- package/lib/pdf/export.ts +1 -10
- package/lib/pptx/export.ts +316 -15
- package/package.json +2 -1
- package/plugins/revela/.codex-plugin/plugin.json +1 -1
package/lib/pptx/export.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { basename, dirname, extname, join, posix as pathPosix, resolve } from "p
|
|
|
22
22
|
import { randomBytes } from "crypto"
|
|
23
23
|
import { pathToFileURL } from "url"
|
|
24
24
|
import { findChromePath, launchChrome } from "../browser/chrome"
|
|
25
|
+
import { withExportBaseHref } from "../export/html"
|
|
25
26
|
|
|
26
27
|
const CANVAS_W = 1920
|
|
27
28
|
const CANVAS_H = 1080
|
|
@@ -344,6 +345,7 @@ async function preparePage(
|
|
|
344
345
|
document.documentElement.style.scrollSnapType = "none"
|
|
345
346
|
document.documentElement.style.overflow = "visible"
|
|
346
347
|
document.body.style.overflow = "visible"
|
|
348
|
+
document.body.style.margin = "0"
|
|
347
349
|
|
|
348
350
|
const slides = Array.from(document.querySelectorAll(".slide")) as HTMLElement[]
|
|
349
351
|
if (slides.length === 0) {
|
|
@@ -366,7 +368,24 @@ async function preparePage(
|
|
|
366
368
|
exportStyle = document.createElement("style")
|
|
367
369
|
exportStyle.id = "revela-pptx-export-style"
|
|
368
370
|
exportStyle.textContent = `
|
|
371
|
+
html, body {
|
|
372
|
+
scroll-snap-type: none !important;
|
|
373
|
+
overflow: visible !important;
|
|
374
|
+
}
|
|
375
|
+
.slide {
|
|
376
|
+
width: 1920px !important;
|
|
377
|
+
min-width: 1920px !important;
|
|
378
|
+
height: 1080px !important;
|
|
379
|
+
min-height: 1080px !important;
|
|
380
|
+
display: flex !important;
|
|
381
|
+
align-items: center !important;
|
|
382
|
+
justify-content: center !important;
|
|
383
|
+
overflow: hidden !important;
|
|
384
|
+
scroll-snap-align: none !important;
|
|
385
|
+
}
|
|
369
386
|
.slide-canvas {
|
|
387
|
+
width: 1920px !important;
|
|
388
|
+
height: 1080px !important;
|
|
370
389
|
transform: none !important;
|
|
371
390
|
transform-origin: top left !important;
|
|
372
391
|
transition: none !important;
|
|
@@ -466,6 +485,48 @@ async function readSlideMeta(
|
|
|
466
485
|
})
|
|
467
486
|
}
|
|
468
487
|
|
|
488
|
+
async function rasterizeFormulaNodes(page: Page, diagnostics: string[]): Promise<number> {
|
|
489
|
+
const handles = await page.$$(".template-text-panel-formula, .text-panel-formula")
|
|
490
|
+
let rasterized = 0
|
|
491
|
+
|
|
492
|
+
for (const handle of handles) {
|
|
493
|
+
try {
|
|
494
|
+
const box = await handle.boundingBox()
|
|
495
|
+
if (!box || box.width <= 0 || box.height <= 0) continue
|
|
496
|
+
|
|
497
|
+
const base64 = await (handle as any).screenshot({
|
|
498
|
+
encoding: "base64",
|
|
499
|
+
omitBackground: true,
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
await handle.evaluate((node, payload) => {
|
|
503
|
+
const element = node as HTMLElement
|
|
504
|
+
const img = document.createElement("img")
|
|
505
|
+
img.src = `data:image/png;base64,${payload.base64}`
|
|
506
|
+
img.alt = element.getAttribute("data-latex") || "Formula"
|
|
507
|
+
img.setAttribute("data-revela-pptx-formula-raster", "true")
|
|
508
|
+
img.style.width = `${payload.width}px`
|
|
509
|
+
img.style.height = `${payload.height}px`
|
|
510
|
+
img.style.maxWidth = "100%"
|
|
511
|
+
img.style.display = "block"
|
|
512
|
+
img.style.objectFit = "contain"
|
|
513
|
+
element.replaceChildren(img)
|
|
514
|
+
}, {
|
|
515
|
+
base64,
|
|
516
|
+
width: Math.ceil(box.width),
|
|
517
|
+
height: Math.ceil(box.height),
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
rasterized += 1
|
|
521
|
+
} catch (error) {
|
|
522
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
523
|
+
diagnostics.push(`formula rasterize failed: ${message}`)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return rasterized
|
|
528
|
+
}
|
|
529
|
+
|
|
469
530
|
async function exportSlidePptx(
|
|
470
531
|
page: Page,
|
|
471
532
|
diagnostics: string[],
|
|
@@ -499,15 +560,199 @@ async function exportSlidePptx(
|
|
|
499
560
|
})
|
|
500
561
|
}
|
|
501
562
|
|
|
502
|
-
target.scrollIntoView({ block: "center", inline: "center" })
|
|
503
563
|
target.style.transform = "none"
|
|
504
564
|
target.style.transformOrigin = "top left"
|
|
505
565
|
target.style.transition = "none"
|
|
506
566
|
target.style.animation = "none"
|
|
507
567
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
|
508
568
|
|
|
569
|
+
const host = document.createElement("div")
|
|
570
|
+
host.id = "revela-pptx-export-root"
|
|
571
|
+
host.style.position = "fixed"
|
|
572
|
+
host.style.left = "0"
|
|
573
|
+
host.style.top = "0"
|
|
574
|
+
host.style.width = "1920px"
|
|
575
|
+
host.style.height = "1080px"
|
|
576
|
+
host.style.overflow = "hidden"
|
|
577
|
+
host.style.zIndex = "2147483647"
|
|
578
|
+
host.style.background = "transparent"
|
|
579
|
+
|
|
580
|
+
const exportTarget = targetSlide.cloneNode(true) as HTMLElement
|
|
581
|
+
exportTarget.style.width = "1920px"
|
|
582
|
+
exportTarget.style.minWidth = "1920px"
|
|
583
|
+
exportTarget.style.height = "1080px"
|
|
584
|
+
exportTarget.style.minHeight = "1080px"
|
|
585
|
+
exportTarget.style.position = "relative"
|
|
586
|
+
exportTarget.style.left = "0"
|
|
587
|
+
exportTarget.style.top = "0"
|
|
588
|
+
exportTarget.style.margin = "0"
|
|
589
|
+
exportTarget.style.display = "flex"
|
|
590
|
+
exportTarget.style.alignItems = "center"
|
|
591
|
+
exportTarget.style.justifyContent = "center"
|
|
592
|
+
exportTarget.style.overflow = "hidden"
|
|
593
|
+
exportTarget.style.transform = "none"
|
|
594
|
+
exportTarget.style.transformOrigin = "top left"
|
|
595
|
+
exportTarget.style.transition = "none"
|
|
596
|
+
exportTarget.style.animation = "none"
|
|
597
|
+
const exportCanvas = exportTarget.querySelector(".slide-canvas") as HTMLElement | null
|
|
598
|
+
if (!exportCanvas) {
|
|
599
|
+
host.remove()
|
|
600
|
+
throw new Error(`Missing cloned .slide-canvas for slide ${index + 1}`)
|
|
601
|
+
}
|
|
602
|
+
exportCanvas.style.width = "1920px"
|
|
603
|
+
exportCanvas.style.minWidth = "1920px"
|
|
604
|
+
exportCanvas.style.height = "1080px"
|
|
605
|
+
exportCanvas.style.minHeight = "1080px"
|
|
606
|
+
exportCanvas.style.position = "relative"
|
|
607
|
+
exportCanvas.style.left = "0"
|
|
608
|
+
exportCanvas.style.top = "0"
|
|
609
|
+
exportCanvas.style.flexShrink = "0"
|
|
610
|
+
exportCanvas.style.transform = "none"
|
|
611
|
+
exportCanvas.style.transformOrigin = "top left"
|
|
612
|
+
exportCanvas.style.transition = "none"
|
|
613
|
+
exportCanvas.style.animation = "none"
|
|
614
|
+
|
|
615
|
+
const splitCssList = (value: string): string[] => {
|
|
616
|
+
const parts: string[] = []
|
|
617
|
+
let depth = 0
|
|
618
|
+
let quote: string | null = null
|
|
619
|
+
let start = 0
|
|
620
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
621
|
+
const char = value[i]
|
|
622
|
+
if (quote) {
|
|
623
|
+
if (char === quote && value[i - 1] !== "\\") quote = null
|
|
624
|
+
continue
|
|
625
|
+
}
|
|
626
|
+
if (char === "\"" || char === "'") {
|
|
627
|
+
quote = char
|
|
628
|
+
continue
|
|
629
|
+
}
|
|
630
|
+
if (char === "(") depth += 1
|
|
631
|
+
if (char === ")") depth = Math.max(0, depth - 1)
|
|
632
|
+
if (char === "," && depth === 0) {
|
|
633
|
+
parts.push(value.slice(start, i).trim())
|
|
634
|
+
start = i + 1
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
parts.push(value.slice(start).trim())
|
|
638
|
+
return parts.filter(Boolean)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const extractCssUrl = (value: string): string | null => {
|
|
642
|
+
const match = value.match(/url\((["']?)(.*?)\1\)/i)
|
|
643
|
+
return match?.[2] ?? null
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const normalizeBackgroundLayersForPptx = (canvas: HTMLElement, sourceStyle: CSSStyleDeclaration) => {
|
|
647
|
+
const computed = sourceStyle
|
|
648
|
+
const imageLayers = splitCssList(computed.backgroundImage)
|
|
649
|
+
const hasUrlLayer = imageLayers.some((layer) => /url\(/i.test(layer))
|
|
650
|
+
const paddingLeft = Number.parseFloat(computed.paddingLeft) || 0
|
|
651
|
+
const paddingRight = Number.parseFloat(computed.paddingRight) || 0
|
|
652
|
+
const paddingTop = Number.parseFloat(computed.paddingTop) || 0
|
|
653
|
+
const paddingBottom = Number.parseFloat(computed.paddingBottom) || 0
|
|
654
|
+
const hasPadding = paddingLeft > 0 || paddingRight > 0 || paddingTop > 0 || paddingBottom > 0
|
|
655
|
+
if (!hasUrlLayer && !hasPadding) return
|
|
656
|
+
|
|
657
|
+
const sizeLayers = splitCssList(computed.backgroundSize)
|
|
658
|
+
const positionLayers = splitCssList(computed.backgroundPosition)
|
|
659
|
+
const repeatLayers = splitCssList(computed.backgroundRepeat)
|
|
660
|
+
const layerCount = imageLayers.length
|
|
661
|
+
const layers = hasUrlLayer ? document.createElement("div") : null
|
|
662
|
+
if (layers) {
|
|
663
|
+
layers.setAttribute("data-revela-pptx-background-layers", "true")
|
|
664
|
+
layers.style.position = "absolute"
|
|
665
|
+
layers.style.inset = "0"
|
|
666
|
+
layers.style.pointerEvents = "none"
|
|
667
|
+
layers.style.zIndex = "0"
|
|
668
|
+
layers.style.overflow = "hidden"
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (layers) imageLayers.forEach((layer, layerIndex) => {
|
|
672
|
+
if (layer === "none") return
|
|
673
|
+
const zIndex = String(layerCount - layerIndex)
|
|
674
|
+
const url = extractCssUrl(layer)
|
|
675
|
+
const size = sizeLayers[layerIndex] ?? sizeLayers[0] ?? "auto"
|
|
676
|
+
const position = positionLayers[layerIndex] ?? positionLayers[0] ?? "50% 50%"
|
|
677
|
+
const repeat = repeatLayers[layerIndex] ?? repeatLayers[0] ?? "repeat"
|
|
678
|
+
if (url) {
|
|
679
|
+
const img = document.createElement("img")
|
|
680
|
+
img.src = url
|
|
681
|
+
img.alt = ""
|
|
682
|
+
img.setAttribute("aria-hidden", "true")
|
|
683
|
+
img.style.position = "absolute"
|
|
684
|
+
img.style.inset = "0"
|
|
685
|
+
img.style.width = "100%"
|
|
686
|
+
img.style.height = "100%"
|
|
687
|
+
img.style.display = "block"
|
|
688
|
+
img.style.zIndex = zIndex
|
|
689
|
+
img.style.objectFit = size.includes("contain") ? "contain" : "cover"
|
|
690
|
+
img.style.objectPosition = position
|
|
691
|
+
if (!repeat.includes("no-repeat") && !size.includes("cover") && !size.includes("contain")) {
|
|
692
|
+
img.style.objectFit = "fill"
|
|
693
|
+
}
|
|
694
|
+
layers.appendChild(img)
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const gradient = document.createElement("div")
|
|
699
|
+
gradient.style.position = "absolute"
|
|
700
|
+
gradient.style.inset = "0"
|
|
701
|
+
gradient.style.zIndex = zIndex
|
|
702
|
+
gradient.style.backgroundImage = layer
|
|
703
|
+
gradient.style.backgroundSize = size
|
|
704
|
+
gradient.style.backgroundPosition = position
|
|
705
|
+
gradient.style.backgroundRepeat = repeat
|
|
706
|
+
layers.appendChild(gradient)
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
if (layers && !layers.childElementCount) return
|
|
710
|
+
|
|
711
|
+
const content = document.createElement("div")
|
|
712
|
+
content.setAttribute("data-revela-pptx-content-layer", "true")
|
|
713
|
+
content.style.position = "absolute"
|
|
714
|
+
content.style.zIndex = String(layerCount + 1)
|
|
715
|
+
content.style.left = `${paddingLeft}px`
|
|
716
|
+
content.style.top = `${paddingTop}px`
|
|
717
|
+
content.style.width = `${Math.max(1, 1920 - paddingLeft - paddingRight)}px`
|
|
718
|
+
content.style.height = `${Math.max(1, 1080 - paddingTop - paddingBottom)}px`
|
|
719
|
+
content.style.minHeight = "0"
|
|
720
|
+
content.style.boxSizing = "border-box"
|
|
721
|
+
while (canvas.firstChild) {
|
|
722
|
+
content.appendChild(canvas.firstChild)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
canvas.style.padding = "0"
|
|
726
|
+
if (layers) {
|
|
727
|
+
canvas.style.backgroundImage = "none"
|
|
728
|
+
canvas.style.backgroundColor = computed.backgroundColor
|
|
729
|
+
canvas.appendChild(layers)
|
|
730
|
+
}
|
|
731
|
+
canvas.appendChild(content)
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
normalizeBackgroundLayersForPptx(exportCanvas, window.getComputedStyle(target))
|
|
735
|
+
host.appendChild(exportTarget)
|
|
736
|
+
document.body.appendChild(host)
|
|
737
|
+
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
|
|
738
|
+
|
|
739
|
+
const rect = exportTarget.getBoundingClientRect()
|
|
740
|
+
const canvasRect = exportCanvas.getBoundingClientRect()
|
|
741
|
+
if (Math.abs(rect.width - 1920) > 2 || Math.abs(rect.height - 1080) > 2) {
|
|
742
|
+
host.remove()
|
|
743
|
+
throw new Error(
|
|
744
|
+
`Slide ${index + 1} export slide is ${Math.round(rect.width)}x${Math.round(rect.height)}, expected 1920x1080.`
|
|
745
|
+
)
|
|
746
|
+
}
|
|
747
|
+
if (Math.abs(canvasRect.width - 1920) > 2 || Math.abs(canvasRect.height - 1080) > 2) {
|
|
748
|
+
host.remove()
|
|
749
|
+
throw new Error(
|
|
750
|
+
`Slide ${index + 1} export canvas is ${Math.round(canvasRect.width)}x${Math.round(canvasRect.height)}, expected 1920x1080.`
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
|
|
509
754
|
await Promise.all(
|
|
510
|
-
Array.from(
|
|
755
|
+
Array.from(exportTarget.querySelectorAll("img")).map(async (img) => {
|
|
511
756
|
if (img.complete) return
|
|
512
757
|
await new Promise((resolve) => {
|
|
513
758
|
img.addEventListener("load", resolve, { once: true })
|
|
@@ -516,15 +761,19 @@ async function exportSlidePptx(
|
|
|
516
761
|
})
|
|
517
762
|
)
|
|
518
763
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
764
|
+
try {
|
|
765
|
+
const blob: Blob = await domToPptx.exportToPptx(exportTarget, {
|
|
766
|
+
fileName: `slide-${index + 1}.pptx`,
|
|
767
|
+
skipDownload: true,
|
|
768
|
+
svgAsVector: false,
|
|
769
|
+
autoEmbedFonts,
|
|
770
|
+
width: 10,
|
|
771
|
+
height: 5.625,
|
|
772
|
+
})
|
|
773
|
+
return Array.from(new Uint8Array(await blob.arrayBuffer()))
|
|
774
|
+
} finally {
|
|
775
|
+
host.remove()
|
|
776
|
+
}
|
|
528
777
|
}, { index: slide.index, autoEmbedFonts: options.autoEmbedFonts })
|
|
529
778
|
|
|
530
779
|
return {
|
|
@@ -717,6 +966,55 @@ function setSpeakerNotes(files: ZipFiles, notesPath: string, notes: string | nul
|
|
|
717
966
|
files[notesPath] = xmlToBytes(doc)
|
|
718
967
|
}
|
|
719
968
|
|
|
969
|
+
function stripSvgBlipFallbacks(pptxBytes: Uint8Array): Uint8Array {
|
|
970
|
+
const files = unzipSync(pptxBytes)
|
|
971
|
+
const orphanedSvgParts = new Set<string>()
|
|
972
|
+
|
|
973
|
+
for (const slidePath of Object.keys(files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path))) {
|
|
974
|
+
const slideXml = getFileText(files, slidePath)
|
|
975
|
+
if (!slideXml.includes("asvg:svgBlip")) continue
|
|
976
|
+
|
|
977
|
+
const slideDoc = parseXml(slideXml)
|
|
978
|
+
const relsPath = relsPathForPart(slidePath)
|
|
979
|
+
const relsDoc = files[relsPath] ? parseXml(getFileText(files, relsPath)) : null
|
|
980
|
+
const svgRelIds = new Set<string>()
|
|
981
|
+
|
|
982
|
+
for (const svgBlip of Array.from(slideDoc.getElementsByTagName("asvg:svgBlip"))) {
|
|
983
|
+
const relId = svgBlip.getAttribute("r:embed")
|
|
984
|
+
if (relId) svgRelIds.add(relId)
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
for (const blip of Array.from(slideDoc.getElementsByTagName("a:blip"))) {
|
|
988
|
+
for (const extLst of Array.from(blip.getElementsByTagName("a:extLst"))) {
|
|
989
|
+
if (Array.from(extLst.getElementsByTagName("asvg:svgBlip")).length === 0) continue
|
|
990
|
+
blip.removeChild(extLst)
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (relsDoc && svgRelIds.size > 0) {
|
|
995
|
+
const relRoot = relsDoc.getElementsByTagName("Relationships")[0]
|
|
996
|
+
for (const rel of Array.from(relsDoc.getElementsByTagName("Relationship"))) {
|
|
997
|
+
const relId = rel.getAttribute("Id")
|
|
998
|
+
if (!relId || !svgRelIds.has(relId)) continue
|
|
999
|
+
const target = rel.getAttribute("Target")
|
|
1000
|
+
if (target) orphanedSvgParts.add(resolveRelationshipTarget(slidePath, target))
|
|
1001
|
+
relRoot?.removeChild(rel)
|
|
1002
|
+
}
|
|
1003
|
+
files[relsPath] = xmlToBytes(relsDoc)
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
files[slidePath] = xmlToBytes(slideDoc)
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
for (const partPath of orphanedSvgParts) {
|
|
1010
|
+
if (/^ppt\/media\/.+\.svg$/i.test(partPath)) {
|
|
1011
|
+
delete files[partPath]
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return zipSync(files, { level: 0 })
|
|
1016
|
+
}
|
|
1017
|
+
|
|
720
1018
|
export function applySpeakerNotesToPptx(pptxBytes: Uint8Array, notesBySlide: Array<string | null | undefined>): Uint8Array {
|
|
721
1019
|
const files = unzipSync(pptxBytes)
|
|
722
1020
|
|
|
@@ -724,7 +1022,7 @@ export function applySpeakerNotesToPptx(pptxBytes: Uint8Array, notesBySlide: Arr
|
|
|
724
1022
|
setSpeakerNotes(files, `ppt/notesSlides/notesSlide${index + 1}.xml`, notes ?? null)
|
|
725
1023
|
})
|
|
726
1024
|
|
|
727
|
-
return zipSync(files)
|
|
1025
|
+
return stripSvgBlipFallbacks(zipSync(files))
|
|
728
1026
|
}
|
|
729
1027
|
|
|
730
1028
|
function updateAppProperties(files: ZipFiles, slideCount: number): void {
|
|
@@ -898,7 +1196,7 @@ function mergeSingleSlidePptx(slides: ExportedSlide[]): Uint8Array {
|
|
|
898
1196
|
mergedFiles["ppt/_rels/presentation.xml.rels"] = xmlToBytes(presentationRelsDoc)
|
|
899
1197
|
updateAppProperties(mergedFiles, slides.length)
|
|
900
1198
|
|
|
901
|
-
return zipSync(mergedFiles, { level: 0 })
|
|
1199
|
+
return stripSvgBlipFallbacks(zipSync(mergedFiles, { level: 0 }))
|
|
902
1200
|
}
|
|
903
1201
|
|
|
904
1202
|
export async function exportToPptx(
|
|
@@ -937,7 +1235,7 @@ export async function exportToPptx(
|
|
|
937
1235
|
const prepareStart = Date.now()
|
|
938
1236
|
const originalHtml = readFileSync(abs, "utf-8")
|
|
939
1237
|
const localized = await localizeExternalImages(originalHtml, tmpDir)
|
|
940
|
-
const patchedHtml = await inlineImageAssets(localized.html, abs)
|
|
1238
|
+
const patchedHtml = withExportBaseHref(await inlineImageAssets(localized.html, abs), abs)
|
|
941
1239
|
tmpHtmlPath = join(tmpDir, "index.html")
|
|
942
1240
|
writeFileSync(tmpHtmlPath, patchedHtml, "utf-8")
|
|
943
1241
|
timingsMs.prepareMs = Date.now() - prepareStart
|
|
@@ -961,6 +1259,7 @@ export async function exportToPptx(
|
|
|
961
1259
|
try {
|
|
962
1260
|
const pageSetupStart = Date.now()
|
|
963
1261
|
const { page, slideCount, diagnostics } = await preparePage(browser, tmpHtmlPath, domToPptxBundlePath)
|
|
1262
|
+
const rasterizedFormulaCount = await rasterizeFormulaNodes(page, diagnostics)
|
|
964
1263
|
timingsMs.pageSetupMs = Date.now() - pageSetupStart
|
|
965
1264
|
const exported: ExportedSlide[] = []
|
|
966
1265
|
const failures: SlideFailure[] = []
|
|
@@ -969,7 +1268,9 @@ export async function exportToPptx(
|
|
|
969
1268
|
const slides = applySpeakerNotesOverride(await readSlideMeta(page, slideCount), options?.speakerNotes)
|
|
970
1269
|
await emitProgress(options, {
|
|
971
1270
|
kind: "stage",
|
|
972
|
-
message:
|
|
1271
|
+
message: rasterizedFormulaCount > 0
|
|
1272
|
+
? `Deck ready. Rasterized ${rasterizedFormulaCount} formula text member(s). Exporting ${slides.length} slide(s) to editable PPTX parts...`
|
|
1273
|
+
: `Deck ready. Exporting ${slides.length} slide(s) to editable PPTX parts...`,
|
|
973
1274
|
})
|
|
974
1275
|
|
|
975
1276
|
const slideExportStart = Date.now()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyber-dash-tech/revela",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.8",
|
|
4
4
|
"description": "Codex-first CLI/MCP workspace for trusted narrative artifacts from local sources, research, and evidence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"dom-to-pptx": "^1.1.6",
|
|
69
69
|
"fflate": "^0.8.2",
|
|
70
70
|
"jimp": "^1.6.1",
|
|
71
|
+
"katex": "^0.17.0",
|
|
71
72
|
"mammoth": "^1.12.0",
|
|
72
73
|
"pdf-lib": "^1.17.1",
|
|
73
74
|
"puppeteer-core": "^24.40.0",
|