fix: PDF/A-3 metadata and ICC profile validation errors#111
Merged
jslno merged 1 commit intojslno:mainfrom Feb 7, 2026
Merged
Conversation
Contributor
|
@chrizzellu is attempting to deploy a commit to the better-auth-extended Team on Vercel. A member of the Team first needs to authorize it. |
commit: |
1f4ee52 to
c476306
Compare
8f4a912 to
5fc6277
Compare
Fixes multiple PDF/A-3 validation errors reported by official Factur-X validators. Relates to jslno#110. ## Changes ### 1. Add dc:title and dc:description to XMP metadata (formatter.ts) The Info dictionary fields Title and Subject (set via pdf-lib's setTitle/setSubject) had no corresponding XMP entries, causing PDF/A validation errors: - "Info /Title and XMP dc:title['x-default'] are not equivalent" - "Info /Subject and XMP dc:description['x-default'] are not equivalent" Now both fields are included in the XMP metadata template when provided via metadata options. Uses rdf:Alt with xml:lang="x-default" per XMP specification. ### 2. Fix dc:creator to use metadata.author (formatter.ts) dc:creator in XMP was set to metadata.creator, but per the PDF/XMP spec, dc:creator should match Info /Author (metadata.author), not Info /Creator (metadata.creator). xmp:CreatorTool already correctly uses metadata.creator. Falls back to metadata.creator when author is not provided, for backward compatibility. ### 3. Add N entry to ICC profile stream dictionary (formatter.ts) The sRGB ICC profile stream was missing the required N (number of color components) entry, causing: - "The N entry (value null) does not match the number of components in the embedded ICC profile (color space RGB)" Added N: 3 to the profile stream dictionary (RGB = 3 components). ### 4. Save with useObjectStreams: false (create.ts) PDF/A validators flag xref streams (from object streams) as non-compliant. Saving with useObjectStreams: false forces traditional cross-reference tables for maximum compatibility. Co-authored-by: Cursor <cursoragent@cursor.com>
5fc6277 to
1c883fc
Compare
jslno
approved these changes
Feb 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes PDF/A-3B validation errors reported by the official Factur-X validator at https://services.fnfe-mpe.org/. PDFs generated by
embedInPdfnow pass both PDF and XML validation. Relates to #110.Root Cause
The XMP metadata stream contained
xmlns:about=""(an empty XML namespace declaration), which is forbidden by the XML Namespace specification. This caused every strict XML parser — including the PDF/A validator — to reject the entire XMP document. The validator then:pdfaid:part=3, so it defaulted to testing as PDF/A-1bnullThis single typo (
xmlns:aboutinstead ofrdf:about) was the root cause of all reported PDF validation errors.Changes
1. Fix invalid
xmlns:about=""→rdf:about=""(formatter.ts)The Factur-X
rdf:Descriptionblock forfx:DocumentTypeetc. used@xmlns:aboutwhich generatesxmlns:about=""— an empty namespace, invalid XML. Changed to@rdf:aboutwhich generates the correctrdf:about=""attribute.This was the critical fix that made the entire XMP parseable.
2. Add
dc:titleanddc:descriptionto XMP metadata (formatter.ts)The Info dictionary fields Title and Subject (set via
setTitle/setSubject) had no corresponding XMP entries, causing metadata mismatch errors. Now included with correctrdf:Alt/xml:lang="x-default"structure per XMP spec.3. Fix
dc:creatorto usemetadata.author(formatter.ts)dc:creatorwas set tometadata.creator, but per PDF/XMP spec:dc:creatormust match Info/Author(=metadata.author)xmp:CreatorToolmust match Info/Creator(=metadata.creator)Falls back to
metadata.creatorwhen author is not provided.4. Encode XMP as UTF-8 bytes (formatter.ts)
The XMP string was passed directly to
context.stream()withLength: xmp.length. JavaScript's.lengthcounts UTF-16 code units, but the PDF stream contains UTF-8 bytes. The\uFEFFBOM (1 JS char → 3 UTF-8 bytes) caused a length mismatch that made validators fail to read the stream. Now usesTextEncoder().encode()for correct byte-level handling.5. Add
N: 3to ICC profile stream (formatter.ts)The sRGB ICC profile stream was missing the required
Nentry (number of color components), causing: "The N entry (value null) does not match the number of components in the embedded ICC profile (color space RGB)"6. Load PDF with
updateMetadata: false(create.ts)PDFDocument.load()defaults toupdateMetadata: true, which auto-setsProducer = "pdf-lib"andCreator = "pdf-lib"in the Info dictionary beforeembedInPdfcan set the correct values. Now disabled to prevent stale metadata.7. Save with
useObjectStreams: false(create.ts)Forces traditional cross-reference tables instead of xref streams for maximum PDF/A compatibility.
Validation Results
Before (from issue #110):
After:
Tested with Puppeteer-generated PDFs validated against the official Factur-X validator.
Test plan
formatter.test.tsunrelated — require Java)