@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.
Files changed (2) hide show
  1. package/lib/pptx/export.ts +54 -17
  2. package/package.json +1 -1
@@ -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 urlPattern = /(?:src=["']|url\(["']?)([^"')>\s]+)/g
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.size === 0) return htmlContent
216
+ if (refs.length === 0) return htmlContent
209
217
 
210
218
  const replacements = new Map<string, string>()
211
219
  await Promise.allSettled(
212
- Array.from(refs).map(async (ref) => {
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
- async function localizeExternalImages(htmlContent: string, tmpDir: string): Promise<LocalizeExternalImagesResult> {
227
- const urlPattern = /(?:src=["']|url\(["']?)(https?:\/\/[^"')>\s]+)/g
228
- const uniqueUrls = new Set<string>()
229
- let match: RegExpExecArray | null
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
- while ((match = urlPattern.exec(htmlContent)) !== null) {
232
- uniqueUrls.add(match[1])
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.4.2",
3
+ "version": "0.4.6",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",