mnfst 0.5.74 → 0.5.75

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.
@@ -242,10 +242,30 @@ function initializeExportPlugin() {
242
242
  }
243
243
  }
244
244
 
245
+ // Chromium and Safari reject SVG-image decoding past roughly 16,000px on
246
+ // either axis. A long article at pixelRatio: 2 easily exceeds that. When
247
+ // the target is the whole body we cap dimensions so the assembled SVG
248
+ // stays decodable; explicit targets keep their natural size.
249
+ const SAFE_MAX_DIMENSION = 14000;
250
+
251
+ function applySafeDimensions(target, so) {
252
+ const isWholeBody = target === document.body || target === document.documentElement;
253
+ if (!isWholeBody) return so;
254
+ const rect = target.getBoundingClientRect();
255
+ const width = Math.max(rect.width, target.scrollWidth || 0);
256
+ const height = Math.max(rect.height, target.scrollHeight || 0);
257
+ const ratio = so.pixelRatio || 2;
258
+ const limit = SAFE_MAX_DIMENSION / ratio;
259
+ const out = { ...so };
260
+ if (width > limit) out.canvasWidth = Math.floor(limit);
261
+ if (height > limit) out.canvasHeight = Math.floor(limit);
262
+ return out;
263
+ }
264
+
245
265
  async function exportImage(opts, filename, ext) {
246
266
  const lib = await loadHtmlToImage();
247
267
  const target = resolveTarget(opts);
248
- const so = snapshotOptions(opts);
268
+ const so = applySafeDimensions(target, snapshotOptions(opts));
249
269
  let dataUrl;
250
270
  if (ext === 'png') dataUrl = await snapshotWithFallback(lib, 'toPng', target, so);
251
271
  else if (ext === 'jpeg') {
@@ -262,10 +282,20 @@ function initializeExportPlugin() {
262
282
  triggerDownload(dataUrl, filename);
263
283
  }
264
284
 
285
+ // Whole-page PDF: route through the browser's native print pipeline.
286
+ // It handles multi-page layout, page breaks, vector text, and the page's
287
+ // own @media print CSS — far more reliable than rasterizing a long page
288
+ // and embedding it as a single image. The user sees the print dialog and
289
+ // picks "Save as PDF" (or any installed PDF printer). Element-scoped PDFs
290
+ // continue to use html-to-image + jsPDF, which is well-behaved at small
291
+ // target sizes.
265
292
  async function exportPdf(opts, filename) {
293
+ if (!opts.target) {
294
+ return printToPdf(filename);
295
+ }
266
296
  const [imgLib, jsPDFCtor] = await Promise.all([loadHtmlToImage(), loadJsPDF()]);
267
297
  const target = resolveTarget(opts);
268
- const so = snapshotOptions(opts);
298
+ const so = applySafeDimensions(target, snapshotOptions(opts));
269
299
  if (!so.backgroundColor) so.backgroundColor = effectivePageBackground();
270
300
  const dataUrl = await snapshotWithFallback(imgLib, 'toPng', target, so);
271
301
  const img = await loadImage(dataUrl);
@@ -282,6 +312,20 @@ function initializeExportPlugin() {
282
312
  pdf.save(filename);
283
313
  }
284
314
 
315
+ function printToPdf(filename) {
316
+ // The browser's "Save as PDF" dialog seeds its default filename from
317
+ // document.title. Swap it briefly so the suggested name matches the
318
+ // user's intent, then restore after the dialog closes.
319
+ const original = document.title;
320
+ const cleaned = String(filename || '').replace(/\.pdf$/i, '') || original;
321
+ document.title = cleaned;
322
+ try { window.print(); }
323
+ finally {
324
+ // Wait one frame so the print dialog reads the swapped title first.
325
+ setTimeout(() => { document.title = original; }, 0);
326
+ }
327
+ }
328
+
285
329
  function effectivePageBackground() {
286
330
  let el = document.body;
287
331
  while (el && el !== document.documentElement.parentElement) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.74",
3
+ "version": "0.5.75",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",