pdfn 0.8.0 → 0.8.2

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/README.md CHANGED
@@ -31,11 +31,9 @@ npx pdfn dev --open # Start and open browser
31
31
  - Live preview with hot reload
32
32
  - Inspector panel (performance, debug overlays)
33
33
  - Accessibility checker (axe-core)
34
- - PDF/A standard dropdown — requires `PDFN_API_KEY`
34
+ - PDF/A compliance dropdown
35
35
 
36
- **PDF/A Standards:**
37
-
38
- Generate archival PDFs via pdfn Cloud:
36
+ **PDF/A Compliance:**
39
37
 
40
38
  | Standard | Description |
41
39
  |----------|-------------|
@@ -43,7 +41,11 @@ Generate archival PDFs via pdfn Cloud:
43
41
  | PDF/A-2b | PDF 1.7 archival, allows transparency |
44
42
  | PDF/A-3b | Like PDF/A-2b plus embedded files |
45
43
 
46
- Select a standard from the dropdown in the dev server UI. PDF/A downloads require a pdfn Cloud API key (`PDFN_API_KEY`).
44
+ > `pdfn dev` focuses on layout preview. PDF/A archival compliance (validation, metadata, color profiles) is applied by pdfn Cloud as post-processing.
45
+ >
46
+ > **Layout is identical** whether you use pdfn dev, pdfn Cloud, or self-host. Compliance does not change layout — it only adds validation and archival metadata.
47
+
48
+ To generate PDF/A-compliant PDFs, set `PDFN_API_KEY`. Without an API key, requests with a standard will fail.
47
49
 
48
50
  **Server API:**
49
51
 
package/dist/cli.js CHANGED
@@ -1708,7 +1708,7 @@ async function generatePdf(page, html, options = {}) {
1708
1708
  contentLoadTime = performance.now() - contentStart;
1709
1709
  const pagedStart = performance.now();
1710
1710
  await page.waitForFunction(
1711
- () => window.PDFN?.ready === true,
1711
+ () => typeof window.PDFN === "undefined" || window.PDFN?.ready === true,
1712
1712
  { timeout }
1713
1713
  );
1714
1714
  pagedJsTime = performance.now() - pagedStart;
@@ -1764,12 +1764,27 @@ async function generatePdf(page, html, options = {}) {
1764
1764
  function createGenerateHandler(browserManager, options = {}) {
1765
1765
  const { timeout = 3e4, onSuccess, onError } = options;
1766
1766
  return async (req, res) => {
1767
- const { html, options: pdfOptions } = req.body;
1767
+ const { html, standard, options: pdfOptions } = req.body;
1768
1768
  const format = req.query.format;
1769
1769
  if (!html) {
1770
1770
  res.status(400).json({ error: "HTML content is required" });
1771
1771
  return;
1772
1772
  }
1773
+ if (standard) {
1774
+ res.status(400).json({
1775
+ error: `PDF/A archival compliance requires the pdfn Cloud compliance pipeline.
1776
+
1777
+ Layout is identical in local dev. Compliance does not change layout or rendering \u2014 it only adds validation and archival metadata.
1778
+
1779
+ To generate ${standard}-compliant PDFs:
1780
+ Set: PDFN_API_KEY=pdfn_live_...
1781
+ Get key at: https://console.pdfn.dev
1782
+
1783
+ To preview layout without compliance:
1784
+ Remove the 'standard' option`
1785
+ });
1786
+ return;
1787
+ }
1773
1788
  if (format === "html") {
1774
1789
  res.setHeader("Content-Type", "text/html; charset=utf-8");
1775
1790
  res.send(html);
@@ -2642,7 +2657,7 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
2642
2657
 
2643
2658
  .btn-primary:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
2644
2659
 
2645
- .conformance-select {
2660
+ .standard-select {
2646
2661
  padding: 6px 10px;
2647
2662
  background: var(--surface-2);
2648
2663
  border: 1px solid #333;
@@ -2652,8 +2667,8 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
2652
2667
  cursor: pointer;
2653
2668
  }
2654
2669
 
2655
- .conformance-select:hover { border-color: #444; }
2656
- .conformance-select:focus { outline: none; border-color: var(--primary); }
2670
+ .standard-select:hover { border-color: #444; }
2671
+ .standard-select:focus { outline: none; border-color: var(--primary); }
2657
2672
 
2658
2673
  .cloud-key-message {
2659
2674
  display: none;
@@ -2730,12 +2745,11 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
2730
2745
  <div class="page-info" id="page-info"></div>
2731
2746
  </div>
2732
2747
  <div class="context-actions">
2733
- <select id="conformance-select" class="conformance-select" title="PDF conformance level">
2748
+ <select id="standard-select" class="standard-select" title="PDF standard">
2734
2749
  <option value="">Standard PDF (Local)</option>
2735
2750
  <option value="PDF/A-1b">PDF/A-1b (Cloud)</option>
2736
2751
  <option value="PDF/A-2b">PDF/A-2b (Cloud)</option>
2737
2752
  <option value="PDF/A-3b">PDF/A-3b (Cloud)</option>
2738
- <option value="PDF/UA">PDF/UA (Cloud)</option>
2739
2753
  </select>
2740
2754
  <button class="btn" id="preview-pdf" title="Preview PDF">
2741
2755
  <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -3243,23 +3257,23 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
3243
3257
 
3244
3258
  document.getElementById('view-html').href = htmlUrl;
3245
3259
 
3246
- // Preview button handler (local only - hidden when conformance selected)
3260
+ // Preview button handler (local only - hidden when standard selected)
3247
3261
  document.getElementById('preview-pdf').onclick = () => {
3248
3262
  window.open(pdfUrl, '_blank');
3249
3263
  };
3250
3264
 
3251
3265
  // Download button handler
3252
3266
  document.getElementById('download-pdf').onclick = async () => {
3253
- const conformance = document.getElementById('conformance-select').value;
3267
+ const standard = document.getElementById('standard-select').value;
3254
3268
  const btn = document.getElementById('download-pdf');
3255
3269
 
3256
- if (conformance) {
3257
- // Use pdfn cloud for conformance PDFs
3270
+ if (standard) {
3271
+ // Use pdfn cloud for PDF/A standard
3258
3272
  btn.innerHTML = spinnerSvg + ' Generating...';
3259
3273
  btn.disabled = true;
3260
3274
 
3261
3275
  try {
3262
- const response = await fetch('/api/template/' + templateId + '/pdf-conformance?conformance=' + encodeURIComponent(conformance));
3276
+ const response = await fetch('/api/template/' + templateId + '/pdf-standard?standard=' + encodeURIComponent(standard));
3263
3277
  if (!response.ok) {
3264
3278
  const error = await response.json();
3265
3279
  throw new Error(error.error || 'Failed to generate PDF');
@@ -3309,20 +3323,20 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
3309
3323
  const downloadIcon = '<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>';
3310
3324
  const cloudDownloadIcon = '<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"/></svg>';
3311
3325
 
3312
- // Update Preview and Download buttons based on conformance selection
3326
+ // Update Preview and Download buttons based on standard selection
3313
3327
  function updateActionButtons() {
3314
- const conformance = document.getElementById('conformance-select').value;
3328
+ const standard = document.getElementById('standard-select').value;
3315
3329
  const previewBtn = document.getElementById('preview-pdf');
3316
3330
  const downloadBtn = document.getElementById('download-pdf');
3317
3331
  const cloudMessage = document.getElementById('cloud-key-message');
3318
- const needsCloud = !!conformance;
3332
+ const needsCloud = !!standard;
3319
3333
  const cloudAvailable = hasCloudAccess;
3320
3334
 
3321
3335
  if (needsCloud) {
3322
3336
  // Cloud mode - hide Preview, show cloud Download
3323
3337
  previewBtn.style.display = 'none';
3324
3338
  downloadBtn.innerHTML = cloudDownloadIcon + ' Download';
3325
- downloadBtn.title = 'Download ' + conformance + ' PDF via pdfn Cloud';
3339
+ downloadBtn.title = 'Download ' + standard + ' PDF via pdfn Cloud';
3326
3340
 
3327
3341
  if (!cloudAvailable) {
3328
3342
  // Show message and disable Download
@@ -3353,7 +3367,7 @@ function createPreviewHTML(templates, activeTemplate, hasCloudAccess) {
3353
3367
  }
3354
3368
 
3355
3369
  // Conformance select change handler
3356
- document.getElementById('conformance-select').onchange = updateActionButtons;
3370
+ document.getElementById('standard-select').onchange = updateActionButtons;
3357
3371
 
3358
3372
  // Accessibility check handler
3359
3373
  let a11yHasRun = false;
@@ -3756,22 +3770,22 @@ async function startDevServer(options) {
3756
3770
  res.status(500).send(`Error generating PDF: ${error}`);
3757
3771
  }
3758
3772
  });
3759
- app.get("/api/template/:id/pdf-conformance", async (req, res) => {
3773
+ app.get("/api/template/:id/pdf-standard", async (req, res) => {
3760
3774
  const template = templates.find((t) => t.id === req.params.id);
3761
3775
  if (!template) {
3762
3776
  res.status(404).json({ error: "Template not found" });
3763
3777
  return;
3764
3778
  }
3765
- const conformance = req.query.conformance;
3766
- const validConformance = ["PDF/A-1b", "PDF/A-2b", "PDF/A-3b", "PDF/UA"];
3767
- if (!conformance || !validConformance.includes(conformance)) {
3768
- res.status(400).json({ error: `Invalid conformance. Must be one of: ${validConformance.join(", ")}` });
3779
+ const standard = req.query.standard;
3780
+ const validStandards = ["PDF/A-1b", "PDF/A-2b", "PDF/A-3b"];
3781
+ if (!standard || !validStandards.includes(standard)) {
3782
+ res.status(400).json({ error: `Invalid standard. Must be one of: ${validStandards.join(", ")}` });
3769
3783
  return;
3770
3784
  }
3771
3785
  const apiKey = process.env.PDFN_API_KEY;
3772
3786
  if (!apiKey) {
3773
3787
  res.status(400).json({
3774
- error: "PDFN_API_KEY required for conformance PDFs. Get one at https://console.pdfn.dev"
3788
+ error: "PDFN_API_KEY required for PDF/A standard. Get one at https://console.pdfn.dev"
3775
3789
  });
3776
3790
  return;
3777
3791
  }
@@ -3784,7 +3798,7 @@ async function startDevServer(options) {
3784
3798
  "Content-Type": "application/json",
3785
3799
  "Authorization": `Bearer ${apiKey}`
3786
3800
  },
3787
- body: JSON.stringify({ html, conformance })
3801
+ body: JSON.stringify({ html, standard })
3788
3802
  });
3789
3803
  if (!response.ok) {
3790
3804
  const error = await response.json().catch(() => ({ message: response.statusText }));
@@ -3796,7 +3810,7 @@ async function startDevServer(options) {
3796
3810
  console.log(
3797
3811
  chalk.green(" \u2713"),
3798
3812
  template.file,
3799
- chalk.dim(`\u2192 ${conformance}`),
3813
+ chalk.dim(`\u2192 ${standard}`),
3800
3814
  chalk.magenta("(Cloud)"),
3801
3815
  chalk.dim("\u2022"),
3802
3816
  chalk.cyan(`${duration}ms`),
@@ -3808,7 +3822,7 @@ async function startDevServer(options) {
3808
3822
  res.send(pdfBuffer);
3809
3823
  } catch (error) {
3810
3824
  const message = error instanceof Error ? error.message : "Unknown error";
3811
- console.log(chalk.red(" \u2717"), template.file, chalk.red(`${conformance} generation failed:`), message);
3825
+ console.log(chalk.red(" \u2717"), template.file, chalk.red(`${standard} generation failed:`), message);
3812
3826
  res.status(500).json({ error: message });
3813
3827
  }
3814
3828
  });