@muhammara/native-core 7.0.0-alpha.1 → 7.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +45 -0
- package/lib/Recipe.js +48 -3
- package/lib/recipe/appendPage.js +16 -7
- package/lib/recipe/info.js +57 -54
- package/lib/recipe/insertPage.js +5 -7
- package/lib/recipe/replaceText.js +64 -0
- package/lib/recipe/split.js +2 -6
- package/lib/recipe/utils.js +31 -27
- package/muhammara.d.ts +72 -6
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -23,6 +23,51 @@ exports.createMuhammara = function createMuhammara(muhammara) {
|
|
|
23
23
|
eventParams.writer = this;
|
|
24
24
|
this.getEvents().emit(eventName, eventParams);
|
|
25
25
|
};
|
|
26
|
+
muhammara.PDFWriter.prototype.replaceObject = function (
|
|
27
|
+
pageIndex,
|
|
28
|
+
sourceObjectId,
|
|
29
|
+
replacementObjectId,
|
|
30
|
+
options,
|
|
31
|
+
) {
|
|
32
|
+
if (options && options.scope === "global") {
|
|
33
|
+
var copyingContext = this.createPDFCopyingContextForModifiedFile();
|
|
34
|
+
var pageCount = copyingContext.getSourceDocumentParser().getPagesCount();
|
|
35
|
+
|
|
36
|
+
copyingContext.end();
|
|
37
|
+
for (var index = 0; index < pageCount; ++index) {
|
|
38
|
+
this.replaceObject(index, sourceObjectId, replacementObjectId);
|
|
39
|
+
}
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
var copyingContext = this.createPDFCopyingContextForModifiedFile();
|
|
44
|
+
var parser = copyingContext.getSourceDocumentParser();
|
|
45
|
+
var pageObjectId = parser.getPageObjectID(pageIndex);
|
|
46
|
+
var pageObject = parser.parsePage(pageIndex).getDictionary().toJSObject();
|
|
47
|
+
var objectsContext = this.getObjectsContext();
|
|
48
|
+
var pageDictionary;
|
|
49
|
+
|
|
50
|
+
objectsContext.startModifiedIndirectObject(pageObjectId);
|
|
51
|
+
pageDictionary = objectsContext.startDictionary();
|
|
52
|
+
|
|
53
|
+
Object.getOwnPropertyNames(pageObject).forEach(function (key) {
|
|
54
|
+
var value = pageObject[key];
|
|
55
|
+
|
|
56
|
+
pageDictionary.writeKey(key);
|
|
57
|
+
if (
|
|
58
|
+
value.getType() === muhammara.ePDFObjectIndirectObjectReference &&
|
|
59
|
+
value.toPDFIndirectObjectReference().getObjectID() === sourceObjectId
|
|
60
|
+
) {
|
|
61
|
+
pageDictionary.writeObjectReferenceValue(replacementObjectId);
|
|
62
|
+
} else {
|
|
63
|
+
copyingContext.copyDirectObjectAsIs(value);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
objectsContext.endDictionary(pageDictionary).endIndirectObject();
|
|
68
|
+
copyingContext.end();
|
|
69
|
+
return this;
|
|
70
|
+
};
|
|
26
71
|
muhammara.PDFStreamForResponse = require("./lib/PDFStreamForResponse");
|
|
27
72
|
muhammara.PDFWStreamForFile = require("./lib/PDFWStreamForFile");
|
|
28
73
|
muhammara.PDFRStreamForFile = require("./lib/PDFRStreamForFile");
|
package/lib/Recipe.js
CHANGED
|
@@ -11,7 +11,7 @@ const streams = require("memory-streams");
|
|
|
11
11
|
* @param {string|Buffer} src - The file path or Buffer of the source file.
|
|
12
12
|
* @param {string} [output] - The path of the output file uses src if its not a buffer.
|
|
13
13
|
* @param {Object} [options] - The options for pdfDoc
|
|
14
|
-
* @param {number} [options.version] - The pdf version
|
|
14
|
+
* @param {number} [options.version] - The pdf version: 1.0 through 1.7 or 2.0, defaults to 1.7
|
|
15
15
|
* @param {string} [options.author] - The author
|
|
16
16
|
* @param {string} [options.title] - The title
|
|
17
17
|
* @param {string} [options.subject] - The subject
|
|
@@ -121,7 +121,7 @@ class Recipe {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
_getVersion(version) {
|
|
124
|
-
const supportedVersions = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7];
|
|
124
|
+
const supportedVersions = [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 2.0];
|
|
125
125
|
if (!supportedVersions.includes(version)) {
|
|
126
126
|
version = 1.7;
|
|
127
127
|
}
|
|
@@ -149,12 +149,14 @@ class Recipe {
|
|
|
149
149
|
*/
|
|
150
150
|
read(inSrc) {
|
|
151
151
|
const isForExternal = inSrc ? true : false;
|
|
152
|
+
let pdfReader = null;
|
|
153
|
+
let isAdopted = false;
|
|
152
154
|
try {
|
|
153
155
|
let src = isForExternal ? inSrc : this.src;
|
|
154
156
|
if (this.isBufferSrc) {
|
|
155
157
|
src = new muhammara.PDFRStreamForBuffer(this.src);
|
|
156
158
|
}
|
|
157
|
-
|
|
159
|
+
pdfReader = muhammara.createReader(src, this.encryptOptions);
|
|
158
160
|
const pages = pdfReader.getPagesCount();
|
|
159
161
|
if (pages == 0) {
|
|
160
162
|
// broken or modify password protected
|
|
@@ -206,13 +208,50 @@ class Recipe {
|
|
|
206
208
|
metadata[page.pageNumber] = page;
|
|
207
209
|
}
|
|
208
210
|
if (!isForExternal) {
|
|
211
|
+
this._releaseReader();
|
|
209
212
|
this.pdfReader = pdfReader;
|
|
210
213
|
this.metadata = metadata;
|
|
214
|
+
isAdopted = true;
|
|
211
215
|
}
|
|
212
216
|
return metadata;
|
|
213
217
|
} catch (err) {
|
|
214
218
|
throw new Error(err);
|
|
219
|
+
} finally {
|
|
220
|
+
// Only the recipe source reader outlives read(); anything else would keep
|
|
221
|
+
// the file open until the process exits.
|
|
222
|
+
if (pdfReader && !isAdopted) {
|
|
223
|
+
pdfReader.end();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Release the source reader and the file handle it holds.
|
|
230
|
+
* @private
|
|
231
|
+
* @returns {void}
|
|
232
|
+
*/
|
|
233
|
+
_releaseReader() {
|
|
234
|
+
const pdfReader = this.pdfReader;
|
|
235
|
+
if (!pdfReader) {
|
|
236
|
+
return;
|
|
215
237
|
}
|
|
238
|
+
this.pdfReader = null;
|
|
239
|
+
pdfReader.end();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get the source reader, which endPDF() releases.
|
|
244
|
+
* @private
|
|
245
|
+
* @returns {Object} The source PDF reader.
|
|
246
|
+
* @throws {Error} If the reader was already released by endPDF().
|
|
247
|
+
*/
|
|
248
|
+
_getReader() {
|
|
249
|
+
if (!this.pdfReader) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
"The source PDF reader has been released by endPDF(). Read the source before ending the document.",
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return this.pdfReader;
|
|
216
255
|
}
|
|
217
256
|
|
|
218
257
|
/**
|
|
@@ -256,6 +295,12 @@ class Recipe {
|
|
|
256
295
|
this._writeInfo();
|
|
257
296
|
this.writer.end();
|
|
258
297
|
}
|
|
298
|
+
|
|
299
|
+
// Every step above may still read the source; _insertPages() and _encrypt()
|
|
300
|
+
// below rename the output, which is the source itself when no separate
|
|
301
|
+
// output was given, and Windows refuses that while the reader holds it.
|
|
302
|
+
this._releaseReader();
|
|
303
|
+
|
|
259
304
|
if (this.needToInsertPages) {
|
|
260
305
|
if (this.isBufferSrc) {
|
|
261
306
|
// eslint-disable-next-line no-console
|
package/lib/recipe/appendPage.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const muhammara = require("../muhammara");
|
|
2
|
-
const
|
|
2
|
+
const utils = require("./utils");
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Append pages from the other pdf to the current pdf
|
|
@@ -16,10 +16,19 @@ exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
|
16
16
|
}
|
|
17
17
|
// Using stream so it can be closed to release reader resource (Issue #61)
|
|
18
18
|
const instream = new muhammara.PDFRStreamForFile(pdfSrc);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
let pageCount;
|
|
20
|
+
try {
|
|
21
|
+
const pdfReader = muhammara.createReader(instream);
|
|
22
|
+
try {
|
|
23
|
+
pageCount = pdfReader.getPagesCount();
|
|
24
|
+
} finally {
|
|
25
|
+
pdfReader.end();
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
// An unreadable source must not leave the file open, which would keep it
|
|
29
|
+
// locked on Windows for the life of the process.
|
|
30
|
+
instream.close();
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
// prevent unmatched pagenumber
|
|
25
34
|
const transformPageNumber = (pageNum) => {
|
|
@@ -35,11 +44,11 @@ exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
|
35
44
|
}
|
|
36
45
|
});
|
|
37
46
|
if (pages.length > 0) {
|
|
38
|
-
|
|
47
|
+
utils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc, {
|
|
39
48
|
specificRanges: pages,
|
|
40
49
|
});
|
|
41
50
|
} else {
|
|
42
|
-
|
|
51
|
+
utils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc);
|
|
43
52
|
}
|
|
44
53
|
return this;
|
|
45
54
|
};
|
package/lib/recipe/info.js
CHANGED
|
@@ -33,60 +33,63 @@ exports._readInfo = function _readInfo() {
|
|
|
33
33
|
? new muhammara.PDFRStreamForBuffer(this.src)
|
|
34
34
|
: this.src;
|
|
35
35
|
const copyCtx = this.writer.createPDFCopyingContext(copyFrom);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
try {
|
|
37
|
+
const infoDict = copyCtx
|
|
38
|
+
.getSourceDocumentParser()
|
|
39
|
+
.queryDictionaryObject(
|
|
40
|
+
copyCtx.getSourceDocumentParser().getTrailer(),
|
|
41
|
+
"Info",
|
|
42
|
+
);
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
const oldInfo =
|
|
45
|
+
infoDict && infoDict.toJSObject ? infoDict.toJSObject() : null;
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
47
|
+
if (oldInfo) {
|
|
48
|
+
this.infoDictionary = {};
|
|
49
|
+
Object.getOwnPropertyNames(oldInfo).forEach((key) => {
|
|
50
|
+
if (!oldInfo[key]) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const oldInforSrc = this._parseObjectByType(oldInfo[key]);
|
|
54
|
+
if (!oldInforSrc) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
switch (key) {
|
|
58
|
+
case "Trapped":
|
|
59
|
+
if (oldInforSrc && oldInforSrc.value) {
|
|
60
|
+
this.infoDictionary.trapped = oldInforSrc.value;
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
case "CreationDate":
|
|
64
|
+
if (oldInforSrc && oldInforSrc.value) {
|
|
65
|
+
this.infoDictionary.creationDate = oldInforSrc.value;
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case "ModDate":
|
|
69
|
+
if (oldInforSrc && oldInforSrc.value) {
|
|
70
|
+
this.infoDictionary.modDate = oldInforSrc.value;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case "Creator":
|
|
74
|
+
if (oldInforSrc && oldInforSrc.toText) {
|
|
75
|
+
this.infoDictionary.creator = oldInforSrc.toText();
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
case "Producer":
|
|
79
|
+
if (oldInforSrc && oldInforSrc.toText) {
|
|
80
|
+
this.infoDictionary.producer = oldInforSrc.toText();
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
if (oldInforSrc && oldInforSrc.toText) {
|
|
85
|
+
this.infoDictionary[key.toLowerCase()] = oldInforSrc.toText();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
copyCtx.end();
|
|
88
92
|
}
|
|
89
|
-
copyCtx.end();
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
return this.infoDictionary;
|
|
@@ -168,7 +171,7 @@ exports._writeInfo = function _writeInfo() {
|
|
|
168
171
|
infoDictionary.producer =
|
|
169
172
|
"MuhammaraJS (https://github.com/julianhille/MuhammaraJS)";
|
|
170
173
|
infoDictionary.creator =
|
|
171
|
-
"
|
|
174
|
+
"Muhammara-Recipe (https://github.com/julianhille/MuhammaraJS)";
|
|
172
175
|
|
|
173
176
|
fields.forEach((item) => {
|
|
174
177
|
let value = options[item.key];
|
|
@@ -225,7 +228,7 @@ exports.structure = function structure(output) {
|
|
|
225
228
|
// const outputFileType = path.extname(output);
|
|
226
229
|
const outputFile = fs.openSync(output, "w");
|
|
227
230
|
const muhammara = this.muhammara;
|
|
228
|
-
const pdfReader = this.
|
|
231
|
+
const pdfReader = this._getReader();
|
|
229
232
|
|
|
230
233
|
const tabWidth = " ";
|
|
231
234
|
const structures = [
|
|
@@ -331,7 +334,7 @@ exports._parseObjectByType = function _parseObjectByType(inObject) {
|
|
|
331
334
|
return;
|
|
332
335
|
}
|
|
333
336
|
const muhammara = this.muhammara;
|
|
334
|
-
const pdfReader = this.
|
|
337
|
+
const pdfReader = this._getReader();
|
|
335
338
|
const type = inObject.getType();
|
|
336
339
|
const label = muhammara.getTypeLabel(type);
|
|
337
340
|
const saveToObject = this.pdfStructure || {};
|
package/lib/recipe/insertPage.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const muhammara = require("../muhammara");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const
|
|
3
|
+
const utils = require("./utils");
|
|
4
4
|
/**
|
|
5
5
|
* Insert a page from the other pdf
|
|
6
6
|
* @name insertPage
|
|
@@ -53,7 +53,7 @@ exports._insertPages = function _insertPages() {
|
|
|
53
53
|
const toAppendPage = pageNumber - 1;
|
|
54
54
|
if (toAppendPage >= 0) {
|
|
55
55
|
const specificRanges = [[lastInsertedOriginal, toAppendPage]];
|
|
56
|
-
|
|
56
|
+
utils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, tmp, {
|
|
57
57
|
specificRanges,
|
|
58
58
|
});
|
|
59
59
|
}
|
|
@@ -65,11 +65,9 @@ exports._insertPages = function _insertPages() {
|
|
|
65
65
|
const specificRanges = [
|
|
66
66
|
[info.srcPageNumber - 1, info.srcPageNumber - 1],
|
|
67
67
|
];
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{ specificRanges },
|
|
72
|
-
);
|
|
68
|
+
utils.appendPDFPagesFromPDFWithAnnotations(pdfWriter, info.pdfSrc, {
|
|
69
|
+
specificRanges,
|
|
70
|
+
});
|
|
73
71
|
});
|
|
74
72
|
}
|
|
75
73
|
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
function escapePDFLiteralString(value) {
|
|
2
|
+
return value.replace(/([\\()])/g, "\\$1");
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Replace literal text-showing operands in a page's single content stream.
|
|
7
|
+
*
|
|
8
|
+
* @name replaceText
|
|
9
|
+
* @function
|
|
10
|
+
* @memberof Recipe#
|
|
11
|
+
* @param {string} text Text to replace.
|
|
12
|
+
* @param {string} replacement Replacement text.
|
|
13
|
+
* @param {number} [pageNumber=1] One-based page number.
|
|
14
|
+
*/
|
|
15
|
+
exports.replaceText = function replaceText(text, replacement, pageNumber) {
|
|
16
|
+
pageNumber = pageNumber || 1;
|
|
17
|
+
|
|
18
|
+
if (typeof text !== "string" || typeof replacement !== "string") {
|
|
19
|
+
throw new TypeError("replaceText expects text and replacement strings");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var pageIndex = pageNumber - 1;
|
|
23
|
+
var page = this.pdfReader.parsePage(pageIndex).getDictionary();
|
|
24
|
+
var contents = page.queryObject("Contents");
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
!contents ||
|
|
28
|
+
contents.getType() !== this.muhammara.ePDFObjectIndirectObjectReference
|
|
29
|
+
) {
|
|
30
|
+
throw new Error("replaceText supports pages with one content stream");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var contentsObjectId = contents.toPDFIndirectObjectReference().getObjectID();
|
|
34
|
+
var stream = this.pdfReader.parseNewObject(contentsObjectId).toPDFStream();
|
|
35
|
+
var streamReader = this.pdfReader.startReadingFromStream(stream);
|
|
36
|
+
var chunks = [];
|
|
37
|
+
|
|
38
|
+
while (streamReader.notEnded()) {
|
|
39
|
+
chunks.push(Buffer.from(streamReader.read(65536)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
var source = Buffer.concat(chunks).toString("latin1");
|
|
43
|
+
var textPattern = new RegExp(
|
|
44
|
+
"\\(" + escapePDFLiteralString(text) + "\\)(\\s+Tj\\b)",
|
|
45
|
+
"g",
|
|
46
|
+
);
|
|
47
|
+
var replaced = source.replace(
|
|
48
|
+
textPattern,
|
|
49
|
+
"(" + escapePDFLiteralString(replacement) + ")$1",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (replaced === source) {
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var objectsContext = this.writer.getObjectsContext();
|
|
57
|
+
var replacementObjectId = objectsContext.startNewIndirectObject();
|
|
58
|
+
var replacementStream = objectsContext.startUnfilteredPDFStream();
|
|
59
|
+
|
|
60
|
+
replacementStream.getWriteStream().write(Array.from(Buffer.from(replaced)));
|
|
61
|
+
objectsContext.endPDFStream(replacementStream).endIndirectObject();
|
|
62
|
+
this.writer.replaceObject(pageIndex, contentsObjectId, replacementObjectId);
|
|
63
|
+
return this;
|
|
64
|
+
};
|
package/lib/recipe/split.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const muhammara = require("../muhammara");
|
|
2
2
|
const path = require("path");
|
|
3
|
-
const
|
|
3
|
+
const utils = require("./utils");
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Split the pdf
|
|
@@ -16,11 +16,7 @@ exports.split = function split(outputDir = "", prefix) {
|
|
|
16
16
|
for (let i = 0; i < this.metadata.pages; i++) {
|
|
17
17
|
const newPdf = path.join(outputDir, `${prefix}-${i + 1}.pdf`);
|
|
18
18
|
const pdfWriter = muhammara.createWriter(newPdf);
|
|
19
|
-
|
|
20
|
-
pdfWriter,
|
|
21
|
-
this.pdfReader,
|
|
22
|
-
i,
|
|
23
|
-
);
|
|
19
|
+
utils.appendPDFPageFromPDFWithAnnotations(pdfWriter, this._getReader(), i);
|
|
24
20
|
pdfWriter.end();
|
|
25
21
|
}
|
|
26
22
|
return this;
|
package/lib/recipe/utils.js
CHANGED
|
@@ -14,28 +14,30 @@ function appendPDFPageFromPDFWithAnnotations(
|
|
|
14
14
|
pageNumber,
|
|
15
15
|
) {
|
|
16
16
|
const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
try {
|
|
18
|
+
const cpyCxtParser = cpyCxt.getSourceDocumentParser();
|
|
19
|
+
const pageDictionary = cpyCxtParser.parsePageDictionary(pageNumber);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
if (!pageDictionary.exists(ANNOTATION_PREFIX)) {
|
|
22
|
+
cpyCxt.appendPDFPageFromPDF(pageNumber);
|
|
23
|
+
} else {
|
|
24
|
+
let reffedObjects;
|
|
25
|
+
pdfWriter.getEvents().once("OnPageWrite", (params) => {
|
|
26
|
+
params.pageDictionaryContext.writeKey(ANNOTATION_PREFIX);
|
|
27
|
+
reffedObjects = cpyCxt.copyDirectObjectWithDeepCopy(
|
|
28
|
+
pageDictionary.queryObject(ANNOTATION_PREFIX),
|
|
29
|
+
);
|
|
30
|
+
});
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
cpyCxt.appendPDFPageFromPDF(pageNumber);
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
if (reffedObjects && reffedObjects.length > 0) {
|
|
35
|
+
cpyCxt.copyNewObjectsForDirectObject(reffedObjects);
|
|
36
|
+
}
|
|
35
37
|
}
|
|
38
|
+
} finally {
|
|
39
|
+
cpyCxt.end();
|
|
36
40
|
}
|
|
37
|
-
|
|
38
|
-
cpyCxt.end();
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -51,21 +53,23 @@ function appendPDFPagesFromPDFWithAnnotations(
|
|
|
51
53
|
options = {},
|
|
52
54
|
) {
|
|
53
55
|
const cpyCxt = pdfWriter.createPDFCopyingContext(sourcePDFPath);
|
|
54
|
-
|
|
56
|
+
try {
|
|
57
|
+
const cpyCxtParser = cpyCxt.getSourceDocumentParser();
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (options.specificRanges && options.specificRanges.length) {
|
|
60
|
+
for (const [start, end] of options.specificRanges) {
|
|
61
|
+
for (let i = start; i <= end; ++i) {
|
|
62
|
+
appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
for (let i = 0; i < cpyCxtParser.getPagesCount(); ++i) {
|
|
59
67
|
appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
appendPDFPageFromPDFWithAnnotations(pdfWriter, sourcePDFPath, i);
|
|
65
|
-
}
|
|
70
|
+
} finally {
|
|
71
|
+
cpyCxt.end();
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
cpyCxt.end();
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
exports.ANNOTATION_PREFIX = ANNOTATION_PREFIX;
|
package/muhammara.d.ts
CHANGED
|
@@ -327,6 +327,16 @@ declare namespace muhammara {
|
|
|
327
327
|
export type PDFObjectType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
|
328
328
|
export function getTypeLabel(type: PDFObjectType): string;
|
|
329
329
|
|
|
330
|
+
export const ePDFPageContentItemText = 0;
|
|
331
|
+
export const ePDFPageContentItemPath = 1;
|
|
332
|
+
export const ePDFPageContentItemXObject = 2;
|
|
333
|
+
export const ePDFPageContentItemShading = 3;
|
|
334
|
+
export type PDFPageContentItemType =
|
|
335
|
+
| typeof ePDFPageContentItemText
|
|
336
|
+
| typeof ePDFPageContentItemPath
|
|
337
|
+
| typeof ePDFPageContentItemXObject
|
|
338
|
+
| typeof ePDFPageContentItemShading;
|
|
339
|
+
|
|
330
340
|
export const ePDFPageBoxMediaBox = 0;
|
|
331
341
|
export const ePDFPageBoxCropBox = 1;
|
|
332
342
|
export const ePDFPageBoxBleedBox = 2;
|
|
@@ -400,6 +410,28 @@ declare namespace muhammara {
|
|
|
400
410
|
textMatrix: [number, number, number, number, number, number];
|
|
401
411
|
}
|
|
402
412
|
|
|
413
|
+
/** A content-stream operation that produces a page mark. */
|
|
414
|
+
export interface PDFPageContentItem {
|
|
415
|
+
type: PDFPageContentItemType;
|
|
416
|
+
operation: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Per-call extraction budget. Every field is clamped to a built-in ceiling,
|
|
421
|
+
* so a caller may tighten a limit but never raise it above the default.
|
|
422
|
+
* Omitted fields keep the ceiling.
|
|
423
|
+
*/
|
|
424
|
+
export interface PDFExtractionLimits {
|
|
425
|
+
/** Extracted elements or items. Default and ceiling: 100000. */
|
|
426
|
+
maxElements?: number;
|
|
427
|
+
/** Pending operands per operator. Default and ceiling: 1024. */
|
|
428
|
+
maxOperands?: number;
|
|
429
|
+
/** Total extracted text. Default and ceiling: 16777216 (16 MiB). */
|
|
430
|
+
maxTextBytes?: number;
|
|
431
|
+
/** Content-stream objects parsed. Default and ceiling: 1000000. */
|
|
432
|
+
maxParsedObjects?: number;
|
|
433
|
+
}
|
|
434
|
+
|
|
403
435
|
export interface RectangleDimension {
|
|
404
436
|
width: Width;
|
|
405
437
|
height: Height;
|
|
@@ -444,15 +476,33 @@ declare namespace muhammara {
|
|
|
444
476
|
): undefined | PDFObject;
|
|
445
477
|
parseNewObject(objectId: number): PDFObject;
|
|
446
478
|
getPageObjectID(objectId: number): number;
|
|
447
|
-
parsePageDictionary(
|
|
448
|
-
parsePage(
|
|
479
|
+
parsePageDictionary(pageIndex: number): PDFDictionary;
|
|
480
|
+
parsePage(pageIndex: number): PDFPageInput;
|
|
449
481
|
/**
|
|
450
482
|
* Returns text-showing operations in PDF content-stream drawing order.
|
|
451
|
-
* Does not decode font character maps or calculate glyph bounds.
|
|
452
|
-
*
|
|
453
|
-
*
|
|
483
|
+
* Does not decode font character maps or calculate glyph bounds.
|
|
484
|
+
*
|
|
485
|
+
* Throws when the page exceeds the extraction budget. `limits` may only
|
|
486
|
+
* tighten the defaults: higher values are clamped to the built-in ceilings
|
|
487
|
+
* of 1,000,000 content objects, 100,000 text operations, 1024 operands, and
|
|
488
|
+
* 16 MiB of text.
|
|
489
|
+
*/
|
|
490
|
+
extractPageText(
|
|
491
|
+
pageIndex: number,
|
|
492
|
+
limits?: PDFExtractionLimits,
|
|
493
|
+
): PDFTextElement[];
|
|
494
|
+
/**
|
|
495
|
+
* Returns every direct content-stream operation that produces a page mark.
|
|
496
|
+
* White-on-white content is included; non-painting operations are excluded.
|
|
497
|
+
*
|
|
498
|
+
* Shares `extractPageText`'s budget and clamping. `limits.maxTextBytes` is
|
|
499
|
+
* accepted for signature parity but has no effect here, because items carry
|
|
500
|
+
* an operator name rather than extracted text.
|
|
454
501
|
*/
|
|
455
|
-
|
|
502
|
+
extractPageContentItems(
|
|
503
|
+
pageIndex: number,
|
|
504
|
+
limits?: PDFExtractionLimits,
|
|
505
|
+
): PDFPageContentItem[];
|
|
456
506
|
getObjectsCount(): number;
|
|
457
507
|
isEncrypted(): boolean;
|
|
458
508
|
getXrefSize(): number;
|
|
@@ -747,6 +797,16 @@ declare namespace muhammara {
|
|
|
747
797
|
export type inInterPagesCallback = () => {};
|
|
748
798
|
|
|
749
799
|
export interface PDFWriter {
|
|
800
|
+
/**
|
|
801
|
+
* Replace direct references to an object in a page dictionary.
|
|
802
|
+
* Available only when modifying an existing PDF.
|
|
803
|
+
*/
|
|
804
|
+
replaceObject(
|
|
805
|
+
pageIndex: number,
|
|
806
|
+
sourceObjectId: number,
|
|
807
|
+
replacementObjectId: number,
|
|
808
|
+
options?: ObjectReplacementOptions,
|
|
809
|
+
): this;
|
|
750
810
|
end(): PDFWriter;
|
|
751
811
|
createPage(x: PosX, y: PosY, width: Width, height: Height): PDFPage;
|
|
752
812
|
createPage(): PDFPage;
|
|
@@ -845,6 +905,10 @@ declare namespace muhammara {
|
|
|
845
905
|
): void;
|
|
846
906
|
}
|
|
847
907
|
|
|
908
|
+
export interface ObjectReplacementOptions {
|
|
909
|
+
scope?: "global";
|
|
910
|
+
}
|
|
911
|
+
|
|
848
912
|
namespace Recipe {
|
|
849
913
|
type CommentOptionsFlag =
|
|
850
914
|
| "invisible"
|
|
@@ -1179,6 +1243,8 @@ declare namespace muhammara {
|
|
|
1179
1243
|
|
|
1180
1244
|
editPage(pageNumber: number): Recipe;
|
|
1181
1245
|
|
|
1246
|
+
replaceText(text: string, replacement: string, pageNumber?: number): Recipe;
|
|
1247
|
+
|
|
1182
1248
|
pageInfo(pageNumber: number): {
|
|
1183
1249
|
width: number;
|
|
1184
1250
|
height: number;
|
package/package.json
CHANGED