Skip to content

Commit

Permalink
Merge pull request #1036 from Hopding/1033-cleanup
Browse files Browse the repository at this point in the history
Deterministic key generation using pseudo randomness (tweaks)
  • Loading branch information
Hopding committed Oct 16, 2021
2 parents 07bf247 + 60b3498 commit 2f36ae3
Show file tree
Hide file tree
Showing 13 changed files with 79 additions and 60 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,15 +493,15 @@ const secondDonorPdfBytes = ...
const firstDonorPdfDoc = await PDFDocument.load(firstDonorPdfBytes)
const secondDonorPdfDoc = await PDFDocument.load(secondDonorPdfBytes)

// Copy the 1st page from the first donor document, and
// Copy the 1st page from the first donor document, and
// the 743rd page from the second donor document
const [firstDonorPage] = await pdfDoc.copyPages(firstDonorPdfDoc, [0])
const [secondDonorPage] = await pdfDoc.copyPages(secondDonorPdfDoc, [742])

// Add the first copied page
pdfDoc.addPage(firstDonorPage)

// Insert the second copied page to index 0, so it will be the
// Insert the second copied page to index 0, so it will be the
// first page in `pdfDoc`
pdfDoc.insertPage(0, secondDonorPage)

Expand Down Expand Up @@ -606,11 +606,11 @@ const preamble = await pdfDoc.embedPage(usConstitutionPdf.getPages()[1], {
top: 575,
})

// Get the width/height of the American flag PDF scaled down to 30% of
// Get the width/height of the American flag PDF scaled down to 30% of
// its original size
const americanFlagDims = americanFlag.scale(0.3)

// Get the width/height of the preamble clipping scaled up to 225% of
// Get the width/height of the preamble clipping scaled up to 225% of
// its original size
const preambleDims = preamble.scale(2.25)

Expand Down Expand Up @@ -813,8 +813,8 @@ import { PDFDocument } from 'pdf-lib'
const existingPdfBytes = ...

// Load a PDFDocument without updating its existing metadata
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
updateMetadata: false
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
updateMetadata: false
})

// Print all available metadata fields
Expand Down Expand Up @@ -1228,7 +1228,7 @@ When working with PDFs, you will frequently come across the terms "character enc
const pdfDoc = await PDFDocument.create()
const courierFont = await pdfDoc.embedFont(StandardFonts.Courier)
const page = pdfDoc.addPage()
page.drawText('Some boring latin text in the Courier font', {
page.drawText('Some boring latin text in the Courier font', {
font: courierFont,
})
```
Expand All @@ -1248,7 +1248,7 @@ When working with PDFs, you will frequently come across the terms "character enc
const ubuntuFont = await pdfDoc.embedFont(fontBytes)
const page = pdfDoc.addPage()
page.drawText('Some fancy Unicode text in the ŪЬȕǹƚü font', {
page.drawText('Some fancy Unicode text in the ŪЬȕǹƚü font', {
font: ubuntuFont,
})
```
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"DkDavid (https://github.com/DkDavid)",
"Bj Tecu (https://github.com/btecu)",
"Brent McSharry (https://github.com/mcshaz)",
"Tim Knapp (https://github.com/duffyd)"
"Tim Knapp (https://github.com/duffyd)",
"Ching Chang (https://github.com/ChingChang9)"
],
"scripts": {
"release:latest": "yarn publish --tag latest && yarn pack && yarn release:tag",
Expand Down
9 changes: 4 additions & 5 deletions src/api/PDFPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import {
PDFArray,
} from 'src/core';
import {
addRandomSuffix,
assertEachIs,
assertIs,
assertMultiple,
Expand Down Expand Up @@ -700,7 +699,7 @@ export default class PDFPage {
// TODO: Reuse image Font name if we've already added this image to Resources.Fonts
assertIs(font, 'font', [[PDFFont, 'PDFFont']]);
this.font = font;
this.fontKey = addRandomSuffix(this.font.name);
this.fontKey = this.doc.context.addRandomSuffix(this.font.name);
this.node.setFontDictionary(PDFName.of(this.fontKey), this.font.ref);
}

Expand Down Expand Up @@ -1060,7 +1059,7 @@ export default class PDFPage {
assertRangeOrUndefined(options.opacity, 'opacity.opacity', 0, 1);
assertIsOneOfOrUndefined(options.blendMode, 'options.blendMode', BlendMode);

const xObjectKey = addRandomSuffix('Image', 10);
const xObjectKey = this.doc.context.addRandomSuffix('Image', 10);
this.node.setXObject(PDFName.of(xObjectKey), image.ref);

const graphicsStateKey = this.maybeEmbedGraphicsState({
Expand Down Expand Up @@ -1135,7 +1134,7 @@ export default class PDFPage {
assertRangeOrUndefined(options.opacity, 'opacity.opacity', 0, 1);
assertIsOneOfOrUndefined(options.blendMode, 'options.blendMode', BlendMode);

const xObjectKey = addRandomSuffix('EmbeddedPdfPage', 10);
const xObjectKey = this.doc.context.addRandomSuffix('EmbeddedPdfPage', 10);
this.node.setXObject(PDFName.of(xObjectKey), embeddedPage.ref);

const graphicsStateKey = this.maybeEmbedGraphicsState({
Expand Down Expand Up @@ -1592,7 +1591,7 @@ export default class PDFPage {
return undefined;
}

const key = addRandomSuffix('GS', 10);
const key = this.doc.context.addRandomSuffix('GS', 10);

const graphicsState = this.doc.context.obj({
Type: 'ExtGState',
Expand Down
13 changes: 3 additions & 10 deletions src/api/form/PDFField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,7 @@ import {
PDFAcroTerminal,
AnnotationFlags,
} from 'src/core';
import {
addRandomSuffix,
assertIs,
assertMultiple,
assertOrUndefined,
} from 'src/utils';
import { assertIs, assertMultiple, assertOrUndefined } from 'src/utils';
import { ImageAlignment } from '../image';
import PDFImage from '../PDFImage';
import { drawImage, rotateInPlace } from '../operations';
Expand Down Expand Up @@ -321,9 +316,7 @@ export default class PDFField {
);
widget.setRectangle(rect);

if(typeof pageRef !== 'undefined'){
widget.setP(pageRef);
}
if (pageRef) widget.setP(pageRef);

const ac = widget.getOrCreateAppearanceCharacteristics();
if (backgroundColor) {
Expand Down Expand Up @@ -495,7 +488,7 @@ export default class PDFField {
options.y = adj.height - borderWidth - imageDims.height;
}

const imageName = addRandomSuffix('Image', 10);
const imageName = this.doc.context.addRandomSuffix('Image', 10);
const appearance = [...rotate, ...drawImage(imageName, options)];
////////////

Expand Down
4 changes: 2 additions & 2 deletions src/api/form/PDFForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
PDFName,
PDFWidgetAnnotation,
} from 'src/core';
import { addRandomSuffix, assertIs, Cache, assertOrUndefined } from 'src/utils';
import { assertIs, Cache, assertOrUndefined } from 'src/utils';

export interface FlattenOptions {
updateFieldAppearances: boolean;
Expand Down Expand Up @@ -550,7 +550,7 @@ export default class PDFForm {
const page = this.findWidgetPage(widget);
const widgetRef = this.findWidgetAppearanceRef(field, widget);

const xObjectKey = addRandomSuffix('FlatWidget', 10);
const xObjectKey = this.doc.context.addRandomSuffix('FlatWidget', 10);
page.node.setXObject(PDFName.of(xObjectKey), widgetRef);

const rectangle = widget.getRectangle();
Expand Down
2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export * from 'src/api/form';
export * from 'src/api/text';
export * from 'src/api/colors';
export * from 'src/api/errors';
export * from "src/api/image";
export * from 'src/api/image';
export * from 'src/api/objects';
export * from 'src/api/operations';
export * from 'src/api/operators';
Expand Down
10 changes: 5 additions & 5 deletions src/api/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,19 +443,19 @@ export const rotateInPlace = (options: {
rotation: 0 | 90 | 180 | 270;
}) =>
options.rotation === 0 ? [
translate(0, 0),
rotateDegrees(0)
translate(0, 0),
rotateDegrees(0)
]
: options.rotation === 90 ? [
translate(options.width, 0),
translate(options.width, 0),
rotateDegrees(90)
]
: options.rotation === 180 ? [
translate(options.width, options.height),
translate(options.width, options.height),
rotateDegrees(180)
]
: options.rotation === 270 ? [
translate(0, options.height),
translate(0, options.height),
rotateDegrees(270)
]
: []; // Invalid rotation - noop
Expand Down
7 changes: 7 additions & 0 deletions src/core/PDFContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import PDFOperator from 'src/core/operators/PDFOperator';
import Ops from 'src/core/operators/PDFOperatorNames';
import PDFContentStream from 'src/core/structures/PDFContentStream';
import { typedArrayFor } from 'src/utils';
import { SimpleRNG } from 'src/utils/rng';

type LookupKey = PDFRef | PDFObject | undefined;

Expand Down Expand Up @@ -54,6 +55,7 @@ class PDFContext {
Info?: PDFObject;
ID?: PDFObject;
};
rng: SimpleRNG;

private readonly indirectObjects: Map<PDFRef, PDFObject>;

Expand All @@ -66,6 +68,7 @@ class PDFContext {
this.trailerInfo = {};

this.indirectObjects = new Map();
this.rng = SimpleRNG.withSeed(1);
}

assign(ref: PDFRef, object: PDFObject): void {
Expand Down Expand Up @@ -287,6 +290,10 @@ class PDFContext {
this.popGraphicsStateContentStreamRef = this.register(stream);
return this.popGraphicsStateContentStreamRef;
}

addRandomSuffix(prefix: string, suffixLength = 4): string {
return `${prefix}-${Math.floor(this.rng.nextInt() * 10 ** suffixLength)}`;
}
}

export default PDFContext;
4 changes: 2 additions & 2 deletions src/core/annotation/PDFWidgetAnnotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class PDFWidgetAnnotation extends PDFAnnotation {
return undefined;
}

setP(page: PDFRef){
this.dict.set(PDFName.of('P'),page);
setP(page: PDFRef) {
this.dict.set(PDFName.of('P'), page);
}

setDefaultAppearance(appearance: string) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/embedders/CustomFontEmbedder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import PDFRef from 'src/core/objects/PDFRef';
import PDFString from 'src/core/objects/PDFString';
import PDFContext from 'src/core/PDFContext';
import {
addRandomSuffix,
byAscendingId,
Cache,
sortedUniq,
Expand Down Expand Up @@ -106,7 +105,8 @@ class CustomFontEmbedder {
}

embedIntoContext(context: PDFContext, ref?: PDFRef): Promise<PDFRef> {
this.baseFontName = this.customName || addRandomSuffix(this.fontName);
this.baseFontName =
this.customName || context.addRandomSuffix(this.fontName);
return this.embedFontDict(context, ref);
}

Expand Down
21 changes: 21 additions & 0 deletions src/utils/rng.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Generates a pseudo random number. Although it is not cryptographically secure
* and uniformly distributed, it is not a concern for the intended use-case,
* which is to generate distinct numbers.
*
* Credit: https://stackoverflow.com/a/19303725/10254049
*/
export class SimpleRNG {
static withSeed = (seed: number) => new SimpleRNG(seed);

private seed: number;

private constructor(seed: number) {
this.seed = seed;
}

nextInt(): number {
const x = Math.sin(this.seed++) * 10000;
return x - Math.floor(x);
}
}
32 changes: 8 additions & 24 deletions tests/api/PDFDocument.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,39 +142,21 @@ describe(`PDFDocument`, () => {
});

describe(`embedFont() method`, () => {
it(`serializes the same value on every save when using a custom font name`, async () => {
it(`serializes the same value on every save`, async () => {
const customFont = fs.readFileSync('assets/fonts/ubuntu/Ubuntu-B.ttf');
const customName = 'Custom-Font-Name';
const pdfDoc1 = await PDFDocument.create({ updateMetadata: false });
const pdfDoc2 = await PDFDocument.create({ updateMetadata: false });

pdfDoc1.registerFontkit(fontkit);
pdfDoc2.registerFontkit(fontkit);

await pdfDoc1.embedFont(customFont, { customName });
await pdfDoc2.embedFont(customFont, { customName });

const savedDoc1 = await pdfDoc1.save();
const savedDoc2 = await pdfDoc2.save();

expect(savedDoc1).toEqual(savedDoc2);
});

it(`does not serialize the same on save when not using a custom font name`, async () => {
const customFont = fs.readFileSync('assets/fonts/ubuntu/Ubuntu-B.ttf');
const pdfDoc1 = await PDFDocument.create();
const pdfDoc2 = await PDFDocument.create();

pdfDoc1.registerFontkit(fontkit);
pdfDoc2.registerFontkit(fontkit);

await pdfDoc1.embedFont(customFont);
await pdfDoc2.embedFont(customFont);

const savedDoc1 = await pdfDoc1.save();
const savedDoc2 = await pdfDoc2.save();

expect(savedDoc1).not.toEqual(savedDoc2);
expect(savedDoc1).toEqual(savedDoc2);
});
});

Expand Down Expand Up @@ -516,19 +498,21 @@ describe(`copy() method`, () => {
srcDoc.setModificationDate(modificationDate);
pdfDoc = await srcDoc.copy();
});

it(`Returns a pdf with the same number of pages`, async () => {
expect(pdfDoc.getPageCount()).toBe(srcDoc.getPageCount());
});

it(`Can copy author, creationDate, creator, producer, subject, title, defaultWordBreaks`, async () => {
expect(pdfDoc.getAuthor()).toBe(srcDoc.getAuthor());
expect(pdfDoc.getCreationDate()).toStrictEqual(srcDoc.getCreationDate());
expect(pdfDoc.getCreator()).toBe(srcDoc.getCreator());
expect(pdfDoc.getModificationDate()).toStrictEqual(srcDoc.getModificationDate());
expect(pdfDoc.getModificationDate()).toStrictEqual(
srcDoc.getModificationDate(),
);
expect(pdfDoc.getProducer()).toBe(srcDoc.getProducer());
expect(pdfDoc.getSubject()).toBe(srcDoc.getSubject());
expect(pdfDoc.getTitle()).toBe(srcDoc.getTitle());
expect(pdfDoc.defaultWordBreaks).toEqual(srcDoc.defaultWordBreaks);
});
});
});
14 changes: 14 additions & 0 deletions tests/utils/rng.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SimpleRNG } from 'src/utils/rng';

describe(`psuedo random numbers`, () => {
it(`generates distinct numbers`, () => {
const rng = SimpleRNG.withSeed(1);
expect(rng.nextInt()).not.toEqual(rng.nextInt());
});

it(`generates the same number across different SimpleRNG`, () => {
const rng = SimpleRNG.withSeed(1);
expect(rng.nextInt()).toEqual(0.7098480789645691);
expect(rng.nextInt()).toEqual(0.9742682568175951);
});
});

0 comments on commit 2f36ae3

Please sign in to comment.