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