h17-sspdf 0.5.0 → 1.0.0

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/core/pdf-core.js CHANGED
@@ -123,6 +123,8 @@ class PDFCore {
123
123
  this.pageHeight = this.doc.internal.pageSize.getHeight();
124
124
  this.backgroundColor = this._resolveColor(this.page.backgroundColor);
125
125
  this.lastDrawnBounds = null;
126
+ this.documentStateDepth = 0;
127
+ this.hasDeferredInitialRenderState = Array.isArray(theme.customFonts) && theme.customFonts.length > 0;
126
128
  this.defaultRenderState = this._buildDefaultRenderState(this.page);
127
129
  this.contentTopY = this.marginTopMm + this.headerHeightMm;
128
130
  this.contentBottomY = this.pageHeight - this.marginBottomMm - this.footerHeightMm;
@@ -138,7 +140,12 @@ class PDFCore {
138
140
  });
139
141
 
140
142
  this.paintBackground();
141
- this.applyDefaultRenderState();
143
+ // Custom fonts are registered by renderDocument() after core construction.
144
+ // Deferring the first setFont() avoids jsPDF warnings when page.defaultText
145
+ // uses a custom family that is not yet in the font map.
146
+ if (!this.hasDeferredInitialRenderState) {
147
+ this.applyDefaultRenderState();
148
+ }
142
149
  }
143
150
 
144
151
  /**
@@ -178,9 +185,11 @@ class PDFCore {
178
185
  * Force a new page and reset cursor to top margin.
179
186
  */
180
187
  addPage() {
188
+ const reopenedStateDepth = this._closeDocumentStatesForPageBreak();
181
189
  this.doc.addPage();
182
190
  this.paintBackground();
183
191
  this.applyDefaultRenderState();
192
+ this._reopenDocumentStatesAfterPageBreak(reopenedStateDepth);
184
193
  this.cursorY = this.contentTopY;
185
194
  }
186
195
 
@@ -198,6 +207,7 @@ class PDFCore {
198
207
  && typeof this.doc.restoreGraphicsState === "function";
199
208
  if (canSaveGraphicsState) {
200
209
  this.doc.saveGraphicsState();
210
+ this.documentStateDepth += 1;
201
211
  }
202
212
 
203
213
  try {
@@ -205,6 +215,7 @@ class PDFCore {
205
215
  } finally {
206
216
  if (canSaveGraphicsState) {
207
217
  this.doc.restoreGraphicsState();
218
+ this.documentStateDepth = Math.max(0, this.documentStateDepth - 1);
208
219
  }
209
220
  this.applyDefaultRenderState();
210
221
  }
@@ -498,11 +509,21 @@ class PDFCore {
498
509
  const baseline = y + baselineOffsetMm;
499
510
 
500
511
  if (markerStyle.shape) {
501
- // Vector shape marker: renders via core/shapes.js, no text encoding needed
512
+ // Vector shape marker: renders via core/shapes.js, no text encoding needed.
513
+ // Wrapped in saveGraphicsState/restoreGraphicsState to isolate draw state
514
+ // mutations (setLineCap, setFillColor, etc.) from the main content stream.
515
+ // Without this, accumulated state operators can cause print rendering issues
516
+ // where printer RIPs interpret the stacked state differently than screen viewers.
502
517
  const { renderShape, getShapeWidth } = require("./shapes");
503
518
  const shapeColor = markerStyle.shapeColor || markerStyle.color || [0, 0, 0];
504
519
  const shapeSize = markerStyle.shapeSize || 1;
520
+ if (typeof this.doc.saveGraphicsState === "function") {
521
+ this.doc.saveGraphicsState();
522
+ }
505
523
  renderShape(markerStyle.shape, this.doc, x, baseline, shapeColor, shapeSize, textFontSize);
524
+ if (typeof this.doc.restoreGraphicsState === "function") {
525
+ this.doc.restoreGraphicsState();
526
+ }
506
527
  this.applyDefaultRenderState();
507
528
  } else {
508
529
  // Text-based marker (existing behavior)
@@ -1070,6 +1091,29 @@ class PDFCore {
1070
1091
  fillColor: this._resolveColor(page.defaultFillColor),
1071
1092
  };
1072
1093
  }
1094
+
1095
+ _closeDocumentStatesForPageBreak() {
1096
+ if (this.documentStateDepth <= 0) {
1097
+ return 0;
1098
+ }
1099
+
1100
+ for (let i = 0; i < this.documentStateDepth; i += 1) {
1101
+ this.doc.restoreGraphicsState();
1102
+ }
1103
+
1104
+ return this.documentStateDepth;
1105
+ }
1106
+
1107
+ _reopenDocumentStatesAfterPageBreak(depth) {
1108
+ const count = Number(depth) || 0;
1109
+ if (count <= 0) {
1110
+ return;
1111
+ }
1112
+
1113
+ for (let i = 0; i < count; i += 1) {
1114
+ this.doc.saveGraphicsState();
1115
+ }
1116
+ }
1073
1117
  }
1074
1118
 
1075
1119
  module.exports = {
@@ -47,6 +47,7 @@ function renderDocument(input) {
47
47
 
48
48
  const core = new PDFCore(runtimeTheme);
49
49
  registerThemeFonts(core, runtimeTheme);
50
+ core.applyDefaultRenderState();
50
51
 
51
52
  installPageTemplates(core, runtimeTheme, built.pageTemplates);
52
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "h17-sspdf",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "Declarative PDF engine - define layout once, feed it JSON, get consistent PDFs",
5
5
  "main": "index.js",
6
6
  "author": "Hugo Palma",
@@ -1,7 +1,7 @@
1
1
  /** @license
2
2
  *
3
3
  * jsPDF - PDF Document creation from JavaScript
4
- * Version 4.1.0 Built on 2026-02-02T10:38:25.210Z
4
+ * Version 4.2.1 Built on 2026-03-17T11:11:27.057Z
5
5
  * CommitID 00000000
6
6
  *
7
7
  * Copyright (c) 2010-2025 James Hall <james@parall.ax>, https://github.com/MrRio/jsPDF
@@ -3823,6 +3823,47 @@ function jsPDF(options) {
3823
3823
  });
3824
3824
  });
3825
3825
 
3826
+ var clearDomNode = function(node) {
3827
+ while (node.firstChild) {
3828
+ node.removeChild(node.firstChild);
3829
+ }
3830
+ };
3831
+
3832
+ var initializeNewWindow = function(window) {
3833
+ var targetDocument = window.document;
3834
+ var html = targetDocument.documentElement;
3835
+ var head = targetDocument.head;
3836
+ var body = targetDocument.body;
3837
+ var style;
3838
+
3839
+ if (!head) {
3840
+ head = targetDocument.createElement("head");
3841
+ html.appendChild(head);
3842
+ }
3843
+
3844
+ if (!body) {
3845
+ body = targetDocument.createElement("body");
3846
+ html.appendChild(body);
3847
+ }
3848
+
3849
+ clearDomNode(head);
3850
+ clearDomNode(body);
3851
+
3852
+ style = targetDocument.createElement("style");
3853
+ style.appendChild(
3854
+ targetDocument.createTextNode(
3855
+ "html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;}"
3856
+ )
3857
+ );
3858
+
3859
+ head.appendChild(style);
3860
+
3861
+ return {
3862
+ document: targetDocument,
3863
+ body: body
3864
+ };
3865
+ };
3866
+
3826
3867
  /**
3827
3868
  * Generates the PDF document.
3828
3869
  *
@@ -3900,7 +3941,7 @@ function jsPDF(options) {
3900
3941
  }
3901
3942
  return (
3902
3943
  "data:application/pdf;filename=" +
3903
- options.filename +
3944
+ encodeURIComponent(options.filename) +
3904
3945
  ";base64," +
3905
3946
  dataURI
3906
3947
  );
@@ -3910,29 +3951,34 @@ function jsPDF(options) {
3910
3951
  ) {
3911
3952
  var pdfObjectUrl =
3912
3953
  "https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.1.1/pdfobject.min.js";
3913
- var integrity =
3914
- ' integrity="sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==" crossorigin="anonymous"';
3954
+ var useDefaultPdfObjectUrl = !options.pdfObjectUrl;
3915
3955
 
3916
- if (options.pdfObjectUrl) {
3956
+ if (!useDefaultPdfObjectUrl) {
3917
3957
  pdfObjectUrl = options.pdfObjectUrl;
3918
- integrity = "";
3919
3958
  }
3920
3959
 
3921
- var htmlForNewWindow =
3922
- "<html>" +
3923
- '<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style><body><script src="' +
3924
- pdfObjectUrl +
3925
- '"' +
3926
- integrity +
3927
- '></script><script >PDFObject.embed("' +
3928
- this.output("dataurlstring") +
3929
- '", ' +
3930
- JSON.stringify(options) +
3931
- ");</script></body></html>";
3932
3960
  var nW = globalObject.open();
3933
3961
 
3934
3962
  if (nW !== null) {
3935
- nW.document.write(htmlForNewWindow);
3963
+ var initializedPdfObjectWindow = initializeNewWindow(nW);
3964
+ var pdfObjectScript = initializedPdfObjectWindow.document.createElement(
3965
+ "script"
3966
+ );
3967
+ var scope = this;
3968
+
3969
+ pdfObjectScript.src = pdfObjectUrl;
3970
+
3971
+ if (useDefaultPdfObjectUrl) {
3972
+ pdfObjectScript.integrity =
3973
+ "sha512-4ze/a9/4jqu+tX9dfOqJYSvyYd5M6qum/3HpCLr+/Jqf0whc37VUbkpNGHR7/8pSnCFw47T1fmIpwBV7UySh3g==";
3974
+ pdfObjectScript.crossOrigin = "anonymous";
3975
+ }
3976
+
3977
+ pdfObjectScript.onload = function() {
3978
+ nW.PDFObject.embed(scope.output("dataurlstring"), options);
3979
+ };
3980
+
3981
+ initializedPdfObjectWindow.body.appendChild(pdfObjectScript);
3936
3982
  }
3937
3983
  return nW;
3938
3984
  } else {
@@ -3945,30 +3991,33 @@ function jsPDF(options) {
3945
3991
  Object.prototype.toString.call(globalObject) === "[object Window]"
3946
3992
  ) {
3947
3993
  var pdfJsUrl = options.pdfJsUrl || "examples/PDF.js/web/viewer.html";
3948
- var htmlForPDFjsNewWindow =
3949
- "<html>" +
3950
- "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" +
3951
- '<body><iframe id="pdfViewer" src="' +
3952
- pdfJsUrl +
3953
- "?file=&downloadName=" +
3954
- options.filename +
3955
- '" width="500px" height="400px" />' +
3956
- "</body></html>";
3957
3994
  var PDFjsNewWindow = globalObject.open();
3958
3995
 
3959
3996
  if (PDFjsNewWindow !== null) {
3960
- PDFjsNewWindow.document.write(htmlForPDFjsNewWindow);
3997
+ var initializedPdfJsWindow = initializeNewWindow(PDFjsNewWindow);
3998
+ var pdfViewer = initializedPdfJsWindow.document.createElement(
3999
+ "iframe"
4000
+ );
4001
+ var pdfJsQueryChar = pdfJsUrl.indexOf("?") === -1 ? "?" : "&";
3961
4002
  var scope = this;
3962
- PDFjsNewWindow.document.documentElement.querySelector(
3963
- "#pdfViewer"
3964
- ).onload = function() {
4003
+
4004
+ pdfViewer.id = "pdfViewer";
4005
+ pdfViewer.width = "500px";
4006
+ pdfViewer.height = "400px";
4007
+ pdfViewer.src =
4008
+ pdfJsUrl +
4009
+ pdfJsQueryChar +
4010
+ "file=&downloadName=" +
4011
+ encodeURIComponent(options.filename);
4012
+
4013
+ pdfViewer.onload = function() {
3965
4014
  PDFjsNewWindow.document.title = options.filename;
3966
- PDFjsNewWindow.document.documentElement
3967
- .querySelector("#pdfViewer")
3968
- .contentWindow.PDFViewerApplication.open(
3969
- scope.output("bloburl")
3970
- );
4015
+ pdfViewer.contentWindow.PDFViewerApplication.open(
4016
+ scope.output("bloburl")
4017
+ );
3971
4018
  };
4019
+
4020
+ initializedPdfJsWindow.body.appendChild(pdfViewer);
3972
4021
  }
3973
4022
  return PDFjsNewWindow;
3974
4023
  } else {
@@ -3980,17 +4029,17 @@ function jsPDF(options) {
3980
4029
  if (
3981
4030
  Object.prototype.toString.call(globalObject) === "[object Window]"
3982
4031
  ) {
3983
- var htmlForDataURLNewWindow =
3984
- "<html>" +
3985
- "<style>html, body { padding: 0; margin: 0; } iframe { width: 100%; height: 100%; border: 0;} </style>" +
3986
- "<body>" +
3987
- '<iframe src="' +
3988
- this.output("datauristring", options) +
3989
- '"></iframe>' +
3990
- "</body></html>";
3991
4032
  var dataURLNewWindow = globalObject.open();
3992
4033
  if (dataURLNewWindow !== null) {
3993
- dataURLNewWindow.document.write(htmlForDataURLNewWindow);
4034
+ var initializedDataUrlWindow = initializeNewWindow(
4035
+ dataURLNewWindow
4036
+ );
4037
+ var dataUrlFrame = initializedDataUrlWindow.document.createElement(
4038
+ "iframe"
4039
+ );
4040
+
4041
+ dataUrlFrame.src = this.output("datauristring", options);
4042
+ initializedDataUrlWindow.body.appendChild(dataUrlFrame);
3994
4043
  dataURLNewWindow.document.title = options.filename;
3995
4044
  }
3996
4045
  if (dataURLNewWindow || typeof safari === "undefined")
@@ -6902,7 +6951,7 @@ jsPDF.API = {
6902
6951
  * @type {string}
6903
6952
  * @memberof jsPDF#
6904
6953
  */
6905
- jsPDF.version = "4.1.0";
6954
+ jsPDF.version = "4.2.1";
6906
6955
 
6907
6956
  /* global jsPDF */
6908
6957
 
@@ -9035,7 +9084,11 @@ var AcroFormButton = function() {
9035
9084
  return _AS;
9036
9085
  },
9037
9086
  set: function(value) {
9038
- _AS = value;
9087
+ var name = value === undefined || value === null ? "" : value.toString();
9088
+ if (name.substr(0, 1) === "/") {
9089
+ name = name.substr(1);
9090
+ }
9091
+ _AS = "/" + pdfEscapeName(name);
9039
9092
  }
9040
9093
  });
9041
9094
 
@@ -9188,7 +9241,11 @@ var AcroFormChildClass = function() {
9188
9241
  return _AS;
9189
9242
  },
9190
9243
  set: function(value) {
9191
- _AS = value;
9244
+ var name = value === undefined || value === null ? "" : value.toString();
9245
+ if (name.substr(0, 1) === "/") {
9246
+ name = name.substr(1);
9247
+ }
9248
+ _AS = "/" + pdfEscapeName(name);
9192
9249
  }
9193
9250
  });
9194
9251
 
@@ -9205,7 +9262,11 @@ var AcroFormChildClass = function() {
9205
9262
  return _AS.substr(1, _AS.length - 1);
9206
9263
  },
9207
9264
  set: function(value) {
9208
- _AS = "/" + value;
9265
+ var name = value === undefined || value === null ? "" : value.toString();
9266
+ if (name.substr(0, 1) === "/") {
9267
+ name = name.substr(1);
9268
+ }
9269
+ _AS = "/" + pdfEscapeName(name);
9209
9270
  }
9210
9271
  });
9211
9272
  this.caption = "l";
@@ -11214,6 +11275,9 @@ var AcroForm = jsPDF.AcroForm;
11214
11275
  getVerticalCoordinateString(anno.bounds.y + anno.bounds.h) +
11215
11276
  "] ";
11216
11277
  var color = anno.color || "#000000";
11278
+ var defaultStyle =
11279
+ "font: Helvetica,sans-serif 12.0pt; text-align:left; color:#" +
11280
+ color;
11217
11281
  line =
11218
11282
  "<</Type /Annot /Subtype /" +
11219
11283
  "FreeText" +
@@ -11222,10 +11286,7 @@ var AcroForm = jsPDF.AcroForm;
11222
11286
  "/Contents (" +
11223
11287
  escape(encryptor(anno.contents)) +
11224
11288
  ")";
11225
- line +=
11226
- " /DS(font: Helvetica,sans-serif 12.0pt; text-align:left; color:#" +
11227
- color +
11228
- ")";
11289
+ line += " /DS(" + escape(encryptor(defaultStyle)) + ")";
11229
11290
  line += " /Border [0 0 0]";
11230
11291
  line += " >>";
11231
11292
  this.internal.write(line);
@@ -17156,12 +17217,32 @@ function parseFontFamily(input) {
17156
17217
  * @returns {jsPDF}
17157
17218
  */
17158
17219
  jsPDFAPI.addJS = function(javascript) {
17159
- // FIX: Move variables inside function scope to prevent shared state
17160
- // between multiple jsPDF instances
17161
17220
  var jsNamesObj;
17162
17221
  var jsJsObj;
17163
- var text = javascript;
17164
-
17222
+ // Escape only unescaped parentheses, without double-escaping already escaped ones
17223
+ function escapeParens(str) {
17224
+ let out = "";
17225
+ for (let i = 0; i < str.length; i++) {
17226
+ const ch = str[i];
17227
+ if (ch === "(" || ch === ")") {
17228
+ // Count preceding backslashes to determine if the paren is already escaped
17229
+ let bs = 0;
17230
+ for (let j = i - 1; j >= 0 && str[j] === "\\"; j--) {
17231
+ bs++;
17232
+ }
17233
+ if (bs % 2 === 0) {
17234
+ out += "\\" + ch;
17235
+ } else {
17236
+ out += ch;
17237
+ }
17238
+ } else {
17239
+ out += ch;
17240
+ }
17241
+ }
17242
+ return out;
17243
+ }
17244
+ const text = escapeParens(javascript);
17245
+
17165
17246
  this.internal.events.subscribe("postPutResources", function() {
17166
17247
  jsNamesObj = this.internal.newObject();
17167
17248
  this.internal.out("<<");
@@ -17172,10 +17253,12 @@ function parseFontFamily(input) {
17172
17253
  jsJsObj = this.internal.newObject();
17173
17254
  this.internal.out("<<");
17174
17255
  this.internal.out("/S /JavaScript");
17256
+ // The sanitized 'text' is now safe to be enclosed in parentheses
17175
17257
  this.internal.out("/JS (" + text + ")");
17176
17258
  this.internal.out(">>");
17177
17259
  this.internal.out("endobj");
17178
17260
  });
17261
+
17179
17262
  this.internal.events.subscribe("putCatalog", function() {
17180
17263
  if (jsNamesObj !== undefined && jsJsObj !== undefined) {
17181
17264
  this.internal.out("/Names <</JavaScript " + jsNamesObj + " 0 R>>");
@@ -18330,6 +18413,11 @@ function GifReader(buf) {
18330
18413
  this.decodeAndBlitFrameBGRA = function(frame_num, pixels) {
18331
18414
  var frame = this.frameInfo(frame_num);
18332
18415
  var num_pixels = frame.width * frame.height;
18416
+
18417
+ if (num_pixels > 512 * 1024 * 1024) {
18418
+ throw new Error("Image dimensions exceed 512MB, which is too large.");
18419
+ }
18420
+
18333
18421
  var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
18334
18422
  GifReaderLZWOutputIndexStream(
18335
18423
  buf,
@@ -18402,6 +18490,11 @@ function GifReader(buf) {
18402
18490
  this.decodeAndBlitFrameRGBA = function(frame_num, pixels) {
18403
18491
  var frame = this.frameInfo(frame_num);
18404
18492
  var num_pixels = frame.width * frame.height;
18493
+
18494
+ if (num_pixels > 512 * 1024 * 1024) {
18495
+ throw new Error("Image dimensions exceed 512MB, which is too large.");
18496
+ }
18497
+
18405
18498
  var index_stream = new Uint8Array(num_pixels); // At most 8-bit indices.
18406
18499
  GifReaderLZWOutputIndexStream(
18407
18500
  buf,