modern-pdf-lib 0.13.0 → 0.14.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/README.md +13 -9
- package/dist/index.cjs +1227 -257
- package/dist/index.d.cts +405 -15
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +405 -15
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1222 -258
- package/dist/{pdfCatalog-CTfeeqtF.mjs → pdfCatalog-BB2Wnmud.mjs} +23 -9
- package/dist/{pdfCatalog-y_XG8Hq1.cjs → pdfCatalog-COKoYQ8C.cjs} +23 -9
- package/dist/{pdfPage-BebMv6fN.cjs → pdfPage-DBfdinTR.cjs} +42 -19
- package/dist/{pdfPage-B7vA518n.mjs → pdfPage-N1K2U3jI.mjs} +42 -19
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
const require_pdfPage = require('./pdfPage-
|
|
3
|
-
const require_pdfCatalog = require('./pdfCatalog-
|
|
2
|
+
const require_pdfPage = require('./pdfPage-DBfdinTR.cjs');
|
|
3
|
+
const require_pdfCatalog = require('./pdfCatalog-COKoYQ8C.cjs');
|
|
4
4
|
const require_libdeflateWasm = require('./libdeflateWasm-OkNoqBnO.cjs');
|
|
5
5
|
const require_fontSubset = require('./fontSubset-pFc8Dueu.cjs');
|
|
6
6
|
const require_pngEmbed = require('./pngEmbed-OYyOe_W0.cjs');
|
|
@@ -200,20 +200,19 @@ var PdfWriter = class {
|
|
|
200
200
|
*/
|
|
201
201
|
writeObjectStream(entries, xrefEntries) {
|
|
202
202
|
const serializedObjects = [];
|
|
203
|
-
new StringByteWriter();
|
|
204
203
|
for (const entry of entries) {
|
|
205
204
|
const objWriter = new StringByteWriter();
|
|
206
205
|
entry.object.serialize(objWriter);
|
|
207
206
|
serializedObjects.push(objWriter.toUint8Array());
|
|
208
207
|
}
|
|
209
|
-
|
|
208
|
+
const headerParts = [];
|
|
210
209
|
let dataOffset = 0;
|
|
211
210
|
for (let i = 0; i < entries.length; i++) {
|
|
212
|
-
|
|
213
|
-
headerStr += `${entries[i].ref.objectNumber} ${dataOffset}`;
|
|
211
|
+
headerParts.push(`${entries[i].ref.objectNumber} ${dataOffset}`);
|
|
214
212
|
dataOffset += serializedObjects[i].length;
|
|
215
213
|
if (i < entries.length - 1) dataOffset += 1;
|
|
216
214
|
}
|
|
215
|
+
const headerStr = headerParts.join(" ");
|
|
217
216
|
const headerBytes = encoder$5.encode(headerStr + " ");
|
|
218
217
|
const firstOffset = headerBytes.length;
|
|
219
218
|
let totalDataLen = firstOffset;
|
|
@@ -3755,7 +3754,7 @@ const isDelimiter = /* @__PURE__ */ (() => {
|
|
|
3755
3754
|
* `hexVal[b]` is the numeric value (0-15) of a hex character, or -1 if
|
|
3756
3755
|
* the byte is not a valid hex digit.
|
|
3757
3756
|
*/
|
|
3758
|
-
const hexVal = /* @__PURE__ */ (() => {
|
|
3757
|
+
const hexVal$1 = /* @__PURE__ */ (() => {
|
|
3759
3758
|
const t = new Int8Array(256).fill(-1);
|
|
3760
3759
|
for (let i = 0; i <= 9; i++) t[CH_0 + i] = i;
|
|
3761
3760
|
for (let i = 0; i < 6; i++) {
|
|
@@ -4052,7 +4051,7 @@ var PdfLexer = class {
|
|
|
4052
4051
|
const d = this._data;
|
|
4053
4052
|
let pos = startPos + 1;
|
|
4054
4053
|
let depth = 1;
|
|
4055
|
-
|
|
4054
|
+
const parts = [];
|
|
4056
4055
|
while (pos < this.len && depth > 0) {
|
|
4057
4056
|
const c = d[pos];
|
|
4058
4057
|
if (c === CH_BACKSLASH) {
|
|
@@ -4061,35 +4060,35 @@ var PdfLexer = class {
|
|
|
4061
4060
|
const esc = d[pos];
|
|
4062
4061
|
switch (esc) {
|
|
4063
4062
|
case 110:
|
|
4064
|
-
|
|
4063
|
+
parts.push("\n");
|
|
4065
4064
|
pos++;
|
|
4066
4065
|
break;
|
|
4067
4066
|
case 114:
|
|
4068
|
-
|
|
4067
|
+
parts.push("\r");
|
|
4069
4068
|
pos++;
|
|
4070
4069
|
break;
|
|
4071
4070
|
case 116:
|
|
4072
|
-
|
|
4071
|
+
parts.push(" ");
|
|
4073
4072
|
pos++;
|
|
4074
4073
|
break;
|
|
4075
4074
|
case 98:
|
|
4076
|
-
|
|
4075
|
+
parts.push("\b");
|
|
4077
4076
|
pos++;
|
|
4078
4077
|
break;
|
|
4079
4078
|
case 102:
|
|
4080
|
-
|
|
4079
|
+
parts.push("\f");
|
|
4081
4080
|
pos++;
|
|
4082
4081
|
break;
|
|
4083
4082
|
case CH_LPAREN:
|
|
4084
|
-
|
|
4083
|
+
parts.push("(");
|
|
4085
4084
|
pos++;
|
|
4086
4085
|
break;
|
|
4087
4086
|
case CH_RPAREN:
|
|
4088
|
-
|
|
4087
|
+
parts.push(")");
|
|
4089
4088
|
pos++;
|
|
4090
4089
|
break;
|
|
4091
4090
|
case CH_BACKSLASH:
|
|
4092
|
-
|
|
4091
|
+
parts.push("\\");
|
|
4093
4092
|
pos++;
|
|
4094
4093
|
break;
|
|
4095
4094
|
case WS_LF:
|
|
@@ -4111,9 +4110,9 @@ var PdfLexer = class {
|
|
|
4111
4110
|
pos++;
|
|
4112
4111
|
}
|
|
4113
4112
|
}
|
|
4114
|
-
|
|
4113
|
+
parts.push(String.fromCharCode(octal & 255));
|
|
4115
4114
|
} else {
|
|
4116
|
-
|
|
4115
|
+
parts.push(String.fromCharCode(esc));
|
|
4117
4116
|
pos++;
|
|
4118
4117
|
}
|
|
4119
4118
|
break;
|
|
@@ -4122,23 +4121,23 @@ var PdfLexer = class {
|
|
|
4122
4121
|
}
|
|
4123
4122
|
if (c === CH_LPAREN) {
|
|
4124
4123
|
depth++;
|
|
4125
|
-
|
|
4124
|
+
parts.push("(");
|
|
4126
4125
|
pos++;
|
|
4127
4126
|
continue;
|
|
4128
4127
|
}
|
|
4129
4128
|
if (c === CH_RPAREN) {
|
|
4130
4129
|
depth--;
|
|
4131
|
-
if (depth > 0)
|
|
4130
|
+
if (depth > 0) parts.push(")");
|
|
4132
4131
|
pos++;
|
|
4133
4132
|
continue;
|
|
4134
4133
|
}
|
|
4135
4134
|
if (c === WS_CR) {
|
|
4136
|
-
|
|
4135
|
+
parts.push("\n");
|
|
4137
4136
|
pos++;
|
|
4138
4137
|
if (pos < this.len && d[pos] === WS_LF) pos++;
|
|
4139
4138
|
continue;
|
|
4140
4139
|
}
|
|
4141
|
-
|
|
4140
|
+
parts.push(String.fromCharCode(c));
|
|
4142
4141
|
pos++;
|
|
4143
4142
|
}
|
|
4144
4143
|
if (depth !== 0) throw new PdfParseError({
|
|
@@ -4151,7 +4150,7 @@ var PdfLexer = class {
|
|
|
4151
4150
|
this.position = pos;
|
|
4152
4151
|
return {
|
|
4153
4152
|
type: TokenType$1.LiteralString,
|
|
4154
|
-
value:
|
|
4153
|
+
value: parts.join(""),
|
|
4155
4154
|
offset: startPos
|
|
4156
4155
|
};
|
|
4157
4156
|
}
|
|
@@ -4164,7 +4163,8 @@ var PdfLexer = class {
|
|
|
4164
4163
|
readHexString(startPos) {
|
|
4165
4164
|
const d = this._data;
|
|
4166
4165
|
let pos = startPos + 1;
|
|
4167
|
-
|
|
4166
|
+
const bytes = [];
|
|
4167
|
+
let hi = -1;
|
|
4168
4168
|
while (pos < this.len) {
|
|
4169
4169
|
const c = d[pos];
|
|
4170
4170
|
if (c === CH_GT) {
|
|
@@ -4175,27 +4175,26 @@ var PdfLexer = class {
|
|
|
4175
4175
|
pos++;
|
|
4176
4176
|
continue;
|
|
4177
4177
|
}
|
|
4178
|
-
|
|
4178
|
+
const v = hexVal$1[c];
|
|
4179
|
+
if (v === -1) throw new PdfParseError({
|
|
4179
4180
|
message: `PdfLexer: invalid hex digit 0x${c.toString(16).padStart(2, "0")} at offset ${pos} in hex string starting at ${startPos}`,
|
|
4180
4181
|
offset: pos,
|
|
4181
4182
|
expected: "hex digit (0-9, a-f, A-F)",
|
|
4182
4183
|
actual: `0x${c.toString(16).padStart(2, "0")}`,
|
|
4183
4184
|
data: this._data
|
|
4184
4185
|
});
|
|
4185
|
-
|
|
4186
|
+
if (hi === -1) hi = v;
|
|
4187
|
+
else {
|
|
4188
|
+
bytes.push(hi << 4 | v);
|
|
4189
|
+
hi = -1;
|
|
4190
|
+
}
|
|
4186
4191
|
pos++;
|
|
4187
4192
|
}
|
|
4188
|
-
if (
|
|
4189
|
-
let result = "";
|
|
4190
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
4191
|
-
const hi = hexVal[hex.charCodeAt(i)];
|
|
4192
|
-
const lo = hexVal[hex.charCodeAt(i + 1)];
|
|
4193
|
-
result += String.fromCharCode(hi << 4 | lo);
|
|
4194
|
-
}
|
|
4193
|
+
if (hi !== -1) bytes.push(hi << 4);
|
|
4195
4194
|
this.position = pos;
|
|
4196
4195
|
return {
|
|
4197
4196
|
type: TokenType$1.HexString,
|
|
4198
|
-
value:
|
|
4197
|
+
value: String.fromCharCode.apply(null, bytes),
|
|
4199
4198
|
offset: startPos
|
|
4200
4199
|
};
|
|
4201
4200
|
}
|
|
@@ -4208,7 +4207,7 @@ var PdfLexer = class {
|
|
|
4208
4207
|
readName(startPos) {
|
|
4209
4208
|
const d = this._data;
|
|
4210
4209
|
let pos = startPos + 1;
|
|
4211
|
-
|
|
4210
|
+
const parts = ["/"];
|
|
4212
4211
|
while (pos < this.len) {
|
|
4213
4212
|
const c = d[pos];
|
|
4214
4213
|
if (isWhitespace[c] || isDelimiter[c]) break;
|
|
@@ -4220,8 +4219,8 @@ var PdfLexer = class {
|
|
|
4220
4219
|
actual: "end of input",
|
|
4221
4220
|
data: this._data
|
|
4222
4221
|
});
|
|
4223
|
-
const hi = hexVal[d[pos + 1]];
|
|
4224
|
-
const lo = hexVal[d[pos + 2]];
|
|
4222
|
+
const hi = hexVal$1[d[pos + 1]];
|
|
4223
|
+
const lo = hexVal$1[d[pos + 2]];
|
|
4225
4224
|
if (hi === -1 || lo === -1) throw new PdfParseError({
|
|
4226
4225
|
message: `PdfLexer: invalid #XX escape in name at offset ${pos}`,
|
|
4227
4226
|
offset: pos,
|
|
@@ -4229,17 +4228,17 @@ var PdfLexer = class {
|
|
|
4229
4228
|
actual: `#${String.fromCharCode(d[pos + 1])}${String.fromCharCode(d[pos + 2])}`,
|
|
4230
4229
|
data: this._data
|
|
4231
4230
|
});
|
|
4232
|
-
|
|
4231
|
+
parts.push(String.fromCharCode(hi << 4 | lo));
|
|
4233
4232
|
pos += 3;
|
|
4234
4233
|
continue;
|
|
4235
4234
|
}
|
|
4236
|
-
|
|
4235
|
+
parts.push(String.fromCharCode(c));
|
|
4237
4236
|
pos++;
|
|
4238
4237
|
}
|
|
4239
4238
|
this.position = pos;
|
|
4240
4239
|
return {
|
|
4241
4240
|
type: TokenType$1.Name,
|
|
4242
|
-
value:
|
|
4241
|
+
value: parts.join(""),
|
|
4243
4242
|
offset: startPos
|
|
4244
4243
|
};
|
|
4245
4244
|
}
|
|
@@ -4354,10 +4353,7 @@ var PdfLexer = class {
|
|
|
4354
4353
|
* because it avoids the per-call overhead of the streaming decoder.
|
|
4355
4354
|
*/
|
|
4356
4355
|
bytesToAscii(from, to) {
|
|
4357
|
-
|
|
4358
|
-
let s = "";
|
|
4359
|
-
for (let i = from; i < to; i++) s += String.fromCharCode(d[i]);
|
|
4360
|
-
return s;
|
|
4356
|
+
return String.fromCharCode.apply(null, this._data.subarray(from, to));
|
|
4361
4357
|
}
|
|
4362
4358
|
};
|
|
4363
4359
|
|
|
@@ -5251,18 +5247,20 @@ var XrefParser = class {
|
|
|
5251
5247
|
parseTraditionalXref(offset) {
|
|
5252
5248
|
const entries = [];
|
|
5253
5249
|
let pos = offset;
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5250
|
+
if (!this.isTraditionalXref(pos)) {
|
|
5251
|
+
const xrefTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 4));
|
|
5252
|
+
throw new PdfParseError({
|
|
5253
|
+
message: `Invalid PDF: expected "xref" keyword at offset ${offset}, found "${xrefTag}".`,
|
|
5254
|
+
offset,
|
|
5255
|
+
expected: "\"xref\" keyword",
|
|
5256
|
+
actual: `"${xrefTag}"`,
|
|
5257
|
+
data: this.data
|
|
5258
|
+
});
|
|
5259
|
+
}
|
|
5262
5260
|
pos += 4;
|
|
5263
5261
|
pos = this.skipWhitespaceAt(pos);
|
|
5264
5262
|
while (pos < this.data.length) {
|
|
5265
|
-
if (
|
|
5263
|
+
if (this.matchesBytes(pos, 116, 114, 97, 105, 108, 101, 114)) break;
|
|
5266
5264
|
const headerLine = this.readLineAt(pos);
|
|
5267
5265
|
const headerMatch = headerLine.text.trim().match(/^(\d+)\s+(\d+)/);
|
|
5268
5266
|
if (!headerMatch) throw new PdfParseError({
|
|
@@ -5277,35 +5275,45 @@ var XrefParser = class {
|
|
|
5277
5275
|
pos = headerLine.nextPos;
|
|
5278
5276
|
for (let i = 0; i < count; i++) {
|
|
5279
5277
|
const objectNumber = firstObjNum + i;
|
|
5280
|
-
const
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5278
|
+
const parsed = this.parseXrefEntryDirect(pos);
|
|
5279
|
+
if (parsed) {
|
|
5280
|
+
entries.push({
|
|
5281
|
+
objectNumber,
|
|
5282
|
+
generationNumber: parsed.gen,
|
|
5283
|
+
offset: parsed.offset,
|
|
5284
|
+
type: parsed.type
|
|
5285
|
+
});
|
|
5286
|
+
pos = parsed.nextPos;
|
|
5287
|
+
} else {
|
|
5288
|
+
const entryText = this.readXrefEntryAt(pos);
|
|
5289
|
+
const entryMatch = entryText.text.trim().match(/^(\d{10})\s+(\d{5})\s+([fn])/);
|
|
5290
|
+
if (!entryMatch) throw new PdfParseError({
|
|
5291
|
+
message: `Invalid PDF: malformed xref entry at offset ${pos} for object ${objectNumber}: "${entryText.text.trim()}"`,
|
|
5292
|
+
offset: pos,
|
|
5293
|
+
expected: "xref entry \"OOOOOOOOOO GGGGG f/n\"",
|
|
5294
|
+
actual: `"${entryText.text.trim()}"`,
|
|
5295
|
+
data: this.data
|
|
5296
|
+
});
|
|
5297
|
+
entries.push({
|
|
5298
|
+
objectNumber,
|
|
5299
|
+
generationNumber: parseInt(entryMatch[2], 10),
|
|
5300
|
+
offset: parseInt(entryMatch[1], 10),
|
|
5301
|
+
type: entryMatch[3] === "n" ? "in-use" : "free"
|
|
5302
|
+
});
|
|
5303
|
+
pos = entryText.nextPos;
|
|
5304
|
+
}
|
|
5299
5305
|
}
|
|
5300
5306
|
}
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5307
|
+
if (!this.matchesBytes(pos, 116, 114, 97, 105, 108, 101, 114)) {
|
|
5308
|
+
const trailerTag = TEXT_DECODER$1.decode(this.data.subarray(pos, pos + 7));
|
|
5309
|
+
throw new PdfParseError({
|
|
5310
|
+
message: `Invalid PDF: expected "trailer" keyword at offset ${pos}, found "${trailerTag}".`,
|
|
5311
|
+
offset: pos,
|
|
5312
|
+
expected: "\"trailer\" keyword",
|
|
5313
|
+
actual: `"${trailerTag}"`,
|
|
5314
|
+
data: this.data
|
|
5315
|
+
});
|
|
5316
|
+
}
|
|
5309
5317
|
pos += 7;
|
|
5310
5318
|
const trailerObj = this.objectParser.parseObjectAt(pos);
|
|
5311
5319
|
if (trailerObj.kind !== "dict") throw new PdfParseError({
|
|
@@ -5460,17 +5468,33 @@ var XrefParser = class {
|
|
|
5460
5468
|
*/
|
|
5461
5469
|
rebuildXrefFromScan() {
|
|
5462
5470
|
const entries = /* @__PURE__ */ new Map();
|
|
5463
|
-
const
|
|
5464
|
-
const
|
|
5465
|
-
let
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5469
|
-
|
|
5471
|
+
const d = this.data;
|
|
5472
|
+
const len = d.length;
|
|
5473
|
+
for (let i = 0; i < len - 2; i++) {
|
|
5474
|
+
if (d[i] !== 111 || d[i + 1] !== 98 || d[i + 2] !== 106) continue;
|
|
5475
|
+
if (i + 3 < len) {
|
|
5476
|
+
const after = d[i + 3];
|
|
5477
|
+
if (after > 32 && after !== 37 && after !== 40 && after !== 41 && after !== 47 && after !== 60 && after !== 62 && after !== 91 && after !== 93 && after !== 123 && after !== 125) continue;
|
|
5478
|
+
}
|
|
5479
|
+
let j = i - 1;
|
|
5480
|
+
while (j >= 0 && (d[j] === 32 || d[j] === 10 || d[j] === 13 || d[j] === 9 || d[j] === 0)) j--;
|
|
5481
|
+
const genEnd = j + 1;
|
|
5482
|
+
while (j >= 0 && d[j] >= 48 && d[j] <= 57) j--;
|
|
5483
|
+
const genStart = j + 1;
|
|
5484
|
+
if (genStart >= genEnd) continue;
|
|
5485
|
+
while (j >= 0 && (d[j] === 32 || d[j] === 10 || d[j] === 13 || d[j] === 9 || d[j] === 0)) j--;
|
|
5486
|
+
const objEnd = j + 1;
|
|
5487
|
+
while (j >= 0 && d[j] >= 48 && d[j] <= 57) j--;
|
|
5488
|
+
const objStart = j + 1;
|
|
5489
|
+
if (objStart >= objEnd) continue;
|
|
5490
|
+
let objectNumber = 0;
|
|
5491
|
+
for (let k = objStart; k < objEnd; k++) objectNumber = objectNumber * 10 + (d[k] - 48);
|
|
5492
|
+
let gen = 0;
|
|
5493
|
+
for (let k = genStart; k < genEnd; k++) gen = gen * 10 + (d[k] - 48);
|
|
5470
5494
|
entries.set(objectNumber, {
|
|
5471
5495
|
objectNumber,
|
|
5472
5496
|
generationNumber: gen,
|
|
5473
|
-
offset,
|
|
5497
|
+
offset: objStart,
|
|
5474
5498
|
type: "in-use"
|
|
5475
5499
|
});
|
|
5476
5500
|
}
|
|
@@ -5483,7 +5507,11 @@ var XrefParser = class {
|
|
|
5483
5507
|
});
|
|
5484
5508
|
let rootRef;
|
|
5485
5509
|
let infoRef;
|
|
5486
|
-
|
|
5510
|
+
let trailerIdx = -1;
|
|
5511
|
+
for (let i = len - 7; i >= 0; i--) if (d[i] === 116 && d[i + 1] === 114 && d[i + 2] === 97 && d[i + 3] === 105 && d[i + 4] === 108 && d[i + 5] === 101 && d[i + 6] === 114) {
|
|
5512
|
+
trailerIdx = i;
|
|
5513
|
+
break;
|
|
5514
|
+
}
|
|
5487
5515
|
if (trailerIdx !== -1) try {
|
|
5488
5516
|
const trailerObj = this.objectParser.parseObjectAt(trailerIdx + 7);
|
|
5489
5517
|
if (trailerObj.kind === "dict") {
|
|
@@ -5524,6 +5552,48 @@ var XrefParser = class {
|
|
|
5524
5552
|
* Determine whether the data at the given offset starts with the
|
|
5525
5553
|
* "xref" keyword (indicating a traditional xref table).
|
|
5526
5554
|
*/
|
|
5555
|
+
/**
|
|
5556
|
+
* Check if the bytes at `pos` match the given byte values.
|
|
5557
|
+
*/
|
|
5558
|
+
matchesBytes(pos, ...bytes) {
|
|
5559
|
+
if (pos + bytes.length > this.data.length) return false;
|
|
5560
|
+
for (let i = 0; i < bytes.length; i++) if (this.data[pos + i] !== bytes[i]) return false;
|
|
5561
|
+
return true;
|
|
5562
|
+
}
|
|
5563
|
+
/**
|
|
5564
|
+
* Parse a standard xref entry directly from bytes, avoiding TextDecoder
|
|
5565
|
+
* and regex. Returns null if the bytes don't form a valid entry.
|
|
5566
|
+
*
|
|
5567
|
+
* Standard format: `OOOOOOOOOO GGGGG f/n` (18 significant bytes)
|
|
5568
|
+
*/
|
|
5569
|
+
parseXrefEntryDirect(pos) {
|
|
5570
|
+
const d = this.data;
|
|
5571
|
+
if (pos + 18 > d.length) return null;
|
|
5572
|
+
let offset = 0;
|
|
5573
|
+
for (let k = 0; k < 10; k++) {
|
|
5574
|
+
const b = d[pos + k];
|
|
5575
|
+
if (b < 48 || b > 57) return null;
|
|
5576
|
+
offset = offset * 10 + (b - 48);
|
|
5577
|
+
}
|
|
5578
|
+
if (d[pos + 10] !== 32) return null;
|
|
5579
|
+
let gen = 0;
|
|
5580
|
+
for (let k = 0; k < 5; k++) {
|
|
5581
|
+
const b = d[pos + 11 + k];
|
|
5582
|
+
if (b < 48 || b > 57) return null;
|
|
5583
|
+
gen = gen * 10 + (b - 48);
|
|
5584
|
+
}
|
|
5585
|
+
if (d[pos + 16] !== 32) return null;
|
|
5586
|
+
const marker = d[pos + 17];
|
|
5587
|
+
if (marker !== 110 && marker !== 102) return null;
|
|
5588
|
+
let nextPos = pos + 18;
|
|
5589
|
+
while (nextPos < d.length && (d[nextPos] === 32 || d[nextPos] === 13 || d[nextPos] === 10)) nextPos++;
|
|
5590
|
+
return {
|
|
5591
|
+
offset,
|
|
5592
|
+
gen,
|
|
5593
|
+
type: marker === 110 ? "in-use" : "free",
|
|
5594
|
+
nextPos
|
|
5595
|
+
};
|
|
5596
|
+
}
|
|
5527
5597
|
isTraditionalXref(offset) {
|
|
5528
5598
|
if (offset + 4 > this.data.length) return false;
|
|
5529
5599
|
return this.data[offset] === 120 && this.data[offset + 1] === 114 && this.data[offset + 2] === 101 && this.data[offset + 3] === 102;
|
|
@@ -6237,15 +6307,76 @@ function padPassword(password) {
|
|
|
6237
6307
|
return result;
|
|
6238
6308
|
}
|
|
6239
6309
|
/**
|
|
6240
|
-
*
|
|
6241
|
-
*
|
|
6242
|
-
*
|
|
6310
|
+
* Prepare a password for R=5/R=6 (AES-256) using SASLprep (RFC 4013).
|
|
6311
|
+
*
|
|
6312
|
+
* ISO 32000-2 SS7.6.3.1 requires SASLprep normalization for passwords
|
|
6313
|
+
* used with encryption revision 5 and 6. The steps are:
|
|
6314
|
+
*
|
|
6315
|
+
* 1. Map: Convert non-ASCII space characters to U+0020, remove
|
|
6316
|
+
* "commonly mapped to nothing" characters (RFC 3454 B.1).
|
|
6317
|
+
* 2. Normalize: Apply Unicode NFKC normalization.
|
|
6318
|
+
* 3. Prohibit: Reject characters from RFC 3454 prohibited tables.
|
|
6319
|
+
* 4. Bidi: Check bidirectional string rules.
|
|
6320
|
+
*
|
|
6321
|
+
* The result is truncated to 127 UTF-8 bytes per the PDF spec.
|
|
6243
6322
|
*/
|
|
6244
6323
|
function preparePasswordV5(password) {
|
|
6245
|
-
const
|
|
6324
|
+
const prepared = saslprep(password);
|
|
6325
|
+
const encoded = new TextEncoder().encode(prepared);
|
|
6246
6326
|
return encoded.length > 127 ? encoded.subarray(0, 127) : encoded;
|
|
6247
6327
|
}
|
|
6248
6328
|
/**
|
|
6329
|
+
* Simplified SASLprep (RFC 4013) profile of stringprep (RFC 3454).
|
|
6330
|
+
*
|
|
6331
|
+
* Covers the mapping, normalization, and prohibited-character steps
|
|
6332
|
+
* needed for PDF password preparation. Bidi checking is omitted
|
|
6333
|
+
* since PDF passwords are typically LTR and the spec allows
|
|
6334
|
+
* implementations to skip it.
|
|
6335
|
+
*
|
|
6336
|
+
* @internal
|
|
6337
|
+
*/
|
|
6338
|
+
function saslprep(input) {
|
|
6339
|
+
let mapped = "";
|
|
6340
|
+
for (const ch of input) {
|
|
6341
|
+
const cp = ch.codePointAt(0);
|
|
6342
|
+
if (isMappedToNothing(cp)) continue;
|
|
6343
|
+
if (isNonAsciiSpace(cp)) {
|
|
6344
|
+
mapped += " ";
|
|
6345
|
+
continue;
|
|
6346
|
+
}
|
|
6347
|
+
mapped += ch;
|
|
6348
|
+
}
|
|
6349
|
+
const normalized = mapped.normalize("NFKC");
|
|
6350
|
+
for (const ch of normalized) {
|
|
6351
|
+
const cp = ch.codePointAt(0);
|
|
6352
|
+
if (isProhibited(cp)) throw new Error(`Password contains prohibited character U+${cp.toString(16).toUpperCase().padStart(4, "0")} (SASLprep)`);
|
|
6353
|
+
}
|
|
6354
|
+
return normalized;
|
|
6355
|
+
}
|
|
6356
|
+
/** RFC 3454 Table B.1 — Commonly mapped to nothing. */
|
|
6357
|
+
function isMappedToNothing(cp) {
|
|
6358
|
+
return cp === 173 || cp === 6150 || cp === 8203 || cp === 8288 || cp === 65279 || cp === 847 || cp === 6155 || cp === 6156 || cp === 6157 || cp === 8204 || cp === 8205 || cp >= 65024 && cp <= 65039;
|
|
6359
|
+
}
|
|
6360
|
+
/** RFC 3454 Table C.1.2 — Non-ASCII space characters. */
|
|
6361
|
+
function isNonAsciiSpace(cp) {
|
|
6362
|
+
return cp === 160 || cp === 5760 || cp === 8192 || cp === 8193 || cp === 8194 || cp === 8195 || cp === 8196 || cp === 8197 || cp === 8198 || cp === 8199 || cp === 8200 || cp === 8201 || cp === 8202 || cp === 8239 || cp === 8287 || cp === 12288;
|
|
6363
|
+
}
|
|
6364
|
+
/**
|
|
6365
|
+
* RFC 3454 prohibited tables (C.2.1, C.2.2, C.3, C.4, C.5, C.6, C.7, C.8, C.9).
|
|
6366
|
+
* Simplified to the ranges most likely to appear in passwords.
|
|
6367
|
+
*/
|
|
6368
|
+
function isProhibited(cp) {
|
|
6369
|
+
if (cp <= 31 || cp === 127) return true;
|
|
6370
|
+
if (cp >= 128 && cp <= 159 || cp === 1757 || cp === 1807 || cp === 6158 || cp >= 8204 && cp <= 8205 || cp >= 8232 && cp <= 8233 || cp >= 8288 && cp <= 8291 || cp >= 8298 && cp <= 8303 || cp === 65279 || cp >= 65529 && cp <= 65532 || cp >= 119155 && cp <= 119162) return true;
|
|
6371
|
+
if (cp >= 57344 && cp <= 63743 || cp >= 983040 && cp <= 1048573 || cp >= 1048576 && cp <= 1114109) return true;
|
|
6372
|
+
if (cp >= 64976 && cp <= 65007 || (cp & 65535) === 65534 || (cp & 65535) === 65535) return true;
|
|
6373
|
+
if (cp >= 55296 && cp <= 57343) return true;
|
|
6374
|
+
if (cp === 65529 || cp === 65530 || cp === 65531 || cp === 65532) return true;
|
|
6375
|
+
if (cp === 832 || cp === 833 || cp === 8206 || cp === 8207 || cp >= 8234 && cp <= 8238 || cp >= 8298 && cp <= 8303) return true;
|
|
6376
|
+
if (cp === 917505 || cp >= 917536 && cp <= 917631) return true;
|
|
6377
|
+
return false;
|
|
6378
|
+
}
|
|
6379
|
+
/**
|
|
6249
6380
|
* Concatenate multiple Uint8Arrays into one.
|
|
6250
6381
|
*/
|
|
6251
6382
|
function concat$1(...arrays) {
|
|
@@ -6321,10 +6452,12 @@ function computeOwnerPasswordValue(ownerPassword, userPassword, revision, keyLen
|
|
|
6321
6452
|
if (revision >= 3) for (let i = 0; i < 50; i++) hash = md5(hash.subarray(0, keyByteLength));
|
|
6322
6453
|
const rc4Key = hash.subarray(0, keyByteLength);
|
|
6323
6454
|
let encrypted = rc4(rc4Key, padPassword(userPassword));
|
|
6324
|
-
if (revision >= 3)
|
|
6455
|
+
if (revision >= 3) {
|
|
6325
6456
|
const modKey = new Uint8Array(rc4Key.length);
|
|
6326
|
-
for (let
|
|
6327
|
-
|
|
6457
|
+
for (let i = 1; i <= 19; i++) {
|
|
6458
|
+
for (let j = 0; j < rc4Key.length; j++) modKey[j] = (rc4Key[j] ^ i) & 255;
|
|
6459
|
+
encrypted = rc4(modKey, encrypted);
|
|
6460
|
+
}
|
|
6328
6461
|
}
|
|
6329
6462
|
return encrypted;
|
|
6330
6463
|
}
|
|
@@ -6354,10 +6487,12 @@ function computeUserPasswordR2(fileKey) {
|
|
|
6354
6487
|
*/
|
|
6355
6488
|
function computeUserPasswordR3R4(fileKey, fileId) {
|
|
6356
6489
|
let encrypted = rc4(fileKey, md5(concat$1(PASSWORD_PADDING, fileId)));
|
|
6357
|
-
|
|
6490
|
+
{
|
|
6358
6491
|
const modKey = new Uint8Array(fileKey.length);
|
|
6359
|
-
for (let
|
|
6360
|
-
|
|
6492
|
+
for (let i = 1; i <= 19; i++) {
|
|
6493
|
+
for (let j = 0; j < fileKey.length; j++) modKey[j] = (fileKey[j] ^ i) & 255;
|
|
6494
|
+
encrypted = rc4(modKey, encrypted);
|
|
6495
|
+
}
|
|
6361
6496
|
}
|
|
6362
6497
|
const result = new Uint8Array(32);
|
|
6363
6498
|
result.set(encrypted.subarray(0, 16));
|
|
@@ -6423,9 +6558,17 @@ async function algorithm2B(password, salt, uKey) {
|
|
|
6423
6558
|
let K = await sha256(uKey ? concat$1(password, salt, uKey) : concat$1(password, salt));
|
|
6424
6559
|
let round = 0;
|
|
6425
6560
|
while (true) {
|
|
6426
|
-
const
|
|
6427
|
-
const
|
|
6428
|
-
|
|
6561
|
+
const pLen = password.length;
|
|
6562
|
+
const kLen = K.length;
|
|
6563
|
+
const uLen = uKey ? uKey.length : 0;
|
|
6564
|
+
const blockLen = pLen + kLen + uLen;
|
|
6565
|
+
const K1 = new Uint8Array(blockLen * 64);
|
|
6566
|
+
for (let i = 0; i < 64; i++) {
|
|
6567
|
+
const base = i * blockLen;
|
|
6568
|
+
K1.set(password, base);
|
|
6569
|
+
K1.set(K, base + pLen);
|
|
6570
|
+
if (uKey) K1.set(uKey, base + pLen + kLen);
|
|
6571
|
+
}
|
|
6429
6572
|
const E = await aesEncryptCBCNoPad(K.subarray(0, 16), K.subarray(16, 32), K1);
|
|
6430
6573
|
let bigModVal = 0;
|
|
6431
6574
|
for (let i = 0; i < 16; i++) bigModVal = (bigModVal * 256 + E[i]) % 3;
|
|
@@ -6512,11 +6655,33 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
|
|
|
6512
6655
|
}
|
|
6513
6656
|
}
|
|
6514
6657
|
/**
|
|
6658
|
+
* LRU cache for file encryption keys.
|
|
6659
|
+
*
|
|
6660
|
+
* Keyed on a hash derived from password + encryption dict parameters,
|
|
6661
|
+
* so re-opening the same PDF with the same password skips the expensive
|
|
6662
|
+
* key derivation (especially for R=6 which runs 64+ rounds of AES+SHA).
|
|
6663
|
+
*/
|
|
6664
|
+
const fileKeyCache = /* @__PURE__ */ new Map();
|
|
6665
|
+
const FILE_KEY_CACHE_MAX = 32;
|
|
6666
|
+
/**
|
|
6667
|
+
* Build a cache key string from the inputs that uniquely identify
|
|
6668
|
+
* a key derivation result.
|
|
6669
|
+
*/
|
|
6670
|
+
function buildCacheKey(password, dict, fileId) {
|
|
6671
|
+
const oHex = Array.from(dict.ownerKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6672
|
+
const uHex = Array.from(dict.userKey.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6673
|
+
const fHex = Array.from(fileId.subarray(0, 16), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
6674
|
+
return `${dict.revision}:${dict.permissions}:${password}:${oHex}:${uHex}:${fHex}`;
|
|
6675
|
+
}
|
|
6676
|
+
/**
|
|
6515
6677
|
* Compute the file encryption key from a password and encryption dict.
|
|
6516
6678
|
*
|
|
6517
6679
|
* Tries the password as both user and owner password. Returns the key
|
|
6518
6680
|
* on the first successful match, or throws if neither works.
|
|
6519
6681
|
*
|
|
6682
|
+
* Results are cached so that re-opening the same PDF with the same
|
|
6683
|
+
* password skips the expensive key derivation.
|
|
6684
|
+
*
|
|
6520
6685
|
* @param password The password to try.
|
|
6521
6686
|
* @param dict Encryption dictionary values.
|
|
6522
6687
|
* @param fileId The first element of the /ID array (unused for R>=5).
|
|
@@ -6524,23 +6689,35 @@ async function computeEncryptionKeyR6(password, dict, isOwner) {
|
|
|
6524
6689
|
* @throws If the password is incorrect.
|
|
6525
6690
|
*/
|
|
6526
6691
|
async function computeFileEncryptionKey(password, dict, fileId) {
|
|
6692
|
+
const ck = buildCacheKey(password, dict, fileId);
|
|
6693
|
+
const cached = fileKeyCache.get(ck);
|
|
6694
|
+
if (cached) return cached.slice();
|
|
6695
|
+
let result;
|
|
6527
6696
|
if (dict.revision >= 6) {
|
|
6528
6697
|
const userKey = await computeEncryptionKeyR6(password, dict, false);
|
|
6529
|
-
if (userKey)
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6698
|
+
if (userKey) result = userKey;
|
|
6699
|
+
else {
|
|
6700
|
+
const ownerKey = await computeEncryptionKeyR6(password, dict, true);
|
|
6701
|
+
if (ownerKey) result = ownerKey;
|
|
6702
|
+
else throw new Error("Incorrect password for R=6 encryption");
|
|
6703
|
+
}
|
|
6704
|
+
} else if (dict.revision === 5) {
|
|
6535
6705
|
const userKey = await computeEncryptionKeyR5(password, dict, false);
|
|
6536
|
-
if (userKey)
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
if (await
|
|
6543
|
-
|
|
6706
|
+
if (userKey) result = userKey;
|
|
6707
|
+
else {
|
|
6708
|
+
const ownerKey = await computeEncryptionKeyR5(password, dict, true);
|
|
6709
|
+
if (ownerKey) result = ownerKey;
|
|
6710
|
+
else throw new Error("Incorrect password for R=5 encryption");
|
|
6711
|
+
}
|
|
6712
|
+
} else if (await verifyUserPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4(password, dict, fileId);
|
|
6713
|
+
else if (await verifyOwnerPassword(password, dict, fileId)) result = computeEncryptionKeyR2R4Bytes(recoverUserKeyFromOwner(password, dict), dict, fileId);
|
|
6714
|
+
else throw new Error("Incorrect password");
|
|
6715
|
+
if (fileKeyCache.size >= FILE_KEY_CACHE_MAX) {
|
|
6716
|
+
const firstKey = fileKeyCache.keys().next().value;
|
|
6717
|
+
if (firstKey !== void 0) fileKeyCache.delete(firstKey);
|
|
6718
|
+
}
|
|
6719
|
+
fileKeyCache.set(ck, result.slice());
|
|
6720
|
+
return result;
|
|
6544
6721
|
}
|
|
6545
6722
|
/**
|
|
6546
6723
|
* Compute encryption key from raw password bytes (already padded/recovered).
|
|
@@ -6803,6 +6980,15 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
|
|
|
6803
6980
|
perms;
|
|
6804
6981
|
/** The file ID (first element of /ID array). */
|
|
6805
6982
|
fileId;
|
|
6983
|
+
/**
|
|
6984
|
+
* Cache for per-object derived keys (V=1-4 only).
|
|
6985
|
+
* Key: `(objNum << 16) | genNum` — unique integer per object.
|
|
6986
|
+
* Value: the derived encryption key.
|
|
6987
|
+
*
|
|
6988
|
+
* Avoids recomputing MD5(fileKey + objNum + genNum [+ sAlT]) for
|
|
6989
|
+
* every string and stream in the same object.
|
|
6990
|
+
*/
|
|
6991
|
+
objectKeyCache = /* @__PURE__ */ new Map();
|
|
6806
6992
|
constructor(params) {
|
|
6807
6993
|
this.fileKey = params.fileKey;
|
|
6808
6994
|
this.version = params.version;
|
|
@@ -6955,6 +7141,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
|
|
|
6955
7141
|
*/
|
|
6956
7142
|
deriveObjectKey(objNum, genNum) {
|
|
6957
7143
|
if (this.version === 5) return this.fileKey;
|
|
7144
|
+
const cacheKey = objNum << 16 | genNum;
|
|
7145
|
+
const cached = this.objectKeyCache.get(cacheKey);
|
|
7146
|
+
if (cached) return cached;
|
|
6958
7147
|
const extra = this.useAes ? 4 : 0;
|
|
6959
7148
|
const input = new Uint8Array(this.fileKey.length + 5 + extra);
|
|
6960
7149
|
input.set(this.fileKey, 0);
|
|
@@ -6972,7 +7161,9 @@ var PdfEncryptionHandler = class PdfEncryptionHandler {
|
|
|
6972
7161
|
}
|
|
6973
7162
|
const hash = md5(input);
|
|
6974
7163
|
const keyLen = Math.min(this.keyLengthBits / 8 + 5, 16);
|
|
6975
|
-
|
|
7164
|
+
const key = hash.subarray(0, keyLen);
|
|
7165
|
+
this.objectKeyCache.set(cacheKey, key);
|
|
7166
|
+
return key;
|
|
6976
7167
|
}
|
|
6977
7168
|
/**
|
|
6978
7169
|
* Encrypt raw data for a specific object.
|
|
@@ -8577,7 +8768,17 @@ const NS_PDFAID = "http://www.aiim.org/pdfa/ns/id/";
|
|
|
8577
8768
|
* @internal
|
|
8578
8769
|
*/
|
|
8579
8770
|
function escapeXml(str) {
|
|
8580
|
-
|
|
8771
|
+
const parts = [];
|
|
8772
|
+
for (let i = 0; i < str.length; i++) {
|
|
8773
|
+
const c = str.charCodeAt(i);
|
|
8774
|
+
if (c === 38) parts.push("&");
|
|
8775
|
+
else if (c === 60) parts.push("<");
|
|
8776
|
+
else if (c === 62) parts.push(">");
|
|
8777
|
+
else if (c === 34) parts.push(""");
|
|
8778
|
+
else if (c === 39) parts.push("'");
|
|
8779
|
+
else parts.push(str[i]);
|
|
8780
|
+
}
|
|
8781
|
+
return parts.join("");
|
|
8581
8782
|
}
|
|
8582
8783
|
/**
|
|
8583
8784
|
* Format a Date as an ISO 8601 string for XMP.
|
|
@@ -9880,9 +10081,10 @@ function remapRef$1(sourceRef, context) {
|
|
|
9880
10081
|
context.refMap.set(sourceRef.objectNumber, placeholderRef);
|
|
9881
10082
|
return placeholderRef;
|
|
9882
10083
|
}
|
|
10084
|
+
let streamHash;
|
|
9883
10085
|
if (sourceObj.kind === "stream") {
|
|
9884
|
-
|
|
9885
|
-
const dedup = context.hashMap.get(
|
|
10086
|
+
streamHash = hashBytes(sourceObj.data);
|
|
10087
|
+
const dedup = context.hashMap.get(streamHash);
|
|
9886
10088
|
if (dedup) {
|
|
9887
10089
|
context.refMap.set(sourceRef.objectNumber, dedup);
|
|
9888
10090
|
return dedup;
|
|
@@ -9892,10 +10094,7 @@ function remapRef$1(sourceRef, context) {
|
|
|
9892
10094
|
context.refMap.set(sourceRef.objectNumber, targetRef);
|
|
9893
10095
|
const clonedObj = deepClone$1(sourceObj, context);
|
|
9894
10096
|
context.targetRegistry.assign(targetRef, clonedObj);
|
|
9895
|
-
if (
|
|
9896
|
-
const hash = hashBytes(sourceObj.data);
|
|
9897
|
-
context.hashMap.set(hash, targetRef);
|
|
9898
|
-
}
|
|
10097
|
+
if (streamHash !== void 0) context.hashMap.set(streamHash, targetRef);
|
|
9899
10098
|
return targetRef;
|
|
9900
10099
|
}
|
|
9901
10100
|
/**
|
|
@@ -10126,23 +10325,73 @@ function findStringForward(data, needle, startOffset) {
|
|
|
10126
10325
|
return -1;
|
|
10127
10326
|
}
|
|
10128
10327
|
/**
|
|
10129
|
-
*
|
|
10130
|
-
* via incremental update.
|
|
10131
|
-
*
|
|
10132
|
-
* This function:
|
|
10133
|
-
* 1. Appends a new signature field and value to the PDF
|
|
10134
|
-
* 2. Inserts an empty `/Contents` placeholder of the specified size
|
|
10135
|
-
* 3. Computes the `/ByteRange` that excludes the `/Contents` value
|
|
10328
|
+
* Build a PDF content stream for a visible signature appearance.
|
|
10136
10329
|
*
|
|
10137
|
-
*
|
|
10138
|
-
*
|
|
10330
|
+
* Renders a bordered rectangle with optional background color and
|
|
10331
|
+
* text lines rendered in Helvetica.
|
|
10139
10332
|
*
|
|
10140
|
-
* @
|
|
10141
|
-
* @param signatureFieldName The name for the signature field.
|
|
10142
|
-
* @param placeholderSize Size in bytes for the signature. Default 8192.
|
|
10143
|
-
* @returns The prepared PDF and ByteRange info.
|
|
10333
|
+
* @internal
|
|
10144
10334
|
*/
|
|
10145
|
-
function
|
|
10335
|
+
function buildSignatureAppearanceStream(options) {
|
|
10336
|
+
const [, , w, h] = options.rect;
|
|
10337
|
+
const fontSize = options.fontSize ?? 10;
|
|
10338
|
+
const borderWidth = options.borderWidth ?? 1;
|
|
10339
|
+
const borderColor = options.borderColor ?? [
|
|
10340
|
+
0,
|
|
10341
|
+
0,
|
|
10342
|
+
0
|
|
10343
|
+
];
|
|
10344
|
+
const bgColor = options.backgroundColor;
|
|
10345
|
+
const lines = options.textLines;
|
|
10346
|
+
const ops = [];
|
|
10347
|
+
ops.push("q");
|
|
10348
|
+
if (bgColor) {
|
|
10349
|
+
ops.push(`${n$5(bgColor[0])} ${n$5(bgColor[1])} ${n$5(bgColor[2])} rg`);
|
|
10350
|
+
ops.push(`0 0 ${n$5(w)} ${n$5(h)} re`);
|
|
10351
|
+
ops.push("f");
|
|
10352
|
+
}
|
|
10353
|
+
if (borderWidth > 0) {
|
|
10354
|
+
ops.push(`${n$5(borderColor[0])} ${n$5(borderColor[1])} ${n$5(borderColor[2])} RG`);
|
|
10355
|
+
ops.push(`${n$5(borderWidth)} w`);
|
|
10356
|
+
const bw2 = borderWidth / 2;
|
|
10357
|
+
ops.push(`${n$5(bw2)} ${n$5(bw2)} ${n$5(w - borderWidth)} ${n$5(h - borderWidth)} re`);
|
|
10358
|
+
ops.push("S");
|
|
10359
|
+
}
|
|
10360
|
+
if (lines.length > 0) {
|
|
10361
|
+
const margin = borderWidth + 4;
|
|
10362
|
+
const lineHeight = fontSize * 1.2;
|
|
10363
|
+
ops.push("BT");
|
|
10364
|
+
ops.push(`/F1 ${n$5(fontSize)} Tf`);
|
|
10365
|
+
ops.push("0 0 0 rg");
|
|
10366
|
+
const startY = h - margin - fontSize;
|
|
10367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10368
|
+
const y = startY - i * lineHeight;
|
|
10369
|
+
if (y < margin) break;
|
|
10370
|
+
ops.push(`${n$5(margin)} ${n$5(y)} Td`);
|
|
10371
|
+
ops.push(`(${escapePdfString(lines[i])}) Tj`);
|
|
10372
|
+
ops.push(`${n$5(-margin)} ${n$5(-y)} Td`);
|
|
10373
|
+
}
|
|
10374
|
+
ops.push("ET");
|
|
10375
|
+
}
|
|
10376
|
+
ops.push("Q");
|
|
10377
|
+
return ops.join("\n");
|
|
10378
|
+
}
|
|
10379
|
+
/**
|
|
10380
|
+
* Escape a string for use inside a PDF literal string `(...)`.
|
|
10381
|
+
* @internal
|
|
10382
|
+
*/
|
|
10383
|
+
function escapePdfString(str) {
|
|
10384
|
+
return str.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
10385
|
+
}
|
|
10386
|
+
/**
|
|
10387
|
+
* Format a number for PDF operators (max 6 decimal places, no trailing zeros).
|
|
10388
|
+
* @internal
|
|
10389
|
+
*/
|
|
10390
|
+
function n$5(value) {
|
|
10391
|
+
if (Number.isInteger(value)) return value.toString();
|
|
10392
|
+
return value.toFixed(6).replace(/\.?0+$/, "");
|
|
10393
|
+
}
|
|
10394
|
+
function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192, appearance) {
|
|
10146
10395
|
const pdfStr = decoder$4.decode(pdfBytes);
|
|
10147
10396
|
const startxrefIdx = pdfStr.lastIndexOf("startxref");
|
|
10148
10397
|
if (startxrefIdx === -1) throw new Error("Cannot find startxref in PDF — file may be corrupted");
|
|
@@ -10159,9 +10408,21 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
|
|
|
10159
10408
|
const infoMatch = pdfStr.match(/\/Info\s+(\d+)\s+(\d+)\s+R/);
|
|
10160
10409
|
const sigValueObjNum = originalSize;
|
|
10161
10410
|
const sigFieldObjNum = originalSize + 1;
|
|
10162
|
-
|
|
10411
|
+
let apStreamObjNum = -1;
|
|
10412
|
+
let newSize = originalSize + 2;
|
|
10413
|
+
if (appearance) {
|
|
10414
|
+
apStreamObjNum = newSize;
|
|
10415
|
+
newSize++;
|
|
10416
|
+
}
|
|
10163
10417
|
const sigDictStr = buildSignatureDictString(placeholderSize, signatureFieldName);
|
|
10164
|
-
|
|
10418
|
+
let rectStr = "0 0 0 0";
|
|
10419
|
+
if (appearance) {
|
|
10420
|
+
const [x, y, w, h] = appearance.rect;
|
|
10421
|
+
rectStr = `${x} ${y} ${x + w} ${y + h}`;
|
|
10422
|
+
}
|
|
10423
|
+
let sigFieldDict = `<< /Type /Annot /Subtype /Widget /FT /Sig /T (${signatureFieldName}) /V ${sigValueObjNum} 0 R /F 132 /Rect [${rectStr}]`;
|
|
10424
|
+
if (appearance && apStreamObjNum >= 0) sigFieldDict += ` /AP << /N ${apStreamObjNum} 0 R >>`;
|
|
10425
|
+
sigFieldDict += " >>";
|
|
10165
10426
|
let appendix = "\n";
|
|
10166
10427
|
const objOffsets = /* @__PURE__ */ new Map();
|
|
10167
10428
|
const sigValueStart = pdfBytes.length + encoder$3.encode(appendix).length;
|
|
@@ -10174,11 +10435,28 @@ function prepareForSigning(pdfBytes, signatureFieldName, placeholderSize = 8192)
|
|
|
10174
10435
|
appendix += `${sigFieldObjNum} 0 obj\n`;
|
|
10175
10436
|
appendix += sigFieldDict;
|
|
10176
10437
|
appendix += `\nendobj\n`;
|
|
10438
|
+
let apStreamStart = -1;
|
|
10439
|
+
if (appearance && apStreamObjNum >= 0) {
|
|
10440
|
+
apStreamStart = pdfBytes.length + encoder$3.encode(appendix).length;
|
|
10441
|
+
objOffsets.set(apStreamObjNum, apStreamStart);
|
|
10442
|
+
const apContent = buildSignatureAppearanceStream(appearance);
|
|
10443
|
+
const [, , w, h] = appearance.rect;
|
|
10444
|
+
appendix += `${apStreamObjNum} 0 obj\n`;
|
|
10445
|
+
appendix += `<< /Type /XObject /Subtype /Form /BBox [0 0 ${w} ${h}]`;
|
|
10446
|
+
appendix += ` /Resources << /Font << /F1 << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> >> >>`;
|
|
10447
|
+
appendix += ` /Length ${apContent.length} >>\n`;
|
|
10448
|
+
appendix += `stream\n`;
|
|
10449
|
+
appendix += apContent;
|
|
10450
|
+
appendix += `\nendstream\n`;
|
|
10451
|
+
appendix += `endobj\n`;
|
|
10452
|
+
}
|
|
10177
10453
|
const xrefOffset = pdfBytes.length + encoder$3.encode(appendix).length;
|
|
10454
|
+
const objCount = appearance ? 3 : 2;
|
|
10178
10455
|
appendix += "xref\n";
|
|
10179
|
-
appendix += `${sigValueObjNum}
|
|
10456
|
+
appendix += `${sigValueObjNum} ${objCount}\n`;
|
|
10180
10457
|
appendix += `${sigValueStart.toString().padStart(10, "0")} 00000 n \n`;
|
|
10181
10458
|
appendix += `${sigFieldStart.toString().padStart(10, "0")} 00000 n \n`;
|
|
10459
|
+
if (appearance && apStreamStart >= 0) appendix += `${apStreamStart.toString().padStart(10, "0")} 00000 n \n`;
|
|
10182
10460
|
appendix += "trailer\n";
|
|
10183
10461
|
appendix += "<<\n";
|
|
10184
10462
|
appendix += `/Size ${newSize}\n`;
|
|
@@ -10968,7 +11246,32 @@ function parsePkcs7ForCert(pkcs7Bytes) {
|
|
|
10968
11246
|
*/
|
|
10969
11247
|
async function signPdf(pdfBytes, fieldName, options) {
|
|
10970
11248
|
const hashAlgorithm = options.hashAlgorithm ?? "SHA-256";
|
|
10971
|
-
|
|
11249
|
+
let prepareAppearance;
|
|
11250
|
+
if (options.appearance) {
|
|
11251
|
+
const ap = options.appearance;
|
|
11252
|
+
let textLines = ap.text;
|
|
11253
|
+
if (!textLines) {
|
|
11254
|
+
textLines = [];
|
|
11255
|
+
try {
|
|
11256
|
+
const { subjectCN } = extractIssuerAndSerial(options.certificate);
|
|
11257
|
+
if (subjectCN) textLines.push(`Signed by: ${subjectCN}`);
|
|
11258
|
+
} catch {
|
|
11259
|
+
textLines.push("Digitally Signed");
|
|
11260
|
+
}
|
|
11261
|
+
if (options.reason) textLines.push(`Reason: ${options.reason}`);
|
|
11262
|
+
if (options.location) textLines.push(`Location: ${options.location}`);
|
|
11263
|
+
textLines.push(`Date: ${(/* @__PURE__ */ new Date()).toISOString().substring(0, 10)}`);
|
|
11264
|
+
}
|
|
11265
|
+
prepareAppearance = {
|
|
11266
|
+
rect: ap.rect,
|
|
11267
|
+
textLines,
|
|
11268
|
+
fontSize: ap.fontSize,
|
|
11269
|
+
backgroundColor: ap.backgroundColor,
|
|
11270
|
+
borderColor: ap.borderColor,
|
|
11271
|
+
borderWidth: ap.borderWidth
|
|
11272
|
+
};
|
|
11273
|
+
}
|
|
11274
|
+
const { preparedPdf, byteRange } = prepareForSigning(pdfBytes, fieldName, 8192, prepareAppearance);
|
|
10972
11275
|
return embedSignature(preparedPdf, await buildPkcs7Signature(await computeSignatureHash(preparedPdf, byteRange.byteRange, hashAlgorithm), {
|
|
10973
11276
|
signerInfo: {
|
|
10974
11277
|
certificate: options.certificate,
|
|
@@ -14474,7 +14777,6 @@ function read2DMode(reader) {
|
|
|
14474
14777
|
if (bit === 1) {
|
|
14475
14778
|
bit = reader.readBit();
|
|
14476
14779
|
if (bit < 0) return Mode2D.EOL;
|
|
14477
|
-
if (bit === 1) return Mode2D.HORIZONTAL;
|
|
14478
14780
|
if (bit === 0) return Mode2D.VERTICAL_MINUS_1;
|
|
14479
14781
|
return Mode2D.VERTICAL_PLUS_1;
|
|
14480
14782
|
}
|
|
@@ -17605,9 +17907,28 @@ function hexValue(ch) {
|
|
|
17605
17907
|
* `~>` marks end-of-data.
|
|
17606
17908
|
*/
|
|
17607
17909
|
function decodeASCII85(data) {
|
|
17608
|
-
|
|
17609
|
-
|
|
17910
|
+
let out = new Uint8Array(Math.max(data.length * 4 / 5 | 0, 256));
|
|
17911
|
+
let outPos = 0;
|
|
17912
|
+
const group = new Uint8Array(5);
|
|
17913
|
+
let groupLen = 0;
|
|
17610
17914
|
let i = 0;
|
|
17915
|
+
function ensureOut(needed) {
|
|
17916
|
+
if (outPos + needed > out.length) {
|
|
17917
|
+
const newBuf = new Uint8Array(Math.max(out.length * 2, outPos + needed));
|
|
17918
|
+
newBuf.set(out.subarray(0, outPos));
|
|
17919
|
+
out = newBuf;
|
|
17920
|
+
}
|
|
17921
|
+
}
|
|
17922
|
+
function decodeGroup(count) {
|
|
17923
|
+
for (let k = count; k < 5; k++) group[k] = 84;
|
|
17924
|
+
const val = group[0] * 85 * 85 * 85 * 85 + group[1] * 85 * 85 * 85 + group[2] * 85 * 85 + group[3] * 85 + group[4];
|
|
17925
|
+
const numBytes = count === 5 ? 4 : count - 1;
|
|
17926
|
+
ensureOut(numBytes);
|
|
17927
|
+
if (numBytes >= 1) out[outPos++] = val >>> 24 & 255;
|
|
17928
|
+
if (numBytes >= 2) out[outPos++] = val >>> 16 & 255;
|
|
17929
|
+
if (numBytes >= 3) out[outPos++] = val >>> 8 & 255;
|
|
17930
|
+
if (numBytes >= 4) out[outPos++] = val & 255;
|
|
17931
|
+
}
|
|
17611
17932
|
while (i < data.length) {
|
|
17612
17933
|
const ch = data[i];
|
|
17613
17934
|
if (ch === 126) break;
|
|
@@ -17616,8 +17937,12 @@ function decodeASCII85(data) {
|
|
|
17616
17937
|
continue;
|
|
17617
17938
|
}
|
|
17618
17939
|
if (ch === 122) {
|
|
17619
|
-
if (
|
|
17620
|
-
|
|
17940
|
+
if (groupLen > 0) throw new Error("ASCII85Decode: \"z\" inside a group");
|
|
17941
|
+
ensureOut(4);
|
|
17942
|
+
out[outPos++] = 0;
|
|
17943
|
+
out[outPos++] = 0;
|
|
17944
|
+
out[outPos++] = 0;
|
|
17945
|
+
out[outPos++] = 0;
|
|
17621
17946
|
i++;
|
|
17622
17947
|
continue;
|
|
17623
17948
|
}
|
|
@@ -17625,24 +17950,15 @@ function decodeASCII85(data) {
|
|
|
17625
17950
|
i++;
|
|
17626
17951
|
continue;
|
|
17627
17952
|
}
|
|
17628
|
-
group
|
|
17629
|
-
if (
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
group.length = 0;
|
|
17953
|
+
group[groupLen++] = ch - 33;
|
|
17954
|
+
if (groupLen === 5) {
|
|
17955
|
+
decodeGroup(5);
|
|
17956
|
+
groupLen = 0;
|
|
17633
17957
|
}
|
|
17634
17958
|
i++;
|
|
17635
17959
|
}
|
|
17636
|
-
if (
|
|
17637
|
-
|
|
17638
|
-
while (group.length < 5) group.push(84);
|
|
17639
|
-
const val = group[0] * 85 * 85 * 85 * 85 + group[1] * 85 * 85 * 85 + group[2] * 85 * 85 + group[3] * 85 + group[4];
|
|
17640
|
-
const numBytes = origLen - 1;
|
|
17641
|
-
if (numBytes >= 1) result.push(val >>> 24 & 255);
|
|
17642
|
-
if (numBytes >= 2) result.push(val >>> 16 & 255);
|
|
17643
|
-
if (numBytes >= 3) result.push(val >>> 8 & 255);
|
|
17644
|
-
}
|
|
17645
|
-
return new Uint8Array(result);
|
|
17960
|
+
if (groupLen >= 2) decodeGroup(groupLen);
|
|
17961
|
+
return out.subarray(0, outPos);
|
|
17646
17962
|
}
|
|
17647
17963
|
/**
|
|
17648
17964
|
* Decode an LZWDecode stream.
|
|
@@ -17662,6 +17978,10 @@ function decodeLZW(data, parms) {
|
|
|
17662
17978
|
}
|
|
17663
17979
|
/**
|
|
17664
17980
|
* Core LZW decompression.
|
|
17981
|
+
*
|
|
17982
|
+
* Uses a flat pooled buffer for the code table instead of per-entry
|
|
17983
|
+
* Uint8Array allocations, and a pre-allocated output buffer with
|
|
17984
|
+
* manual growth instead of `number[]` + push.
|
|
17665
17985
|
*/
|
|
17666
17986
|
function lzwDecompress(data, earlyChange) {
|
|
17667
17987
|
const CLEAR_TABLE = 256;
|
|
@@ -17686,71 +18006,88 @@ function lzwDecompress(data, earlyChange) {
|
|
|
17686
18006
|
}
|
|
17687
18007
|
return result;
|
|
17688
18008
|
}
|
|
17689
|
-
let
|
|
18009
|
+
let tableBuf = new Uint8Array(65536);
|
|
18010
|
+
const tableOff = new Int32Array(4096);
|
|
18011
|
+
const tableLen = new Int32Array(4096);
|
|
18012
|
+
let tableBufUsed = 256;
|
|
17690
18013
|
let codeSize = 9;
|
|
17691
18014
|
let nextCode = 258;
|
|
18015
|
+
for (let i = 0; i < 256; i++) {
|
|
18016
|
+
tableBuf[i] = i;
|
|
18017
|
+
tableOff[i] = i;
|
|
18018
|
+
tableLen[i] = 1;
|
|
18019
|
+
}
|
|
17692
18020
|
function resetTable() {
|
|
17693
|
-
|
|
17694
|
-
for (let i = 0; i < 256; i++) table[i] = new Uint8Array([i]);
|
|
17695
|
-
table[256] = new Uint8Array(0);
|
|
17696
|
-
table[257] = new Uint8Array(0);
|
|
18021
|
+
tableBufUsed = 256;
|
|
17697
18022
|
nextCode = 258;
|
|
17698
18023
|
codeSize = 9;
|
|
17699
18024
|
}
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
18025
|
+
let out = new Uint8Array(Math.max(data.length * 3, 4096));
|
|
18026
|
+
let outPos = 0;
|
|
18027
|
+
function ensureOut(needed) {
|
|
18028
|
+
if (outPos + needed > out.length) {
|
|
18029
|
+
const newBuf = new Uint8Array(Math.max(out.length * 2, outPos + needed));
|
|
18030
|
+
newBuf.set(out.subarray(0, outPos));
|
|
18031
|
+
out = newBuf;
|
|
18032
|
+
}
|
|
18033
|
+
}
|
|
18034
|
+
function ensureTable(needed) {
|
|
18035
|
+
if (tableBufUsed + needed > tableBuf.length) {
|
|
18036
|
+
const newBuf = new Uint8Array(Math.max(tableBuf.length * 2, tableBufUsed + needed));
|
|
18037
|
+
newBuf.set(tableBuf.subarray(0, tableBufUsed));
|
|
18038
|
+
tableBuf = newBuf;
|
|
18039
|
+
}
|
|
18040
|
+
}
|
|
18041
|
+
function writeEntry(code) {
|
|
18042
|
+
const off = tableOff[code];
|
|
18043
|
+
const len = tableLen[code];
|
|
18044
|
+
ensureOut(len);
|
|
18045
|
+
out.set(tableBuf.subarray(off, off + len), outPos);
|
|
18046
|
+
outPos += len;
|
|
18047
|
+
}
|
|
18048
|
+
function addEntry(prevCode, firstByte) {
|
|
18049
|
+
const prevOff = tableOff[prevCode];
|
|
18050
|
+
const prevLen = tableLen[prevCode];
|
|
18051
|
+
const newLen = prevLen + 1;
|
|
18052
|
+
ensureTable(newLen);
|
|
18053
|
+
tableBuf.set(tableBuf.subarray(prevOff, prevOff + prevLen), tableBufUsed);
|
|
18054
|
+
tableBuf[tableBufUsed + prevLen] = firstByte;
|
|
18055
|
+
tableOff[nextCode] = tableBufUsed;
|
|
18056
|
+
tableLen[nextCode] = newLen;
|
|
18057
|
+
tableBufUsed += newLen;
|
|
18058
|
+
nextCode++;
|
|
18059
|
+
}
|
|
17703
18060
|
let code = readBits(codeSize);
|
|
17704
18061
|
if (code === CLEAR_TABLE) {
|
|
17705
18062
|
resetTable();
|
|
17706
18063
|
code = readBits(codeSize);
|
|
17707
18064
|
}
|
|
17708
|
-
if (code === EOD) return new Uint8Array(
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
for (const b of entry) output.push(b);
|
|
17712
|
-
prevEntry = entry;
|
|
17713
|
-
}
|
|
18065
|
+
if (code === EOD) return new Uint8Array(0);
|
|
18066
|
+
writeEntry(code);
|
|
18067
|
+
let prevCode = code;
|
|
17714
18068
|
while (true) {
|
|
17715
18069
|
code = readBits(codeSize);
|
|
17716
18070
|
if (code === EOD) break;
|
|
17717
18071
|
if (code === CLEAR_TABLE) {
|
|
17718
18072
|
resetTable();
|
|
17719
|
-
prevEntry = null;
|
|
17720
18073
|
code = readBits(codeSize);
|
|
17721
18074
|
if (code === EOD) break;
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
for (const b of entry) output.push(b);
|
|
17725
|
-
prevEntry = entry;
|
|
17726
|
-
}
|
|
18075
|
+
writeEntry(code);
|
|
18076
|
+
prevCode = code;
|
|
17727
18077
|
continue;
|
|
17728
18078
|
}
|
|
17729
|
-
if (code < nextCode
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
if (prevEntry) {
|
|
17733
|
-
const newEntry = new Uint8Array(prevEntry.length + 1);
|
|
17734
|
-
newEntry.set(prevEntry);
|
|
17735
|
-
newEntry[prevEntry.length] = entry[0];
|
|
17736
|
-
table[nextCode] = newEntry;
|
|
17737
|
-
nextCode++;
|
|
17738
|
-
}
|
|
18079
|
+
if (code < nextCode) {
|
|
18080
|
+
writeEntry(code);
|
|
18081
|
+
addEntry(prevCode, tableBuf[tableOff[code]]);
|
|
17739
18082
|
} else {
|
|
17740
|
-
|
|
17741
|
-
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
for (const b of newEntry) output.push(b);
|
|
17745
|
-
table[nextCode] = newEntry;
|
|
17746
|
-
nextCode++;
|
|
17747
|
-
entry = newEntry;
|
|
17748
|
-
}
|
|
17749
|
-
prevEntry = entry;
|
|
18083
|
+
addEntry(prevCode, tableBuf[tableOff[prevCode]]);
|
|
18084
|
+
writeEntry(nextCode - 1);
|
|
18085
|
+
}
|
|
18086
|
+
prevCode = code;
|
|
17750
18087
|
const threshold = (1 << codeSize) - earlyChange;
|
|
17751
18088
|
if (nextCode >= threshold && codeSize < 12) codeSize++;
|
|
17752
18089
|
}
|
|
17753
|
-
return
|
|
18090
|
+
return out.subarray(0, outPos);
|
|
17754
18091
|
}
|
|
17755
18092
|
/**
|
|
17756
18093
|
* Decode a RunLengthDecode stream.
|
|
@@ -17882,7 +18219,6 @@ function embedPageAsFormXObject(page, sourceRegistry, targetRegistry, xObjectNam
|
|
|
17882
18219
|
const pageWidth = page.width;
|
|
17883
18220
|
const pageHeight = page.height;
|
|
17884
18221
|
const contentChunks = [];
|
|
17885
|
-
new TextDecoder();
|
|
17886
18222
|
const originalRefs = page.getOriginalContentRefs();
|
|
17887
18223
|
for (const ref of originalRefs) {
|
|
17888
18224
|
const obj = sourceRegistry.resolve(ref);
|
|
@@ -18400,7 +18736,7 @@ var PdfDocument = class PdfDocument {
|
|
|
18400
18736
|
async embedTrueTypeFont(fontData, options) {
|
|
18401
18737
|
const embeddedFont = await embedFont(fontData);
|
|
18402
18738
|
const metrics = embeddedFont.metrics;
|
|
18403
|
-
const psName = options?.customName
|
|
18739
|
+
const psName = options?.customName ?? metrics.postScriptName ?? metrics.familyName ?? "CustomFont";
|
|
18404
18740
|
const existing = this.embeddedFonts.get(`__ttf__${psName}`);
|
|
18405
18741
|
if (existing) return existing;
|
|
18406
18742
|
this.fontCounter++;
|
|
@@ -18515,7 +18851,7 @@ var PdfDocument = class PdfDocument {
|
|
|
18515
18851
|
async embedCffFont(fontData, options) {
|
|
18516
18852
|
const embeddedFont = await embedFont(fontData);
|
|
18517
18853
|
const metrics = embeddedFont.metrics;
|
|
18518
|
-
const psName = options?.customName
|
|
18854
|
+
const psName = options?.customName ?? metrics.postScriptName ?? metrics.familyName ?? "CFFFont";
|
|
18519
18855
|
const existing = this.embeddedFonts.get(`__cff__${psName}`);
|
|
18520
18856
|
if (existing) return existing;
|
|
18521
18857
|
this.fontCounter++;
|
|
@@ -18747,6 +19083,30 @@ var PdfDocument = class PdfDocument {
|
|
|
18747
19083
|
return imageRef;
|
|
18748
19084
|
}
|
|
18749
19085
|
/**
|
|
19086
|
+
* Embed an image, auto-detecting the format from file headers.
|
|
19087
|
+
*
|
|
19088
|
+
* Inspects the first bytes to determine whether the data is PNG or JPEG,
|
|
19089
|
+
* then delegates to {@link embedPng} or {@link embedJpeg} accordingly.
|
|
19090
|
+
*
|
|
19091
|
+
* @param imageData Raw image file bytes (PNG or JPEG).
|
|
19092
|
+
* @returns An {@link ImageRef} to pass to `page.drawImage()`.
|
|
19093
|
+
* @throws If the image format cannot be detected.
|
|
19094
|
+
*
|
|
19095
|
+
* @example
|
|
19096
|
+
* ```ts
|
|
19097
|
+
* const bytes = new Uint8Array(await readFile('photo.jpg'));
|
|
19098
|
+
* const image = await pdf.embedImage(bytes);
|
|
19099
|
+
* page.drawImage(image, { x: 50, y: 400, width: 200, height: 150 });
|
|
19100
|
+
* ```
|
|
19101
|
+
*/
|
|
19102
|
+
async embedImage(imageData) {
|
|
19103
|
+
const data = imageData instanceof ArrayBuffer ? new Uint8Array(imageData) : imageData;
|
|
19104
|
+
if (data.length < 4) throw new Error("Image data too short to detect format");
|
|
19105
|
+
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return this.embedPng(data);
|
|
19106
|
+
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return this.embedJpeg(data);
|
|
19107
|
+
throw new Error(`Unsupported image format. Expected PNG (89 50 4E 47) or JPEG (FF D8 FF), got ${Array.from(data.subarray(0, 4)).map((b) => b.toString(16).padStart(2, "0")).join(" ")}.`);
|
|
19108
|
+
}
|
|
19109
|
+
/**
|
|
18750
19110
|
* Embed pages from another PDF as Form XObjects.
|
|
18751
19111
|
*
|
|
18752
19112
|
* Each embedded page is turned into a self-contained Form XObject that
|
|
@@ -18805,7 +19165,7 @@ var PdfDocument = class PdfDocument {
|
|
|
18805
19165
|
* @param pages Array of PdfPage instances to embed.
|
|
18806
19166
|
* @returns Array of {@link EmbeddedPdfPage} handles, one per input page.
|
|
18807
19167
|
*/
|
|
18808
|
-
|
|
19168
|
+
embedPages(pages) {
|
|
18809
19169
|
return pages.map((page) => this.embedPage(page));
|
|
18810
19170
|
}
|
|
18811
19171
|
/** Set the document title. */
|
|
@@ -20337,8 +20697,8 @@ function compressStream(stream, level) {
|
|
|
20337
20697
|
* @returns The incremental save result.
|
|
20338
20698
|
*/
|
|
20339
20699
|
async function saveDocumentIncremental(originalBytes, doc, options) {
|
|
20340
|
-
const { buildDocumentStructure } = await Promise.resolve().then(() => require("./pdfCatalog-
|
|
20341
|
-
const { PdfPage: _PdfPage } = await Promise.resolve().then(() => require("./pdfPage-
|
|
20700
|
+
const { buildDocumentStructure } = await Promise.resolve().then(() => require("./pdfCatalog-COKoYQ8C.cjs")).then((n) => n.pdfCatalog_exports);
|
|
20701
|
+
const { PdfPage: _PdfPage } = await Promise.resolve().then(() => require("./pdfPage-DBfdinTR.cjs")).then((n) => n.pdfPage_exports);
|
|
20342
20702
|
const registry = doc.getRegistry();
|
|
20343
20703
|
const structure = buildDocumentStructure(doc.getInternalPages().map((p) => p.finalize()), {
|
|
20344
20704
|
producer: doc.getProducer(),
|
|
@@ -21647,6 +22007,261 @@ var PdfRedactAnnotation = class PdfRedactAnnotation extends require_pdfPage.PdfA
|
|
|
21647
22007
|
}
|
|
21648
22008
|
};
|
|
21649
22009
|
|
|
22010
|
+
//#endregion
|
|
22011
|
+
//#region src/annotation/types/popupAnnotation.ts
|
|
22012
|
+
/**
|
|
22013
|
+
* @module annotation/types/popupAnnotation
|
|
22014
|
+
*
|
|
22015
|
+
* Popup annotation — a floating window that displays the text of its
|
|
22016
|
+
* parent annotation (typically a text/sticky note annotation).
|
|
22017
|
+
*
|
|
22018
|
+
* Popup annotations have no appearance of their own; the PDF viewer
|
|
22019
|
+
* renders them as a resizable window near the parent annotation.
|
|
22020
|
+
*
|
|
22021
|
+
* Reference: PDF 1.7 spec, Section 12.5.6.14 (Popup Annotations).
|
|
22022
|
+
*/
|
|
22023
|
+
/**
|
|
22024
|
+
* A popup annotation (subtype /Popup).
|
|
22025
|
+
*
|
|
22026
|
+
* Displays a floating window containing the text of its parent
|
|
22027
|
+
* annotation. The parent annotation references this popup via its
|
|
22028
|
+
* `/Popup` entry, and this popup references its parent via `/Parent`.
|
|
22029
|
+
*/
|
|
22030
|
+
var PdfPopupAnnotation = class PdfPopupAnnotation extends require_pdfPage.PdfAnnotation {
|
|
22031
|
+
constructor(dict) {
|
|
22032
|
+
super("Popup", dict);
|
|
22033
|
+
}
|
|
22034
|
+
/**
|
|
22035
|
+
* Create a new popup annotation.
|
|
22036
|
+
*
|
|
22037
|
+
* @param options.open Whether the popup is initially open. Default: false.
|
|
22038
|
+
* @param options.parent Reference to the parent annotation (set after registration).
|
|
22039
|
+
*/
|
|
22040
|
+
static create(options) {
|
|
22041
|
+
const annot = new PdfPopupAnnotation(require_pdfPage.buildAnnotationDict("Popup", options));
|
|
22042
|
+
if (options.open !== void 0) annot.setOpen(options.open);
|
|
22043
|
+
return annot;
|
|
22044
|
+
}
|
|
22045
|
+
/**
|
|
22046
|
+
* Create a PdfPopupAnnotation from an existing dictionary.
|
|
22047
|
+
*/
|
|
22048
|
+
static fromDict(dict, _resolver) {
|
|
22049
|
+
return new PdfPopupAnnotation(dict);
|
|
22050
|
+
}
|
|
22051
|
+
/** Whether the popup is initially open. */
|
|
22052
|
+
isOpen() {
|
|
22053
|
+
const obj = this.dict.get("/Open");
|
|
22054
|
+
if (obj && obj.kind === "bool") return obj.value;
|
|
22055
|
+
return false;
|
|
22056
|
+
}
|
|
22057
|
+
/** Set the initial open state. */
|
|
22058
|
+
setOpen(open) {
|
|
22059
|
+
this.dict.set("/Open", require_pdfCatalog.PdfBool.of(open));
|
|
22060
|
+
}
|
|
22061
|
+
/**
|
|
22062
|
+
* Set the parent annotation reference.
|
|
22063
|
+
* The parent is the annotation whose text this popup displays.
|
|
22064
|
+
*/
|
|
22065
|
+
setParent(parentRef) {
|
|
22066
|
+
this.dict.set("/Parent", parentRef);
|
|
22067
|
+
}
|
|
22068
|
+
/** Get the parent annotation reference, if set. */
|
|
22069
|
+
getParent() {
|
|
22070
|
+
const obj = this.dict.get("/Parent");
|
|
22071
|
+
if (obj && obj.kind === "ref") return obj;
|
|
22072
|
+
}
|
|
22073
|
+
};
|
|
22074
|
+
|
|
22075
|
+
//#endregion
|
|
22076
|
+
//#region src/annotation/types/caretAnnotation.ts
|
|
22077
|
+
/**
|
|
22078
|
+
* @module annotation/types/caretAnnotation
|
|
22079
|
+
*
|
|
22080
|
+
* Caret annotation — marks a text insertion point in the document.
|
|
22081
|
+
*
|
|
22082
|
+
* A caret annotation indicates where text should be inserted. It is
|
|
22083
|
+
* typically used in document review workflows to suggest additions.
|
|
22084
|
+
* The annotation renders as a caret (^) symbol at the specified
|
|
22085
|
+
* location.
|
|
22086
|
+
*
|
|
22087
|
+
* Reference: PDF 1.7 spec, Section 12.5.6.11 (Caret Annotations).
|
|
22088
|
+
*/
|
|
22089
|
+
/**
|
|
22090
|
+
* A caret annotation (subtype /Caret).
|
|
22091
|
+
*
|
|
22092
|
+
* Marks an insertion point in the text. Used in review workflows
|
|
22093
|
+
* to indicate where new content should be added.
|
|
22094
|
+
*/
|
|
22095
|
+
var PdfCaretAnnotation = class PdfCaretAnnotation extends require_pdfPage.PdfAnnotation {
|
|
22096
|
+
constructor(dict) {
|
|
22097
|
+
super("Caret", dict);
|
|
22098
|
+
}
|
|
22099
|
+
/**
|
|
22100
|
+
* Create a new caret annotation.
|
|
22101
|
+
*
|
|
22102
|
+
* @param options.symbol The caret symbol. Default: 'None'.
|
|
22103
|
+
* @param options.caretRect The inner rectangle (RD) that describes
|
|
22104
|
+
* the difference between the annotation rect and the actual caret
|
|
22105
|
+
* position. Format: [left, bottom, right, top] insets.
|
|
22106
|
+
*/
|
|
22107
|
+
static create(options) {
|
|
22108
|
+
const annot = new PdfCaretAnnotation(require_pdfPage.buildAnnotationDict("Caret", options));
|
|
22109
|
+
if (options.symbol !== void 0) annot.setSymbol(options.symbol);
|
|
22110
|
+
if (options.caretRect !== void 0) annot.setCaretRect(options.caretRect);
|
|
22111
|
+
return annot;
|
|
22112
|
+
}
|
|
22113
|
+
/**
|
|
22114
|
+
* Create a PdfCaretAnnotation from an existing dictionary.
|
|
22115
|
+
*/
|
|
22116
|
+
static fromDict(dict, _resolver) {
|
|
22117
|
+
return new PdfCaretAnnotation(dict);
|
|
22118
|
+
}
|
|
22119
|
+
/** Get the caret symbol. Defaults to 'None'. */
|
|
22120
|
+
getSymbol() {
|
|
22121
|
+
const obj = this.dict.get("/Sy");
|
|
22122
|
+
if (obj && obj.kind === "name") {
|
|
22123
|
+
if ((obj.value.startsWith("/") ? obj.value.slice(1) : obj.value) === "P") return "P";
|
|
22124
|
+
}
|
|
22125
|
+
return "None";
|
|
22126
|
+
}
|
|
22127
|
+
/** Set the caret symbol. */
|
|
22128
|
+
setSymbol(symbol) {
|
|
22129
|
+
this.dict.set("/Sy", require_pdfCatalog.PdfName.of(symbol));
|
|
22130
|
+
}
|
|
22131
|
+
/**
|
|
22132
|
+
* Get the inner rectangle differences (RD entry).
|
|
22133
|
+
* Returns [left, bottom, right, top] insets from the annotation rect.
|
|
22134
|
+
*/
|
|
22135
|
+
getCaretRect() {
|
|
22136
|
+
const obj = this.dict.get("/RD");
|
|
22137
|
+
if (obj && obj.kind === "array" && obj.items.length === 4) return obj.items.map((item) => {
|
|
22138
|
+
if (item.kind === "number") return item.value;
|
|
22139
|
+
return 0;
|
|
22140
|
+
});
|
|
22141
|
+
}
|
|
22142
|
+
/** Set the inner rectangle differences (RD entry). */
|
|
22143
|
+
setCaretRect(rd) {
|
|
22144
|
+
this.dict.set("/RD", require_pdfCatalog.PdfArray.of(rd.map(require_pdfCatalog.PdfNumber.of)));
|
|
22145
|
+
}
|
|
22146
|
+
};
|
|
22147
|
+
|
|
22148
|
+
//#endregion
|
|
22149
|
+
//#region src/annotation/types/fileAttachmentAnnotation.ts
|
|
22150
|
+
/**
|
|
22151
|
+
* @module annotation/types/fileAttachmentAnnotation
|
|
22152
|
+
*
|
|
22153
|
+
* File attachment annotation — embeds a file as an inline annotation
|
|
22154
|
+
* on a page, displayed as a clickable icon.
|
|
22155
|
+
*
|
|
22156
|
+
* Unlike document-level attachments (via `attachFile()`), file attachment
|
|
22157
|
+
* annotations are positioned on a specific page and rendered as an icon
|
|
22158
|
+
* that users can click to open or save the embedded file.
|
|
22159
|
+
*
|
|
22160
|
+
* Reference: PDF 1.7 spec, Section 12.5.6.15 (File Attachment Annotations).
|
|
22161
|
+
*/
|
|
22162
|
+
/**
|
|
22163
|
+
* A file attachment annotation (subtype /FileAttachment).
|
|
22164
|
+
*
|
|
22165
|
+
* Embeds a file directly in the annotation, rendered as a clickable
|
|
22166
|
+
* icon on the page. When the user clicks the icon, the PDF viewer
|
|
22167
|
+
* allows them to open or save the embedded file.
|
|
22168
|
+
*/
|
|
22169
|
+
var PdfFileAttachmentAnnotation = class PdfFileAttachmentAnnotation extends require_pdfPage.PdfAnnotation {
|
|
22170
|
+
/** The raw file data to embed. */
|
|
22171
|
+
fileData;
|
|
22172
|
+
/** The filename to display. */
|
|
22173
|
+
fileName;
|
|
22174
|
+
/** Optional MIME type. */
|
|
22175
|
+
mimeType;
|
|
22176
|
+
/** Optional file description. */
|
|
22177
|
+
fileDescription;
|
|
22178
|
+
constructor(dict) {
|
|
22179
|
+
super("FileAttachment", dict);
|
|
22180
|
+
}
|
|
22181
|
+
/**
|
|
22182
|
+
* Create a new file attachment annotation.
|
|
22183
|
+
*
|
|
22184
|
+
* @param options.file The file data to embed.
|
|
22185
|
+
* @param options.fileName The filename (e.g., 'invoice.xml').
|
|
22186
|
+
* @param options.mimeType Optional MIME type (e.g., 'application/xml').
|
|
22187
|
+
* @param options.description Optional description of the file.
|
|
22188
|
+
* @param options.icon Icon to display. Default: 'GraphPushPin'.
|
|
22189
|
+
*/
|
|
22190
|
+
static create(options) {
|
|
22191
|
+
const annot = new PdfFileAttachmentAnnotation(require_pdfPage.buildAnnotationDict("FileAttachment", options));
|
|
22192
|
+
annot.fileData = options.file;
|
|
22193
|
+
annot.fileName = options.fileName;
|
|
22194
|
+
annot.mimeType = options.mimeType;
|
|
22195
|
+
annot.fileDescription = options.description;
|
|
22196
|
+
if (options.icon !== void 0) annot.setIcon(options.icon);
|
|
22197
|
+
return annot;
|
|
22198
|
+
}
|
|
22199
|
+
/**
|
|
22200
|
+
* Create a PdfFileAttachmentAnnotation from an existing dictionary.
|
|
22201
|
+
*/
|
|
22202
|
+
static fromDict(dict, _resolver) {
|
|
22203
|
+
return new PdfFileAttachmentAnnotation(dict);
|
|
22204
|
+
}
|
|
22205
|
+
/** Get the icon name. Defaults to 'GraphPushPin'. */
|
|
22206
|
+
getIcon() {
|
|
22207
|
+
const obj = this.dict.get("/Name");
|
|
22208
|
+
if (obj && obj.kind === "name") {
|
|
22209
|
+
const val = obj.value.startsWith("/") ? obj.value.slice(1) : obj.value;
|
|
22210
|
+
if ([
|
|
22211
|
+
"GraphPushPin",
|
|
22212
|
+
"PaperclipTag",
|
|
22213
|
+
"Paperclip",
|
|
22214
|
+
"Tag"
|
|
22215
|
+
].includes(val)) return val;
|
|
22216
|
+
}
|
|
22217
|
+
return "GraphPushPin";
|
|
22218
|
+
}
|
|
22219
|
+
/** Set the icon name. */
|
|
22220
|
+
setIcon(icon) {
|
|
22221
|
+
this.dict.set("/Name", require_pdfCatalog.PdfName.of(icon));
|
|
22222
|
+
}
|
|
22223
|
+
/** Get the filename, if set. */
|
|
22224
|
+
getFileName() {
|
|
22225
|
+
const fs = this.dict.get("/FS");
|
|
22226
|
+
if (fs && fs.kind === "dict") {
|
|
22227
|
+
const uf = fs.get("/UF");
|
|
22228
|
+
if (uf && uf.kind === "string") return uf.value;
|
|
22229
|
+
const f = fs.get("/F");
|
|
22230
|
+
if (f && f.kind === "string") return f.value;
|
|
22231
|
+
}
|
|
22232
|
+
return this.fileName;
|
|
22233
|
+
}
|
|
22234
|
+
/**
|
|
22235
|
+
* Build the file specification dictionary and register the embedded
|
|
22236
|
+
* file stream. Call this before serializing the annotation.
|
|
22237
|
+
*
|
|
22238
|
+
* @param registry The document's object registry.
|
|
22239
|
+
* @returns The annotation dict with `/FS` referencing the file.
|
|
22240
|
+
*/
|
|
22241
|
+
buildFileSpec(registry) {
|
|
22242
|
+
if (!this.fileData || !this.fileName) return this.dict;
|
|
22243
|
+
const efStreamDict = new require_pdfCatalog.PdfDict();
|
|
22244
|
+
efStreamDict.set("/Type", require_pdfCatalog.PdfName.of("EmbeddedFile"));
|
|
22245
|
+
if (this.mimeType) efStreamDict.set("/Subtype", require_pdfCatalog.PdfName.of(this.mimeType.replace("/", "#2F")));
|
|
22246
|
+
const params = new require_pdfCatalog.PdfDict();
|
|
22247
|
+
params.set("/Size", require_pdfCatalog.PdfNumber.of(this.fileData.length));
|
|
22248
|
+
efStreamDict.set("/Params", params);
|
|
22249
|
+
const efStream = new require_pdfCatalog.PdfStream(efStreamDict, this.fileData);
|
|
22250
|
+
const efRef = registry.register(efStream);
|
|
22251
|
+
const efDict = new require_pdfCatalog.PdfDict();
|
|
22252
|
+
efDict.set("/F", efRef);
|
|
22253
|
+
const fsDict = new require_pdfCatalog.PdfDict();
|
|
22254
|
+
fsDict.set("/Type", require_pdfCatalog.PdfName.of("Filespec"));
|
|
22255
|
+
fsDict.set("/F", require_pdfCatalog.PdfString.literal(this.fileName));
|
|
22256
|
+
fsDict.set("/UF", require_pdfCatalog.PdfString.literal(this.fileName));
|
|
22257
|
+
fsDict.set("/EF", efDict);
|
|
22258
|
+
if (this.fileDescription) fsDict.set("/Desc", require_pdfCatalog.PdfString.literal(this.fileDescription));
|
|
22259
|
+
const fsRef = registry.register(fsDict);
|
|
22260
|
+
this.dict.set("/FS", fsRef);
|
|
22261
|
+
return this.dict;
|
|
22262
|
+
}
|
|
22263
|
+
};
|
|
22264
|
+
|
|
21650
22265
|
//#endregion
|
|
21651
22266
|
//#region src/parser/textExtractor.ts
|
|
21652
22267
|
/**
|
|
@@ -22445,6 +23060,19 @@ var TokenType = /* @__PURE__ */ function(TokenType) {
|
|
|
22445
23060
|
return TokenType;
|
|
22446
23061
|
}(TokenType || {});
|
|
22447
23062
|
/**
|
|
23063
|
+
* `hexVal[b]` is the numeric value (0-15) of a hex character, or -1 if
|
|
23064
|
+
* the byte is not a valid hex digit.
|
|
23065
|
+
*/
|
|
23066
|
+
const hexVal = /* @__PURE__ */ (() => {
|
|
23067
|
+
const t = new Int8Array(256).fill(-1);
|
|
23068
|
+
for (let i = 0; i <= 9; i++) t[48 + i] = i;
|
|
23069
|
+
for (let i = 0; i < 6; i++) {
|
|
23070
|
+
t[65 + i] = 10 + i;
|
|
23071
|
+
t[97 + i] = 10 + i;
|
|
23072
|
+
}
|
|
23073
|
+
return t;
|
|
23074
|
+
})();
|
|
23075
|
+
/**
|
|
22448
23076
|
* Combined lexer + parser for PDF content streams.
|
|
22449
23077
|
*
|
|
22450
23078
|
* Content streams are simpler than full PDF object syntax — there are no
|
|
@@ -22563,20 +23191,22 @@ var ContentStreamLexer = class {
|
|
|
22563
23191
|
}
|
|
22564
23192
|
const dataStart = this.pos;
|
|
22565
23193
|
let dataEnd = this.pos;
|
|
22566
|
-
|
|
22567
|
-
|
|
22568
|
-
|
|
22569
|
-
|
|
22570
|
-
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
|
|
22574
|
-
|
|
22575
|
-
|
|
22576
|
-
|
|
23194
|
+
let searchFrom = this.pos;
|
|
23195
|
+
while (searchFrom < this.data.length) {
|
|
23196
|
+
const eIdx = this.data.indexOf(69, searchFrom);
|
|
23197
|
+
if (eIdx === -1 || eIdx + 1 >= this.data.length) {
|
|
23198
|
+
this.pos = this.data.length;
|
|
23199
|
+
break;
|
|
23200
|
+
}
|
|
23201
|
+
if (eIdx > dataStart && this.isWhitespace(this.data[eIdx - 1]) && this.data[eIdx + 1] === 73) {
|
|
23202
|
+
const afterEI = eIdx + 2;
|
|
23203
|
+
if (afterEI >= this.data.length || this.isWhitespace(this.data[afterEI])) {
|
|
23204
|
+
dataEnd = eIdx - 1;
|
|
23205
|
+
this.pos = afterEI;
|
|
23206
|
+
break;
|
|
22577
23207
|
}
|
|
22578
23208
|
}
|
|
22579
|
-
|
|
23209
|
+
searchFrom = eIdx + 1;
|
|
22580
23210
|
}
|
|
22581
23211
|
return {
|
|
22582
23212
|
dict,
|
|
@@ -22644,7 +23274,7 @@ var ContentStreamLexer = class {
|
|
|
22644
23274
|
*/
|
|
22645
23275
|
readLiteralString() {
|
|
22646
23276
|
this.pos++;
|
|
22647
|
-
|
|
23277
|
+
const parts = [];
|
|
22648
23278
|
let depth = 1;
|
|
22649
23279
|
while (this.pos < this.data.length && depth > 0) {
|
|
22650
23280
|
const ch = this.data[this.pos];
|
|
@@ -22654,35 +23284,35 @@ var ContentStreamLexer = class {
|
|
|
22654
23284
|
const esc = this.data[this.pos];
|
|
22655
23285
|
switch (esc) {
|
|
22656
23286
|
case 110:
|
|
22657
|
-
|
|
23287
|
+
parts.push("\n");
|
|
22658
23288
|
this.pos++;
|
|
22659
23289
|
break;
|
|
22660
23290
|
case 114:
|
|
22661
|
-
|
|
23291
|
+
parts.push("\r");
|
|
22662
23292
|
this.pos++;
|
|
22663
23293
|
break;
|
|
22664
23294
|
case 116:
|
|
22665
|
-
|
|
23295
|
+
parts.push(" ");
|
|
22666
23296
|
this.pos++;
|
|
22667
23297
|
break;
|
|
22668
23298
|
case 98:
|
|
22669
|
-
|
|
23299
|
+
parts.push("\b");
|
|
22670
23300
|
this.pos++;
|
|
22671
23301
|
break;
|
|
22672
23302
|
case 102:
|
|
22673
|
-
|
|
23303
|
+
parts.push("\f");
|
|
22674
23304
|
this.pos++;
|
|
22675
23305
|
break;
|
|
22676
23306
|
case 40:
|
|
22677
|
-
|
|
23307
|
+
parts.push("(");
|
|
22678
23308
|
this.pos++;
|
|
22679
23309
|
break;
|
|
22680
23310
|
case 41:
|
|
22681
|
-
|
|
23311
|
+
parts.push(")");
|
|
22682
23312
|
this.pos++;
|
|
22683
23313
|
break;
|
|
22684
23314
|
case 92:
|
|
22685
|
-
|
|
23315
|
+
parts.push("\\");
|
|
22686
23316
|
this.pos++;
|
|
22687
23317
|
break;
|
|
22688
23318
|
case 10:
|
|
@@ -22710,29 +23340,29 @@ var ContentStreamLexer = class {
|
|
|
22710
23340
|
}
|
|
22711
23341
|
}
|
|
22712
23342
|
}
|
|
22713
|
-
|
|
23343
|
+
parts.push(String.fromCharCode(octal & 255));
|
|
22714
23344
|
} else {
|
|
22715
|
-
|
|
23345
|
+
parts.push(String.fromCharCode(esc));
|
|
22716
23346
|
this.pos++;
|
|
22717
23347
|
}
|
|
22718
23348
|
break;
|
|
22719
23349
|
}
|
|
22720
23350
|
} else if (ch === 40) {
|
|
22721
23351
|
depth++;
|
|
22722
|
-
|
|
23352
|
+
parts.push("(");
|
|
22723
23353
|
this.pos++;
|
|
22724
23354
|
} else if (ch === 41) {
|
|
22725
23355
|
depth--;
|
|
22726
|
-
if (depth > 0)
|
|
23356
|
+
if (depth > 0) parts.push(")");
|
|
22727
23357
|
this.pos++;
|
|
22728
23358
|
} else {
|
|
22729
|
-
|
|
23359
|
+
parts.push(String.fromCharCode(ch));
|
|
22730
23360
|
this.pos++;
|
|
22731
23361
|
}
|
|
22732
23362
|
}
|
|
22733
23363
|
return {
|
|
22734
23364
|
type: TokenType.String,
|
|
22735
|
-
value:
|
|
23365
|
+
value: parts.join("")
|
|
22736
23366
|
};
|
|
22737
23367
|
}
|
|
22738
23368
|
/**
|
|
@@ -22740,7 +23370,8 @@ var ContentStreamLexer = class {
|
|
|
22740
23370
|
*/
|
|
22741
23371
|
readHexString() {
|
|
22742
23372
|
this.pos++;
|
|
22743
|
-
|
|
23373
|
+
const bytes = [];
|
|
23374
|
+
let hi = -1;
|
|
22744
23375
|
while (this.pos < this.data.length) {
|
|
22745
23376
|
const ch = this.data[this.pos];
|
|
22746
23377
|
if (ch === 62) {
|
|
@@ -22751,18 +23382,22 @@ var ContentStreamLexer = class {
|
|
|
22751
23382
|
this.pos++;
|
|
22752
23383
|
continue;
|
|
22753
23384
|
}
|
|
22754
|
-
|
|
23385
|
+
const v = hexVal[ch];
|
|
23386
|
+
if (v === -1) {
|
|
23387
|
+
this.pos++;
|
|
23388
|
+
continue;
|
|
23389
|
+
}
|
|
23390
|
+
if (hi === -1) hi = v;
|
|
23391
|
+
else {
|
|
23392
|
+
bytes.push(hi << 4 | v);
|
|
23393
|
+
hi = -1;
|
|
23394
|
+
}
|
|
22755
23395
|
this.pos++;
|
|
22756
23396
|
}
|
|
22757
|
-
if (
|
|
22758
|
-
let result = "";
|
|
22759
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
22760
|
-
const byte = parseInt(hex.substring(i, i + 2), 16);
|
|
22761
|
-
if (!isNaN(byte)) result += String.fromCharCode(byte);
|
|
22762
|
-
}
|
|
23397
|
+
if (hi !== -1) bytes.push(hi << 4);
|
|
22763
23398
|
return {
|
|
22764
23399
|
type: TokenType.HexString,
|
|
22765
|
-
value:
|
|
23400
|
+
value: String.fromCharCode.apply(null, bytes)
|
|
22766
23401
|
};
|
|
22767
23402
|
}
|
|
22768
23403
|
/**
|
|
@@ -22770,27 +23405,25 @@ var ContentStreamLexer = class {
|
|
|
22770
23405
|
*/
|
|
22771
23406
|
readName() {
|
|
22772
23407
|
this.pos++;
|
|
22773
|
-
|
|
23408
|
+
const parts = ["/"];
|
|
22774
23409
|
while (this.pos < this.data.length) {
|
|
22775
23410
|
const ch = this.data[this.pos];
|
|
22776
23411
|
if (this.isWhitespace(ch) || this.isDelimiter(ch)) break;
|
|
22777
23412
|
if (ch === 35 && this.pos + 2 < this.data.length) {
|
|
22778
|
-
const hi = this.data[this.pos + 1];
|
|
22779
|
-
const lo = this.data[this.pos + 2];
|
|
22780
|
-
|
|
22781
|
-
|
|
22782
|
-
if (!isNaN(code)) {
|
|
22783
|
-
name += String.fromCharCode(code);
|
|
23413
|
+
const hi = hexVal[this.data[this.pos + 1]];
|
|
23414
|
+
const lo = hexVal[this.data[this.pos + 2]];
|
|
23415
|
+
if (hi !== -1 && lo !== -1) {
|
|
23416
|
+
parts.push(String.fromCharCode(hi << 4 | lo));
|
|
22784
23417
|
this.pos += 3;
|
|
22785
23418
|
continue;
|
|
22786
23419
|
}
|
|
22787
23420
|
}
|
|
22788
|
-
|
|
23421
|
+
parts.push(String.fromCharCode(ch));
|
|
22789
23422
|
this.pos++;
|
|
22790
23423
|
}
|
|
22791
23424
|
return {
|
|
22792
23425
|
type: TokenType.Name,
|
|
22793
|
-
value: require_pdfCatalog.PdfName.of(
|
|
23426
|
+
value: require_pdfCatalog.PdfName.of(parts.join(""))
|
|
22794
23427
|
};
|
|
22795
23428
|
}
|
|
22796
23429
|
/**
|
|
@@ -22884,9 +23517,7 @@ var ContentStreamLexer = class {
|
|
|
22884
23517
|
* Decode a slice of the data as ASCII text.
|
|
22885
23518
|
*/
|
|
22886
23519
|
decodeAscii(start, end) {
|
|
22887
|
-
|
|
22888
|
-
for (let i = start; i < end; i++) s += String.fromCharCode(this.data[i]);
|
|
22889
|
-
return s;
|
|
23520
|
+
return String.fromCharCode.apply(null, this.data.subarray(start, end));
|
|
22890
23521
|
}
|
|
22891
23522
|
};
|
|
22892
23523
|
|
|
@@ -23714,6 +24345,339 @@ function addTrailerId(data) {
|
|
|
23714
24345
|
return concatBytes(...parts);
|
|
23715
24346
|
}
|
|
23716
24347
|
|
|
24348
|
+
//#endregion
|
|
24349
|
+
//#region src/assets/image/imageOptimize.ts
|
|
24350
|
+
/**
|
|
24351
|
+
* Downscale an image to fit within the specified dimensions.
|
|
24352
|
+
*
|
|
24353
|
+
* If the image is already smaller than the target dimensions, it is
|
|
24354
|
+
* returned unchanged.
|
|
24355
|
+
*
|
|
24356
|
+
* @param image - The raw image pixel data.
|
|
24357
|
+
* @param options - Downscaling options (target dimensions, algorithm).
|
|
24358
|
+
* @returns The downscaled image, or the original if no scaling needed.
|
|
24359
|
+
*
|
|
24360
|
+
* @example
|
|
24361
|
+
* ```ts
|
|
24362
|
+
* const result = downscaleImage(rawImage, {
|
|
24363
|
+
* maxWidth: 1024,
|
|
24364
|
+
* maxHeight: 768,
|
|
24365
|
+
* algorithm: 'bilinear',
|
|
24366
|
+
* });
|
|
24367
|
+
* ```
|
|
24368
|
+
*/
|
|
24369
|
+
function downscaleImage(image, options = {}) {
|
|
24370
|
+
const target = computeTargetDimensions(image.width, image.height, options);
|
|
24371
|
+
if (target.width >= image.width && target.height >= image.height) return image;
|
|
24372
|
+
switch (options.algorithm ?? "bilinear") {
|
|
24373
|
+
case "nearest": return resampleNearest(image, target.width, target.height);
|
|
24374
|
+
case "bilinear": return resampleBilinear(image, target.width, target.height);
|
|
24375
|
+
case "lanczos": return resampleLanczos(image, target.width, target.height);
|
|
24376
|
+
default: return resampleBilinear(image, target.width, target.height);
|
|
24377
|
+
}
|
|
24378
|
+
}
|
|
24379
|
+
/**
|
|
24380
|
+
* Recompress raw image pixel data using the specified format.
|
|
24381
|
+
*
|
|
24382
|
+
* @param image - The raw image pixel data.
|
|
24383
|
+
* @param options - Recompression options (format, quality).
|
|
24384
|
+
* @returns The compressed image data.
|
|
24385
|
+
*
|
|
24386
|
+
* @example
|
|
24387
|
+
* ```ts
|
|
24388
|
+
* const result = await recompressImage(rawImage, {
|
|
24389
|
+
* format: 'deflate',
|
|
24390
|
+
* compressionLevel: 9,
|
|
24391
|
+
* });
|
|
24392
|
+
* ```
|
|
24393
|
+
*/
|
|
24394
|
+
async function recompressImage(image, options = {}) {
|
|
24395
|
+
switch (options.format ?? "deflate") {
|
|
24396
|
+
case "deflate": return recompressDeflate(image, options.compressionLevel ?? 6);
|
|
24397
|
+
case "jpeg": return recompressJpeg(image, options.quality ?? 85);
|
|
24398
|
+
default: return {
|
|
24399
|
+
data: image.pixels,
|
|
24400
|
+
width: image.width,
|
|
24401
|
+
height: image.height,
|
|
24402
|
+
channels: image.channels,
|
|
24403
|
+
format: "raw",
|
|
24404
|
+
wasOptimized: false
|
|
24405
|
+
};
|
|
24406
|
+
}
|
|
24407
|
+
}
|
|
24408
|
+
/**
|
|
24409
|
+
* Run the full image optimization pipeline: downscale then recompress.
|
|
24410
|
+
*
|
|
24411
|
+
* @param image - The raw image pixel data.
|
|
24412
|
+
* @param options - Combined optimization options.
|
|
24413
|
+
* @returns The optimized result.
|
|
24414
|
+
*/
|
|
24415
|
+
async function optimizeImage(image, options = {}) {
|
|
24416
|
+
if (options.skipBelowBytes && image.pixels.length < options.skipBelowBytes) return {
|
|
24417
|
+
data: image.pixels,
|
|
24418
|
+
width: image.width,
|
|
24419
|
+
height: image.height,
|
|
24420
|
+
channels: image.channels,
|
|
24421
|
+
format: "raw",
|
|
24422
|
+
wasOptimized: false
|
|
24423
|
+
};
|
|
24424
|
+
return recompressImage(downscaleImage(image, options), options);
|
|
24425
|
+
}
|
|
24426
|
+
/**
|
|
24427
|
+
* Compute target dimensions from options, preserving aspect ratio.
|
|
24428
|
+
* @internal
|
|
24429
|
+
*/
|
|
24430
|
+
function computeTargetDimensions(srcWidth, srcHeight, options) {
|
|
24431
|
+
let targetWidth = srcWidth;
|
|
24432
|
+
let targetHeight = srcHeight;
|
|
24433
|
+
if (options.targetDpi && options.printWidth && options.printHeight) {
|
|
24434
|
+
const printWidthInches = options.printWidth / 72;
|
|
24435
|
+
const printHeightInches = options.printHeight / 72;
|
|
24436
|
+
const dpiWidth = Math.round(printWidthInches * options.targetDpi);
|
|
24437
|
+
const dpiHeight = Math.round(printHeightInches * options.targetDpi);
|
|
24438
|
+
targetWidth = Math.min(targetWidth, dpiWidth);
|
|
24439
|
+
targetHeight = Math.min(targetHeight, dpiHeight);
|
|
24440
|
+
}
|
|
24441
|
+
if (options.maxWidth && targetWidth > options.maxWidth) {
|
|
24442
|
+
const scale = options.maxWidth / targetWidth;
|
|
24443
|
+
targetWidth = options.maxWidth;
|
|
24444
|
+
targetHeight = Math.round(targetHeight * scale);
|
|
24445
|
+
}
|
|
24446
|
+
if (options.maxHeight && targetHeight > options.maxHeight) {
|
|
24447
|
+
const scale = options.maxHeight / targetHeight;
|
|
24448
|
+
targetHeight = options.maxHeight;
|
|
24449
|
+
targetWidth = Math.round(targetWidth * scale);
|
|
24450
|
+
}
|
|
24451
|
+
targetWidth = Math.max(1, targetWidth);
|
|
24452
|
+
targetHeight = Math.max(1, targetHeight);
|
|
24453
|
+
return {
|
|
24454
|
+
width: targetWidth,
|
|
24455
|
+
height: targetHeight
|
|
24456
|
+
};
|
|
24457
|
+
}
|
|
24458
|
+
/**
|
|
24459
|
+
* Nearest-neighbor resampling.
|
|
24460
|
+
* @internal
|
|
24461
|
+
*/
|
|
24462
|
+
function resampleNearest(src, dstWidth, dstHeight) {
|
|
24463
|
+
const channels = src.channels;
|
|
24464
|
+
const dst = new Uint8Array(dstWidth * dstHeight * channels);
|
|
24465
|
+
const xRatio = src.width / dstWidth;
|
|
24466
|
+
const yRatio = src.height / dstHeight;
|
|
24467
|
+
for (let y = 0; y < dstHeight; y++) {
|
|
24468
|
+
const srcY = Math.min(Math.floor(y * yRatio), src.height - 1);
|
|
24469
|
+
for (let x = 0; x < dstWidth; x++) {
|
|
24470
|
+
const srcX = Math.min(Math.floor(x * xRatio), src.width - 1);
|
|
24471
|
+
const srcIdx = (srcY * src.width + srcX) * channels;
|
|
24472
|
+
const dstIdx = (y * dstWidth + x) * channels;
|
|
24473
|
+
for (let c = 0; c < channels; c++) dst[dstIdx + c] = src.pixels[srcIdx + c];
|
|
24474
|
+
}
|
|
24475
|
+
}
|
|
24476
|
+
return {
|
|
24477
|
+
pixels: dst,
|
|
24478
|
+
width: dstWidth,
|
|
24479
|
+
height: dstHeight,
|
|
24480
|
+
channels,
|
|
24481
|
+
bitsPerChannel: src.bitsPerChannel
|
|
24482
|
+
};
|
|
24483
|
+
}
|
|
24484
|
+
/**
|
|
24485
|
+
* Bilinear interpolation resampling.
|
|
24486
|
+
* @internal
|
|
24487
|
+
*/
|
|
24488
|
+
function resampleBilinear(src, dstWidth, dstHeight) {
|
|
24489
|
+
const channels = src.channels;
|
|
24490
|
+
const dst = new Uint8Array(dstWidth * dstHeight * channels);
|
|
24491
|
+
const xRatio = (src.width - 1) / Math.max(1, dstWidth - 1);
|
|
24492
|
+
const yRatio = (src.height - 1) / Math.max(1, dstHeight - 1);
|
|
24493
|
+
for (let y = 0; y < dstHeight; y++) {
|
|
24494
|
+
const srcYf = y * yRatio;
|
|
24495
|
+
const srcY0 = Math.floor(srcYf);
|
|
24496
|
+
const srcY1 = Math.min(srcY0 + 1, src.height - 1);
|
|
24497
|
+
const yFrac = srcYf - srcY0;
|
|
24498
|
+
for (let x = 0; x < dstWidth; x++) {
|
|
24499
|
+
const srcXf = x * xRatio;
|
|
24500
|
+
const srcX0 = Math.floor(srcXf);
|
|
24501
|
+
const srcX1 = Math.min(srcX0 + 1, src.width - 1);
|
|
24502
|
+
const xFrac = srcXf - srcX0;
|
|
24503
|
+
const dstIdx = (y * dstWidth + x) * channels;
|
|
24504
|
+
for (let c = 0; c < channels; c++) {
|
|
24505
|
+
const topLeft = src.pixels[(srcY0 * src.width + srcX0) * channels + c];
|
|
24506
|
+
const topRight = src.pixels[(srcY0 * src.width + srcX1) * channels + c];
|
|
24507
|
+
const bottomLeft = src.pixels[(srcY1 * src.width + srcX0) * channels + c];
|
|
24508
|
+
const bottomRight = src.pixels[(srcY1 * src.width + srcX1) * channels + c];
|
|
24509
|
+
const top = topLeft + (topRight - topLeft) * xFrac;
|
|
24510
|
+
const value = top + (bottomLeft + (bottomRight - bottomLeft) * xFrac - top) * yFrac;
|
|
24511
|
+
dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, value)));
|
|
24512
|
+
}
|
|
24513
|
+
}
|
|
24514
|
+
}
|
|
24515
|
+
return {
|
|
24516
|
+
pixels: dst,
|
|
24517
|
+
width: dstWidth,
|
|
24518
|
+
height: dstHeight,
|
|
24519
|
+
channels,
|
|
24520
|
+
bitsPerChannel: src.bitsPerChannel
|
|
24521
|
+
};
|
|
24522
|
+
}
|
|
24523
|
+
/**
|
|
24524
|
+
* Lanczos kernel function.
|
|
24525
|
+
*
|
|
24526
|
+
* Computes the Lanczos windowed sinc value for a given distance `x`
|
|
24527
|
+
* and window size `a`. For Lanczos-3, `a = 3`.
|
|
24528
|
+
*
|
|
24529
|
+
* @param x - Distance from the center sample.
|
|
24530
|
+
* @param a - Window radius (3 for Lanczos-3).
|
|
24531
|
+
* @returns The kernel weight.
|
|
24532
|
+
* @internal
|
|
24533
|
+
*/
|
|
24534
|
+
function lanczos(x, a = 3) {
|
|
24535
|
+
if (x === 0) return 1;
|
|
24536
|
+
if (Math.abs(x) >= a) return 0;
|
|
24537
|
+
const pix = Math.PI * x;
|
|
24538
|
+
return Math.sin(pix) / pix * (Math.sin(pix / a) / (pix / a));
|
|
24539
|
+
}
|
|
24540
|
+
/**
|
|
24541
|
+
* Lanczos-3 resampling.
|
|
24542
|
+
*
|
|
24543
|
+
* Uses a 6-tap (a=3) windowed sinc filter in both dimensions for
|
|
24544
|
+
* high-quality downscaling. This is the best quality option but
|
|
24545
|
+
* also the slowest.
|
|
24546
|
+
*
|
|
24547
|
+
* @internal
|
|
24548
|
+
*/
|
|
24549
|
+
function resampleLanczos(src, dstWidth, dstHeight) {
|
|
24550
|
+
const channels = src.channels;
|
|
24551
|
+
const a = 3;
|
|
24552
|
+
const dst = new Uint8Array(dstWidth * dstHeight * channels);
|
|
24553
|
+
const xRatio = src.width / dstWidth;
|
|
24554
|
+
const yRatio = src.height / dstHeight;
|
|
24555
|
+
for (let y = 0; y < dstHeight; y++) {
|
|
24556
|
+
const srcYf = (y + .5) * yRatio - .5;
|
|
24557
|
+
for (let x = 0; x < dstWidth; x++) {
|
|
24558
|
+
const srcXf = (x + .5) * xRatio - .5;
|
|
24559
|
+
const dstIdx = (y * dstWidth + x) * channels;
|
|
24560
|
+
const sum = new Float64Array(channels);
|
|
24561
|
+
let weightSum = 0;
|
|
24562
|
+
const yStart = Math.floor(srcYf) - a + 1;
|
|
24563
|
+
const yEnd = Math.floor(srcYf) + a;
|
|
24564
|
+
const xStart = Math.floor(srcXf) - a + 1;
|
|
24565
|
+
const xEnd = Math.floor(srcXf) + a;
|
|
24566
|
+
for (let sy = yStart; sy <= yEnd; sy++) {
|
|
24567
|
+
const wy = lanczos(srcYf - sy, a);
|
|
24568
|
+
if (wy === 0) continue;
|
|
24569
|
+
const clampedY = Math.max(0, Math.min(src.height - 1, sy));
|
|
24570
|
+
for (let sx = xStart; sx <= xEnd; sx++) {
|
|
24571
|
+
const wx = lanczos(srcXf - sx, a);
|
|
24572
|
+
if (wx === 0) continue;
|
|
24573
|
+
const w = wx * wy;
|
|
24574
|
+
const clampedX = Math.max(0, Math.min(src.width - 1, sx));
|
|
24575
|
+
const srcIdx = (clampedY * src.width + clampedX) * channels;
|
|
24576
|
+
for (let c = 0; c < channels; c++) sum[c] = (sum[c] ?? 0) + src.pixels[srcIdx + c] * w;
|
|
24577
|
+
weightSum += w;
|
|
24578
|
+
}
|
|
24579
|
+
}
|
|
24580
|
+
if (weightSum > 0) for (let c = 0; c < channels; c++) dst[dstIdx + c] = Math.round(Math.max(0, Math.min(255, sum[c] / weightSum)));
|
|
24581
|
+
}
|
|
24582
|
+
}
|
|
24583
|
+
return {
|
|
24584
|
+
pixels: dst,
|
|
24585
|
+
width: dstWidth,
|
|
24586
|
+
height: dstHeight,
|
|
24587
|
+
channels,
|
|
24588
|
+
bitsPerChannel: src.bitsPerChannel
|
|
24589
|
+
};
|
|
24590
|
+
}
|
|
24591
|
+
/**
|
|
24592
|
+
* Recompress image data using deflate (for PDF FlateDecode).
|
|
24593
|
+
* @internal
|
|
24594
|
+
*/
|
|
24595
|
+
async function recompressDeflate(image, level) {
|
|
24596
|
+
if (typeof CompressionStream !== "undefined") {
|
|
24597
|
+
const cs = new CompressionStream("deflate");
|
|
24598
|
+
const writer = cs.writable.getWriter();
|
|
24599
|
+
const reader = cs.readable.getReader();
|
|
24600
|
+
const chunks = [];
|
|
24601
|
+
writer.write(new Uint8Array(image.pixels)).catch(() => {});
|
|
24602
|
+
writer.close().catch(() => {});
|
|
24603
|
+
while (true) {
|
|
24604
|
+
const { done, value } = await reader.read();
|
|
24605
|
+
if (done) break;
|
|
24606
|
+
chunks.push(value);
|
|
24607
|
+
}
|
|
24608
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
24609
|
+
const result = new Uint8Array(totalLength);
|
|
24610
|
+
let pos = 0;
|
|
24611
|
+
for (const chunk of chunks) {
|
|
24612
|
+
result.set(chunk, pos);
|
|
24613
|
+
pos += chunk.length;
|
|
24614
|
+
}
|
|
24615
|
+
return {
|
|
24616
|
+
data: result,
|
|
24617
|
+
width: image.width,
|
|
24618
|
+
height: image.height,
|
|
24619
|
+
channels: image.channels,
|
|
24620
|
+
format: "deflate",
|
|
24621
|
+
wasOptimized: true
|
|
24622
|
+
};
|
|
24623
|
+
}
|
|
24624
|
+
try {
|
|
24625
|
+
const { deflateSync } = await import("fflate");
|
|
24626
|
+
return {
|
|
24627
|
+
data: deflateSync(image.pixels, { level }),
|
|
24628
|
+
width: image.width,
|
|
24629
|
+
height: image.height,
|
|
24630
|
+
channels: image.channels,
|
|
24631
|
+
format: "deflate",
|
|
24632
|
+
wasOptimized: true
|
|
24633
|
+
};
|
|
24634
|
+
} catch {
|
|
24635
|
+
return {
|
|
24636
|
+
data: image.pixels,
|
|
24637
|
+
width: image.width,
|
|
24638
|
+
height: image.height,
|
|
24639
|
+
channels: image.channels,
|
|
24640
|
+
format: "raw",
|
|
24641
|
+
wasOptimized: false
|
|
24642
|
+
};
|
|
24643
|
+
}
|
|
24644
|
+
}
|
|
24645
|
+
/**
|
|
24646
|
+
* Recompress image data as JPEG.
|
|
24647
|
+
* @internal
|
|
24648
|
+
*/
|
|
24649
|
+
/**
|
|
24650
|
+
* Recompress image data as JPEG.
|
|
24651
|
+
*
|
|
24652
|
+
* JPEG encoding in pure JS is complex (DCT, Huffman coding, quantization).
|
|
24653
|
+
* A full implementation requires either:
|
|
24654
|
+
*
|
|
24655
|
+
* 1. **WASM-based encoder** (preferred) -- compile libjpeg-turbo or mozjpeg
|
|
24656
|
+
* to WASM, feed raw pixels, get JPEG bytes back.
|
|
24657
|
+
* 2. **Canvas API** (browser-only fallback) -- use `OffscreenCanvas` with
|
|
24658
|
+
* `convertToBlob({ type: 'image/jpeg', quality })`.
|
|
24659
|
+
* 3. **Pure JS encoder** (last resort) -- very slow but works everywhere.
|
|
24660
|
+
*
|
|
24661
|
+
* Until a WASM encoder module is bundled, this function returns the input
|
|
24662
|
+
* data unchanged. The caller receives `format: 'raw'` and
|
|
24663
|
+
* `wasOptimized: false` to indicate that JPEG encoding was not applied.
|
|
24664
|
+
*
|
|
24665
|
+
* @param image - The raw image pixel data.
|
|
24666
|
+
* @param quality - JPEG quality 1-100 (reserved for future use).
|
|
24667
|
+
* @returns The input data as-is, marked as un-optimized.
|
|
24668
|
+
* @internal
|
|
24669
|
+
*/
|
|
24670
|
+
async function recompressJpeg(image, quality) {
|
|
24671
|
+
return {
|
|
24672
|
+
data: image.pixels,
|
|
24673
|
+
width: image.width,
|
|
24674
|
+
height: image.height,
|
|
24675
|
+
channels: image.channels,
|
|
24676
|
+
format: "raw",
|
|
24677
|
+
wasOptimized: false
|
|
24678
|
+
};
|
|
24679
|
+
}
|
|
24680
|
+
|
|
23717
24681
|
//#endregion
|
|
23718
24682
|
//#region src/errors.ts
|
|
23719
24683
|
/**
|
|
@@ -23921,6 +24885,7 @@ exports.PdfAnnotation = require_pdfPage.PdfAnnotation;
|
|
|
23921
24885
|
exports.PdfArray = require_pdfCatalog.PdfArray;
|
|
23922
24886
|
exports.PdfBool = require_pdfCatalog.PdfBool;
|
|
23923
24887
|
exports.PdfButtonField = PdfButtonField;
|
|
24888
|
+
exports.PdfCaretAnnotation = PdfCaretAnnotation;
|
|
23924
24889
|
exports.PdfCheckboxField = PdfCheckboxField;
|
|
23925
24890
|
exports.PdfCircleAnnotation = PdfCircleAnnotation;
|
|
23926
24891
|
exports.PdfDict = require_pdfCatalog.PdfDict;
|
|
@@ -23928,6 +24893,7 @@ exports.PdfDocument = PdfDocument;
|
|
|
23928
24893
|
exports.PdfDropdownField = PdfDropdownField;
|
|
23929
24894
|
exports.PdfEncryptionHandler = PdfEncryptionHandler;
|
|
23930
24895
|
exports.PdfField = PdfField;
|
|
24896
|
+
exports.PdfFileAttachmentAnnotation = PdfFileAttachmentAnnotation;
|
|
23931
24897
|
exports.PdfForm = PdfForm;
|
|
23932
24898
|
exports.PdfFreeTextAnnotation = PdfFreeTextAnnotation;
|
|
23933
24899
|
exports.PdfHighlightAnnotation = PdfHighlightAnnotation;
|
|
@@ -23947,6 +24913,7 @@ exports.PdfPage = require_pdfPage.PdfPage;
|
|
|
23947
24913
|
exports.PdfParseError = PdfParseError;
|
|
23948
24914
|
exports.PdfPolyLineAnnotation = PdfPolyLineAnnotation;
|
|
23949
24915
|
exports.PdfPolygonAnnotation = PdfPolygonAnnotation;
|
|
24916
|
+
exports.PdfPopupAnnotation = PdfPopupAnnotation;
|
|
23950
24917
|
exports.PdfRadioGroup = PdfRadioGroup;
|
|
23951
24918
|
exports.PdfRedactAnnotation = PdfRedactAnnotation;
|
|
23952
24919
|
exports.PdfRef = require_pdfCatalog.PdfRef;
|
|
@@ -24033,6 +25000,7 @@ exports.decodePermissions = decodePermissions;
|
|
|
24033
25000
|
exports.decodeStream = decodeStream;
|
|
24034
25001
|
exports.degrees = require_pdfPage.degrees;
|
|
24035
25002
|
exports.degreesToRadians = require_pdfPage.degreesToRadians;
|
|
25003
|
+
exports.downscaleImage = downscaleImage;
|
|
24036
25004
|
exports.drawImageWithMatrix = require_pdfPage.drawImageWithMatrix;
|
|
24037
25005
|
exports.drawImageXObject = require_pdfPage.drawImageXObject;
|
|
24038
25006
|
exports.drawObject = require_pdfPage.drawXObject;
|
|
@@ -24110,6 +25078,7 @@ exports.moveTextOp = require_pdfPage.moveText;
|
|
|
24110
25078
|
exports.moveTextSetLeading = require_pdfPage.moveTextSetLeading;
|
|
24111
25079
|
exports.moveToOp = require_pdfPage.moveTo;
|
|
24112
25080
|
exports.nextLineOp = require_pdfPage.nextLine;
|
|
25081
|
+
exports.optimizeImage = optimizeImage;
|
|
24113
25082
|
exports.parseContentStream = parseContentStream;
|
|
24114
25083
|
exports.parseSvg = require_pdfPage.parseSvg;
|
|
24115
25084
|
exports.parseSvgColor = require_pdfPage.parseSvgColor;
|
|
@@ -24125,6 +25094,7 @@ exports.radialGradient = require_pdfPage.radialGradient;
|
|
|
24125
25094
|
exports.radians = require_pdfPage.radians;
|
|
24126
25095
|
exports.radiansToDegrees = require_pdfPage.radiansToDegrees;
|
|
24127
25096
|
exports.rc4 = rc4;
|
|
25097
|
+
exports.recompressImage = recompressImage;
|
|
24128
25098
|
exports.rectangleOp = require_pdfPage.rectangle;
|
|
24129
25099
|
exports.removePage = removePage;
|
|
24130
25100
|
exports.removePages = removePages;
|