@cyber-dash-tech/revela 0.4.2 → 0.4.6
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/lib/pptx/export.ts +54 -17
- package/package.json +1 -1
package/lib/pptx/export.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { pathToFileURL } from "url"
|
|
|
24
24
|
|
|
25
25
|
const CANVAS_W = 1920
|
|
26
26
|
const CANVAS_H = 1080
|
|
27
|
+
const MIN_PPTX_FONT_SIZE_PT = 6
|
|
27
28
|
const PPT_REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
|
28
29
|
const requireFromExportModule = createRequire(import.meta.url)
|
|
29
30
|
|
|
@@ -169,6 +170,19 @@ function isLocalImageRef(ref: string): boolean {
|
|
|
169
170
|
return IMAGE_EXTS.has(extname(pathPart).toLowerCase())
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
export function extractImageAssetRefsForPptx(htmlContent: string): string[] {
|
|
174
|
+
const assetRefPattern = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))|url\(\s*(?:"([^"]*)"|'([^']*)'|([^\s)]+))\s*\)/g
|
|
175
|
+
const refs = new Set<string>()
|
|
176
|
+
let match: RegExpExecArray | null
|
|
177
|
+
|
|
178
|
+
while ((match = assetRefPattern.exec(htmlContent)) !== null) {
|
|
179
|
+
const ref = match.slice(1).find((value): value is string => value !== undefined)
|
|
180
|
+
if (ref) refs.add(ref.trim())
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return Array.from(refs)
|
|
184
|
+
}
|
|
185
|
+
|
|
172
186
|
async function toDataUrlFromRef(ref: string, baseDir: string): Promise<string | null> {
|
|
173
187
|
if (!ref || ref.startsWith("data:") || ref.startsWith("blob:") || ref.startsWith("#")) {
|
|
174
188
|
return null
|
|
@@ -195,21 +209,15 @@ async function toDataUrlFromRef(ref: string, baseDir: string): Promise<string |
|
|
|
195
209
|
}
|
|
196
210
|
}
|
|
197
211
|
|
|
198
|
-
async function inlineImageAssets(htmlContent: string, htmlFilePath: string): Promise<string> {
|
|
212
|
+
export async function inlineImageAssets(htmlContent: string, htmlFilePath: string): Promise<string> {
|
|
199
213
|
const baseDir = dirname(resolve(htmlFilePath))
|
|
200
|
-
const
|
|
201
|
-
const refs = new Set<string>()
|
|
202
|
-
let match: RegExpExecArray | null
|
|
203
|
-
|
|
204
|
-
while ((match = urlPattern.exec(htmlContent)) !== null) {
|
|
205
|
-
refs.add(match[1])
|
|
206
|
-
}
|
|
214
|
+
const refs = extractImageAssetRefsForPptx(htmlContent)
|
|
207
215
|
|
|
208
|
-
if (refs.
|
|
216
|
+
if (refs.length === 0) return htmlContent
|
|
209
217
|
|
|
210
218
|
const replacements = new Map<string, string>()
|
|
211
219
|
await Promise.allSettled(
|
|
212
|
-
|
|
220
|
+
refs.map(async (ref) => {
|
|
213
221
|
const dataUrl = await toDataUrlFromRef(ref, baseDir)
|
|
214
222
|
if (dataUrl) replacements.set(ref, dataUrl)
|
|
215
223
|
})
|
|
@@ -223,15 +231,44 @@ async function inlineImageAssets(htmlContent: string, htmlFilePath: string): Pro
|
|
|
223
231
|
return patched
|
|
224
232
|
}
|
|
225
233
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
export function enforceMinimumPptxFontSize(
|
|
235
|
+
pptxBytes: Uint8Array,
|
|
236
|
+
minFontSizePt = MIN_PPTX_FONT_SIZE_PT,
|
|
237
|
+
): Uint8Array {
|
|
238
|
+
const files = unzipSync(pptxBytes)
|
|
239
|
+
const minSz = Math.round(minFontSizePt * 100)
|
|
240
|
+
const textPropertyTags = ["a:rPr", "a:defRPr", "a:endParaRPr"]
|
|
230
241
|
|
|
231
|
-
|
|
232
|
-
|
|
242
|
+
for (const path of Object.keys(files)) {
|
|
243
|
+
if (!/^ppt\/slides\/slide\d+\.xml$/.test(path)) continue
|
|
244
|
+
|
|
245
|
+
const doc = parseXml(strFromU8(files[path]))
|
|
246
|
+
let changed = false
|
|
247
|
+
|
|
248
|
+
for (const tag of textPropertyTags) {
|
|
249
|
+
for (const node of Array.from(doc.getElementsByTagName(tag))) {
|
|
250
|
+
const raw = node.getAttribute("sz")
|
|
251
|
+
if (!raw) continue
|
|
252
|
+
|
|
253
|
+
const sz = Number(raw)
|
|
254
|
+
if (!Number.isFinite(sz) || sz >= minSz) continue
|
|
255
|
+
|
|
256
|
+
node.setAttribute("sz", String(minSz))
|
|
257
|
+
changed = true
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (changed) files[path] = xmlToBytes(doc)
|
|
233
262
|
}
|
|
234
263
|
|
|
264
|
+
return zipSync(files)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function localizeExternalImages(htmlContent: string, tmpDir: string): Promise<LocalizeExternalImagesResult> {
|
|
268
|
+
const uniqueUrls = new Set(
|
|
269
|
+
extractImageAssetRefsForPptx(htmlContent).filter((ref) => ref.startsWith("http://") || ref.startsWith("https://"))
|
|
270
|
+
)
|
|
271
|
+
|
|
235
272
|
if (uniqueUrls.size === 0) {
|
|
236
273
|
return {
|
|
237
274
|
html: htmlContent,
|
|
@@ -509,7 +546,7 @@ async function exportSlidePptx(
|
|
|
509
546
|
|
|
510
547
|
return {
|
|
511
548
|
...slide,
|
|
512
|
-
bytes: Uint8Array.from(pptxBytes),
|
|
549
|
+
bytes: enforceMinimumPptxFontSize(Uint8Array.from(pptxBytes)),
|
|
513
550
|
}
|
|
514
551
|
} catch (error) {
|
|
515
552
|
throw formatSlideFailure(error, diagnostics.slice(diagStart ?? 0), slide)
|