@muhammara/native-core 7.0.0-beta.3 → 7.0.0-rc.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.
Files changed (43) hide show
  1. package/README.md +57 -7
  2. package/THIRD_PARTY_NOTICES.md +39 -0
  3. package/index.js +120 -1
  4. package/lib/PDFRStreamForBuffer.js +52 -7
  5. package/lib/PDFRStreamForFile.js +56 -7
  6. package/lib/PDFStreamForResponse.js +22 -5
  7. package/lib/PDFWStreamForBuffer.js +49 -10
  8. package/lib/PDFWStreamForFile.js +26 -5
  9. package/lib/Recipe.js +86 -30
  10. package/lib/content-stream.js +503 -0
  11. package/lib/css-colors.js +149 -0
  12. package/lib/font-text.js +1461 -0
  13. package/lib/glyph-list.js +1127 -0
  14. package/lib/recipe/annotation.js +482 -105
  15. package/lib/recipe/appendPage.js +8 -1
  16. package/lib/recipe/colors.js +132 -35
  17. package/lib/recipe/coordinate.js +82 -16
  18. package/lib/recipe/encrypt.js +32 -13
  19. package/lib/recipe/font.js +70 -25
  20. package/lib/recipe/graphics-state.js +3 -2
  21. package/lib/recipe/htmlToTextObjects.js +154 -21
  22. package/lib/recipe/image.js +34 -9
  23. package/lib/recipe/info.js +93 -22
  24. package/lib/recipe/insertPage.js +12 -3
  25. package/lib/recipe/overlay.js +4 -2
  26. package/lib/recipe/page-references.js +436 -0
  27. package/lib/recipe/page.js +379 -275
  28. package/lib/recipe/parameters.js +11 -2
  29. package/lib/recipe/replaceText.js +464 -30
  30. package/lib/recipe/shapes.js +418 -45
  31. package/lib/recipe/split.js +6 -2
  32. package/lib/recipe/table.js +260 -173
  33. package/lib/recipe/text.helper.js +176 -10
  34. package/lib/recipe/text.js +420 -115
  35. package/lib/recipe/utils.js +66 -3
  36. package/lib/recipe/vector-line.js +101 -39
  37. package/lib/recipe/vector-polygon.js +18 -6
  38. package/lib/recipe/vector.helper.js +150 -13
  39. package/lib/recipe/vector.js +150 -53
  40. package/lib/recipe/xObjectForm.js +73 -10
  41. package/lib/recipe-constants.js +373 -0
  42. package/muhammara.d.ts +3487 -152
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -1,9 +1,59 @@
1
- # MuhammaraJS Native Core
1
+ # @muhammara/native-core
2
2
 
3
- Internal shared JavaScript runtime for the MuhammaraJS native packages. Install
4
- `@muhammara/native` for prebuilt binaries or `@muhammara/native-with-source` for
5
- local source builds.
3
+ [![npm version](https://img.shields.io/npm/v/%40muhammara%2Fnative-core.svg)](https://www.npmjs.com/package/@muhammara/native)
4
+ [![License](https://img.shields.io/npm/l/%40muhammara%2Fnative-core.svg)](https://github.com/julianhille/MuhammaraJS/blob/develop/LICENSE)
5
+ [![Documentation](https://img.shields.io/badge/docs-readthedocs-blue.svg)](https://muhammarajs.readthedocs.io/)
6
6
 
7
- See the [Recipe API invariants](https://muhammarajs.readthedocs.io/latest/recipe/invariants/)
8
- for behavior that is not obvious from the method signatures alone, notably the
9
- direct-write behavior of file-backed `endPDF()`.
7
+ **This is an internal package. Do not install it directly.**
8
+
9
+ `@muhammara/native-core` holds the JavaScript half of the MuhammaraJS native
10
+ packages: the low-level API wrapper, the Recipe API, and the TypeScript
11
+ declarations. It contains no native binary and does nothing on its own. Both
12
+ user-facing native packages depend on it and install it for you:
13
+
14
+ ```sh
15
+ npm install @muhammara/native # prebuilt binary
16
+ npm install @muhammara/native-with-source # prebuilt or local source build
17
+ ```
18
+
19
+ Its version always matches the native package that depends on it, so do not
20
+ pin or upgrade it separately.
21
+
22
+ ## How The Packages Fit Together
23
+
24
+ MuhammaraJS is one PDF engine shipped in two ways. The same C++ library,
25
+ [PDF-Writer](https://github.com/galkahana/PDF-Writer), sits at the bottom of
26
+ every package; what differs is how it reaches your JavaScript.
27
+
28
+ ```text
29
+ PDF-Writer (C++)
30
+ ┌────────────┴────────────┐
31
+ Node.js native addon WebAssembly build
32
+ │ │
33
+ @muhammara/native-core @muhammara/wasm
34
+ (shared JS, internal) (browsers, Workers, Node.js)
35
+ ┌───────┴────────┐
36
+ @muhammara/native @muhammara/native-with-source
37
+ (prebuilt only) (prebuilt or source build)
38
+ ```
39
+
40
+ | Package | Install it when |
41
+ | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
42
+ | [`@muhammara/native`](https://www.npmjs.com/package/@muhammara/native) | You run Node.js on a platform with a prebuilt binary. This is the default choice. |
43
+ | [`@muhammara/native-with-source`](https://www.npmjs.com/package/@muhammara/native-with-source) | No prebuilt matches your platform, or Electron must rebuild the addon. |
44
+ | [`@muhammara/wasm`](https://www.npmjs.com/package/@muhammara/wasm) | You work in a browser or Web Worker, or cannot load native addons (for example some serverless runtimes). |
45
+ | [`@muhammara/native-core`](https://www.npmjs.com/package/@muhammara/native-core) | Never directly. Both native packages depend on it and install it for you. |
46
+
47
+ Both native packages expose the identical API, so switching between them is a
48
+ dependency change, not a code change. The Wasm package produces the same PDFs,
49
+ but it is byte-first (`Uint8Array`, `ArrayBuffer`, `Blob`, `File`) instead of
50
+ path-, stream-, and `Buffer`-oriented, so its JavaScript API is separate.
51
+
52
+ ## Links
53
+
54
+ - [Documentation](https://muhammarajs.readthedocs.io/)
55
+ - [Recipe API invariants](https://muhammarajs.readthedocs.io/en/latest/recipe/invariants.html) —
56
+ behavior not obvious from the method signatures, such as the direct-write
57
+ behavior of file-backed `endPDF()`
58
+ - [Changelog](https://github.com/julianhille/MuhammaraJS/blob/develop/CHANGELOG.md)
59
+ - [Issues](https://github.com/julianhille/MuhammaraJS/issues)
@@ -197,3 +197,42 @@ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
197
197
  CONDITIONS OF ANY KIND, either express or implied. See the License for the
198
198
  specific language governing permissions and limitations under the License.
199
199
  ```
200
+
201
+ ## Adobe Glyph List
202
+
203
+ `lib/glyph-list.js` holds a table derived from the Adobe Glyph List 2.0
204
+ (`glyphlist.txt`, September 20, 2002), which maps glyph names to Unicode. The
205
+ table re-encodes the list and normalizes its values to NFC; it is not a copy or
206
+ version of the Adobe document.
207
+
208
+ ```
209
+ Copyright (c) 1997,1998,2002,2007 Adobe Systems Incorporated
210
+
211
+ Permission is hereby granted, free of charge, to any person obtaining a
212
+ copy of this documentation file to use, copy, publish, distribute,
213
+ sublicense, and/or sell copies of the documentation, and to permit
214
+ others to do the same, provided that:
215
+ - No modification, editing or other alteration of this document is
216
+ allowed; and
217
+ - The above copyright notice and this permission notice shall be
218
+ included in all copies of the documentation.
219
+
220
+ Permission is hereby granted, free of charge, to any person obtaining a
221
+ copy of this documentation file, to create their own derivative works
222
+ from the content of this document to use, copy, publish, distribute,
223
+ sublicense, and/or sell the derivative works, and to permit others to do
224
+ the same, provided that the derived work is not represented as being a
225
+ copy or version of this document.
226
+
227
+ Adobe shall not be liable to any party for any loss of revenue or profit
228
+ or for indirect, incidental, special, consequential, or other similar
229
+ damages, whether based on tort (including without limitation negligence
230
+ or strict liability), contract or other legal or equitable grounds even
231
+ if Adobe has been advised or had reason to know of the possibility of
232
+ such damages. The Adobe materials are provided on an "AS IS" basis.
233
+ Adobe specifically disclaims all express, statutory, or implied
234
+ warranties relating to the Adobe materials, including but not limited to
235
+ those concerning merchantability or fitness for a particular purpose or
236
+ non-infringement of any third party rights regarding the Adobe
237
+ materials.
238
+ ```
package/index.js CHANGED
@@ -1,6 +1,63 @@
1
1
  "use strict";
2
2
 
3
3
  var path = require("path");
4
+ var fontText = require("./lib/font-text");
5
+
6
+ // Addons whose reader already decodes text, so a second createMuhammara call
7
+ // on the same addon does not wrap it twice.
8
+ var decodingAddons = new WeakSet();
9
+
10
+ /**
11
+ * Make `extractPageText()` add decoded Unicode `text` to each element. The
12
+ * addon exports its reader class for this step only; the export is removed
13
+ * again so `PDFReader` stays out of the public API.
14
+ *
15
+ * @param {object} muhammara The native addon.
16
+ * @returns {void}
17
+ * @throws {Error} If the addon does not export its reader class, so decoded
18
+ * text would silently be missing.
19
+ */
20
+ function decodeExtractedText(muhammara) {
21
+ if (decodingAddons.has(muhammara)) return;
22
+ var PDFReader = muhammara.PDFReader;
23
+ if (typeof PDFReader !== "function") {
24
+ throw new Error(
25
+ "The muhammara native addon does not export PDFReader; rebuild it from this version's sources",
26
+ );
27
+ }
28
+ delete muhammara.PDFReader;
29
+ decodingAddons.add(muhammara);
30
+
31
+ var extractPageText = PDFReader.prototype.extractPageText;
32
+ /**
33
+ * Extract a page's text operations with their decoded Unicode `text`.
34
+ *
35
+ * @param {number} pageIndex Zero-based page index.
36
+ * @param {object} [limits] Extraction limits.
37
+ * @param {{decodeText?: boolean}} [options] Extraction options;
38
+ * `decodeText: false` skips decoding and leaves out `text`.
39
+ * @returns {Array<object>} Text elements.
40
+ * @throws {TypeError} If `options` is not an object or `decodeText` is not a
41
+ * boolean.
42
+ */
43
+ PDFReader.prototype.extractPageText = function (pageIndex, limits, options) {
44
+ var decode = fontText.decodeTextOption(options);
45
+ // Forward only the arguments the addon accepts, exactly as given.
46
+ var elements = extractPageText.apply(
47
+ this,
48
+ Array.prototype.slice.call(arguments, 0, 2),
49
+ );
50
+ return decode
51
+ ? fontText.decodeTextElements(
52
+ this,
53
+ muhammara,
54
+ pageIndex,
55
+ elements,
56
+ limits,
57
+ )
58
+ : elements;
59
+ };
60
+ }
4
61
 
5
62
  /**
6
63
  * Attach the shared JavaScript API to an implementation package's loaded addon.
@@ -12,10 +69,22 @@ exports.createMuhammara = function createMuhammara(muhammara) {
12
69
  var bindingModule = require.resolve("./lib/muhammara");
13
70
  var recipeDirectory = path.join(__dirname, "lib", "recipe") + path.sep;
14
71
 
72
+ /**
73
+ * Returns the writer's event emitter, created on first use.
74
+ * @returns {import("events").EventEmitter} The emitter for writer events.
75
+ */
15
76
  muhammara.PDFWriter.prototype.getEvents = function () {
16
77
  if (!this.events) this.events = new (require("events").EventEmitter)();
17
78
  return this.events;
18
79
  };
80
+ /**
81
+ * Emits an event on the writer's emitter after setting `eventParams.writer`
82
+ * to this writer.
83
+ * @param {string|symbol} eventName - The event name.
84
+ * @param {Object} eventParams - The event parameters; gains a `writer` key.
85
+ * @returns {void}
86
+ * @throws {TypeError} If eventParams is not an object.
87
+ */
19
88
  muhammara.PDFWriter.prototype.triggerDocumentExtensionEvent = function (
20
89
  eventName,
21
90
  eventParams,
@@ -23,13 +92,27 @@ exports.createMuhammara = function createMuhammara(muhammara) {
23
92
  eventParams.writer = this;
24
93
  this.getEvents().emit(eventName, eventParams);
25
94
  };
95
+ /**
96
+ * Replaces direct references to an object in a page dictionary. Available
97
+ * only when modifying an existing PDF.
98
+ * @param {number} pageIndex - The zero-based page index; ignored for the
99
+ * global scope.
100
+ * @param {number} sourceObjectId - The object ID to stop referencing.
101
+ * @param {number} replacementObjectId - The object ID to reference instead.
102
+ * @param {Object} [options] - The options.
103
+ * @param {string} [options.scope] - ObjectReplacementScope.GLOBAL to
104
+ * replace on every page.
105
+ * @returns {Object} This writer.
106
+ * @throws {Error} If the writer does not modify a PDF or the page does not
107
+ * exist.
108
+ */
26
109
  muhammara.PDFWriter.prototype.replaceObject = function (
27
110
  pageIndex,
28
111
  sourceObjectId,
29
112
  replacementObjectId,
30
113
  options,
31
114
  ) {
32
- if (options && options.scope === "global") {
115
+ if (options && options.scope === muhammara.ObjectReplacementScope.GLOBAL) {
33
116
  var copyingContext = this.createPDFCopyingContextForModifiedFile();
34
117
  var pageCount = copyingContext.getSourceDocumentParser().getPagesCount();
35
118
 
@@ -68,11 +151,47 @@ exports.createMuhammara = function createMuhammara(muhammara) {
68
151
  copyingContext.end();
69
152
  return this;
70
153
  };
154
+ decodeExtractedText(muhammara);
71
155
  muhammara.PDFStreamForResponse = require("./lib/PDFStreamForResponse");
72
156
  muhammara.PDFWStreamForFile = require("./lib/PDFWStreamForFile");
73
157
  muhammara.PDFRStreamForFile = require("./lib/PDFRStreamForFile");
74
158
  muhammara.PDFRStreamForBuffer = require("./lib/PDFRStreamForBuffer");
75
159
  muhammara.PDFWStreamForBuffer = require("./lib/PDFWStreamForBuffer");
160
+ muhammara.DrawingPathType = Object.freeze({
161
+ STROKE: "stroke",
162
+ FILL: "fill",
163
+ CLIP: "clip",
164
+ });
165
+ muhammara.ImageFit = Object.freeze({
166
+ ALWAYS: "always",
167
+ OVERFLOW: "overflow",
168
+ });
169
+ muhammara.DeviceColorSpace = Object.freeze({
170
+ RGB: "rgb",
171
+ GRAY: "gray",
172
+ CMYK: "cmyk",
173
+ });
174
+ muhammara.PageBox = Object.freeze({
175
+ MEDIA: "media",
176
+ CROP: "crop",
177
+ BLEED: "bleed",
178
+ TRIM: "trim",
179
+ ART: "art",
180
+ });
181
+ muhammara.PDFImageType = Object.freeze({
182
+ PDF: "PDF",
183
+ JPG: "JPG",
184
+ TIFF: "TIFF",
185
+ PNG: "PNG",
186
+ });
187
+ muhammara.EEncoding = Object.freeze({
188
+ TEXT: "text",
189
+ CODE: "code",
190
+ HEX: "hex",
191
+ });
192
+ muhammara.ObjectReplacementScope = Object.freeze({
193
+ GLOBAL: "global",
194
+ });
76
195
  muhammara.LineCapStyle = Object.freeze({
77
196
  LINECAP_BUTT: 0,
78
197
  LINECAP_ROUND: 1,
@@ -4,6 +4,11 @@
4
4
  @author Luciano Júnior
5
5
  */
6
6
 
7
+ /**
8
+ * Creates a read stream over bytes in memory.
9
+ * @constructor
10
+ * @param {Buffer|Uint8Array} buffer - The bytes to read; not copied.
11
+ */
7
12
  function PDFRStreamForBuffer(buffer) {
8
13
  this.buffer = buffer;
9
14
  this.rposition = 0;
@@ -11,35 +16,75 @@ function PDFRStreamForBuffer(buffer) {
11
16
  this.mStartPosition = 0;
12
17
  }
13
18
 
19
+ /**
20
+ * Reads the next bytes and advances the position by the amount read.
21
+ * @param {number} inAmount - The maximum number of bytes to read.
22
+ * @returns {Buffer} The bytes read; shorter than requested at the end.
23
+ */
14
24
  PDFRStreamForBuffer.prototype.read = function (inAmount) {
15
- var amountToRead = inAmount;
16
- var arr = Array.from(
17
- this.buffer.subarray(this.rposition, this.rposition + amountToRead),
25
+ // Copy, so callers cannot change the source through the returned chunk.
26
+ var bytes = Buffer.from(
27
+ this.buffer.subarray(this.rposition, this.rposition + inAmount),
18
28
  );
19
- this.rposition += amountToRead;
20
- return arr;
29
+ this.rposition += inAmount;
30
+ return bytes;
21
31
  };
22
32
 
33
+ /**
34
+ * Tells whether bytes remain after the current position.
35
+ * @returns {boolean} True while the end has not been reached.
36
+ */
23
37
  PDFRStreamForBuffer.prototype.notEnded = function () {
24
38
  return this.rposition < this.fileSize;
25
39
  };
26
40
 
41
+ /**
42
+ * Moves to a position relative to the start position, clamped to the data.
43
+ * @param {number} inPosition - The byte offset from the start position.
44
+ * @returns {void}
45
+ */
27
46
  PDFRStreamForBuffer.prototype.setPosition = function (inPosition) {
28
- this.rposition = this.mStartPosition + inPosition;
47
+ this.rposition = Math.min(
48
+ Math.max(this.mStartPosition + inPosition, 0),
49
+ this.fileSize,
50
+ );
29
51
  };
30
52
 
53
+ /**
54
+ * Moves to a position counted back from the end, clamped to the data.
55
+ * @param {number} inPosition - The number of bytes before the end.
56
+ * @returns {void}
57
+ */
31
58
  PDFRStreamForBuffer.prototype.setPositionFromEnd = function (inPosition) {
32
- this.rposition = this.fileSize - inPosition;
59
+ this.rposition = Math.min(
60
+ Math.max(this.fileSize - inPosition, 0),
61
+ this.fileSize,
62
+ );
33
63
  };
34
64
 
65
+ /**
66
+ * Advances the position without reading.
67
+ * @param {number} inAmount - The number of bytes to skip.
68
+ * @returns {void}
69
+ */
35
70
  PDFRStreamForBuffer.prototype.skip = function (inAmount) {
36
71
  this.rposition += inAmount;
37
72
  };
38
73
 
74
+ /**
75
+ * Returns the position relative to the start position.
76
+ * @returns {number} The current byte offset.
77
+ */
39
78
  PDFRStreamForBuffer.prototype.getCurrentPosition = function () {
40
79
  return this.rposition - this.mStartPosition;
41
80
  };
42
81
 
82
+ /**
83
+ * Sets the offset that later positions are counted from, for PDF data that
84
+ * does not begin at byte zero.
85
+ * @param {number} inPosition - The absolute byte offset of the start.
86
+ * @returns {void}
87
+ */
43
88
  PDFRStreamForBuffer.prototype.moveStartPosition = function (inPosition) {
44
89
  this.mStartPosition = inPosition;
45
90
  };
@@ -3,6 +3,12 @@ var fs = require("fs");
3
3
  PDFRStreamForFile is an implementation of a read stream using the supplied file path.
4
4
  */
5
5
 
6
+ /**
7
+ * Opens a file for reading.
8
+ * @constructor
9
+ * @param {string} inPath - The file path.
10
+ * @throws {Error} If the file cannot be opened or read.
11
+ */
6
12
  function PDFRStreamForFile(inPath) {
7
13
  this.rs = fs.openSync(inPath, "r");
8
14
  this.path = inPath;
@@ -11,42 +17,85 @@ function PDFRStreamForFile(inPath) {
11
17
  this.mStartPosition = 0;
12
18
  }
13
19
 
20
+ /**
21
+ * Reads the next bytes and advances the position by the amount read.
22
+ * @param {number} inAmount - The maximum number of bytes to read.
23
+ * @returns {Buffer} The bytes read; shorter than requested at the end.
24
+ */
14
25
  PDFRStreamForFile.prototype.read = function (inAmount) {
15
- var buffer = Buffer.alloc(inAmount * 2);
26
+ var buffer = Buffer.alloc(inAmount);
16
27
  var bytesRead = fs.readSync(this.rs, buffer, 0, inAmount, this.rposition);
17
- var arr = [];
18
-
19
- for (var i = 0; i < bytesRead; ++i) arr.push(buffer[i]);
20
28
  this.rposition += bytesRead;
21
- return arr;
29
+ return buffer.subarray(0, bytesRead);
22
30
  };
23
31
 
32
+ /**
33
+ * Tells whether bytes remain after the current position.
34
+ * @returns {boolean} True while the end has not been reached.
35
+ */
24
36
  PDFRStreamForFile.prototype.notEnded = function () {
25
37
  return this.rposition < this.fileSize;
26
38
  };
27
39
 
40
+ /**
41
+ * Moves to a position relative to the start position, clamped to the data.
42
+ * @param {number} inPosition - The byte offset from the start position.
43
+ * @returns {void}
44
+ */
28
45
  PDFRStreamForFile.prototype.setPosition = function (inPosition) {
29
- this.rposition = this.mStartPosition + inPosition;
46
+ this.rposition = Math.min(
47
+ Math.max(this.mStartPosition + inPosition, 0),
48
+ this.fileSize,
49
+ );
30
50
  };
31
51
 
52
+ /**
53
+ * Moves to a position counted back from the end, clamped to the data.
54
+ * @param {number} inPosition - The number of bytes before the end.
55
+ * @returns {void}
56
+ */
32
57
  PDFRStreamForFile.prototype.setPositionFromEnd = function (inPosition) {
33
- this.rposition = this.fileSize - inPosition;
58
+ this.rposition = Math.min(
59
+ Math.max(this.fileSize - inPosition, 0),
60
+ this.fileSize,
61
+ );
34
62
  };
35
63
 
64
+ /**
65
+ * Advances the position without reading.
66
+ * @param {number} inAmount - The number of bytes to skip.
67
+ * @returns {void}
68
+ */
36
69
  PDFRStreamForFile.prototype.skip = function (inAmount) {
37
70
  this.rposition += inAmount;
38
71
  };
39
72
 
73
+ /**
74
+ * Returns the position relative to the start position.
75
+ * @returns {number} The current byte offset.
76
+ */
40
77
  PDFRStreamForFile.prototype.getCurrentPosition = function () {
41
78
  return this.rposition - this.mStartPosition;
42
79
  };
43
80
 
81
+ /**
82
+ * Sets the offset that later positions are counted from, for PDF data that
83
+ * does not begin at byte zero.
84
+ * @param {number} inPosition - The absolute byte offset of the start.
85
+ * @returns {void}
86
+ */
44
87
  PDFRStreamForFile.prototype.moveStartPosition = function (inPosition) {
45
88
  this.mStartPosition = inPosition;
46
89
  };
47
90
 
48
91
  function noop() {}
49
92
 
93
+ /**
94
+ * Closes the file.
95
+ * @param {Function} [inCallback] - Called once the file is closed, with the
96
+ * close error if any.
97
+ * @returns {void}
98
+ */
50
99
  PDFRStreamForFile.prototype.close = function (inCallback) {
51
100
  fs.close(this.rs, inCallback ? inCallback : noop);
52
101
  };
@@ -2,19 +2,36 @@
2
2
  PDFStreamForResponse is an implementation of a write stream that writes directly to an HTTP response.
3
3
  Using this stream frees the user from having to create a PDF file on disk when generating on-demand PDFs
4
4
  */
5
+ /**
6
+ * Creates a write stream that writes to an HTTP response.
7
+ * @constructor
8
+ * @param {{write: function(Buffer): *}} inResponse - The response, or any
9
+ * object with a write method.
10
+ */
5
11
  function PDFStreamForResponse(inResponse) {
6
12
  this.response = inResponse;
7
13
  this.position = 0;
8
14
  }
9
15
 
10
- PDFStreamForResponse.prototype.write = function (inBytesArray) {
11
- if (inBytesArray.length > 0) {
12
- this.response.write(Buffer.from(inBytesArray));
13
- this.position += inBytesArray.length;
14
- return inBytesArray.length;
16
+ /**
17
+ * Writes bytes to the response.
18
+ * @param {Buffer|number[]} inBytes - The bytes to write.
19
+ * @returns {number} The number of bytes written.
20
+ */
21
+ PDFStreamForResponse.prototype.write = function (inBytes) {
22
+ // Arrays of byte values are still accepted from direct callers.
23
+ if (!Buffer.isBuffer(inBytes)) inBytes = Buffer.from(inBytes);
24
+ if (inBytes.length > 0) {
25
+ this.response.write(inBytes);
26
+ this.position += inBytes.length;
27
+ return inBytes.length;
15
28
  } else return 0;
16
29
  };
17
30
 
31
+ /**
32
+ * Returns the number of bytes written so far.
33
+ * @returns {number} The current byte offset.
34
+ */
18
35
  PDFStreamForResponse.prototype.getCurrentPosition = function () {
19
36
  return this.position;
20
37
  };
@@ -1,23 +1,62 @@
1
+ /*
2
+ PDFWStreamForBuffer is an implementation of a write stream that collects
3
+ the written bytes in memory. Read the result from its `buffer` property.
4
+ */
5
+ /**
6
+ * Creates a write stream that collects the bytes in memory.
7
+ * @constructor
8
+ */
1
9
  function PDFWStreamForBuffer() {
2
- this.buffer = null;
10
+ this.chunks = [];
11
+ this.joined = null;
3
12
  this.position = 0;
4
13
  }
5
14
 
6
- PDFWStreamForBuffer.prototype.write = function (inBytesArray) {
7
- if (inBytesArray.length > 0) {
8
- if (!this.buffer) {
9
- this.buffer = Buffer.from(inBytesArray);
10
- } else {
11
- this.buffer = Buffer.concat([this.buffer, Buffer.from(inBytesArray)]);
12
- }
15
+ Object.defineProperty(PDFWStreamForBuffer.prototype, "buffer", {
16
+ /**
17
+ * The bytes written so far, or null before the first write. Chunks are
18
+ * joined on first access instead of on every write.
19
+ * @returns {Buffer|null} The written bytes.
20
+ */
21
+ get: function () {
22
+ if (this.chunks.length === 0) return this.joined;
23
+ this.joined = this.joined
24
+ ? Buffer.concat([this.joined].concat(this.chunks))
25
+ : Buffer.concat(this.chunks);
26
+ this.chunks = [];
27
+ return this.joined;
28
+ },
29
+ /**
30
+ * Replaces the collected bytes.
31
+ * @param {Buffer|null} value - The new contents.
32
+ */
33
+ set: function (value) {
34
+ this.chunks = [];
35
+ this.joined = value;
36
+ },
37
+ });
13
38
 
14
- this.position += inBytesArray.length;
15
- return inBytesArray.length;
39
+ /**
40
+ * Collects a copy of the bytes.
41
+ * @param {Buffer|Uint8Array|number[]} inBytes - The bytes to write.
42
+ * @returns {number} The number of bytes written.
43
+ */
44
+ PDFWStreamForBuffer.prototype.write = function (inBytes) {
45
+ if (inBytes.length > 0) {
46
+ // Copy, so a caller reusing its buffer cannot change collected output,
47
+ // and so arrays of byte values keep working.
48
+ this.chunks.push(Buffer.from(inBytes));
49
+ this.position += inBytes.length;
50
+ return inBytes.length;
16
51
  }
17
52
 
18
53
  return 0;
19
54
  };
20
55
 
56
+ /**
57
+ * Returns the number of bytes written so far.
58
+ * @returns {number} The current byte offset.
59
+ */
21
60
  PDFWStreamForBuffer.prototype.getCurrentPosition = function () {
22
61
  return this.position;
23
62
  };
@@ -4,24 +4,45 @@ var fs = require("fs");
4
4
  PDFWStreamForFile is an implementation of a write stream using the supplied file path.
5
5
  */
6
6
 
7
+ /**
8
+ * Creates a write stream to a file, replacing an existing one.
9
+ * @constructor
10
+ * @param {string} inPath - The file path.
11
+ */
7
12
  function PDFWStreamForFile(inPath) {
8
13
  this.ws = fs.createWriteStream(inPath);
9
14
  this.position = 0;
10
15
  this.path = inPath;
11
16
  }
12
17
 
13
- PDFWStreamForFile.prototype.write = function (inBytesArray) {
14
- if (inBytesArray.length > 0) {
15
- this.ws.write(Buffer.from(inBytesArray));
16
- this.position += inBytesArray.length;
17
- return inBytesArray.length;
18
+ /**
19
+ * Writes bytes to the file.
20
+ * @param {Buffer|number[]} inBytes - The bytes to write.
21
+ * @returns {number} The number of bytes written.
22
+ */
23
+ PDFWStreamForFile.prototype.write = function (inBytes) {
24
+ // Arrays of byte values are still accepted from direct callers.
25
+ if (!Buffer.isBuffer(inBytes)) inBytes = Buffer.from(inBytes);
26
+ if (inBytes.length > 0) {
27
+ this.ws.write(inBytes);
28
+ this.position += inBytes.length;
29
+ return inBytes.length;
18
30
  } else return 0;
19
31
  };
20
32
 
33
+ /**
34
+ * Returns the number of bytes written so far.
35
+ * @returns {number} The current byte offset.
36
+ */
21
37
  PDFWStreamForFile.prototype.getCurrentPosition = function () {
22
38
  return this.position;
23
39
  };
24
40
 
41
+ /**
42
+ * Flushes and closes the file.
43
+ * @param {Function} [inCallback] - Called once the file is closed.
44
+ * @returns {void}
45
+ */
25
46
  PDFWStreamForFile.prototype.close = function (inCallback) {
26
47
  if (this.ws) {
27
48
  var self = this;