@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.
- package/lib/pdf/export.ts +80 -1
- 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
|
|
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) {
|