@stackforge-eu/factur-x 1.0.7 → 1.0.8
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/CHANGELOG.md +17 -0
- package/dist/index.cjs +18 -2
- package/dist/index.js +19 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.8] — 2026-05-27
|
|
11
|
+
|
|
12
|
+
- **PDF trailer was missing the `/ID` entry required by PDF/A-3.** ISO 19005-3
|
|
13
|
+
(and PDF 1.7 §14.4) require the trailer dictionary to carry an `/ID` array of
|
|
14
|
+
two byte strings: the first immutable identifier of the original document,
|
|
15
|
+
the second updated on every modification. `pdf-lib` initializes
|
|
16
|
+
`trailerInfo = {}` and never writes one on save, so veraPDF and the FNFE-MPE
|
|
17
|
+
Factur-X profile rejected every PDF this library produced as non-conformant.
|
|
18
|
+
Added `ensureIdTrailer` in `src/core/embed.ts` which runs after the first
|
|
19
|
+
`pdfDoc.save()` when `addPdfA3Metadata` is on: it computes an MD5 over the
|
|
20
|
+
serialized bytes (per PDF 1.7 §14.4 — uniqueness, not cryptographic strength,
|
|
21
|
+
is what matters), preserves any pre-existing first identifier, and writes the
|
|
22
|
+
pair before re-saving. Regression tests added in `tests/embed.test.ts`
|
|
23
|
+
covering both the no-existing-trailer and existing-trailer paths (thanks
|
|
24
|
+
@sco-indy, #3).
|
|
25
|
+
|
|
10
26
|
## [1.0.7] — 2026-05-27
|
|
11
27
|
|
|
12
28
|
- **`fx:ConformanceLevel` in the XMP metadata used the raw `Profile` enum
|
|
@@ -113,6 +129,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
113
129
|
- Deno compatibility test suite
|
|
114
130
|
|
|
115
131
|
[Unreleased]: https://github.com/StackForge-EU/factur-x/commits/main
|
|
132
|
+
[1.0.8]: https://github.com/StackForge-EU/factur-x/compare/v1.0.7...v1.0.8
|
|
116
133
|
[1.0.7]: https://github.com/StackForge-EU/factur-x/compare/v1.0.6...v1.0.7
|
|
117
134
|
[1.0.6]: https://github.com/StackForge-EU/factur-x/compare/v1.0.5...v1.0.6
|
|
118
135
|
[1.0.5]: https://github.com/StackForge-EU/factur-x/compare/v1.0.4...v1.0.5
|
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,7 @@ var fs = require('fs');
|
|
|
4
4
|
var path = require('path');
|
|
5
5
|
var url = require('url');
|
|
6
6
|
var console = require('console');
|
|
7
|
+
var crypto = require('crypto');
|
|
7
8
|
var pdfLib = require('pdf-lib');
|
|
8
9
|
|
|
9
10
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -1122,6 +1123,17 @@ function resolveXmpConformanceLevel(profile, flavor) {
|
|
|
1122
1123
|
if (flavor === "xrechnung" /* XRECHNUNG */) return "XRECHNUNG";
|
|
1123
1124
|
return XMP_CONFORMANCE_LEVEL[profile];
|
|
1124
1125
|
}
|
|
1126
|
+
function ensureIdTrailer(pdfDoc, pdfContentBytes) {
|
|
1127
|
+
const existingIdTrailer = pdfDoc.context.trailerInfo.ID;
|
|
1128
|
+
const currentContentHash = pdfLib.PDFHexString.of(crypto.createHash("md5").update(pdfContentBytes).digest("hex"));
|
|
1129
|
+
const hasValidIdTrailer = existingIdTrailer instanceof pdfLib.PDFArray && existingIdTrailer.size() === 2;
|
|
1130
|
+
if (hasValidIdTrailer) {
|
|
1131
|
+
const existingFirstId = existingIdTrailer.lookup(0);
|
|
1132
|
+
pdfDoc.context.trailerInfo.ID = pdfDoc.context.obj([existingFirstId, currentContentHash]);
|
|
1133
|
+
} else {
|
|
1134
|
+
pdfDoc.context.trailerInfo.ID = pdfDoc.context.obj([currentContentHash, currentContentHash]);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1125
1137
|
function ensureOutputIntent(pdfDoc, iccBytes, identifier) {
|
|
1126
1138
|
const existing = pdfDoc.catalog.lookup(pdfLib.PDFName.of("OutputIntents"));
|
|
1127
1139
|
if (existing instanceof pdfLib.PDFArray && existing.size() > 0) return;
|
|
@@ -1397,9 +1409,13 @@ async function embedFacturX(options) {
|
|
|
1397
1409
|
const metadataRef = pdfDoc.context.register(metadataStream);
|
|
1398
1410
|
pdfDoc.catalog.set(pdfLib.PDFName.of("Metadata"), metadataRef);
|
|
1399
1411
|
}
|
|
1400
|
-
|
|
1412
|
+
let pdfContentBytes = await pdfDoc.save();
|
|
1413
|
+
if (addPdfA3Metadata) {
|
|
1414
|
+
ensureIdTrailer(pdfDoc, pdfContentBytes);
|
|
1415
|
+
pdfContentBytes = await pdfDoc.save();
|
|
1416
|
+
}
|
|
1401
1417
|
return {
|
|
1402
|
-
pdf:
|
|
1418
|
+
pdf: pdfContentBytes,
|
|
1403
1419
|
xml,
|
|
1404
1420
|
...validation ? { validation } : {},
|
|
1405
1421
|
...xsdValidation ? { xsdValidation } : {}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import console from 'console';
|
|
5
|
-
import {
|
|
5
|
+
import { createHash } from 'crypto';
|
|
6
|
+
import { AFRelationship, PDFDocument, PDFName, PDFArray, PDFDict, PDFStream, PDFRawStream, decodePDFRawStream, PDFString, PDFHexString, PDFRef } from 'pdf-lib';
|
|
6
7
|
|
|
7
8
|
// src/types/input.ts
|
|
8
9
|
var DocumentTypeCode = /* @__PURE__ */ ((DocumentTypeCode2) => {
|
|
@@ -1092,6 +1093,17 @@ function resolveXmpConformanceLevel(profile, flavor) {
|
|
|
1092
1093
|
if (flavor === "xrechnung" /* XRECHNUNG */) return "XRECHNUNG";
|
|
1093
1094
|
return XMP_CONFORMANCE_LEVEL[profile];
|
|
1094
1095
|
}
|
|
1096
|
+
function ensureIdTrailer(pdfDoc, pdfContentBytes) {
|
|
1097
|
+
const existingIdTrailer = pdfDoc.context.trailerInfo.ID;
|
|
1098
|
+
const currentContentHash = PDFHexString.of(createHash("md5").update(pdfContentBytes).digest("hex"));
|
|
1099
|
+
const hasValidIdTrailer = existingIdTrailer instanceof PDFArray && existingIdTrailer.size() === 2;
|
|
1100
|
+
if (hasValidIdTrailer) {
|
|
1101
|
+
const existingFirstId = existingIdTrailer.lookup(0);
|
|
1102
|
+
pdfDoc.context.trailerInfo.ID = pdfDoc.context.obj([existingFirstId, currentContentHash]);
|
|
1103
|
+
} else {
|
|
1104
|
+
pdfDoc.context.trailerInfo.ID = pdfDoc.context.obj([currentContentHash, currentContentHash]);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1095
1107
|
function ensureOutputIntent(pdfDoc, iccBytes, identifier) {
|
|
1096
1108
|
const existing = pdfDoc.catalog.lookup(PDFName.of("OutputIntents"));
|
|
1097
1109
|
if (existing instanceof PDFArray && existing.size() > 0) return;
|
|
@@ -1367,9 +1379,13 @@ async function embedFacturX(options) {
|
|
|
1367
1379
|
const metadataRef = pdfDoc.context.register(metadataStream);
|
|
1368
1380
|
pdfDoc.catalog.set(PDFName.of("Metadata"), metadataRef);
|
|
1369
1381
|
}
|
|
1370
|
-
|
|
1382
|
+
let pdfContentBytes = await pdfDoc.save();
|
|
1383
|
+
if (addPdfA3Metadata) {
|
|
1384
|
+
ensureIdTrailer(pdfDoc, pdfContentBytes);
|
|
1385
|
+
pdfContentBytes = await pdfDoc.save();
|
|
1386
|
+
}
|
|
1371
1387
|
return {
|
|
1372
|
-
pdf:
|
|
1388
|
+
pdf: pdfContentBytes,
|
|
1373
1389
|
xml,
|
|
1374
1390
|
...validation ? { validation } : {},
|
|
1375
1391
|
...xsdValidation ? { xsdValidation } : {}
|