@formfeed/engine 0.3.21 → 0.3.22

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/index.cjs CHANGED
@@ -60,6 +60,12 @@ __export(index_exports, {
60
60
  INVOICE_DATA_KEY: () => INVOICE_DATA_KEY,
61
61
  OFFICE_FILLED_MAX_BYTES: () => OFFICE_FILLED_MAX_BYTES,
62
62
  OfficeTemplateError: () => OfficeTemplateError,
63
+ PDFA_DEFAULT_LEVEL: () => PDFA_DEFAULT_LEVEL,
64
+ PDFA_HEADER_LENGTH: () => PDFA_HEADER_LENGTH,
65
+ PDFA_LEVELS: () => PDFA_LEVELS,
66
+ PDFA_MAX_MESSAGES: () => PDFA_MAX_MESSAGES,
67
+ PDFA_REFUSALS: () => PDFA_REFUSALS,
68
+ PDFA_STANDARDS: () => PDFA_STANDARDS,
63
69
  PDFUA_STANDARD: () => PDFUA_STANDARD,
64
70
  RenderError: () => RenderError,
65
71
  RenderLimitError: () => RenderLimitError,
@@ -152,6 +158,8 @@ __export(index_exports, {
152
158
  isApitemplateRegion: () => isApitemplateRegion,
153
159
  isOfficeKind: () => isOfficeKind,
154
160
  isOutputFormat: () => isOutputFormat,
161
+ isPdfaResult: () => isPdfaResult,
162
+ isPdfaSetting: () => isPdfaSetting,
155
163
  isScss: () => isScss,
156
164
  isUaSetting: () => isUaSetting,
157
165
  jinja2Engine: () => jinja2Engine,
@@ -184,6 +192,10 @@ __export(index_exports, {
184
192
  parseDesign: () => parseDesign,
185
193
  parseSourceAttribute: () => parseSourceAttribute,
186
194
  partialInclude: () => partialInclude,
195
+ pdfaFromEinvoice: () => pdfaFromEinvoice,
196
+ pdfaHeaderValue: () => pdfaHeaderValue,
197
+ pdfaResult: () => pdfaResult,
198
+ pdfaStandard: () => pdfaStandard,
187
199
  printReset: () => printReset,
188
200
  qrSvg: () => qrSvg,
189
201
  readJsreportExport: () => readJsreportExport,
@@ -194,6 +206,7 @@ __export(index_exports, {
194
206
  renderVersion: () => renderVersion,
195
207
  resolveEinvoiceOptions: () => resolveEinvoiceOptions,
196
208
  resolvePath: () => resolvePath,
209
+ resolvePdfa: () => resolvePdfa,
197
210
  resolveTranslation: () => resolveTranslation,
198
211
  resolveUa: () => resolveUa,
199
212
  scanBlocks: () => scanBlocks,
@@ -324,7 +337,7 @@ function toNumber(value) {
324
337
  // packages/engine/src/lib/codes/epc.ts
325
338
  function epcPayload(input) {
326
339
  const amount2 = input.amount === void 0 || input.amount === "" ? "" : `EUR${toNumber(input.amount).toFixed(2)}`;
327
- return [
340
+ const elements = [
328
341
  "BCD",
329
342
  "002",
330
343
  "1",
@@ -336,8 +349,12 @@ function epcPayload(input) {
336
349
  (input.purpose ?? "").slice(0, 4),
337
350
  (input.reference ?? "").slice(0, 35),
338
351
  input.reference ? "" : (input.text ?? "").slice(0, 140),
352
+ // the beneficiary to originator information, which a payment here has no field for
339
353
  ""
340
- ].join("\n");
354
+ ];
355
+ while (elements.length > 0 && elements[elements.length - 1] === "")
356
+ elements.pop();
357
+ return elements.join("\n");
341
358
  }
342
359
  var IBAN_LENGTHS = {
343
360
  AD: 24,
@@ -394,34 +411,73 @@ function validateEpc(input) {
394
411
  const problems = [];
395
412
  const name = String(input.name ?? "").trim();
396
413
  if (!name) problems.push({ field: "name", code: "required" });
397
- else if (name.length > 70) problems.push({ field: "name", code: "too_long", max: 70, actual: name.length });
414
+ else if (name.length > 70)
415
+ problems.push({
416
+ field: "name",
417
+ code: "too_long",
418
+ max: 70,
419
+ actual: name.length
420
+ });
398
421
  const iban2 = normaliseIban(String(input.iban ?? ""));
399
422
  if (!iban2) problems.push({ field: "iban", code: "required" });
400
- else if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(iban2)) problems.push({ field: "iban", code: "iban_characters" });
423
+ else if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(iban2))
424
+ problems.push({ field: "iban", code: "iban_characters" });
401
425
  else {
402
426
  const expected = IBAN_LENGTHS[iban2.slice(0, 2)];
403
427
  if (expected !== void 0 && iban2.length !== expected)
404
- problems.push({ field: "iban", code: "iban_country_length", max: expected, actual: iban2.length });
405
- else if (!ibanChecksum(iban2)) problems.push({ field: "iban", code: "iban_checksum" });
428
+ problems.push({
429
+ field: "iban",
430
+ code: "iban_country_length",
431
+ max: expected,
432
+ actual: iban2.length
433
+ });
434
+ else if (!ibanChecksum(iban2))
435
+ problems.push({ field: "iban", code: "iban_checksum" });
406
436
  }
407
437
  const bic = String(input.bic ?? "").replace(/\s/g, "").toUpperCase();
408
- if (bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(bic)) problems.push({ field: "bic", code: "bic_format" });
438
+ if (bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(bic))
439
+ problems.push({ field: "bic", code: "bic_format" });
409
440
  if (input.amount !== void 0 && input.amount !== "") {
410
441
  const amount2 = toNumber(input.amount);
411
- if (Number.isNaN(amount2)) problems.push({ field: "amount", code: "not_a_number" });
412
- else if (amount2 < 0.01 || amount2 > 99999999999e-2) problems.push({ field: "amount", code: "out_of_range" });
442
+ if (Number.isNaN(amount2))
443
+ problems.push({ field: "amount", code: "not_a_number" });
444
+ else if (amount2 < 0.01 || amount2 > 99999999999e-2)
445
+ problems.push({ field: "amount", code: "out_of_range" });
413
446
  }
414
447
  const purpose = String(input.purpose ?? "");
415
- if (purpose.length > 4) problems.push({ field: "purpose", code: "too_long", max: 4, actual: purpose.length });
448
+ if (purpose.length > 4)
449
+ problems.push({
450
+ field: "purpose",
451
+ code: "too_long",
452
+ max: 4,
453
+ actual: purpose.length
454
+ });
416
455
  const reference = String(input.reference ?? "").trim();
417
456
  const text4 = String(input.text ?? "").trim();
418
- if (reference && text4) problems.push({ field: "reference", code: "reference_or_text" });
457
+ if (reference && text4)
458
+ problems.push({ field: "reference", code: "reference_or_text" });
419
459
  if (reference.length > 35)
420
- problems.push({ field: "reference", code: "too_long", max: 35, actual: reference.length });
421
- if (text4.length > 140) problems.push({ field: "text", code: "too_long", max: 140, actual: text4.length });
460
+ problems.push({
461
+ field: "reference",
462
+ code: "too_long",
463
+ max: 35,
464
+ actual: reference.length
465
+ });
466
+ if (text4.length > 140)
467
+ problems.push({
468
+ field: "text",
469
+ code: "too_long",
470
+ max: 140,
471
+ actual: text4.length
472
+ });
422
473
  const bytes = byteLength(epcPayload(input));
423
474
  if (bytes > EPC_MAX_BYTES)
424
- problems.push({ field: "payload", code: "too_long", max: EPC_MAX_BYTES, actual: bytes });
475
+ problems.push({
476
+ field: "payload",
477
+ code: "too_long",
478
+ max: EPC_MAX_BYTES,
479
+ actual: bytes
480
+ });
425
481
  return problems;
426
482
  }
427
483
 
@@ -11501,6 +11557,89 @@ function staticAudit(input) {
11501
11557
  return findings;
11502
11558
  }
11503
11559
 
11560
+ // packages/engine/src/lib/pdfa/index.ts
11561
+ var PDFA_LEVELS = ["2b", "3b"];
11562
+ var PDFA_DEFAULT_LEVEL = "3b";
11563
+ var PDFA_STANDARDS = {
11564
+ "2b": "PDF/A-2b",
11565
+ "3b": "PDF/A-3b"
11566
+ };
11567
+ function resolvePdfa(setting) {
11568
+ if (setting === true) return PDFA_DEFAULT_LEVEL;
11569
+ return typeof setting === "string" && PDFA_LEVELS.includes(setting) ? setting : null;
11570
+ }
11571
+ function isPdfaSetting(value) {
11572
+ return value === null || typeof value === "boolean" || typeof value === "string" && PDFA_LEVELS.includes(value);
11573
+ }
11574
+ function pdfaStandard(level) {
11575
+ return PDFA_STANDARDS[level];
11576
+ }
11577
+ var PDFA_REFUSALS = {
11578
+ password: "pdf.pdfa cannot be combined with post.password: PDF/A does not allow encryption",
11579
+ einvoice: 'pdf.pdfa "2b" cannot be combined with an e-invoice: the invoice XML is an attachment, which PDF/A-2 does not allow. An e-invoice is a PDF/A-3b already; ask for "3b" or leave pdf.pdfa out',
11580
+ invalid: 'settings.pdf.pdfa is "2b", "3b", true, false or null'
11581
+ };
11582
+ var PDFA_MAX_MESSAGES = 50;
11583
+ var asMessage = (message) => ({
11584
+ severity: message.severity === "warning" ? "warning" : "error",
11585
+ rule: typeof message.rule === "string" ? message.rule : null,
11586
+ message: typeof message.message === "string" ? message.message : "",
11587
+ location: typeof message.location === "string" ? message.location : null
11588
+ });
11589
+ var capped = (messages, truncated) => {
11590
+ const sorted = [
11591
+ ...messages.filter((message) => message.severity === "error"),
11592
+ ...messages.filter((message) => message.severity !== "error")
11593
+ ];
11594
+ return sorted.length > PDFA_MAX_MESSAGES || truncated ? { messages: sorted.slice(0, PDFA_MAX_MESSAGES), truncated: true } : { messages: sorted };
11595
+ };
11596
+ function pdfaResult(level, report, pdfSha256) {
11597
+ return {
11598
+ standard: PDFA_STANDARDS[level],
11599
+ conformant: report.valid === true,
11600
+ validator: report.validator,
11601
+ id: report.id,
11602
+ versions: { ...report.versions ?? {} },
11603
+ pdf_sha256: pdfSha256,
11604
+ ...capped(
11605
+ (Array.isArray(report.messages) ? report.messages : []).map(asMessage),
11606
+ report.truncated === true
11607
+ )
11608
+ };
11609
+ }
11610
+ function pdfaFromEinvoice(validation) {
11611
+ if (!validation || validation.pdfa == null) return null;
11612
+ const messages = validation.messages.filter((message) => message.part === "pdf").map((message) => ({
11613
+ severity: message.severity,
11614
+ rule: message.rule,
11615
+ message: message.message,
11616
+ location: message.location
11617
+ }));
11618
+ const versions = { ...validation.versions ?? {} };
11619
+ return {
11620
+ standard: PDFA_STANDARDS["3b"],
11621
+ conformant: !messages.some((message) => message.severity === "error"),
11622
+ validator: `veraPDF ${versions["verapdf"] ?? "unknown"}`,
11623
+ id: validation.id ?? "",
11624
+ versions,
11625
+ pdf_sha256: validation.pdf_sha256 ?? null,
11626
+ ...capped(messages, false)
11627
+ };
11628
+ }
11629
+ function isPdfaResult(value) {
11630
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
11631
+ const report = value;
11632
+ return typeof report.standard === "string" && Object.values(PDFA_STANDARDS).includes(report.standard) && typeof report.conformant === "boolean";
11633
+ }
11634
+ var PDFA_HEADER_LENGTH = 2e3;
11635
+ function pdfaHeaderValue(result, limit = PDFA_HEADER_LENGTH) {
11636
+ const value = encodeURIComponent(JSON.stringify(result));
11637
+ if (value.length <= limit) return value;
11638
+ return encodeURIComponent(
11639
+ JSON.stringify({ ...result, messages: [], truncated: true })
11640
+ );
11641
+ }
11642
+
11504
11643
  // packages/engine/src/lib/office/errors.ts
11505
11644
  var OfficeError = class extends Error {
11506
11645
  constructor(code, message, details = {}) {
package/index.js CHANGED
@@ -101,7 +101,7 @@ function toNumber(value) {
101
101
  // packages/engine/src/lib/codes/epc.ts
102
102
  function epcPayload(input) {
103
103
  const amount2 = input.amount === void 0 || input.amount === "" ? "" : `EUR${toNumber(input.amount).toFixed(2)}`;
104
- return [
104
+ const elements = [
105
105
  "BCD",
106
106
  "002",
107
107
  "1",
@@ -113,8 +113,12 @@ function epcPayload(input) {
113
113
  (input.purpose ?? "").slice(0, 4),
114
114
  (input.reference ?? "").slice(0, 35),
115
115
  input.reference ? "" : (input.text ?? "").slice(0, 140),
116
+ // the beneficiary to originator information, which a payment here has no field for
116
117
  ""
117
- ].join("\n");
118
+ ];
119
+ while (elements.length > 0 && elements[elements.length - 1] === "")
120
+ elements.pop();
121
+ return elements.join("\n");
118
122
  }
119
123
  var IBAN_LENGTHS = {
120
124
  AD: 24,
@@ -171,34 +175,73 @@ function validateEpc(input) {
171
175
  const problems = [];
172
176
  const name = String(input.name ?? "").trim();
173
177
  if (!name) problems.push({ field: "name", code: "required" });
174
- else if (name.length > 70) problems.push({ field: "name", code: "too_long", max: 70, actual: name.length });
178
+ else if (name.length > 70)
179
+ problems.push({
180
+ field: "name",
181
+ code: "too_long",
182
+ max: 70,
183
+ actual: name.length
184
+ });
175
185
  const iban2 = normaliseIban(String(input.iban ?? ""));
176
186
  if (!iban2) problems.push({ field: "iban", code: "required" });
177
- else if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(iban2)) problems.push({ field: "iban", code: "iban_characters" });
187
+ else if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]{11,30}$/.test(iban2))
188
+ problems.push({ field: "iban", code: "iban_characters" });
178
189
  else {
179
190
  const expected = IBAN_LENGTHS[iban2.slice(0, 2)];
180
191
  if (expected !== void 0 && iban2.length !== expected)
181
- problems.push({ field: "iban", code: "iban_country_length", max: expected, actual: iban2.length });
182
- else if (!ibanChecksum(iban2)) problems.push({ field: "iban", code: "iban_checksum" });
192
+ problems.push({
193
+ field: "iban",
194
+ code: "iban_country_length",
195
+ max: expected,
196
+ actual: iban2.length
197
+ });
198
+ else if (!ibanChecksum(iban2))
199
+ problems.push({ field: "iban", code: "iban_checksum" });
183
200
  }
184
201
  const bic = String(input.bic ?? "").replace(/\s/g, "").toUpperCase();
185
- if (bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(bic)) problems.push({ field: "bic", code: "bic_format" });
202
+ if (bic && !/^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(bic))
203
+ problems.push({ field: "bic", code: "bic_format" });
186
204
  if (input.amount !== void 0 && input.amount !== "") {
187
205
  const amount2 = toNumber(input.amount);
188
- if (Number.isNaN(amount2)) problems.push({ field: "amount", code: "not_a_number" });
189
- else if (amount2 < 0.01 || amount2 > 99999999999e-2) problems.push({ field: "amount", code: "out_of_range" });
206
+ if (Number.isNaN(amount2))
207
+ problems.push({ field: "amount", code: "not_a_number" });
208
+ else if (amount2 < 0.01 || amount2 > 99999999999e-2)
209
+ problems.push({ field: "amount", code: "out_of_range" });
190
210
  }
191
211
  const purpose = String(input.purpose ?? "");
192
- if (purpose.length > 4) problems.push({ field: "purpose", code: "too_long", max: 4, actual: purpose.length });
212
+ if (purpose.length > 4)
213
+ problems.push({
214
+ field: "purpose",
215
+ code: "too_long",
216
+ max: 4,
217
+ actual: purpose.length
218
+ });
193
219
  const reference = String(input.reference ?? "").trim();
194
220
  const text4 = String(input.text ?? "").trim();
195
- if (reference && text4) problems.push({ field: "reference", code: "reference_or_text" });
221
+ if (reference && text4)
222
+ problems.push({ field: "reference", code: "reference_or_text" });
196
223
  if (reference.length > 35)
197
- problems.push({ field: "reference", code: "too_long", max: 35, actual: reference.length });
198
- if (text4.length > 140) problems.push({ field: "text", code: "too_long", max: 140, actual: text4.length });
224
+ problems.push({
225
+ field: "reference",
226
+ code: "too_long",
227
+ max: 35,
228
+ actual: reference.length
229
+ });
230
+ if (text4.length > 140)
231
+ problems.push({
232
+ field: "text",
233
+ code: "too_long",
234
+ max: 140,
235
+ actual: text4.length
236
+ });
199
237
  const bytes = byteLength(epcPayload(input));
200
238
  if (bytes > EPC_MAX_BYTES)
201
- problems.push({ field: "payload", code: "too_long", max: EPC_MAX_BYTES, actual: bytes });
239
+ problems.push({
240
+ field: "payload",
241
+ code: "too_long",
242
+ max: EPC_MAX_BYTES,
243
+ actual: bytes
244
+ });
202
245
  return problems;
203
246
  }
204
247
 
@@ -11278,6 +11321,89 @@ function staticAudit(input) {
11278
11321
  return findings;
11279
11322
  }
11280
11323
 
11324
+ // packages/engine/src/lib/pdfa/index.ts
11325
+ var PDFA_LEVELS = ["2b", "3b"];
11326
+ var PDFA_DEFAULT_LEVEL = "3b";
11327
+ var PDFA_STANDARDS = {
11328
+ "2b": "PDF/A-2b",
11329
+ "3b": "PDF/A-3b"
11330
+ };
11331
+ function resolvePdfa(setting) {
11332
+ if (setting === true) return PDFA_DEFAULT_LEVEL;
11333
+ return typeof setting === "string" && PDFA_LEVELS.includes(setting) ? setting : null;
11334
+ }
11335
+ function isPdfaSetting(value) {
11336
+ return value === null || typeof value === "boolean" || typeof value === "string" && PDFA_LEVELS.includes(value);
11337
+ }
11338
+ function pdfaStandard(level) {
11339
+ return PDFA_STANDARDS[level];
11340
+ }
11341
+ var PDFA_REFUSALS = {
11342
+ password: "pdf.pdfa cannot be combined with post.password: PDF/A does not allow encryption",
11343
+ einvoice: 'pdf.pdfa "2b" cannot be combined with an e-invoice: the invoice XML is an attachment, which PDF/A-2 does not allow. An e-invoice is a PDF/A-3b already; ask for "3b" or leave pdf.pdfa out',
11344
+ invalid: 'settings.pdf.pdfa is "2b", "3b", true, false or null'
11345
+ };
11346
+ var PDFA_MAX_MESSAGES = 50;
11347
+ var asMessage = (message) => ({
11348
+ severity: message.severity === "warning" ? "warning" : "error",
11349
+ rule: typeof message.rule === "string" ? message.rule : null,
11350
+ message: typeof message.message === "string" ? message.message : "",
11351
+ location: typeof message.location === "string" ? message.location : null
11352
+ });
11353
+ var capped = (messages, truncated) => {
11354
+ const sorted = [
11355
+ ...messages.filter((message) => message.severity === "error"),
11356
+ ...messages.filter((message) => message.severity !== "error")
11357
+ ];
11358
+ return sorted.length > PDFA_MAX_MESSAGES || truncated ? { messages: sorted.slice(0, PDFA_MAX_MESSAGES), truncated: true } : { messages: sorted };
11359
+ };
11360
+ function pdfaResult(level, report, pdfSha256) {
11361
+ return {
11362
+ standard: PDFA_STANDARDS[level],
11363
+ conformant: report.valid === true,
11364
+ validator: report.validator,
11365
+ id: report.id,
11366
+ versions: { ...report.versions ?? {} },
11367
+ pdf_sha256: pdfSha256,
11368
+ ...capped(
11369
+ (Array.isArray(report.messages) ? report.messages : []).map(asMessage),
11370
+ report.truncated === true
11371
+ )
11372
+ };
11373
+ }
11374
+ function pdfaFromEinvoice(validation) {
11375
+ if (!validation || validation.pdfa == null) return null;
11376
+ const messages = validation.messages.filter((message) => message.part === "pdf").map((message) => ({
11377
+ severity: message.severity,
11378
+ rule: message.rule,
11379
+ message: message.message,
11380
+ location: message.location
11381
+ }));
11382
+ const versions = { ...validation.versions ?? {} };
11383
+ return {
11384
+ standard: PDFA_STANDARDS["3b"],
11385
+ conformant: !messages.some((message) => message.severity === "error"),
11386
+ validator: `veraPDF ${versions["verapdf"] ?? "unknown"}`,
11387
+ id: validation.id ?? "",
11388
+ versions,
11389
+ pdf_sha256: validation.pdf_sha256 ?? null,
11390
+ ...capped(messages, false)
11391
+ };
11392
+ }
11393
+ function isPdfaResult(value) {
11394
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
11395
+ const report = value;
11396
+ return typeof report.standard === "string" && Object.values(PDFA_STANDARDS).includes(report.standard) && typeof report.conformant === "boolean";
11397
+ }
11398
+ var PDFA_HEADER_LENGTH = 2e3;
11399
+ function pdfaHeaderValue(result, limit = PDFA_HEADER_LENGTH) {
11400
+ const value = encodeURIComponent(JSON.stringify(result));
11401
+ if (value.length <= limit) return value;
11402
+ return encodeURIComponent(
11403
+ JSON.stringify({ ...result, messages: [], truncated: true })
11404
+ );
11405
+ }
11406
+
11281
11407
  // packages/engine/src/lib/office/errors.ts
11282
11408
  var OfficeError = class extends Error {
11283
11409
  constructor(code, message, details = {}) {
@@ -14201,6 +14327,12 @@ export {
14201
14327
  INVOICE_DATA_KEY,
14202
14328
  OFFICE_FILLED_MAX_BYTES,
14203
14329
  OfficeTemplateError,
14330
+ PDFA_DEFAULT_LEVEL,
14331
+ PDFA_HEADER_LENGTH,
14332
+ PDFA_LEVELS,
14333
+ PDFA_MAX_MESSAGES,
14334
+ PDFA_REFUSALS,
14335
+ PDFA_STANDARDS,
14204
14336
  PDFUA_STANDARD,
14205
14337
  RenderError,
14206
14338
  RenderLimitError,
@@ -14293,6 +14425,8 @@ export {
14293
14425
  isApitemplateRegion,
14294
14426
  isOfficeKind,
14295
14427
  isOutputFormat,
14428
+ isPdfaResult,
14429
+ isPdfaSetting,
14296
14430
  isScss,
14297
14431
  isUaSetting,
14298
14432
  jinja2Engine,
@@ -14325,6 +14459,10 @@ export {
14325
14459
  parseDesign,
14326
14460
  parseSourceAttribute,
14327
14461
  partialInclude,
14462
+ pdfaFromEinvoice,
14463
+ pdfaHeaderValue,
14464
+ pdfaResult,
14465
+ pdfaStandard,
14328
14466
  printReset,
14329
14467
  qrSvg,
14330
14468
  readJsreportExport,
@@ -14335,6 +14473,7 @@ export {
14335
14473
  renderVersion,
14336
14474
  resolveEinvoiceOptions,
14337
14475
  resolvePath,
14476
+ resolvePdfa,
14338
14477
  resolveTranslation,
14339
14478
  resolveUa,
14340
14479
  scanBlocks,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formfeed/engine",
3
- "version": "0.3.21",
3
+ "version": "0.3.22",
4
4
  "description": "The Formfeed template engine (Jinja2, Liquid, Handlebars) for the browser and Node.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/types/index.d.cts CHANGED
@@ -35,6 +35,8 @@ export type { CheckOptions, DisplayCheck, DisplayValue, EinvoiceDisplay, Einvoic
35
35
  export { ACCESSIBILITY_HEADER_LENGTH, ACCESSIBILITY_MAX_ELEMENTS, ACCESSIBILITY_MAX_FAILURES, AUDIT_CODES, AUDIT_RULES, AUDIT_SEVERITY, AUDIT_SNIPPET_LENGTH, AUDIT_TITLES, PDFUA_STANDARD, UA_CHECKS, UA_REFUSALS, accessibilityHeader, accessibilityHeaderValue, accessibilityResult, auditMessage, isUaSetting, resolveUa, } from './lib/accessibility/index.cjs';
36
36
  export type { AccessibilityFailure, AccessibilityResult, AccessibilityWarning, AuditArgs, AuditCode, AuditFinding, AuditLink, AuditOptions, AuditReport, AuditSeverity, UaCheck, UaOptions, UaSetting, ValidatorFailure, ValidatorVerdict, } from './lib/accessibility/index.cjs';
37
37
  export { staticAudit, staticAuditDiagnostics, } from './lib/accessibility/static.cjs';
38
+ export { PDFA_DEFAULT_LEVEL, PDFA_HEADER_LENGTH, PDFA_LEVELS, PDFA_MAX_MESSAGES, PDFA_REFUSALS, PDFA_STANDARDS, isPdfaResult, isPdfaSetting, pdfaFromEinvoice, pdfaHeaderValue, pdfaResult, pdfaStandard, resolvePdfa, } from './lib/pdfa/index.cjs';
39
+ export type { PdfaLevel, PdfaMessage, PdfaResult, PdfaSetting, PdfaStandard, PdfaValidatorReport, } from './lib/pdfa/index.cjs';
38
40
  export type { StaticAuditDiagnostic, StaticAuditInput, } from './lib/accessibility/static.cjs';
39
41
  export { auditRuntime } from './lib/generated/frames.cjs';
40
42
  export { encodeTemplateText } from './lib/source-edit.cjs';
package/types/index.d.ts CHANGED
@@ -35,6 +35,8 @@ export type { CheckOptions, DisplayCheck, DisplayValue, EinvoiceDisplay, Einvoic
35
35
  export { ACCESSIBILITY_HEADER_LENGTH, ACCESSIBILITY_MAX_ELEMENTS, ACCESSIBILITY_MAX_FAILURES, AUDIT_CODES, AUDIT_RULES, AUDIT_SEVERITY, AUDIT_SNIPPET_LENGTH, AUDIT_TITLES, PDFUA_STANDARD, UA_CHECKS, UA_REFUSALS, accessibilityHeader, accessibilityHeaderValue, accessibilityResult, auditMessage, isUaSetting, resolveUa, } from './lib/accessibility/index.js';
36
36
  export type { AccessibilityFailure, AccessibilityResult, AccessibilityWarning, AuditArgs, AuditCode, AuditFinding, AuditLink, AuditOptions, AuditReport, AuditSeverity, UaCheck, UaOptions, UaSetting, ValidatorFailure, ValidatorVerdict, } from './lib/accessibility/index.js';
37
37
  export { staticAudit, staticAuditDiagnostics, } from './lib/accessibility/static.js';
38
+ export { PDFA_DEFAULT_LEVEL, PDFA_HEADER_LENGTH, PDFA_LEVELS, PDFA_MAX_MESSAGES, PDFA_REFUSALS, PDFA_STANDARDS, isPdfaResult, isPdfaSetting, pdfaFromEinvoice, pdfaHeaderValue, pdfaResult, pdfaStandard, resolvePdfa, } from './lib/pdfa/index.js';
39
+ export type { PdfaLevel, PdfaMessage, PdfaResult, PdfaSetting, PdfaStandard, PdfaValidatorReport, } from './lib/pdfa/index.js';
38
40
  export type { StaticAuditDiagnostic, StaticAuditInput, } from './lib/accessibility/static.js';
39
41
  export { auditRuntime } from './lib/generated/frames.js';
40
42
  export { encodeTemplateText } from './lib/source-edit.js';
@@ -1,4 +1,5 @@
1
1
  import type { UaSetting } from './accessibility/index.cjs';
2
+ import type { PdfaSetting } from './pdfa/index.cjs';
2
3
  import type { BrandContext } from './brand.cjs';
3
4
  import type { EinvoiceOptionsInput } from './einvoice/schema.cjs';
4
5
  import type { EngineId, RenderContext } from './types.cjs';
@@ -56,6 +57,13 @@ export interface TemplateSettings {
56
57
  * implies `tagged`. Read it through `resolveUa`.
57
58
  */
58
59
  ua?: UaSetting;
60
+ /**
61
+ * PDF/A (plan 26): the finished PDF is converted to PDF/A-2b or PDF/A-3b and held to it by the
62
+ * validator; a file that fails fails the render, there is no mode that delivers it. `true` is
63
+ * `'3b'`. An e-invoice is a PDF/A-3b already, so `'2b'` beside one is refused. Read it through
64
+ * `resolvePdfa`.
65
+ */
66
+ pdfa?: PdfaSetting;
59
67
  metadata?: {
60
68
  title?: string;
61
69
  author?: string;
@@ -1,4 +1,5 @@
1
1
  import type { UaSetting } from './accessibility/index.js';
2
+ import type { PdfaSetting } from './pdfa/index.js';
2
3
  import type { BrandContext } from './brand.js';
3
4
  import type { EinvoiceOptionsInput } from './einvoice/schema.js';
4
5
  import type { EngineId, RenderContext } from './types.js';
@@ -56,6 +57,13 @@ export interface TemplateSettings {
56
57
  * implies `tagged`. Read it through `resolveUa`.
57
58
  */
58
59
  ua?: UaSetting;
60
+ /**
61
+ * PDF/A (plan 26): the finished PDF is converted to PDF/A-2b or PDF/A-3b and held to it by the
62
+ * validator; a file that fails fails the render, there is no mode that delivers it. `true` is
63
+ * `'3b'`. An e-invoice is a PDF/A-3b already, so `'2b'` beside one is refused. Read it through
64
+ * `resolvePdfa`.
65
+ */
66
+ pdfa?: PdfaSetting;
59
67
  metadata?: {
60
68
  title?: string;
61
69
  author?: string;
@@ -29,7 +29,11 @@ export interface EpcPayment {
29
29
  }
30
30
  /** What a caller actually hands over: the same fields, none of them guaranteed to be there. */
31
31
  export type EpcInput = Partial<EpcPayment>;
32
- /** EPC QR (GiroCode) payload, version 002, UTF-8, SCT. */
32
+ /**
33
+ * EPC QR (GiroCode) payload, version 002, UTF-8, SCT. It ends with the last populated element:
34
+ * "The last populated element is not followed by any character or element separator" (EPC069-12
35
+ * v3.1 §2.2), so the empty elements after it are left out, line breaks and all.
36
+ */
33
37
  export declare function epcPayload(input: EpcInput): string;
34
38
  /**
35
39
  * What is wrong with a payment, as a code rather than a sentence: the engine has no locale, and the
@@ -29,7 +29,11 @@ export interface EpcPayment {
29
29
  }
30
30
  /** What a caller actually hands over: the same fields, none of them guaranteed to be there. */
31
31
  export type EpcInput = Partial<EpcPayment>;
32
- /** EPC QR (GiroCode) payload, version 002, UTF-8, SCT. */
32
+ /**
33
+ * EPC QR (GiroCode) payload, version 002, UTF-8, SCT. It ends with the last populated element:
34
+ * "The last populated element is not followed by any character or element separator" (EPC069-12
35
+ * v3.1 §2.2), so the empty elements after it are left out, line breaks and all.
36
+ */
33
37
  export declare function epcPayload(input: EpcInput): string;
34
38
  /**
35
39
  * What is wrong with a payment, as a code rather than a sentence: the engine has no locale, and the
@@ -0,0 +1,112 @@
1
+ import type { EinvoiceValidation } from '../einvoice/report.cjs';
2
+ /**
3
+ * `@formfeed/engine/pdfa` (plan 26): what `pdf.pdfa` asks for, what it cannot go with, and the
4
+ * report a render keeps of what the validator said. No template engine is imported here and nothing
5
+ * of Node, so the gateway Worker, the render-worker, the editor and the CLI all read the same names.
6
+ *
7
+ * PDF/A is ISO 19005, a file format for keeping documents: the fonts embedded, the colours defined,
8
+ * nothing that needs the network or a password. Whether an archive keeps them as a law asks (the
9
+ * GoBD, for one) is a matter of how the archive is run, not of the file, so nothing here or on any
10
+ * page says "revisionssicher", "GoBD-konform", "certified" or "archive-compliant": the report says
11
+ * which part and level a file was held to, and by which validator.
12
+ */
13
+ /**
14
+ * The levels a render can ask for. 3b is the default and what an e-invoice is; 2b is for archives
15
+ * that take PDF/A-2 and not PDF/A-3, which may carry any file as an attachment. PDF/A-1 forbids the
16
+ * transparency Chromium writes, so it is not offered.
17
+ */
18
+ export declare const PDFA_LEVELS: readonly ["2b", "3b"];
19
+ export type PdfaLevel = (typeof PDFA_LEVELS)[number];
20
+ /** What `true` stands for. */
21
+ export declare const PDFA_DEFAULT_LEVEL: PdfaLevel;
22
+ /** The standard a level names, as the report and the `X-Formfeed-PDF-A` header write it. */
23
+ export declare const PDFA_STANDARDS: {
24
+ readonly '2b': "PDF/A-2b";
25
+ readonly '3b': "PDF/A-3b";
26
+ };
27
+ export type PdfaStandard = (typeof PDFA_STANDARDS)[PdfaLevel];
28
+ /** `pdf.pdfa` as a template's settings or a request write it. */
29
+ export type PdfaSetting = PdfaLevel | boolean | null;
30
+ /**
31
+ * `pdf.pdfa` as a render takes it: a level, `true` for the default level, and anything else off.
32
+ * There is no check to choose (plan 26, decision 2): a file that fails PDF/A fails the render.
33
+ */
34
+ export declare function resolvePdfa(setting: unknown): PdfaLevel | null;
35
+ /** Whether a `pdf.pdfa` value is one the API takes: a level, a boolean, or null. */
36
+ export declare function isPdfaSetting(value: unknown): value is PdfaSetting;
37
+ export declare function pdfaStandard(level: PdfaLevel): PdfaStandard;
38
+ /**
39
+ * What `pdf.pdfa` cannot go with, in the words every place that refuses it uses (the gateway before
40
+ * a render costs anything, the worker for whoever calls it directly, `formfeed validate`, the API's
41
+ * template validation). A merge, a watermark and Word and PowerPoint templates go with it, since all
42
+ * of them come before the conversion.
43
+ */
44
+ export declare const PDFA_REFUSALS: {
45
+ readonly password: "pdf.pdfa cannot be combined with post.password: PDF/A does not allow encryption";
46
+ readonly einvoice: "pdf.pdfa \"2b\" cannot be combined with an e-invoice: the invoice XML is an attachment, which PDF/A-2 does not allow. An e-invoice is a PDF/A-3b already; ask for \"3b\" or leave pdf.pdfa out";
47
+ readonly invalid: "settings.pdf.pdfa is \"2b\", \"3b\", true, false or null";
48
+ };
49
+ /** How many findings a render keeps; a render row travels in every list and webhook. */
50
+ export declare const PDFA_MAX_MESSAGES = 50;
51
+ /** One rule of PDF/A the file failed, as the validator reports it. */
52
+ export interface PdfaMessage {
53
+ severity: 'error' | 'warning';
54
+ /** A clause of ISO 19005 and veraPDF's test number, `6.2.11.4.1-1`. */
55
+ rule: string | null;
56
+ message: string;
57
+ /** veraPDF's path to the object of the PDF. */
58
+ location: string | null;
59
+ }
60
+ /**
61
+ * `pdfa` on a render (plan 26): `null` unless the render asked for PDF/A or is an e-invoice with
62
+ * `pdf.pdfa` beside it. `conformant` is the validator's word on the file it judged; a render that
63
+ * succeeded delivered exactly that file, and one that failed delivered nothing.
64
+ */
65
+ export interface PdfaResult {
66
+ standard: PdfaStandard;
67
+ conformant: boolean;
68
+ /** The validator and its version: its rule set moves between releases. */
69
+ validator: string;
70
+ /** The validator's own id for this report, which its log names too. */
71
+ id: string;
72
+ /** The versions of what converted and judged the file: `verapdf`, `pdfbox` (and `mustang` for an e-invoice). */
73
+ versions: Record<string, string>;
74
+ /** SHA-256 of the PDF that was judged: the delivered file when the render succeeded. */
75
+ pdf_sha256: string | null;
76
+ messages: PdfaMessage[];
77
+ /** More findings than the render keeps. */
78
+ truncated?: boolean;
79
+ }
80
+ /** The sidecar's report of `POST /pdf/pdfa` (`ArchiveReport` in `apps/conformance`). */
81
+ export interface PdfaValidatorReport {
82
+ id: string;
83
+ valid: boolean;
84
+ pdfa: string;
85
+ validator: string;
86
+ messages: Array<{
87
+ severity?: string;
88
+ rule?: string | null;
89
+ message?: string;
90
+ location?: string | null;
91
+ }>;
92
+ truncated?: boolean;
93
+ versions?: Record<string, string>;
94
+ }
95
+ /** The report a render keeps, from the sidecar's, with the hash of the file it judged. */
96
+ export declare function pdfaResult(level: PdfaLevel, report: PdfaValidatorReport, pdfSha256: string | null): PdfaResult;
97
+ /**
98
+ * The `pdfa` block of an e-invoice that asked for PDF/A too (plan 26, decision 8): an e-invoice is
99
+ * a PDF/A-3b, converted and judged once, so the block is made from the e-invoice's own validation
100
+ * and its PDF findings. Null while that validation ended at the XML, before there was a file.
101
+ */
102
+ export declare function pdfaFromEinvoice(validation: EinvoiceValidation | null | undefined): PdfaResult | null;
103
+ /** Whether a stored value is a report: the API shows it, and null otherwise. */
104
+ export declare function isPdfaResult(value: unknown): value is PdfaResult;
105
+ /** How long the report may be as a header value: proxies refuse a response whose headers grow past a few kilobytes. */
106
+ export declare const PDFA_HEADER_LENGTH = 2000;
107
+ /**
108
+ * The report as the worker's binary answer carries it in `x-formfeed-pdfa`, percent-encoded: the
109
+ * gateway records it with the render and `preview-render` shows it. A file that was delivered
110
+ * passed, so its report is short; one that is too long anyway loses its findings and says so.
111
+ */
112
+ export declare function pdfaHeaderValue(result: PdfaResult, limit?: number): string;
@@ -0,0 +1,112 @@
1
+ import type { EinvoiceValidation } from '../einvoice/report.js';
2
+ /**
3
+ * `@formfeed/engine/pdfa` (plan 26): what `pdf.pdfa` asks for, what it cannot go with, and the
4
+ * report a render keeps of what the validator said. No template engine is imported here and nothing
5
+ * of Node, so the gateway Worker, the render-worker, the editor and the CLI all read the same names.
6
+ *
7
+ * PDF/A is ISO 19005, a file format for keeping documents: the fonts embedded, the colours defined,
8
+ * nothing that needs the network or a password. Whether an archive keeps them as a law asks (the
9
+ * GoBD, for one) is a matter of how the archive is run, not of the file, so nothing here or on any
10
+ * page says "revisionssicher", "GoBD-konform", "certified" or "archive-compliant": the report says
11
+ * which part and level a file was held to, and by which validator.
12
+ */
13
+ /**
14
+ * The levels a render can ask for. 3b is the default and what an e-invoice is; 2b is for archives
15
+ * that take PDF/A-2 and not PDF/A-3, which may carry any file as an attachment. PDF/A-1 forbids the
16
+ * transparency Chromium writes, so it is not offered.
17
+ */
18
+ export declare const PDFA_LEVELS: readonly ["2b", "3b"];
19
+ export type PdfaLevel = (typeof PDFA_LEVELS)[number];
20
+ /** What `true` stands for. */
21
+ export declare const PDFA_DEFAULT_LEVEL: PdfaLevel;
22
+ /** The standard a level names, as the report and the `X-Formfeed-PDF-A` header write it. */
23
+ export declare const PDFA_STANDARDS: {
24
+ readonly '2b': "PDF/A-2b";
25
+ readonly '3b': "PDF/A-3b";
26
+ };
27
+ export type PdfaStandard = (typeof PDFA_STANDARDS)[PdfaLevel];
28
+ /** `pdf.pdfa` as a template's settings or a request write it. */
29
+ export type PdfaSetting = PdfaLevel | boolean | null;
30
+ /**
31
+ * `pdf.pdfa` as a render takes it: a level, `true` for the default level, and anything else off.
32
+ * There is no check to choose (plan 26, decision 2): a file that fails PDF/A fails the render.
33
+ */
34
+ export declare function resolvePdfa(setting: unknown): PdfaLevel | null;
35
+ /** Whether a `pdf.pdfa` value is one the API takes: a level, a boolean, or null. */
36
+ export declare function isPdfaSetting(value: unknown): value is PdfaSetting;
37
+ export declare function pdfaStandard(level: PdfaLevel): PdfaStandard;
38
+ /**
39
+ * What `pdf.pdfa` cannot go with, in the words every place that refuses it uses (the gateway before
40
+ * a render costs anything, the worker for whoever calls it directly, `formfeed validate`, the API's
41
+ * template validation). A merge, a watermark and Word and PowerPoint templates go with it, since all
42
+ * of them come before the conversion.
43
+ */
44
+ export declare const PDFA_REFUSALS: {
45
+ readonly password: "pdf.pdfa cannot be combined with post.password: PDF/A does not allow encryption";
46
+ readonly einvoice: "pdf.pdfa \"2b\" cannot be combined with an e-invoice: the invoice XML is an attachment, which PDF/A-2 does not allow. An e-invoice is a PDF/A-3b already; ask for \"3b\" or leave pdf.pdfa out";
47
+ readonly invalid: "settings.pdf.pdfa is \"2b\", \"3b\", true, false or null";
48
+ };
49
+ /** How many findings a render keeps; a render row travels in every list and webhook. */
50
+ export declare const PDFA_MAX_MESSAGES = 50;
51
+ /** One rule of PDF/A the file failed, as the validator reports it. */
52
+ export interface PdfaMessage {
53
+ severity: 'error' | 'warning';
54
+ /** A clause of ISO 19005 and veraPDF's test number, `6.2.11.4.1-1`. */
55
+ rule: string | null;
56
+ message: string;
57
+ /** veraPDF's path to the object of the PDF. */
58
+ location: string | null;
59
+ }
60
+ /**
61
+ * `pdfa` on a render (plan 26): `null` unless the render asked for PDF/A or is an e-invoice with
62
+ * `pdf.pdfa` beside it. `conformant` is the validator's word on the file it judged; a render that
63
+ * succeeded delivered exactly that file, and one that failed delivered nothing.
64
+ */
65
+ export interface PdfaResult {
66
+ standard: PdfaStandard;
67
+ conformant: boolean;
68
+ /** The validator and its version: its rule set moves between releases. */
69
+ validator: string;
70
+ /** The validator's own id for this report, which its log names too. */
71
+ id: string;
72
+ /** The versions of what converted and judged the file: `verapdf`, `pdfbox` (and `mustang` for an e-invoice). */
73
+ versions: Record<string, string>;
74
+ /** SHA-256 of the PDF that was judged: the delivered file when the render succeeded. */
75
+ pdf_sha256: string | null;
76
+ messages: PdfaMessage[];
77
+ /** More findings than the render keeps. */
78
+ truncated?: boolean;
79
+ }
80
+ /** The sidecar's report of `POST /pdf/pdfa` (`ArchiveReport` in `apps/conformance`). */
81
+ export interface PdfaValidatorReport {
82
+ id: string;
83
+ valid: boolean;
84
+ pdfa: string;
85
+ validator: string;
86
+ messages: Array<{
87
+ severity?: string;
88
+ rule?: string | null;
89
+ message?: string;
90
+ location?: string | null;
91
+ }>;
92
+ truncated?: boolean;
93
+ versions?: Record<string, string>;
94
+ }
95
+ /** The report a render keeps, from the sidecar's, with the hash of the file it judged. */
96
+ export declare function pdfaResult(level: PdfaLevel, report: PdfaValidatorReport, pdfSha256: string | null): PdfaResult;
97
+ /**
98
+ * The `pdfa` block of an e-invoice that asked for PDF/A too (plan 26, decision 8): an e-invoice is
99
+ * a PDF/A-3b, converted and judged once, so the block is made from the e-invoice's own validation
100
+ * and its PDF findings. Null while that validation ended at the XML, before there was a file.
101
+ */
102
+ export declare function pdfaFromEinvoice(validation: EinvoiceValidation | null | undefined): PdfaResult | null;
103
+ /** Whether a stored value is a report: the API shows it, and null otherwise. */
104
+ export declare function isPdfaResult(value: unknown): value is PdfaResult;
105
+ /** How long the report may be as a header value: proxies refuse a response whose headers grow past a few kilobytes. */
106
+ export declare const PDFA_HEADER_LENGTH = 2000;
107
+ /**
108
+ * The report as the worker's binary answer carries it in `x-formfeed-pdfa`, percent-encoded: the
109
+ * gateway records it with the render and `preview-render` shows it. A file that was delivered
110
+ * passed, so its report is short; one that is too long anyway loses its findings and says so.
111
+ */
112
+ export declare function pdfaHeaderValue(result: PdfaResult, limit?: number): string;