@cyber-dash-tech/revela 0.5.0 → 0.5.1

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/pdf/export.ts +80 -1
  2. package/package.json +1 -1
package/lib/pdf/export.ts CHANGED
@@ -57,6 +57,18 @@ const MIME_TO_EXT: Record<string, string> = {
57
57
  "image/avif": ".avif",
58
58
  }
59
59
 
60
+ const IMAGE_EXTS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".avif", ".bmp"])
61
+ const EXT_TO_MIME: Record<string, string> = {
62
+ ".jpg": "image/jpeg",
63
+ ".jpeg": "image/jpeg",
64
+ ".png": "image/png",
65
+ ".gif": "image/gif",
66
+ ".webp": "image/webp",
67
+ ".svg": "image/svg+xml",
68
+ ".avif": "image/avif",
69
+ ".bmp": "image/bmp",
70
+ }
71
+
60
72
  // ── Helpers ──────────────────────────────────────────────────────────────────
61
73
 
62
74
  function findChromePath(): string {
@@ -149,6 +161,72 @@ async function localizeExternalImages(
149
161
  return patched
150
162
  }
151
163
 
164
+ function isLocalImageRef(ref: string): boolean {
165
+ const pathPart = ref.split(/[?#]/)[0]
166
+ return IMAGE_EXTS.has(extname(pathPart).toLowerCase())
167
+ }
168
+
169
+ export function extractImageAssetRefsForPdf(htmlContent: string): string[] {
170
+ const assetRefPattern = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))|url\(\s*(?:"([^"]*)"|'([^']*)'|([^\s)]+))\s*\)/g
171
+ const refs = new Set<string>()
172
+ let match: RegExpExecArray | null
173
+
174
+ while ((match = assetRefPattern.exec(htmlContent)) !== null) {
175
+ const ref = match.slice(1).find((value): value is string => value !== undefined)
176
+ if (ref) refs.add(ref.trim())
177
+ }
178
+
179
+ return Array.from(refs)
180
+ }
181
+
182
+ async function toDataUrlFromRef(ref: string, baseDir: string): Promise<string | null> {
183
+ if (!ref || ref.startsWith("data:") || ref.startsWith("blob:") || ref.startsWith("#")) {
184
+ return null
185
+ }
186
+
187
+ try {
188
+ if (ref.startsWith("http://") || ref.startsWith("https://") || ref.startsWith("//") || ref.startsWith("file://")) {
189
+ return null
190
+ }
191
+
192
+ let filePath: string | null = null
193
+ if (isLocalImageRef(ref)) {
194
+ filePath = resolve(baseDir, decodeURI(ref.split(/[?#]/)[0]))
195
+ }
196
+
197
+ if (!filePath || !existsSync(filePath)) return null
198
+ const ext = extname(filePath).toLowerCase()
199
+ const mime = EXT_TO_MIME[ext]
200
+ if (!mime) return null
201
+ const buf = readFileSync(filePath)
202
+ return `data:${mime};base64,${buf.toString("base64")}`
203
+ } catch {
204
+ return null
205
+ }
206
+ }
207
+
208
+ export async function inlineImageAssetsForPdf(htmlContent: string, htmlFilePath: string): Promise<string> {
209
+ const baseDir = dirname(resolve(htmlFilePath))
210
+ const refs = extractImageAssetRefsForPdf(htmlContent)
211
+
212
+ if (refs.length === 0) return htmlContent
213
+
214
+ const replacements = new Map<string, string>()
215
+ await Promise.allSettled(
216
+ refs.map(async (ref) => {
217
+ const dataUrl = await toDataUrlFromRef(ref, baseDir)
218
+ if (dataUrl) replacements.set(ref, dataUrl)
219
+ })
220
+ )
221
+
222
+ let patched = htmlContent
223
+ for (const [original, replacement] of replacements) {
224
+ const escaped = original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
225
+ patched = patched.replace(new RegExp(escaped, "g"), replacement)
226
+ }
227
+ return patched
228
+ }
229
+
152
230
  // ── Main export ──────────────────────────────────────────────────────────────
153
231
 
154
232
  export interface ExportResult {
@@ -185,7 +263,8 @@ export async function exportToPdf(htmlFilePath: string): Promise<ExportResult> {
185
263
  let tmpHtmlPath: string
186
264
  try {
187
265
  const originalHtml = readFileSync(abs, "utf-8")
188
- const patchedHtml = await localizeExternalImages(originalHtml, tmpDir)
266
+ const localizedHtml = await localizeExternalImages(originalHtml, tmpDir)
267
+ const patchedHtml = await inlineImageAssetsForPdf(localizedHtml, abs)
189
268
  tmpHtmlPath = join(tmpDir, "index.html")
190
269
  writeFileSync(tmpHtmlPath, patchedHtml, "utf-8")
191
270
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",