@muhammara/native-core 7.0.0-beta.4 → 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 +79 -28
  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 +464 -101
  15. package/lib/recipe/appendPage.js +2 -0
  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 +121 -24
  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 +363 -262
  28. package/lib/recipe/parameters.js +8 -1
  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 +41 -8
  33. package/lib/recipe/text.helper.js +172 -8
  34. package/lib/recipe/text.js +294 -56
  35. package/lib/recipe/utils.js +17 -6
  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 +148 -6
  39. package/lib/recipe/vector.js +150 -51
  40. package/lib/recipe/xObjectForm.js +73 -10
  41. package/lib/recipe-constants.js +373 -0
  42. package/muhammara.d.ts +3469 -155
  43. package/package.json +1 -1
@@ -13,31 +13,37 @@
13
13
  // PercentColor component values range from 1 to 100.
14
14
 
15
15
  const { linkPdf } = require("./annotation");
16
+ const muhammara = require("../muhammara");
16
17
 
17
18
  /**
18
19
  * Draw a circle
19
20
  * @name circle
20
21
  * @function
21
22
  * @memberof Recipe#
22
- * @param {number} x - The coordinate x
23
- * @param {number} y - The coordinate y
23
+ * @param {number|"center"} x - The coordinate x of the center
24
+ * @param {number|"center"} y - The coordinate y of the center
24
25
  * @param {number} radius - The radius
25
26
  * @param {Object} [options] - The options
26
27
  * @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
27
28
  * @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
28
- * @param {string|number[]}[ options.fill] - HexColor, PercentColor or DecimalColor
29
+ * @param {string|number[]} [options.fill] - HexColor, PercentColor or DecimalColor
29
30
  * @param {number} [options.lineWidth] - The line width
30
31
  * @param {number} [options.opacity] - The opacity
31
32
  * @param {number[]} [options.dash] - The dash style [number, number]
33
+ * @param {string} [options.link] - Make the circle's bounding square open this URL.
32
34
  * @returns {Recipe} The recipe instance.
35
+ * @throws {TypeError} If no page is active.
33
36
  */
34
37
  exports.circle = function circle(x, y, radius, options = {}) {
38
+ // null options act like omitted options.
39
+ if (options === null) options = {};
40
+ [x, y] = this._centrify(x, y);
35
41
  const { nx, ny } = this._calibrateCoordinate(x, y);
36
42
  const diameter = radius * 2;
37
43
 
38
44
  if (options.fill) {
39
45
  const pathOptions = this._getPathOptions(options, nx, ny);
40
- pathOptions.type = "fill";
46
+ pathOptions.type = muhammara.DrawingPathType.FILL;
41
47
 
42
48
  if (pathOptions.fill !== undefined) {
43
49
  pathOptions.color = pathOptions.fill;
@@ -52,15 +58,20 @@ exports.circle = function circle(x, y, radius, options = {}) {
52
58
  diameter,
53
59
  pathOptions,
54
60
  (ctx, xObject) => {
55
- ctx
56
- .gs(xObject.getGsName(pathOptions.fillGsId))
57
- .drawCircle(radius, radius, radius, pathOptions);
61
+ ctx.gs(xObject.getGsName(pathOptions.fillGsId));
62
+ this._setSeparationColor(xObject, pathOptions.fillModel, false);
63
+ ctx.drawCircle(
64
+ radius,
65
+ radius,
66
+ radius,
67
+ this._devicePathOptions(pathOptions),
68
+ );
58
69
  },
59
70
  );
60
71
  }
61
72
  if (options.stroke || options.color || !options.fill) {
62
73
  const pathOptions = this._getPathOptions(options);
63
- pathOptions.type = "stroke";
74
+ pathOptions.type = muhammara.DrawingPathType.STROKE;
64
75
 
65
76
  if (pathOptions.stroke !== undefined) {
66
77
  pathOptions.color = pathOptions.stroke;
@@ -78,13 +89,18 @@ exports.circle = function circle(x, y, radius, options = {}) {
78
89
  pathOptions,
79
90
  (ctx, xObject) => {
80
91
  ctx.gs(xObject.getGsName(pathOptions.strokeGsId));
92
+ this._setSeparationColor(
93
+ xObject,
94
+ pathOptions.strokeModel || pathOptions.colorModel,
95
+ true,
96
+ );
81
97
  ctx
82
98
  .d(pathOptions.dash, pathOptions.dashPhase)
83
99
  .drawCircle(
84
100
  radius,
85
101
  radius,
86
- radius - pathOptions.width / 2,
87
- pathOptions,
102
+ Math.max(0, radius - pathOptions.width / 2),
103
+ this._devicePathOptions(pathOptions),
88
104
  );
89
105
 
90
106
  // ... requires adjusting the internal drawing to accomodate line thickness.
@@ -101,8 +117,8 @@ exports.circle = function circle(x, y, radius, options = {}) {
101
117
  * @name rectangle
102
118
  * @function
103
119
  * @memberof Recipe#
104
- * @param {number} x - The coordinate x
105
- * @param {number} y - The coordinate y
120
+ * @param {number|"center"} x - The coordinate x of the top-left corner
121
+ * @param {number|"center"} y - The coordinate y of the top-left corner
106
122
  * @param {number} width - The width
107
123
  * @param {number} height - The height
108
124
  * @param {Object} [options] - The options
@@ -118,9 +134,14 @@ exports.circle = function circle(x, y, radius, options = {}) {
118
134
  * When a one to four number array can be used to give specific sizees to each corner.
119
135
  * The numbering starts from the top, left corner, and goes clockwise around the text box.
120
136
  * Missing values in the array are filled in by opposite corner values.
137
+ * @param {string} [options.link] - Make the rectangle open this URL.
138
+ * @param {boolean} [options.useGivenCoords] - Take x and y as PDF coordinates of the bottom-left corner.
121
139
  * @returns {Recipe} The recipe instance.
140
+ * @throws {TypeError} If no page is active.
122
141
  */
123
142
  exports.rectangle = function rectangle(x, y, width, height, options = {}) {
143
+ // null options act like omitted options.
144
+ if (options === null) options = {};
124
145
  const { nx, ny } = options.useGivenCoords
125
146
  ? { nx: x, ny: y }
126
147
  : this._calibrateCoordinate(x, y, 0, -height);
@@ -130,7 +151,7 @@ exports.rectangle = function rectangle(x, y, width, height, options = {}) {
130
151
  pathOptions.useGivenCoords = options.useGivenCoords;
131
152
 
132
153
  if (options.fill) {
133
- pathOptions.type = "fill";
154
+ pathOptions.type = muhammara.DrawingPathType.FILL;
134
155
 
135
156
  if (pathOptions.fill !== undefined) {
136
157
  pathOptions.color = pathOptions.fill;
@@ -153,20 +174,28 @@ exports.rectangle = function rectangle(x, y, width, height, options = {}) {
153
174
  drawRoundedRectangle(ctx, 0, 0, width, height, options.borderRadius);
154
175
  ctx.f();
155
176
  } else {
156
- ctx.drawRectangle(0, 0, width, height, pathOptions);
177
+ ctx.drawRectangle(
178
+ 0,
179
+ 0,
180
+ width,
181
+ height,
182
+ this._devicePathOptions(pathOptions),
183
+ );
157
184
  }
158
185
  },
159
186
  );
160
187
  }
161
188
 
162
189
  if (options.stroke || options.color || !options.fill) {
163
- pathOptions.type = "stroke";
190
+ pathOptions.type = muhammara.DrawingPathType.STROKE;
164
191
 
165
- if (pathOptions.stroke !== undefined) {
166
- pathOptions.color = pathOptions.stroke;
167
- pathOptions.colorspace = pathOptions.strokeModel.colorspace;
168
- colorModel = pathOptions.strokeModel;
169
- }
192
+ // The fill above may have replaced the color; stroke with `color`.
193
+ colorModel =
194
+ pathOptions.stroke !== undefined
195
+ ? pathOptions.strokeModel
196
+ : pathOptions.colorModel;
197
+ pathOptions.color = colorModel.color;
198
+ pathOptions.colorspace = colorModel.colorspace;
170
199
 
171
200
  // To honor the given width and height of the rectangle ...
172
201
 
@@ -186,24 +215,26 @@ exports.rectangle = function rectangle(x, y, width, height, options = {}) {
186
215
  if (options.borderRadius) {
187
216
  ctx.w(pathOptions.width).d(pathOptions.dash, pathOptions.dashPhase);
188
217
 
218
+ const inset = Math.min(margin / 2, width / 2, height / 2);
189
219
  drawRoundedRectangle(
190
220
  ctx,
191
- margin / 2,
192
- margin / 2,
193
- width - margin,
194
- height - margin,
221
+ inset,
222
+ inset,
223
+ width - inset * 2,
224
+ height - inset * 2,
195
225
  options.borderRadius,
226
+ inset,
196
227
  );
197
228
  ctx.S();
198
229
  } else {
199
230
  ctx
200
231
  .d(pathOptions.dash, pathOptions.dashPhase)
201
232
  .drawRectangle(
202
- margin / 2,
203
- margin / 2,
204
- width - margin,
205
- height - margin,
206
- pathOptions,
233
+ Math.min(margin / 2, width / 2),
234
+ Math.min(margin / 2, height / 2),
235
+ Math.max(0, width - margin),
236
+ Math.max(0, height - margin),
237
+ this._devicePathOptions(pathOptions),
207
238
  );
208
239
  }
209
240
  },
@@ -218,7 +249,28 @@ exports.rectangle = function rectangle(x, y, width, height, options = {}) {
218
249
  return this;
219
250
  };
220
251
 
221
- function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
252
+ /**
253
+ * Append a rectangle path with rounded corners.
254
+ * @private
255
+ * @param {Object} ctx - The content context.
256
+ * @param {number} left - The left edge.
257
+ * @param {number} bottom - The bottom edge.
258
+ * @param {number} width - The width.
259
+ * @param {number} height - The height.
260
+ * @param {number|number[]} radii - One radius, or up to four clockwise from
261
+ * the top-left corner; missing ones come from the opposite corner.
262
+ * @param {number} [inset=0] - How far to move the path inside the rectangle.
263
+ * @returns {void}
264
+ */
265
+ function drawRoundedRectangle(
266
+ ctx,
267
+ left,
268
+ bottom,
269
+ width,
270
+ height,
271
+ radii,
272
+ inset = 0,
273
+ ) {
222
274
  let radius = [];
223
275
 
224
276
  // populate radius array accordingly.
@@ -244,6 +296,8 @@ function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
244
296
  break;
245
297
  }
246
298
  }
299
+ // Keep inset corners concentric with the nominal corners.
300
+ radius = radius.map((value) => Math.max(0, value - inset));
247
301
  const K = 0.551784;
248
302
  const right = left + width;
249
303
  const top = bottom + height;
@@ -296,8 +350,8 @@ function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
296
350
  * @name ellipse
297
351
  * @function
298
352
  * @memberof Recipe#
299
- * @param {number} cx x-coordinate of center point of ellipse
300
- * @param {number} cy y-coordinate of center point of ellipse
353
+ * @param {number|"center"} cx x-coordinate of center point of ellipse
354
+ * @param {number|"center"} cy y-coordinate of center point of ellipse
301
355
  * @param {number} rx radius length from the center point along x-axis
302
356
  * @param {number} ry radius length from the center point along y-axis
303
357
  * @param {Object} [options]
@@ -310,8 +364,12 @@ function drawRoundedRectangle(ctx, left, bottom, width, height, radii) {
310
364
  * @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
311
365
  * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
312
366
  * @returns {Recipe} The recipe instance.
367
+ * @throws {TypeError} If no page is active.
313
368
  */
314
369
  exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
370
+ // null options act like omitted options.
371
+ if (options === null) options = {};
372
+ [cx, cy] = this._centrify(cx, cy);
315
373
  const { nx, ny } = this._calibrateCoordinate(cx, cy);
316
374
 
317
375
  const pathOptions = this._getPathOptions(options, nx, ny);
@@ -322,8 +380,8 @@ exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
322
380
 
323
381
  const drawEllipse = (ctx, x, y, w, h) => {
324
382
  const magic = 0.551784; // from https://www.tinaja.com/glib/ellipse4.pdf
325
- const ox = rx * magic; // control point offset horizontal
326
- const oy = ry * magic; // control point offset horizontal
383
+ const ox = (w / 2) * magic; // control point offset horizontal
384
+ const oy = (h / 2) * magic; // control point offset vertical
327
385
  const xe = x + w; // x-end, opposite corner from origin
328
386
  const ye = y + h; // y-end, opposite corner from origin
329
387
  const xm = rx; // x-middle of enclosing rectangle
@@ -359,9 +417,11 @@ exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
359
417
  }
360
418
 
361
419
  if (options.stroke || options.color || !options.fill) {
362
- if (pathOptions.stroke !== undefined) {
363
- colorModel = pathOptions.strokeModel;
364
- }
420
+ // The fill above may have replaced colorModel; stroke with `color`.
421
+ colorModel =
422
+ pathOptions.stroke !== undefined
423
+ ? pathOptions.strokeModel
424
+ : pathOptions.colorModel;
365
425
 
366
426
  // To honor the given width and height of the enclosing rectangle ...
367
427
 
@@ -381,10 +441,10 @@ exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
381
441
  // ... requires adjusting the internal drawing to accomodate line thickness.
382
442
  drawEllipse(
383
443
  ctx,
384
- margin,
385
- margin,
386
- width - pathOptions.width,
387
- height - pathOptions.width,
444
+ Math.min(margin, rx),
445
+ Math.min(margin, ry),
446
+ Math.max(0, width - pathOptions.width),
447
+ Math.max(0, height - pathOptions.width),
388
448
  );
389
449
  ctx.S();
390
450
  },
@@ -394,6 +454,18 @@ exports.ellipse = function ellipse(cx, cy, rx, ry, options = {}) {
394
454
  return this;
395
455
  };
396
456
 
457
+ /**
458
+ * Append a circular arc path built from Bézier segments.
459
+ * @private
460
+ * @param {Object} ctx - The content context.
461
+ * @param {number} x - The center x.
462
+ * @param {number} y - The center y.
463
+ * @param {number} radius - The radius.
464
+ * @param {number} startAngle - The start angle in degrees.
465
+ * @param {number} endAngle - The end angle in degrees.
466
+ * @param {boolean} [fromCenter=false] - Start the path at the center, for a sector.
467
+ * @returns {void}
468
+ */
397
469
  function drawArc(ctx, x, y, radius, startAngle, endAngle, fromCenter = false) {
398
470
  const TWO_PI = 2.0 * Math.PI;
399
471
  const HALF_PI = 0.5 * Math.PI;
@@ -459,8 +531,8 @@ function drawArc(ctx, x, y, radius, startAngle, endAngle, fromCenter = false) {
459
531
  * @name arc
460
532
  * @function
461
533
  * @memberof Recipe#
462
- * @param {number} x - the x coordinate of the arc center point
463
- * @param {number} y - the y coordinate of the arc center point
534
+ * @param {number|"center"} x - the x coordinate of the arc center point
535
+ * @param {number|"center"} y - the y coordinate of the arc center point
464
536
  * @param {number} radius - the distance from the given x,y coordinates from which to produce the arc
465
537
  * @param {number} [startAngle=0] - the start of the arc in degree units +/- 0 through 360. Positive values go clockwise, Negative values, counterclockwise.
466
538
  * @param {number} [endAngle=360] - the end of the arc in degree units +/- 0 through 360. Positive values go clockwise, Negative values, counterclockwise.
@@ -474,6 +546,7 @@ function drawArc(ctx, x, y, radius, startAngle, endAngle, fromCenter = false) {
474
546
  * @param {number} [options.rotation=0] - Accept: +/- 0 through 360.
475
547
  * @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
476
548
  * @returns {Recipe} The recipe instance.
549
+ * @throws {TypeError} If no page is active.
477
550
  */
478
551
  exports.arc = function arc(
479
552
  x,
@@ -483,6 +556,9 @@ exports.arc = function arc(
483
556
  endAngle = 360,
484
557
  options = {},
485
558
  ) {
559
+ // null options act like omitted options.
560
+ if (options === null) options = {};
561
+ [x, y] = this._centrify(x, y);
486
562
  const { nx, ny } = this._calibrateCoordinate(x, y);
487
563
  const diameter = radius * 2;
488
564
  const pathOptions = this._getPathOptions(options, nx, ny);
@@ -517,9 +593,11 @@ exports.arc = function arc(
517
593
  }
518
594
 
519
595
  if (options.stroke || options.color || !options.fill) {
520
- if (pathOptions.stroke !== undefined) {
521
- colorModel = pathOptions.strokeModel;
522
- }
596
+ // The fill above may have replaced colorModel; stroke with `color`.
597
+ colorModel =
598
+ pathOptions.stroke !== undefined
599
+ ? pathOptions.strokeModel
600
+ : pathOptions.colorModel;
523
601
 
524
602
  // To honor the given width and height of the enclosing rectangle ...
525
603
 
@@ -537,7 +615,15 @@ exports.arc = function arc(
537
615
  ctx.w(pathOptions.width).d(pathOptions.dash, pathOptions.dashPhase);
538
616
 
539
617
  // ... requires adjusting the internal drawing to accomodate line thickness.
540
- drawArc(ctx, radius, radius, radius - margin, sAng, eAng, sector);
618
+ drawArc(
619
+ ctx,
620
+ radius,
621
+ radius,
622
+ Math.max(0, radius - margin),
623
+ sAng,
624
+ eAng,
625
+ sector,
626
+ );
541
627
  if (sector) {
542
628
  ctx.h();
543
629
  } // close off path to create a circle sector.
@@ -556,13 +642,14 @@ exports.arc = function arc(
556
642
  * @name pie
557
643
  * @function
558
644
  * @memberof Recipe#
559
- * @param {number} x - the x coordinate of the pie center point
560
- * @param {number} y - the y coordinate of the pie center point
645
+ * @param {number|"center"} x - the x coordinate of the pie center point
646
+ * @param {number|"center"} y - the y coordinate of the pie center point
561
647
  * @param {number} radius - the distance from the center point to the arc
562
648
  * @param {number} [startAngle=0] - the start of the arc in degree units
563
649
  * @param {number} [endAngle=360] - the end of the arc in degree units
564
650
  * @param {Object} [options] - The path options.
565
651
  * @returns {Recipe} The recipe instance.
652
+ * @throws {TypeError} If no page is active.
566
653
  */
567
654
  exports.pie = function pie(x, y, radius, startAngle, endAngle, options = {}) {
568
655
  return this.arc(x, y, radius, startAngle, endAngle, {
@@ -579,14 +666,17 @@ exports.pie = function pie(x, y, radius, startAngle, endAngle, options = {}) {
579
666
  * @param {Recipe.LineStyleOptions} [options] - The line style options.
580
667
  * @param {number} [options.width] - The line width.
581
668
  * @param {number} [options.lineWidth] - Alias for width.
582
- * @param {number} [options.cap] - The PDF line cap style.
583
- * @param {number} [options.join] - The PDF line join style.
669
+ * @param {number} [options.cap] - The PDF line cap style, a `LineCapStyle` value.
670
+ * @param {number} [options.join] - The PDF line join style: 0 miter, 1 round, 2 bevel.
584
671
  * @param {number} [options.miterLimit] - The miter limit.
585
672
  * @param {number[]} [options.dash] - The dash pattern.
586
673
  * @param {number} [options.dashPhase] - The dash pattern phase.
587
674
  * @returns {Recipe} The recipe instance.
675
+ * @throws {TypeError} If no page is active.
588
676
  */
589
677
  exports.lineStyle = function lineStyle(options = {}) {
678
+ // null options act like omitted options.
679
+ if (options === null) options = {};
590
680
  this.current = this.current || {};
591
681
  this.current.lineStyle = this.current.lineStyle || {};
592
682
 
@@ -617,6 +707,13 @@ exports.lineStyle = function lineStyle(options = {}) {
617
707
  return this._setLineStyle(options);
618
708
  };
619
709
 
710
+ /**
711
+ * Write the given line style options to the page content context.
712
+ * @private
713
+ * @param {Object} [options] - The lineStyle() options.
714
+ * @returns {Recipe} The recipe instance.
715
+ * @throws {TypeError} If no page is active.
716
+ */
620
717
  exports._setLineStyle = function _setLineStyle(options = {}) {
621
718
  if (options.width !== undefined || options.lineWidth !== undefined)
622
719
  this.pageContext.w(options.width ?? options.lineWidth);
@@ -639,6 +736,7 @@ exports._setLineStyle = function _setLineStyle(options = {}) {
639
736
  * @memberof Recipe#
640
737
  * @param {number} width - The line width.
641
738
  * @returns {Recipe} The recipe instance.
739
+ * @throws {TypeError} If no page is active.
642
740
  */
643
741
  exports.lineWidth = function lineWidth(width) {
644
742
  return this.lineStyle({ width });
@@ -652,6 +750,7 @@ exports.lineWidth = function lineWidth(width) {
652
750
  * @memberof Recipe#
653
751
  * @param {number} value - The requested opacity from 0 (transparent) to 1 (opaque).
654
752
  * @returns {Recipe} The recipe instance.
753
+ * @throws {RangeError} If the value is not a finite number from 0 to 1.
655
754
  */
656
755
  exports.opacity = function opacity(value) {
657
756
  if (!Number.isFinite(value) || value < 0 || value > 1) {
@@ -1,33 +1,52 @@
1
+ const { Colorspace } = require("../recipe-constants");
2
+
3
+ /**
4
+ * Writes device color operators for a color model.
5
+ * @private
6
+ */
1
7
  const Color = class Color {
8
+ /** Not meant to be instantiated; use the static methods. */
2
9
  constructor() {}
3
10
 
11
+ /**
12
+ * Set the fill color.
13
+ * @param {Object} ctx - The content context.
14
+ * @param {Object} colorModel - The color model from _transformColor().
15
+ * @returns {void}
16
+ */
4
17
  static fill(ctx, colorModel) {
5
18
  switch (colorModel.colorspace) {
6
- case "rgb":
19
+ case Colorspace.RGB:
7
20
  ctx.rg(colorModel.r, colorModel.g, colorModel.b);
8
21
  break;
9
22
 
10
- case "cmyk":
23
+ case Colorspace.CMYK:
11
24
  ctx.k(colorModel.c, colorModel.m, colorModel.y, colorModel.k);
12
25
  break;
13
26
 
14
- case "gray":
27
+ case Colorspace.GRAY:
15
28
  ctx.g(colorModel.gray);
16
29
  break;
17
30
  }
18
31
  }
19
32
 
33
+ /**
34
+ * Set the stroke color.
35
+ * @param {Object} ctx - The content context.
36
+ * @param {Object} colorModel - The color model from _transformColor().
37
+ * @returns {void}
38
+ */
20
39
  static stroke(ctx, colorModel) {
21
40
  switch (colorModel.colorspace) {
22
- case "rgb":
41
+ case Colorspace.RGB:
23
42
  ctx.RG(colorModel.r, colorModel.g, colorModel.b);
24
43
  break;
25
44
 
26
- case "cmyk":
45
+ case Colorspace.CMYK:
27
46
  ctx.K(colorModel.c, colorModel.m, colorModel.y, colorModel.k);
28
47
  break;
29
48
 
30
- case "gray":
49
+ case Colorspace.GRAY:
31
50
  ctx.G(colorModel.gray);
32
51
  break;
33
52
  }
@@ -36,7 +55,17 @@ const Color = class Color {
36
55
 
37
56
  exports.Color = Color;
38
57
 
58
+ /**
59
+ * Creates a writer form XObject extended with Recipe helpers; the
60
+ * constructor returns the form itself.
61
+ * @private
62
+ */
39
63
  exports.xObjectForm = class xObjectForm {
64
+ /**
65
+ * @param {Object} pdfWriter - The PDF writer.
66
+ * @param {number} [width=100] - The form width.
67
+ * @param {number} [height=100] - The form height.
68
+ */
40
69
  constructor(pdfWriter, width = 100, height = 100) {
41
70
  const xObject = pdfWriter.createFormXObject(0, 0, width, height);
42
71
  xObject.pdfWriter = pdfWriter;
@@ -50,32 +79,61 @@ exports.xObjectForm = class xObjectForm {
50
79
  return xObject;
51
80
  }
52
81
 
82
+ /**
83
+ * Store a value on the form, such as its Recipe cache key.
84
+ * @param {string} key - The key.
85
+ * @param {*} value - The value.
86
+ * @returns {void}
87
+ */
53
88
  set(key, value) {
54
89
  this._values = this._values || {};
55
90
  this._values[key] = value;
56
91
  }
57
92
 
93
+ /**
94
+ * @param {string} key - The key.
95
+ * @returns {*} The stored value, or undefined.
96
+ */
58
97
  get(key) {
59
98
  this._values = this._values || {};
60
99
  return this._values[key];
61
100
  }
62
101
 
102
+ /**
103
+ * Map a graphics state into the form resources.
104
+ * @param {number} gsId - The ExtGState object ID.
105
+ * @returns {string} The resource name.
106
+ */
63
107
  getGsName(gsId) {
64
- const resourcesDict = this.getResourcesDictinary();
108
+ const resourcesDict = this.getResourcesDictionary();
65
109
  const gsName = resourcesDict.addExtGStateMapping(gsId);
66
110
  return gsName;
67
111
  }
68
112
 
113
+ /**
114
+ * Map a color space into the form resources.
115
+ * @param {number} csId - The color space object ID.
116
+ * @returns {string} The resource name.
117
+ */
69
118
  getCsName(csId) {
70
- const resourcesDict = this.getResourcesDictinary();
119
+ const resourcesDict = this.getResourcesDictionary();
71
120
  const csName = resourcesDict.addColorSpaceMapping(csId);
72
121
  return csName;
73
122
  }
74
123
 
124
+ /**
125
+ * End the form so it can be placed.
126
+ * @returns {void}
127
+ */
75
128
  end() {
76
129
  this.pdfWriter.endFormXObject(this);
77
130
  }
78
131
 
132
+ /**
133
+ * Set the fill color in the form, including separation colors.
134
+ * @param {Object} colorModel - The color model from _transformColor().
135
+ * @returns {Object} The form.
136
+ */
79
137
  fill(colorModel) {
80
138
  const ctx = this.getContentContext();
81
139
  switch (colorModel.colorspace) {
@@ -83,7 +141,7 @@ exports.xObjectForm = class xObjectForm {
83
141
  Color.fill(ctx, colorModel);
84
142
  break;
85
143
 
86
- case "separation":
144
+ case Colorspace.SEPARATION:
87
145
  ctx.cs(this.getCsName(colorModel.colorspaceId));
88
146
  ctx.scn(1);
89
147
  break;
@@ -91,6 +149,11 @@ exports.xObjectForm = class xObjectForm {
91
149
  return this;
92
150
  }
93
151
 
152
+ /**
153
+ * Set the stroke color in the form, including separation colors.
154
+ * @param {Object} colorModel - The color model from _transformColor().
155
+ * @returns {Object} The form.
156
+ */
94
157
  stroke(colorModel) {
95
158
  const ctx = this.getContentContext();
96
159
  switch (colorModel.colorspace) {
@@ -98,7 +161,7 @@ exports.xObjectForm = class xObjectForm {
98
161
  Color.stroke(ctx, colorModel);
99
162
  break;
100
163
 
101
- case "separation":
164
+ case Colorspace.SEPARATION:
102
165
  ctx.CS(this.getCsName(colorModel.colorspaceId));
103
166
  ctx.SCN(1);
104
167
  break;