pdf-lite 1.7.3-alpha.0 → 1.7.3
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/dist/acroform/appearance/pdf-choice-appearance-stream.d.ts +1 -3
- package/dist/acroform/appearance/pdf-choice-appearance-stream.js +16 -2
- package/dist/acroform/appearance/pdf-text-appearance-stream.d.ts +1 -0
- package/dist/acroform/appearance/pdf-text-appearance-stream.js +29 -6
- package/dist/acroform/fields/pdf-choice-form-field.js +1 -0
- package/dist/acroform/fields/pdf-form-field.js +1 -0
- package/dist/acroform/fields/pdf-text-form-field.js +1 -0
- package/dist/core/decoder.js +1 -1
- package/dist/core/objects/pdf-object.js +1 -1
- package/dist/core/tokeniser.js +4 -1
- package/dist/core/tokens/comment-token.js +1 -1
- package/dist/crypto/key-derivation/key-derivation.js +1 -1
- package/dist/crypto/key-gen/key-gen-rc4-128.js +10 -4
- package/dist/crypto/key-gen/key-gen-rc4-40.js +1 -1
- package/dist/pdf/pdf-document.js +1 -1
- package/dist/signing/signer.js +8 -2
- package/dist/utils/concatUint8Arrays.d.ts +3 -3
- package/dist/utils/concatUint8Arrays.js +10 -7
- package/package.json +1 -1
|
@@ -3,9 +3,6 @@ import { PdfAppearanceStream } from './pdf-appearance-stream.js';
|
|
|
3
3
|
import type { PdfDictionary } from '../../core/objects/pdf-dictionary.js';
|
|
4
4
|
import type { PdfFont } from '../../fonts/pdf-font.js';
|
|
5
5
|
import { PdfFormFieldFlags } from '../fields/pdf-form-field-flags.js';
|
|
6
|
-
/**
|
|
7
|
-
* Appearance stream for choice fields (dropdowns, list boxes).
|
|
8
|
-
*/
|
|
9
6
|
export declare class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
10
7
|
constructor(ctx: {
|
|
11
8
|
rect: [number, number, number, number];
|
|
@@ -18,5 +15,6 @@ export declare class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
18
15
|
reverseEncodingMap?: Map<string, number>;
|
|
19
16
|
displayOptions?: string[];
|
|
20
17
|
selectedIndex?: number;
|
|
18
|
+
quadding?: number;
|
|
21
19
|
});
|
|
22
20
|
}
|
|
@@ -4,6 +4,14 @@ import { PdfFormFieldFlags } from '../fields/pdf-form-field-flags.js';
|
|
|
4
4
|
/**
|
|
5
5
|
* Appearance stream for choice fields (dropdowns, list boxes).
|
|
6
6
|
*/
|
|
7
|
+
/** Compute the X offset for a line of text given the quadding value. */
|
|
8
|
+
function calcTextX(quadding, padding, availableWidth, textWidth) {
|
|
9
|
+
if (quadding === 2)
|
|
10
|
+
return padding + Math.max(availableWidth - textWidth, 0);
|
|
11
|
+
if (quadding === 1)
|
|
12
|
+
return padding + Math.max((availableWidth - textWidth) / 2, 0);
|
|
13
|
+
return padding;
|
|
14
|
+
}
|
|
7
15
|
export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
8
16
|
constructor(ctx) {
|
|
9
17
|
const [x1, y1, x2, y2] = ctx.rect;
|
|
@@ -17,10 +25,14 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
17
25
|
const isUnicode = ctx.isUnicode ?? false;
|
|
18
26
|
const reverseEncodingMap = ctx.reverseEncodingMap;
|
|
19
27
|
const padding = 2;
|
|
28
|
+
const availableWidth = width - 2 * padding;
|
|
20
29
|
const isCombo = new PdfFormFieldFlags(ctx.flags).combo;
|
|
30
|
+
const quadding = ctx.quadding ?? 0;
|
|
21
31
|
const g = new PdfGraphics({ resolvedFonts: ctx.resolvedFonts });
|
|
22
32
|
g.beginMarkedContent();
|
|
23
33
|
g.save();
|
|
34
|
+
// Set DA early so measureTextWidth works for alignment calculations
|
|
35
|
+
g.setDefaultAppearance(ctx.da);
|
|
24
36
|
if (!isCombo && ctx.displayOptions && ctx.displayOptions.length > 0) {
|
|
25
37
|
// Listbox: render all items, highlight the selected one
|
|
26
38
|
const lineHeight = ctx.da.fontSize + 4;
|
|
@@ -39,9 +51,10 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
39
51
|
g.restore();
|
|
40
52
|
}
|
|
41
53
|
const textY = itemY + lineHeight * 0.25;
|
|
54
|
+
const textX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(ctx.displayOptions[i]));
|
|
42
55
|
g.beginText();
|
|
43
56
|
g.setDefaultAppearance(ctx.da);
|
|
44
|
-
g.moveTo(
|
|
57
|
+
g.moveTo(textX, textY);
|
|
45
58
|
g.showText(ctx.displayOptions[i], isUnicode, reverseEncodingMap);
|
|
46
59
|
g.endText();
|
|
47
60
|
}
|
|
@@ -49,9 +62,10 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
|
|
|
49
62
|
else {
|
|
50
63
|
// Combo (dropdown) or no options: show selected value only
|
|
51
64
|
const textY = (height - ctx.da.fontSize) / 2 + ctx.da.fontSize * 0.2;
|
|
65
|
+
const textX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(ctx.value));
|
|
52
66
|
g.beginText();
|
|
53
67
|
g.setDefaultAppearance(ctx.da);
|
|
54
|
-
g.moveTo(
|
|
68
|
+
g.moveTo(textX, textY);
|
|
55
69
|
g.showText(ctx.value, isUnicode, reverseEncodingMap);
|
|
56
70
|
g.endText();
|
|
57
71
|
if (isCombo) {
|
|
@@ -2,6 +2,19 @@ import { PdfDefaultAppearance } from '../fields/pdf-default-appearance.js';
|
|
|
2
2
|
import { PdfAppearanceStream } from './pdf-appearance-stream.js';
|
|
3
3
|
import { PdfGraphics } from './pdf-graphics.js';
|
|
4
4
|
const DEFAULT_FONT_SIZE = 12;
|
|
5
|
+
/** Compute the X offset for a line of text given the quadding value. */
|
|
6
|
+
function calcTextX(quadding, padding, availableWidth, textWidth) {
|
|
7
|
+
if (quadding === 2) {
|
|
8
|
+
// Right-aligned
|
|
9
|
+
return padding + Math.max(availableWidth - textWidth, 0);
|
|
10
|
+
}
|
|
11
|
+
if (quadding === 1) {
|
|
12
|
+
// Center-aligned
|
|
13
|
+
return padding + Math.max((availableWidth - textWidth) / 2, 0);
|
|
14
|
+
}
|
|
15
|
+
// Left-aligned (default)
|
|
16
|
+
return padding;
|
|
17
|
+
}
|
|
5
18
|
/**
|
|
6
19
|
* Appearance stream for text fields (single-line, multiline, comb).
|
|
7
20
|
* Enhanced with word wrapping and automatic font scaling.
|
|
@@ -18,6 +31,7 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
18
31
|
const availableWidth = width - 2 * padding;
|
|
19
32
|
const availableHeight = height - 2 * padding;
|
|
20
33
|
const autoSize = ctx.da.fontSize <= 0;
|
|
34
|
+
const quadding = ctx.quadding ?? 0;
|
|
21
35
|
// Create graphics with font context for text measurement
|
|
22
36
|
const g = new PdfGraphics({
|
|
23
37
|
resolvedFonts: ctx.resolvedFonts,
|
|
@@ -108,11 +122,18 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
108
122
|
}
|
|
109
123
|
else {
|
|
110
124
|
g.beginText();
|
|
111
|
-
|
|
125
|
+
let prevLineX = 0;
|
|
112
126
|
for (let i = 0; i < lines.length; i++) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
127
|
+
const lineText = lines[i].replace(/\r/g, '');
|
|
128
|
+
const lineX = calcTextX(quadding, padding, availableWidth, g.measureTextWidth(lineText));
|
|
129
|
+
if (i === 0) {
|
|
130
|
+
g.moveTo(lineX, startY);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
g.moveTo(lineX - prevLineX, -renderLineHeight);
|
|
134
|
+
}
|
|
135
|
+
prevLineX = lineX;
|
|
136
|
+
g.showText(lineText, isUnicode, reverseEncodingMap);
|
|
116
137
|
}
|
|
117
138
|
g.endText();
|
|
118
139
|
}
|
|
@@ -126,12 +147,14 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
|
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
149
|
const textY = (height - finalFontSize) / 2 + finalFontSize * 0.2;
|
|
150
|
+
const textWidth = g.measureTextWidth(value);
|
|
151
|
+
const textX = calcTextX(quadding, padding, availableWidth, textWidth);
|
|
129
152
|
if (ctx.markdown) {
|
|
130
|
-
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap,
|
|
153
|
+
g.showMarkdown(ctx.markdown, isUnicode, reverseEncodingMap, textX, textY, finalFontSize);
|
|
131
154
|
}
|
|
132
155
|
else {
|
|
133
156
|
g.beginText();
|
|
134
|
-
g.moveTo(
|
|
157
|
+
g.moveTo(textX, textY);
|
|
135
158
|
g.showText(value, isUnicode, reverseEncodingMap);
|
|
136
159
|
g.endText();
|
|
137
160
|
}
|
|
@@ -101,6 +101,7 @@ export class PdfChoiceFormField extends PdfFormField {
|
|
|
101
101
|
reverseEncodingMap,
|
|
102
102
|
displayOptions: this.options.map((opt) => opt.label),
|
|
103
103
|
selectedIndex: this.selectedIndex,
|
|
104
|
+
quadding: this.quadding,
|
|
104
105
|
});
|
|
105
106
|
if (options?.makeReadOnly) {
|
|
106
107
|
this.readOnly = true;
|
package/dist/core/decoder.js
CHANGED
|
@@ -147,7 +147,7 @@ export class PdfDecoder extends IncrementalParser {
|
|
|
147
147
|
const postTokens = this.nextExtraTokens();
|
|
148
148
|
const stream = new PdfStream({
|
|
149
149
|
header,
|
|
150
|
-
original: concatUint8Arrays(
|
|
150
|
+
original: concatUint8Arrays(chunks),
|
|
151
151
|
});
|
|
152
152
|
stream.preStreamDataTokens = preStreamTokens;
|
|
153
153
|
stream.postStreamDataTokens = postStreamDataTokens;
|
|
@@ -62,7 +62,7 @@ export class PdfObject {
|
|
|
62
62
|
/** Converts the object to a ByteArray, optionally padding to a specified length */
|
|
63
63
|
toBytes(padTo) {
|
|
64
64
|
const tokens = this.toTokens();
|
|
65
|
-
const byteArray = concatUint8Arrays(
|
|
65
|
+
const byteArray = concatUint8Arrays(tokens.map((token) => token.toBytes()));
|
|
66
66
|
if (padTo) {
|
|
67
67
|
const paddedArray = new Uint8Array(padTo);
|
|
68
68
|
paddedArray.set(byteArray);
|
package/dist/core/tokeniser.js
CHANGED
|
@@ -292,7 +292,10 @@ export class PdfByteStreamTokeniser extends IncrementalParser {
|
|
|
292
292
|
}
|
|
293
293
|
// Note: If there's no EOL marker, we continue anyway (malformed PDF)
|
|
294
294
|
this.inStream = true;
|
|
295
|
-
return new PdfStartStreamToken(concatUint8Arrays(
|
|
295
|
+
return new PdfStartStreamToken(concatUint8Arrays([
|
|
296
|
+
stringToBytes('stream'),
|
|
297
|
+
new Uint8Array(whitespaceBytes),
|
|
298
|
+
]));
|
|
296
299
|
}
|
|
297
300
|
nextStreamChunkToken() {
|
|
298
301
|
if (!this.inStream) {
|
|
@@ -15,7 +15,7 @@ export class PdfCommentToken extends PdfToken {
|
|
|
15
15
|
}
|
|
16
16
|
static toBytes(comment) {
|
|
17
17
|
const tokenBytes = typeof comment === 'string' ? stringToBytes(comment) : comment;
|
|
18
|
-
return concatUint8Arrays(stringToBytes('%'), tokenBytes);
|
|
18
|
+
return concatUint8Arrays([stringToBytes('%'), tokenBytes]);
|
|
19
19
|
}
|
|
20
20
|
static isEofCommentToken(token) {
|
|
21
21
|
if (!(token instanceof PdfCommentToken))
|
|
@@ -23,7 +23,7 @@ export async function computeMasterKey(password, ownerKey, permissions, id0, key
|
|
|
23
23
|
hashInputParts.push(new Uint8Array([0xff, 0xff, 0xff, 0xff]));
|
|
24
24
|
}
|
|
25
25
|
// Initial MD5 hash
|
|
26
|
-
let digest = await md5(concatUint8Arrays(
|
|
26
|
+
let digest = await md5(concatUint8Arrays(hashInputParts));
|
|
27
27
|
if (keyLengthBits > 40) {
|
|
28
28
|
// Perform 50 iterations of MD5 rehash
|
|
29
29
|
for (let i = 0; i < 50; i++) {
|
|
@@ -81,9 +81,15 @@ export async function computeOValueRc4_128(ownerPassword, userPassword) {
|
|
|
81
81
|
*/
|
|
82
82
|
async function computeEncryptionKeyRc4_128(userPad, oValue, permissions, id, encryptMetadata, revision = 3) {
|
|
83
83
|
const perms = int32ToLittleEndianBytes(permissions);
|
|
84
|
-
let digest = await md5(concatUint8Arrays(
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
let digest = await md5(concatUint8Arrays([
|
|
85
|
+
userPad,
|
|
86
|
+
oValue,
|
|
87
|
+
perms,
|
|
88
|
+
id,
|
|
89
|
+
revision >= 4 && !encryptMetadata
|
|
90
|
+
? new Uint8Array([0xff, 0xff, 0xff, 0xff])
|
|
91
|
+
: new Uint8Array(),
|
|
92
|
+
]));
|
|
87
93
|
// 50 iterations
|
|
88
94
|
for (let i = 0; i < 50; i++) {
|
|
89
95
|
digest = await md5(digest);
|
|
@@ -112,7 +118,7 @@ export async function computeUValueRc4_128(userPassword, oValue, permissions, id
|
|
|
112
118
|
// Step 1: Compute encryption key
|
|
113
119
|
const encryptionKey = await computeEncryptionKeyRc4_128(userPad, oValue, permissions, id, encryptMetadata, revision);
|
|
114
120
|
// Step 2 & 3: MD5 of padding + file ID
|
|
115
|
-
const hash = await md5(concatUint8Arrays(DEFAULT_PADDING, id)); // 16 bytes
|
|
121
|
+
const hash = await md5(concatUint8Arrays([DEFAULT_PADDING, id])); // 16 bytes
|
|
116
122
|
// Step 4: First RC4 encrypt with base key
|
|
117
123
|
let data = await rc4EncryptWithKey(encryptionKey, hash);
|
|
118
124
|
// Step 5: 19 more rounds of RC4 with key XOR i
|
|
@@ -68,7 +68,7 @@ export async function computeORc4_40(ownerPw, userPw) {
|
|
|
68
68
|
export async function computeEncryptionKeyRc4_40(userPw, oValue, permissions, fileId) {
|
|
69
69
|
const userPad = padPassword(userPw);
|
|
70
70
|
const permissionsLE = int32ToLittleEndianBytes(permissions);
|
|
71
|
-
const digest = await md5(concatUint8Arrays(userPad, oValue, permissionsLE, fileId));
|
|
71
|
+
const digest = await md5(concatUint8Arrays([userPad, oValue, permissionsLE, fileId]));
|
|
72
72
|
return digest.slice(0, 5); // 40-bit key
|
|
73
73
|
}
|
|
74
74
|
/**
|
package/dist/pdf/pdf-document.js
CHANGED
|
@@ -952,7 +952,7 @@ export class PdfDocument extends PdfObject {
|
|
|
952
952
|
*/
|
|
953
953
|
toBytes() {
|
|
954
954
|
this.update();
|
|
955
|
-
return concatUint8Arrays(
|
|
955
|
+
return concatUint8Arrays(this.revisions.map((x) => x.toBytes()));
|
|
956
956
|
}
|
|
957
957
|
/**
|
|
958
958
|
* Creates a deep copy of the document.
|
package/dist/signing/signer.js
CHANGED
|
@@ -92,7 +92,10 @@ export class PdfSigner {
|
|
|
92
92
|
];
|
|
93
93
|
signature.setByteRange(byteRange);
|
|
94
94
|
const allBytes = this.document.toBytes();
|
|
95
|
-
const toSign = concatUint8Arrays(
|
|
95
|
+
const toSign = concatUint8Arrays([
|
|
96
|
+
allBytes.slice(byteRange[0], byteRange[1]),
|
|
97
|
+
allBytes.slice(byteRange[2], byteRange[3] + byteRange[2]),
|
|
98
|
+
]);
|
|
96
99
|
const { signedBytes, revocationInfo } = await signature.sign({
|
|
97
100
|
bytes: toSign,
|
|
98
101
|
embedRevocationInfo: !Boolean(dss),
|
|
@@ -276,7 +279,10 @@ export class PdfSigner {
|
|
|
276
279
|
continue;
|
|
277
280
|
}
|
|
278
281
|
// Compute the bytes that were signed (excluding the signature contents)
|
|
279
|
-
const signedBytes = concatUint8Arrays(
|
|
282
|
+
const signedBytes = concatUint8Arrays([
|
|
283
|
+
documentBytes.slice(byteRange[0], byteRange[0] + byteRange[1]),
|
|
284
|
+
documentBytes.slice(byteRange[2], byteRange[2] + byteRange[3]),
|
|
285
|
+
]);
|
|
280
286
|
// Verify the signature
|
|
281
287
|
const result = await signature.verify({
|
|
282
288
|
bytes: signedBytes,
|
|
@@ -7,10 +7,10 @@ import { ByteArray } from '../types.js';
|
|
|
7
7
|
*
|
|
8
8
|
* @example
|
|
9
9
|
* ```typescript
|
|
10
|
-
* const result = concatUint8Arrays(
|
|
10
|
+
* const result = concatUint8Arrays([
|
|
11
11
|
* new Uint8Array([1, 2]),
|
|
12
12
|
* new Uint8Array([3, 4])
|
|
13
|
-
* ) // Returns Uint8Array([1, 2, 3, 4])
|
|
13
|
+
* ]) // Returns Uint8Array([1, 2, 3, 4])
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
|
-
export declare function concatUint8Arrays(
|
|
16
|
+
export declare function concatUint8Arrays(arrays: Uint8Array[]): ByteArray;
|
|
@@ -6,19 +6,22 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
|
-
* const result = concatUint8Arrays(
|
|
9
|
+
* const result = concatUint8Arrays([
|
|
10
10
|
* new Uint8Array([1, 2]),
|
|
11
11
|
* new Uint8Array([3, 4])
|
|
12
|
-
* ) // Returns Uint8Array([1, 2, 3, 4])
|
|
12
|
+
* ]) // Returns Uint8Array([1, 2, 3, 4])
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
|
-
export function concatUint8Arrays(
|
|
16
|
-
|
|
15
|
+
export function concatUint8Arrays(arrays) {
|
|
16
|
+
let totalLength = 0;
|
|
17
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
18
|
+
totalLength += arrays[i].length;
|
|
19
|
+
}
|
|
17
20
|
const result = new Uint8Array(totalLength);
|
|
18
21
|
let offset = 0;
|
|
19
|
-
for (
|
|
20
|
-
result.set(
|
|
21
|
-
offset +=
|
|
22
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
23
|
+
result.set(arrays[i], offset);
|
|
24
|
+
offset += arrays[i].length;
|
|
22
25
|
}
|
|
23
26
|
return result;
|
|
24
27
|
}
|