Skip to content

Commit

Permalink
Support for PDF/A-2 and PDF/A-3 subsets (#1432)
Browse files Browse the repository at this point in the history
* Added PDF/A-2 and PDF/A-3 subsets A and B

It seems like PDF/A-2 and PDF/A-3 are not very different from PDF/A-1 as far as the A and B subsets are concerned (A requires tagging which PDFKit supports already). With this change, we can let the generated PDF present itself as PDF/A-2 or PDF/A-3.

* Updated docs and changelog for new PDF/A subsets

* Fixed an issue where ICC profile path can be wrong for built package but good for tests
  • Loading branch information
andreiaugustin authored Mar 9, 2023
1 parent d81f13b commit c1d7700
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 33 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Unreleased

- Add support for PDF/A-1b and PDF/A-1a
- Add support for PDF/A-1b, PDF/A-1a, PDF/A-2b, PDF/A-2a, PDF/A-3b, PDF/A-3a

### [v0.13.0] - 2021-10-24

Expand Down
12 changes: 8 additions & 4 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,16 +272,20 @@ The restrictions on PDF/A documents are:
- Addition of XMP metadata
- Must define color spaces

Currently, PDFKit aims to support PDF/A-1b and PDF/A-1a standards, also known as level B compliance and level A compliance, respectively.
Currently, PDFKit aims to support PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/A-1a, PDF/A-2a, PDF/A-3a standards, also known as level B conformance and level A conformance, respectively.

In order to create PDF/A documents, set `subset` to either `PDF/A-1` (`PDF/A-1a` or `PDF/A-1b` for A specific conformance level) when creating the `PDFDocument` in `options` object. If `PDF/A-1` is passed, conformance level `B` is used.
In order to create PDF/A documents, set `subset` to either `PDF/A-1` or `PDF/A-1b` for level B (basic) conformance, or `PDF/A-1a` for level A (accessible) conformance when creating the `PDFDocument` in `options` object.

Futhermore, you will need to specify the other options relevant to the PDF/A subset you wish to use, for PDFA-1 being:
Similary, use `PDF/A-2` or `PDF/A-2b` for PDF/A-2 level B conformance and `PDF/A-2a` for PDF/A-2 level A conformance. `PDF/A-3` or `PDF/A-3b` can be used for PDF/A-3 level B conformance and `PDF/A-3a` for PDF/A-3 level A conformance.

Futhermore, you will need to specify the other options relevant to the PDF/A subset you wish to use, for PDF/A-1 being:

- `pdfVersion` set to at least `1.4`
- `tagged` set to `true` for PDF/A-1a

In order to verify PDF/A compliance, veraPDF is an excellent open source validator.
For PDF/A-2 and PDF/A-3, the `pdfVersion` needs to be set to at least `1.7` and `tagged` needs to be `true` for level A conformance.

In order to verify the generated document for PDF/A and its subsets conformance, veraPDF is an excellent open source validator.

### Adding content

Expand Down
29 changes: 14 additions & 15 deletions lib/mixins/pdfa1.js → lib/mixins/pdfa.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@ import fs from 'fs';

export default {

initPDFA1(pOptions) {
switch(pOptions.subset) {
case 'PDF/A-1':
case 'PDF/A-1b':
this.subset = 1;
this.subset_conformance = 'B';
break;
case 'PDF/A-1a':
this.subset = 1;
this.subset_conformance = 'A';
break;
initPDFA(pSubset) {
if (pSubset.charAt(pSubset.length - 3) === '-') {
this.subset_conformance = pSubset.charAt(pSubset.length - 1).toUpperCase();
this.subset = parseInt(pSubset.charAt(pSubset.length - 2));
} else {
// Default to Basic conformance when user doesn't specify
this.subset_conformance = 'B';
this.subset = parseInt(pSubset.charAt(pSubset.length - 1));
}
},

endSubset() {
this._addPdfaMetadata();
this._addColorOutputIntent();
const jsPath = `${__dirname}/data/sRGB_IEC61966_2_1.icc`
const jestPath = `${__dirname}/../color_profiles/sRGB_IEC61966_2_1.icc`
this._addColorOutputIntent(fs.existsSync(jsPath) ? jsPath : jestPath);
},

_addColorOutputIntent() {
const iccProfile = fs.readFileSync(__dirname + '/../color_profiles/sRGB_IEC61966_2_1.icc');
_addColorOutputIntent(pICCPath) {
const iccProfile = fs.readFileSync(pICCPath);

const colorProfileRef = this.ref({
Length: iccProfile.length,
Expand Down Expand Up @@ -55,5 +54,5 @@ export default {
_addPdfaMetadata() {
this.appendXML(this._getPdfaid());
},

}
24 changes: 11 additions & 13 deletions lib/mixins/subsets.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import PDFA1 from './pdfa1';
// import PDFA2 from './pdfa2';
import PDFA from './pdfa';

export default {
_importSubset(subset) {
Object.assign(this, subset)
},

initSubset(options) {

switch (options.subset) {
case 'PDF/A-1':
case 'PDF/A-1a':
case 'PDF/A-1b':
this._importSubset(PDFA1);
this.initPDFA1(options);
case 'PDF/A-2':
case 'PDF/A-2a':
case 'PDF/A-2b':
case 'PDF/A-3':
case 'PDF/A-3a':
case 'PDF/A-3b':
this._importSubset(PDFA);
this.initPDFA(options.subset);
break;
// case 'PDF/A-2':
// case 'PDF/A-2a':
// case 'PDF/A-2b':
// case 'PDF/A-2u':
// this._importSubset(PDFA2);
// this.initPDFA2(options);
// break;
}
}
}

}
88 changes: 88 additions & 0 deletions tests/unit/pdfa2.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import PDFDocument from '../../lib/document';
import { logData, joinTokens } from './helpers';

describe('PDF/A-2', () => {

test('metadata is present', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-2'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
expect(data).toContainChunk([
`11 0 obj`,
`<<\n/length 892\n/Type /Metadata\n/Subtype /XML\n/Length 894\n>>`
]);
});

test('color profile is present', () => {
const expected = [
`10 0 obj`,
joinTokens(
'<<',
'/Type /OutputIntent',
'/S /GTS_PDFA1',
'/Info (sRGB IEC61966-2.1)',
'/OutputConditionIdentifier (sRGB IEC61966-2.1)',
'/DestOutputProfile 9 0 R',
'>>'
),
];
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-2'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
expect(data).toContainChunk(expected);
});

test('metadata contains pdfaid part and conformance', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-2'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:part>2');
expect(metadata).toContain('pdfaid:conformance');
});

test('metadata pdfaid conformance B', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-2b'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:conformance>B');
});

test('metadata pdfaid conformance A', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-2a'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:conformance>A');
});

});
88 changes: 88 additions & 0 deletions tests/unit/pdfa3.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import PDFDocument from '../../lib/document';
import { logData, joinTokens } from './helpers';

describe('PDF/A-3', () => {

test('metadata is present', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-3'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
expect(data).toContainChunk([
`11 0 obj`,
`<<\n/length 892\n/Type /Metadata\n/Subtype /XML\n/Length 894\n>>`
]);
});

test('color profile is present', () => {
const expected = [
`10 0 obj`,
joinTokens(
'<<',
'/Type /OutputIntent',
'/S /GTS_PDFA1',
'/Info (sRGB IEC61966-2.1)',
'/OutputConditionIdentifier (sRGB IEC61966-2.1)',
'/DestOutputProfile 9 0 R',
'>>'
),
];
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-3'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
expect(data).toContainChunk(expected);
});

test('metadata contains pdfaid part and conformance', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-3'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:part>3');
expect(metadata).toContain('pdfaid:conformance');
});

test('metadata pdfaid conformance B', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-3b'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:conformance>B');
});

test('metadata pdfaid conformance A', () => {
let options = {
autoFirstPage: false,
pdfVersion: '1.7',
subset: 'PDF/A-3a'
};
let doc = new PDFDocument(options);
const data = logData(doc);
doc.end();
let metadata = Buffer.from(data[27]).toString();

expect(metadata).toContain('pdfaid:conformance>A');
});

});

0 comments on commit c1d7700

Please sign in to comment.