Skip to content

Commit 87ac8a9

Browse files
committed
Reimplementation of XREF objects registration for propper rearrange of deletions. Deletion of pages objects on page removal. Snapshots ref deletion registration
1 parent f173b86 commit 87ac8a9

File tree

16 files changed

+234
-9
lines changed

16 files changed

+234
-9
lines changed

apps/deno/tests/test20.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ import {
1414

1515
/**
1616
* This test modifies the pdf adding a page and a placeholder for an electronic signature.
17+
* It also deletes the first page of the PDF.
1718
* The file should have an incremental update at the end, and the start of the file be exactly the original file.
1819
*/
1920
export default async (assets: Assets) => {
2021
const pdfDoc = await PDFDocument.load(assets.pdfs.simple, {
2122
forIncrementalUpdate: true,
2223
});
2324
const page = pdfDoc.addPage([500, 200]);
25+
pdfDoc.removePage(0);
2426
const font = pdfDoc.embedStandardFont(StandardFonts.Helvetica);
2527
const fontBold = pdfDoc.embedStandardFont(StandardFonts.HelveticaBoldOblique);
2628
const advertencia = `-- Esta nota es meramente informativa, no es la firma real del documento --`;
@@ -164,5 +166,5 @@ export default async (assets: Assets) => {
164166
acroForm.dict.set(PDFName.of('Fields'), fields);
165167
}
166168
(fields as PDFArray).push(widgetDictRef);
167-
return await pdfDoc.save();
169+
return await pdfDoc.save({ useObjectStreams: false });
168170
};

apps/node/tests/test20.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {
1313

1414
/**
1515
* This test modifies the pdf adding a page and a placeholder for an electronic signature.
16+
* It also deletes the first page of the PDF.
1617
* The file should have an incremental update at the end, and the start of the file be exactly the original file.
1718
*/
1819
export default async (assets: Assets) => {
1920
const pdfDoc = await PDFDocument.load(assets.pdfs.simple, {
2021
forIncrementalUpdate: true,
2122
});
2223
const page = pdfDoc.addPage([500, 200]);
24+
pdfDoc.removePage(0);
2325
const font = pdfDoc.embedStandardFont(StandardFonts.Helvetica);
2426
const fontBold = pdfDoc.embedStandardFont(StandardFonts.HelveticaBoldOblique);
2527
const advertencia = `-- Esta nota es meramente informativa, no es la firma real del documento --`;
@@ -163,5 +165,5 @@ export default async (assets: Assets) => {
163165
acroForm.dict.set(PDFName.of('Fields'), fields);
164166
}
165167
(fields as PDFArray).push(widgetDictRef);
166-
return await pdfDoc.save();
168+
return await pdfDoc.save({ useObjectStreams: false });
167169
};

apps/web/test34.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
forIncrementalUpdate: true,
6666
});
6767
const page = pdfDoc.addPage([500, 150]);
68+
pdfDoc.removePage(0);
6869
const font = pdfDoc.embedStandardFont(StandardFonts.Helvetica);
6970
const fontBold = pdfDoc.embedStandardFont(
7071
StandardFonts.HelveticaBoldOblique,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"scratchpad:flame": "rimraf isolate*.log && node --prof scratchpad/build/scratchpad/index.js && node --prof-process --preprocess -j isolate*.log | flamebearer",
7272
"apps:node": "tsc --build apps/node/tsconfig.json && node apps/node-build/index.js",
7373
"apps:deno": "deno run --allow-read --allow-write --allow-run apps/deno/index.ts",
74-
"apps:web": "http-server -c-1 .",
74+
"apps:web": "http-server -c-1 -o apps/web/test1.html",
7575
"apps:web:mac": "bash -c 'sleep 1 && open http://localhost:8080/apps/web/test1.html' & yarn apps:web",
7676
"apps:rn:ios": "cd apps/rn && yarn add ./../.. --force && react-native run-ios",
7777
"apps:rn:android": "yarn apps:rn:emulator & cd apps/rn && yarn add ./../.. --force && react-native run-android",

src/api/PDFDocument.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,8 +688,10 @@ export default class PDFDocument {
688688
const pageCount = this.getPageCount();
689689
if (this.pageCount === 0) throw new RemovePageFromEmptyDocumentError();
690690
assertRange(index, 'index', 0, pageCount - 1);
691+
const page = this.getPage(index);
691692
this.catalog.removeLeafNode(index);
692693
this.pageCount = pageCount - 1;
694+
this.context.delete(page.ref);
693695
}
694696

695697
/**

src/api/snapshot/DefaultDocumentSnapshot.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { DocumentSnapshot } from './DocumentSnapshot';
44
export class DefaultDocumentSnapshot implements DocumentSnapshot {
55
pdfSize = 0;
66
prevStartXRef = 0;
7+
deletedCount = 0;
78

89
shouldSave(_objectNumber: number): boolean {
910
return true;
@@ -24,6 +25,18 @@ export class DefaultDocumentSnapshot implements DocumentSnapshot {
2425
markObjsForSave(_objs: PDFObject[]): void {
2526
throw new Error('This method should not be called.');
2627
}
28+
29+
markDeletedObj(_obj: PDFObject): void {
30+
throw new Error('This method should not be called.');
31+
}
32+
33+
markDeletedRef(_ref: PDFRef): void {
34+
throw new Error('This method should not be called.');
35+
}
36+
37+
deletedRef(_index: number): PDFRef | null {
38+
throw new Error('This method should not be called.');
39+
}
2740
}
2841

2942
export const defaultDocumentSnapshot = new DefaultDocumentSnapshot();

src/api/snapshot/DocumentSnapshot.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { PDFObject, PDFRef } from '../../core';
33
export interface DocumentSnapshot {
44
pdfSize: number;
55
prevStartXRef: number;
6+
deletedCount: number;
67

78
shouldSave: (objectNumber: number) => boolean;
89

@@ -11,4 +12,9 @@ export interface DocumentSnapshot {
1112

1213
markObjForSave: (obj: PDFObject) => void;
1314
markObjsForSave: (objs: PDFObject[]) => void;
15+
16+
markDeletedRef: (ref: PDFRef) => void;
17+
markDeletedObj: (obj: PDFObject) => void;
18+
19+
deletedRef: (index: number) => PDFRef | null;
1420
}

src/api/snapshot/IncrementalDocumentSnapshot.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import type { DocumentSnapshot } from './DocumentSnapshot';
44
export class IncrementalDocumentSnapshot implements DocumentSnapshot {
55
pdfSize: number;
66
prevStartXRef: number;
7+
deletedCount: number = 0;
78

9+
private deleted: PDFRef[] = [];
810
private lastObjectNumber: number;
911
private changedObjects: number[];
1012

@@ -56,4 +58,22 @@ export class IncrementalDocumentSnapshot implements DocumentSnapshot {
5658
.filter((ref) => ref !== undefined) as PDFRef[],
5759
);
5860
}
61+
62+
markDeletedRef(ref: PDFRef): void {
63+
if (
64+
this.deleted.findIndex((dref) => dref.objectNumber === ref.objectNumber) <
65+
0
66+
)
67+
this.deletedCount = this.deleted.push(ref);
68+
}
69+
70+
markDeletedObj(obj: PDFObject): void {
71+
const oref = this.context.getRef(obj);
72+
if (oref) this.markDeletedRef(oref);
73+
}
74+
75+
deletedRef(index: number): PDFRef | null {
76+
if (index < 0 || index >= this.deleted.length) return null;
77+
return this.deleted[index];
78+
}
5979
}

src/core/PDFContext.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class PDFContext {
6060
largestObjectNumber: number;
6161
header: PDFHeader;
6262
trailerInfo: {
63+
Size?: PDFNumber;
6364
Root?: PDFObject;
6465
Encrypt?: PDFObject;
6566
Info?: PDFObject;
@@ -114,6 +115,7 @@ class PDFContext {
114115
}
115116

116117
delete(ref: PDFRef): boolean {
118+
if (this.snapshot) this.snapshot.markDeletedRef(ref);
117119
return this.indirectObjects.delete(ref);
118120
}
119121

src/core/document/PDFCrossRefSection.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface Entry {
1010

1111
/**
1212
* Entries should be added using the [[addEntry]] and [[addDeletedEntry]]
13-
* methods **in order of ascending object number**.
13+
* methods.
1414
*/
1515
class PDFCrossRefSection {
1616
static create = () =>
@@ -37,6 +37,22 @@ class PDFCrossRefSection {
3737
}
3838

3939
addDeletedEntry(ref: PDFRef, nextFreeObjectNumber: number): void {
40+
// fix the first entry if required
41+
if (!this.subsections.length) {
42+
this.subsections = [
43+
[
44+
{
45+
ref: PDFRef.of(0, 65535),
46+
offset: ref.objectNumber,
47+
deleted: true,
48+
},
49+
],
50+
];
51+
this.chunkIdx = 0;
52+
this.chunkLength = 1;
53+
} else if (!this.subsections[0][0].offset) {
54+
this.subsections[0][0].offset = ref.objectNumber;
55+
}
4056
this.append({ ref, offset: nextFreeObjectNumber, deleted: true });
4157
}
4258

@@ -159,7 +175,35 @@ class PDFCrossRefSection {
159175
const chunk = this.subsections[this.chunkIdx];
160176
const prevEntry = chunk[this.chunkLength - 1];
161177

162-
if (currEntry.ref.objectNumber - prevEntry.ref.objectNumber > 1) {
178+
if (currEntry.ref.objectNumber - prevEntry.ref.objectNumber !== 1) {
179+
// the current chunk is not the right chunk, find the right one, or create a new one
180+
for (let c = 0; c < this.subsections.length; c++) {
181+
const first = this.subsections[c][0];
182+
const last = this.subsections[c][this.subsections[c].length - 1];
183+
if (first.ref.objectNumber > currEntry.ref.objectNumber) {
184+
// goes before this subsection, or at the start of it
185+
if (first.ref.objectNumber - currEntry.ref.objectNumber === 1) {
186+
// first element of subsection
187+
this.subsections[c].unshift(currEntry);
188+
if (c === this.chunkIdx) this.chunkLength += 1;
189+
return;
190+
} else {
191+
// create subsection
192+
this.subsections.splice(c, 0, [currEntry]);
193+
this.chunkIdx++;
194+
return;
195+
}
196+
} else if (last.ref.objectNumber > currEntry.ref.objectNumber) {
197+
// goes in this subsection, find its place..
198+
const cep = this.subsections[c].findIndex(
199+
(ee) => ee.ref.objectNumber > currEntry.ref.objectNumber,
200+
);
201+
this.subsections[c].splice(cep, 0, currEntry);
202+
if (c === this.chunkIdx) this.chunkLength += 1;
203+
}
204+
// bigger, keep looking
205+
}
206+
// if got to here, then a new subsection is required
163207
this.subsections.push([currEntry]);
164208
this.chunkIdx += 1;
165209
this.chunkLength = 1;

0 commit comments

Comments
 (0)