pdf-lite 1.7.4-alpha.5 → 1.7.4-alpha.7

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.
@@ -14,6 +14,7 @@ export declare class PdfAppearanceStream extends PdfIndirectObject<PdfStream> {
14
14
  height?: number;
15
15
  contentStream?: string;
16
16
  resources?: PdfDictionary;
17
+ matrix?: [number, number, number, number, number, number];
17
18
  });
18
19
  get contentStream(): string;
19
20
  set contentStream(newContent: string);
@@ -20,6 +20,9 @@ export class PdfAppearanceStream extends PdfIndirectObject {
20
20
  new PdfNumber(options.width ?? 100),
21
21
  new PdfNumber(options.height ?? 100),
22
22
  ]));
23
+ if (options.matrix) {
24
+ appearanceDict.set('Matrix', new PdfArray(options.matrix.map((v) => new PdfNumber(v))));
25
+ }
23
26
  if (options.resources) {
24
27
  appearanceDict.set('Resources', options.resources);
25
28
  }
@@ -16,5 +16,6 @@ export declare class PdfChoiceAppearanceStream extends PdfAppearanceStream {
16
16
  displayOptions?: string[];
17
17
  selectedIndex?: number;
18
18
  quadding?: number;
19
+ rotation?: number;
19
20
  });
20
21
  }
@@ -20,12 +20,29 @@ export class PdfChoiceAppearanceStream extends PdfAppearanceStream {
20
20
  [x1, x2] = [x2, x1];
21
21
  if (y2 < y1)
22
22
  [y1, y2] = [y2, y1];
23
- const width = x2 - x1;
24
- const height = y2 - y1;
23
+ const rectWidth = x2 - x1;
24
+ const rectHeight = y2 - y1;
25
+ const rotation = ctx.rotation ?? 0;
26
+ const needsSwap = rotation === 90 || rotation === 270;
27
+ const width = needsSwap ? rectHeight : rectWidth;
28
+ const height = needsSwap ? rectWidth : rectHeight;
29
+ // For rotated pages, the BBox uses the displayed (swapped)
30
+ // dimensions and a Matrix entry maps back to annotation space.
31
+ let matrix;
32
+ if (rotation === 90) {
33
+ matrix = [0, 1, -1, 0, rectWidth, 0];
34
+ }
35
+ else if (rotation === 270) {
36
+ matrix = [0, -1, 1, 0, 0, rectHeight];
37
+ }
38
+ else if (rotation === 180) {
39
+ matrix = [-1, 0, 0, -1, rectWidth, rectHeight];
40
+ }
25
41
  super({
26
42
  width,
27
43
  height,
28
44
  resources: ctx.fontResources,
45
+ matrix,
29
46
  });
30
47
  const isUnicode = ctx.isUnicode ?? false;
31
48
  const reverseEncodingMap = ctx.reverseEncodingMap;
@@ -22,5 +22,6 @@ export declare class PdfTextAppearanceStream extends PdfAppearanceStream {
22
22
  markdown?: string;
23
23
  fontVariantNames?: FontVariantNames;
24
24
  quadding?: number;
25
+ rotation?: number;
25
26
  });
26
27
  }
@@ -28,8 +28,17 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
28
28
  [x1, x2] = [x2, x1];
29
29
  if (y2 < y1)
30
30
  [y1, y2] = [y2, y1];
31
- const width = x2 - x1;
32
- const height = y2 - y1;
31
+ const rectWidth = x2 - x1;
32
+ const rectHeight = y2 - y1;
33
+ // For rotated pages, the field rect is in the unrotated coordinate
34
+ // space. A 90° or 270° page rotation swaps how the field appears on
35
+ // screen (tall/narrow rect becomes wide/short when viewed). We swap
36
+ // the layout dimensions so text is laid out for the displayed shape,
37
+ // then apply a rotation matrix to map back into the BBox.
38
+ const rotation = ctx.rotation ?? 0;
39
+ const needsSwap = rotation === 90 || rotation === 270;
40
+ const width = needsSwap ? rectHeight : rectWidth;
41
+ const height = needsSwap ? rectWidth : rectHeight;
33
42
  const value = ctx.value;
34
43
  const isUnicode = ctx.isUnicode ?? false;
35
44
  const reverseEncodingMap = ctx.reverseEncodingMap;
@@ -167,11 +176,24 @@ export class PdfTextAppearanceStream extends PdfAppearanceStream {
167
176
  }
168
177
  g.restore();
169
178
  g.endMarkedContent();
179
+ // For rotated pages, the BBox uses the displayed (swapped)
180
+ // dimensions and a Matrix entry maps back to annotation space.
181
+ let matrix;
182
+ if (rotation === 90) {
183
+ matrix = [0, 1, -1, 0, rectWidth, 0];
184
+ }
185
+ else if (rotation === 270) {
186
+ matrix = [0, -1, 1, 0, 0, rectHeight];
187
+ }
188
+ else if (rotation === 180) {
189
+ matrix = [-1, 0, 0, -1, rectWidth, rectHeight];
190
+ }
170
191
  super({
171
192
  width,
172
193
  height,
173
194
  contentStream: g.build(),
174
195
  resources: ctx.fontResources,
196
+ matrix,
175
197
  });
176
198
  }
177
199
  }
@@ -97,11 +97,15 @@ export class PdfChoiceFormField extends PdfFormField {
97
97
  da: parsed,
98
98
  flags: this.flags,
99
99
  fontResources,
100
+ resolvedFonts: font
101
+ ? new Map([[parsed.fontName, font]])
102
+ : undefined,
100
103
  isUnicode,
101
104
  reverseEncodingMap,
102
105
  displayOptions: this.options.map((opt) => opt.label),
103
106
  selectedIndex: this.selectedIndex,
104
107
  quadding: this.quadding,
108
+ rotation: this.page?.rotate ?? 0,
105
109
  });
106
110
  if (options?.makeReadOnly) {
107
111
  this.readOnly = true;
@@ -55,6 +55,7 @@ export class PdfTextFormField extends PdfFormField {
55
55
  markdown: this.markdownValue,
56
56
  fontVariantNames: variantNames,
57
57
  quadding: this.quadding,
58
+ rotation: this.page?.rotate ?? 0,
58
59
  });
59
60
  if (options?.makeReadOnly) {
60
61
  this.readOnly = true;
@@ -8,6 +8,7 @@ import { IncrementalParser } from './parser/incremental-parser.js';
8
8
  export declare class PdfDecoder extends IncrementalParser<PdfToken, PdfObject> {
9
9
  private ignoreWhitespace;
10
10
  private maxBufferSizeBytes;
11
+ private _bufferByteSize;
11
12
  /**
12
13
  * Creates a new PDF decoder.
13
14
  *
@@ -19,6 +20,7 @@ export declare class PdfDecoder extends IncrementalParser<PdfToken, PdfObject> {
19
20
  ignoreWhitespace?: boolean;
20
21
  maxBufferSizeBytes?: number;
21
22
  });
23
+ feed(...input: (PdfToken | PdfToken[])[]): void;
22
24
  private nextName;
23
25
  private nextIndirectObject;
24
26
  private nextValue;
@@ -34,6 +36,7 @@ export declare class PdfDecoder extends IncrementalParser<PdfToken, PdfObject> {
34
36
  private nextObject;
35
37
  private static isPrimitive;
36
38
  protected bufferSize(): number;
39
+ protected compact(): void;
37
40
  protected canCompact(): boolean;
38
41
  protected parse(): PdfObject;
39
42
  }
@@ -46,6 +46,7 @@ const DEFAULT_MAX_BUFFER_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
46
46
  export class PdfDecoder extends IncrementalParser {
47
47
  ignoreWhitespace = false;
48
48
  maxBufferSizeBytes = DEFAULT_MAX_BUFFER_SIZE_BYTES;
49
+ _bufferByteSize = 0;
49
50
  /**
50
51
  * Creates a new PDF decoder.
51
52
  *
@@ -59,6 +60,20 @@ export class PdfDecoder extends IncrementalParser {
59
60
  this.maxBufferSizeBytes =
60
61
  options?.maxBufferSizeBytes ?? DEFAULT_MAX_BUFFER_SIZE_BYTES;
61
62
  }
63
+ feed(...input) {
64
+ for (const item of input) {
65
+ if (Array.isArray(item)) {
66
+ for (const subItem of item) {
67
+ this._bufferByteSize += subItem.byteLength;
68
+ this.buffer.push(subItem);
69
+ }
70
+ }
71
+ else {
72
+ this._bufferByteSize += item.byteLength;
73
+ this.buffer.push(item);
74
+ }
75
+ }
76
+ }
62
77
  nextName() {
63
78
  const preTokens = this.nextExtraTokens();
64
79
  const token = this.expect(PdfNameToken);
@@ -356,10 +371,20 @@ export class PdfDecoder extends IncrementalParser {
356
371
  token instanceof PdfStringToken);
357
372
  }
358
373
  bufferSize() {
359
- return this.buffer.reduce((acc, obj) => acc + obj.byteLength, 0);
374
+ return this._bufferByteSize;
375
+ }
376
+ compact() {
377
+ if (this.bufferIndex > 0) {
378
+ for (let i = 0; i < this.bufferIndex; i++) {
379
+ this._bufferByteSize -= this.buffer[i].byteLength;
380
+ }
381
+ this.buffer = this.buffer.slice(this.bufferIndex);
382
+ this.bufferIndex = 0;
383
+ }
360
384
  }
361
385
  canCompact() {
362
- return (this.bufferIndex > 50 && this.bufferSize() > this.maxBufferSizeBytes);
386
+ return (this.bufferIndex > 50 &&
387
+ this._bufferByteSize > this.maxBufferSizeBytes);
363
388
  }
364
389
  parse() {
365
390
  return this.nextObject(true);
@@ -61,7 +61,7 @@ export class PdfTokenSerializer extends Parser {
61
61
  if (token instanceof PdfByteOffsetToken) {
62
62
  token.update(currentOffset);
63
63
  }
64
- currentOffset += token.toBytes().length;
64
+ currentOffset += token.byteLength;
65
65
  }
66
66
  }
67
67
  /**
@@ -3,6 +3,7 @@ import { Ref } from '../ref.js';
3
3
  import { PdfToken } from './token.js';
4
4
  export class PdfNumberToken extends PdfToken {
5
5
  #value;
6
+ #cachedBytes;
6
7
  padTo;
7
8
  decimalPlaces;
8
9
  isByteToken = false;
@@ -27,19 +28,28 @@ export class PdfNumberToken extends PdfToken {
27
28
  this.isByteToken = options.isByteToken ?? false;
28
29
  }
29
30
  toBytes() {
30
- return PdfNumberToken.toBytes(this.#value, this.padTo, this.decimalPlaces);
31
+ if (this.#cachedBytes && !this.isByteToken) {
32
+ return this.#cachedBytes;
33
+ }
34
+ const bytes = PdfNumberToken.toBytes(this.#value, this.padTo, this.decimalPlaces);
35
+ if (!this.isByteToken) {
36
+ this.#cachedBytes = bytes;
37
+ }
38
+ return bytes;
31
39
  }
32
40
  get ref() {
33
41
  return this.#value;
34
42
  }
35
43
  set ref(newRef) {
36
44
  this.#value = newRef;
45
+ this.#cachedBytes = undefined;
37
46
  }
38
47
  get value() {
39
48
  return this.#value.resolve();
40
49
  }
41
50
  set value(newValue) {
42
51
  this.#value.update(newValue);
52
+ this.#cachedBytes = undefined;
43
53
  }
44
54
  static getValue(bytes) {
45
55
  if (bytes instanceof Ref) {
@@ -964,12 +964,17 @@ export class PdfDocument extends PdfObject {
964
964
  linkRevisions() {
965
965
  const xrefLookups = this.revisions.map((rev) => rev.xref);
966
966
  const indirectObjects = this.objects.filter((x) => x instanceof PdfIndirectObject);
967
+ // Build offset map once for O(1) lookups across all xrefs
968
+ const offsetMap = new Map();
969
+ for (const obj of indirectObjects) {
970
+ offsetMap.set(obj.offset.resolve(), obj);
971
+ }
967
972
  for (const revision of this.revisions) {
968
973
  revision.xref.linkPrev(xrefLookups);
969
974
  // Walk the entire prev chain to link all xref entries
970
975
  let xref = revision.xref;
971
976
  while (xref) {
972
- xref.linkIndirectObjects(indirectObjects);
977
+ xref.linkIndirectObjects(indirectObjects, offsetMap);
973
978
  xref = xref.prev;
974
979
  }
975
980
  }
@@ -1112,7 +1117,9 @@ export class PdfDocument extends PdfObject {
1112
1117
  * @returns A cloned PdfDocument instance
1113
1118
  */
1114
1119
  cloneImpl() {
1115
- this.update();
1120
+ if (this.isModified()) {
1121
+ this.update();
1122
+ }
1116
1123
  const clonedRevisions = this.revisions.map((rev) => rev.clone());
1117
1124
  const cloned = new PdfDocument({
1118
1125
  revisions: clonedRevisions,
@@ -96,7 +96,7 @@ export declare class PdfXrefLookup {
96
96
  *
97
97
  * @param objects - Array of indirect objects to link
98
98
  */
99
- linkIndirectObjects(objects: PdfIndirectObject[]): void;
99
+ linkIndirectObjects(objects: PdfIndirectObject[], offsetMap?: Map<number, PdfIndirectObject>): void;
100
100
  /**
101
101
  * Links this xref to a previous xref lookup based on the Prev trailer entry.
102
102
  *
@@ -240,7 +240,10 @@ export class PdfXrefLookup {
240
240
  *
241
241
  * @param objects - Array of indirect objects to link
242
242
  */
243
- linkIndirectObjects(objects) {
243
+ linkIndirectObjects(objects, offsetMap) {
244
+ // Use provided map or build one for O(1) lookup
245
+ const byOffset = offsetMap ??
246
+ new Map(objects.map((obj) => [obj.offset.resolve(), obj]));
244
247
  for (const entry of this.entriesValues) {
245
248
  if (entry instanceof PdfXRefStreamCompressedEntry) {
246
249
  continue;
@@ -248,7 +251,7 @@ export class PdfXrefLookup {
248
251
  if (!entry.inUse) {
249
252
  continue;
250
253
  }
251
- const [matchedObject] = objects.filter((obj) => obj.offset.equals(entry.byteOffset.value));
254
+ const matchedObject = byOffset.get(entry.byteOffset.value);
252
255
  if (!matchedObject ||
253
256
  matchedObject.objectNumber !== entry.objectNumber.value) {
254
257
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-lite",
3
- "version": "1.7.4-alpha.5",
3
+ "version": "1.7.4-alpha.7",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "exports": {