Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to set and get print profile (Output Intents) #5

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pdfDoc.registerFontkit(fontkit)
- [Set Viewer Preferences](#set-viewer-preferences)
- [Read Viewer Preferences](#read-viewer-preferences)
- [Draw SVG Paths](#draw-svg-paths)
- [Set Print Profile](#set-print-profile)
- [Deno Usage](#deno-usage)
- [Complete Examples](#complete-examples)
- [Installation](#installation)
Expand Down Expand Up @@ -151,6 +152,8 @@ pdfDoc.registerFontkit(fontkit)
- Set viewer preferences
- Read viewer preferences
- Add attachments
- Set print profile
- Read print profile

## Motivation

Expand Down Expand Up @@ -1047,6 +1050,43 @@ const pdfBytes = await pdfDoc.save()
// • Rendered in an <iframe>
```

### Set Print Profile

*Icc profiles can find in [adobe](https://www.adobe.com/support/downloads/iccprofiles/iccprofiles_win.html) and [color.org](https://www.color.org/registry/index.xalter)*

```js
import { PDFDocument, rgb } from 'pdf-lib'

// Create a new PDFDocument
const pdfDoc = await PDFDocument.create()

// get buffer from icc file
const iccBuffer = fs.readFileSync('path/to/profile.icc');

// example for PDF/X-4
pdfDoc.setPrintProfile({
identifier: 'Coated_FOGRA39',
info: 'Coated FOGRA39 (ISO 12647-2:2004)',
subType: 'GTS_PDFX',
iccBuffer,
});

// example for PDF/A
pdfDoc.setPrintProfile({
identifier: 'sRGB',
subType: 'GTS_PDFA1',
iccBuffer,
});

// save pdf
await pdfDoc.save()

// you can also get print profile from document
const printProfile = pdfDoc.getPrintProfile();

console.log(printProfile)
```

## Deno Usage

`pdf-lib` fully supports the exciting new [Deno](https://deno.land/) runtime! All of the [usage examples](#usage-examples) work in Deno. The only thing you need to do is change the imports for `pdf-lib` and `@pdf-lib/fontkit` to use the [Skypack](https://www.skypack.dev/) CDN, because Deno requires all modules to be referenced via URLs.
Expand Down
Binary file added assets/pdfs/with_print_profile.pdf
Binary file not shown.
Binary file added assets/profiles/CoatedFOGRA39.icc
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"rimraf": "^3.0.2",
"rollup": "^2.17.1",
"rollup-plugin-terser": "^6.1.0",
"ts-jest": "^26.4.4",
"ts-jest": "^29.2.4",
"tslib": "^2.1.0",
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0",
Expand Down
82 changes: 81 additions & 1 deletion src/api/PDFDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
PDFPageLeaf,
PDFPageTree,
PDFParser,
PDFRawStream,
PDFStreamWriter,
PDFString,
PDFWriter,
Expand Down Expand Up @@ -151,7 +152,10 @@ export default class PDFDocument {
throwOnInvalidObject,
capNumbers,
).parseDocument();
if (!!context.lookup(context.trailerInfo.Encrypt) && password!==undefined) {
if (
!!context.lookup(context.trailerInfo.Encrypt) &&
password !== undefined
) {
// Decrypt
const fileIds = context.lookup(context.trailerInfo.ID, PDFArray);
const encryptDict = context.lookup(context.trailerInfo.Encrypt, PDFDict);
Expand Down Expand Up @@ -556,6 +560,82 @@ export default class PDFDocument {
this.getInfoDict().set(key, PDFString.fromDate(modificationDate));
}

/**
* set printing profile of this document.
* @param subType eg. GTS_PDFA1, GTS_PDFX
* @param iccBuffer icc profile buffer content
* @param info string
* @param identifier string
*/
setPrintProfile({
subType,
iccBuffer,
info,
identifier,
}: {
identifier: string;
info?: string;
subType: string;
iccBuffer: Buffer;
}): void {
const iccStream = this.context.stream(iccBuffer, {
Length: iccBuffer.length,
});
const outputIntent = this.context.obj({
Type: 'OutputIntent',
S: subType,
OutputConditionIdentifier: PDFString.of(identifier),
Info: info ? PDFString.of(info) : PDFString.of(identifier),
DestOutputProfile: this.context.register(iccStream),
});
const outputIntentRef = this.context.register(outputIntent);
this.catalog.set(
PDFName.of('OutputIntents'),
this.context.obj([outputIntentRef]),
);
}

/**
* Get printing profile
* @returns printing profile info.
*/
getPrintProfile():
| {
type?: string;
subType?: string;
identifier: string;
info?: string;
iccBuffer?: Uint8Array;
}
| undefined {
const printProfile = this.catalog.lookup(
PDFName.of('OutputIntents'),
) as PDFArray;
if (!printProfile) return undefined;

const object = printProfile.lookup(0) as PDFDict;
if (!object) return undefined;

const type = object.lookup(PDFName.of('Type')) as PDFName;
const subType = object.lookup(PDFName.of('S')) as PDFName;
const identifier = object.lookup(
PDFName.of('OutputConditionIdentifier'),
) as PDFName;
const info = object.lookup(PDFName.of('Info')) as PDFName;

const profile = object.lookup(
PDFName.of('DestOutputProfile'),
) as PDFRawStream;

return {
type: type?.decodeText(),
subType: subType?.decodeText(),
identifier: identifier?.decodeText(),
info: info?.decodeText(),
iccBuffer: profile.contents,
};
}

/**
* Get the number of pages contained in this document. For example:
* ```js
Expand Down
14 changes: 7 additions & 7 deletions src/core/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

import { arrayAsString, isArrayEqual } from '../utils/arrays';
import { stringAsByteArray } from '../utils/strings';
import PDFBool from './objects/PDFBool.js';
import PDFDict from './objects/PDFDict.js';
import PDFName from './objects/PDFName.js';
import PDFNumber from './objects/PDFNumber.js';
import PDFString from './objects/PDFString.js';
import DecryptStream from './streams/DecryptStream.js';
import { StreamType } from './streams/Stream.js';
import PDFBool from './objects/PDFBool';
import PDFDict from './objects/PDFDict';
import PDFName from './objects/PDFName';
import PDFNumber from './objects/PDFNumber';
import PDFString from './objects/PDFString';
import DecryptStream from './streams/DecryptStream';
import { StreamType } from './streams/Stream';

class ARCFourCipher {
private s: Uint8Array;
Expand Down
54 changes: 54 additions & 0 deletions tests/api/PDFDocument.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,4 +573,58 @@ describe(`PDFDocument`, () => {
expect(pdfDoc.defaultWordBreaks).toEqual(srcDoc.defaultWordBreaks);
});
});

describe(`set/get printing profile`, () => {
it(`shoud return undefined when printing profile is not set`, async () => {
// create pdf document
const pdfDoc = await PDFDocument.create();
// save PDF
await pdfDoc.save();
// get print profile
const printProfile = pdfDoc.getPrintProfile();
expect(printProfile).toBe(undefined);
});

it(`shoud be able to read from load pdf`, async () => {
// create pdf document
const pdfDoc = await PDFDocument.load(
fs.readFileSync('assets/pdfs/with_print_profile.pdf'),
{ updateMetadata: false },
);
// get print profile
const printProfile = pdfDoc.getPrintProfile();

expect(printProfile?.type).toBe('OutputIntent');
expect(printProfile?.subType).toBe('GTS_PDFX');
expect(printProfile?.identifier).toBe(
'Coated FOGRA39 (ISO 12647-2:2004)',
);
expect(printProfile?.info).toBe('Coated FOGRA39 (ISO 12647-2:2004)');
expect(Buffer.isBuffer(printProfile?.iccBuffer)).toBe(true);
});

it(`shoud be able to set and get printing profile`, async () => {
// create pdf document
const pdfDoc = await PDFDocument.create();
// get buffer from icc file
const iccBuffer = fs.readFileSync('assets/profiles/CoatedFOGRA39.icc');
// set for PDF/X-4 with Coated FOGRA39
pdfDoc.setPrintProfile({
identifier: 'Coated_FOGRA39',
info: 'Coated FOGRA39 (ISO 12647-2:2004)',
subType: 'GTS_PDFX',
iccBuffer,
});
// save PDF
await pdfDoc.save();
// get print profile
const printProfile = pdfDoc.getPrintProfile();

expect(printProfile?.type).toBe('OutputIntent');
expect(printProfile?.subType).toBe('GTS_PDFX');
expect(printProfile?.identifier).toBe('Coated_FOGRA39');
expect(printProfile?.info).toBe('Coated FOGRA39 (ISO 12647-2:2004)');
expect(Buffer.isBuffer(printProfile?.iccBuffer)).toBe(true);
});
});
});
2 changes: 1 addition & 1 deletion tests/core/streams/Ascii85Stream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import Ascii85Stream from '../../../src/core/streams/Ascii85Stream';
import Stream from '../../../src/core/streams/Stream';

const DIR = `./data/ascii85`;
const DIR = `${__dirname}/data/ascii85`;
const FILES = ['1'];

describe(`Ascii85Stream`, () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/core/streams/AsciiHexStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import AsciiHexStream from '../../../src/core/streams/AsciiHexStream';
import Stream from '../../../src/core/streams/Stream';

const DIR = `./data/asciihex`;
const DIR = `${__dirname}/data/asciihex`;
const FILES = ['1', '2'];

describe(`AsciiHexStream`, () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/core/streams/FlateStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import FlateStream from '../../../src/core/streams/FlateStream';
import Stream from '../../../src/core/streams/Stream';

const DIR = `./data/flate`;
const DIR = `${__dirname}/data/flate`;
const FILES = ['1', '2', '3', '4', '5', '6', '7'];

describe(`FlateStream`, () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/core/streams/LZWStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import LZWStream from '../../../src/core/streams/LZWStream';
import Stream from '../../../src/core/streams/Stream';

const DIR = `./data/lzw`;
const DIR = `${__dirname}/data/lzw`;
const FILES = ['1', '2', '3', '4'];

describe(`LZWStream`, () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/core/streams/RunLengthStream.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from 'fs';
import RunLengthStream from '../../../src/core/streams/RunLengthStream';
import Stream from '../../../src/core/streams/Stream';

const DIR = `./data/runlength`;
const DIR = `${__dirname}/data/runlength`;
const FILES = ['1', '2', '3', '4', '5'];

describe(`RunLengthStream`, () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/utils/base64.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from 'fs';
import { decodeFromBase64DataUri } from '../../src/utils';

const pdfBytes = fs.readFileSync('./data/simple.pdf');
const pdfBase64Bytes = fs.readFileSync('./data/simple.pdf.base64');
const pdfBytes = fs.readFileSync(`${__dirname}/data/simple.pdf`);
const pdfBase64Bytes = fs.readFileSync(`${__dirname}/data/simple.pdf.base64`);

// Jest stalls when comparing large arrays, so we'll use this instead
const arraysAreEqual = (a: Uint8Array, b: Uint8Array) => {
Expand Down
Loading