pdf-catalog-generator 3.1.3 → 3.1.4

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/README.md CHANGED
@@ -175,6 +175,7 @@ Generates a PDF catalog from the provided configuration.
175
175
  - `companyName` (string): Your company name
176
176
  - `companyLogo` (string | null, optional): Logo URL or base64 string
177
177
  - `template` (TemplateType, optional): Template to use (default: 'template1')
178
+ - `imageOptimization` (ImageOptimizationOptions, optional): Resize/compress supported remote image URLs before rendering. Enabled by default.
178
179
 
179
180
  **Returns:** `Promise<Uint8Array>` - PDF file as Uint8Array (works in both Node.js and browser)
180
181
 
@@ -288,6 +289,16 @@ This library is **completely schema-free** - no fields are mandatory! You can us
288
289
  interface ProductData {
289
290
  [key: string]: string | number | boolean | null | undefined;
290
291
  }
292
+
293
+ interface ImageOptimizationOptions {
294
+ enabled?: boolean;
295
+ maxWidth?: number;
296
+ maxHeight?: number;
297
+ quality?: number;
298
+ logoMaxWidth?: number;
299
+ logoMaxHeight?: number;
300
+ logoQuality?: number;
301
+ }
291
302
  ```
292
303
 
293
304
  **Dynamic Field Rendering:**
package/dist/index.d.mts CHANGED
@@ -5,11 +5,21 @@ interface ProductData {
5
5
  [key: string]: string | number | boolean | null | undefined;
6
6
  }
7
7
  type TemplateType = 'template1' | 'template2' | 'template3' | 'template4' | 'template5' | 'template6';
8
+ interface ImageOptimizationOptions {
9
+ enabled?: boolean;
10
+ maxWidth?: number;
11
+ maxHeight?: number;
12
+ quality?: number;
13
+ logoMaxWidth?: number;
14
+ logoMaxHeight?: number;
15
+ logoQuality?: number;
16
+ }
8
17
  interface CatalogConfig {
9
18
  products: ProductData[];
10
19
  companyLogo?: string | null;
11
20
  companyName: string;
12
21
  template?: TemplateType;
22
+ imageOptimization?: ImageOptimizationOptions;
13
23
  }
14
24
  interface TemplateProps {
15
25
  companyLogo?: string | null;
package/dist/index.d.ts CHANGED
@@ -5,11 +5,21 @@ interface ProductData {
5
5
  [key: string]: string | number | boolean | null | undefined;
6
6
  }
7
7
  type TemplateType = 'template1' | 'template2' | 'template3' | 'template4' | 'template5' | 'template6';
8
+ interface ImageOptimizationOptions {
9
+ enabled?: boolean;
10
+ maxWidth?: number;
11
+ maxHeight?: number;
12
+ quality?: number;
13
+ logoMaxWidth?: number;
14
+ logoMaxHeight?: number;
15
+ logoQuality?: number;
16
+ }
8
17
  interface CatalogConfig {
9
18
  products: ProductData[];
10
19
  companyLogo?: string | null;
11
20
  companyName: string;
12
21
  template?: TemplateType;
22
+ imageOptimization?: ImageOptimizationOptions;
13
23
  }
14
24
  interface TemplateProps {
15
25
  companyLogo?: string | null;
package/dist/index.js CHANGED
@@ -1005,8 +1005,137 @@ renderer.Font.registerEmojiSource({
1005
1005
  format: "png",
1006
1006
  url: "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/"
1007
1007
  });
1008
+ function normalizeImageSource(imageSource) {
1009
+ const trimmedImageSource = imageSource.trim();
1010
+ if (trimmedImageSource.startsWith("data:")) {
1011
+ return trimmedImageSource;
1012
+ }
1013
+ if (trimmedImageSource.startsWith("http://") || trimmedImageSource.startsWith("https://")) {
1014
+ return trimmedImageSource;
1015
+ }
1016
+ let mimeType = "image/png";
1017
+ const base64Start = trimmedImageSource.substring(0, 20).toLowerCase();
1018
+ if (base64Start.includes("ivborw0kggo") || trimmedImageSource.startsWith("iVBORw0KGgo")) {
1019
+ mimeType = "image/png";
1020
+ } else if (base64Start.includes("/9j/4aaq") || trimmedImageSource.startsWith("/9j/4AAQ")) {
1021
+ mimeType = "image/jpeg";
1022
+ } else if (base64Start.includes("r0lgodlh") || trimmedImageSource.startsWith("R0lGODlh")) {
1023
+ mimeType = "image/gif";
1024
+ } else if (base64Start.includes("uklgr") || trimmedImageSource.startsWith("UklGR")) {
1025
+ mimeType = "image/webp";
1026
+ }
1027
+ return `data:${mimeType};base64,${trimmedImageSource}`;
1028
+ }
1029
+ var IMAGE_FIELD_NAMES_LOWER = /* @__PURE__ */ new Set([
1030
+ "image",
1031
+ "imageurl",
1032
+ "img",
1033
+ "photo",
1034
+ "picture",
1035
+ "image2",
1036
+ "imageurl2",
1037
+ "img2",
1038
+ "photo2",
1039
+ "picture2"
1040
+ ]);
1041
+ var DEFAULT_IMAGE_OPTIMIZATION = {
1042
+ enabled: true,
1043
+ maxWidth: 900,
1044
+ maxHeight: 900,
1045
+ quality: 70,
1046
+ logoMaxWidth: 400,
1047
+ logoMaxHeight: 400,
1048
+ logoQuality: 70
1049
+ };
1050
+ function getImageOptimizationOptions(options) {
1051
+ return {
1052
+ ...DEFAULT_IMAGE_OPTIMIZATION,
1053
+ ...options
1054
+ };
1055
+ }
1056
+ function getNumericSearchParam(url, key) {
1057
+ const rawValue = url.searchParams.get(key);
1058
+ if (rawValue === null || rawValue.trim() === "") {
1059
+ return null;
1060
+ }
1061
+ const numericValue = Number(rawValue);
1062
+ return Number.isFinite(numericValue) ? numericValue : null;
1063
+ }
1064
+ function optimizeImageUrl(imageSource, options, imageKind) {
1065
+ if (!options.enabled) {
1066
+ return normalizeImageSource(imageSource);
1067
+ }
1068
+ const normalizedSource = normalizeImageSource(imageSource);
1069
+ if (!normalizedSource.startsWith("http://") && !normalizedSource.startsWith("https://")) {
1070
+ return normalizedSource;
1071
+ }
1072
+ let url;
1073
+ try {
1074
+ url = new URL(normalizedSource);
1075
+ } catch {
1076
+ return normalizedSource;
1077
+ }
1078
+ const hostname = url.hostname.toLowerCase();
1079
+ const isImgixStyleHost = hostname.includes("unsplash.com") || hostname.includes("imgix.net") || hostname.includes("builder.io") || hostname.includes("sanity.io");
1080
+ if (!isImgixStyleHost) {
1081
+ return normalizedSource;
1082
+ }
1083
+ const maxWidth = imageKind === "logo" ? options.logoMaxWidth : options.maxWidth;
1084
+ const maxHeight = imageKind === "logo" ? options.logoMaxHeight : options.maxHeight;
1085
+ const quality = imageKind === "logo" ? options.logoQuality : options.quality;
1086
+ url.searchParams.set("auto", "format,compress");
1087
+ url.searchParams.set("fit", "max");
1088
+ const currentWidth = getNumericSearchParam(url, "w");
1089
+ if (currentWidth === null || currentWidth > maxWidth) {
1090
+ url.searchParams.set("w", String(maxWidth));
1091
+ }
1092
+ const currentHeight = getNumericSearchParam(url, "h");
1093
+ if (currentHeight === null || currentHeight > maxHeight) {
1094
+ url.searchParams.set("h", String(maxHeight));
1095
+ }
1096
+ const currentQuality = getNumericSearchParam(url, "q");
1097
+ if (currentQuality === null || currentQuality > quality) {
1098
+ url.searchParams.set("q", String(quality));
1099
+ }
1100
+ return url.toString();
1101
+ }
1102
+ function normalizeProductImages(products, options) {
1103
+ const imageCache = /* @__PURE__ */ new Map();
1104
+ const normalizeCachedImage = (value, imageKind) => {
1105
+ const cacheKey = `${imageKind}:${value}`;
1106
+ const cachedValue = imageCache.get(cacheKey);
1107
+ if (cachedValue) {
1108
+ return cachedValue;
1109
+ }
1110
+ const normalizedValue = optimizeImageUrl(value, options, imageKind);
1111
+ imageCache.set(cacheKey, normalizedValue);
1112
+ return normalizedValue;
1113
+ };
1114
+ return products.map((product) => {
1115
+ const normalizedProduct = { ...product };
1116
+ for (const [key, value] of Object.entries(product)) {
1117
+ if (typeof value === "string" && value.trim() !== "" && IMAGE_FIELD_NAMES_LOWER.has(key.toLowerCase())) {
1118
+ normalizedProduct[key] = normalizeCachedImage(value, "product");
1119
+ }
1120
+ }
1121
+ return normalizedProduct;
1122
+ });
1123
+ }
1124
+ async function renderPdfToUint8Array(doc) {
1125
+ const isNodeRuntime = typeof process !== "undefined" && typeof process.versions?.node === "string";
1126
+ if (isNodeRuntime && typeof renderer.renderToBuffer === "function") {
1127
+ const buffer = await renderer.renderToBuffer(doc);
1128
+ return buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
1129
+ }
1130
+ const blob = await renderer.pdf(doc).toBlob();
1131
+ const arrayBuffer = await blob.arrayBuffer();
1132
+ return new Uint8Array(arrayBuffer);
1133
+ }
1008
1134
  async function generateProductCatalog(config) {
1009
- const { products, companyLogo, companyName, template = "template1" } = config;
1135
+ const { products, companyLogo, companyName, template = "template1", imageOptimization } = config;
1136
+ const optimizationOptions = getImageOptimizationOptions(imageOptimization);
1137
+ const normalizedProducts = normalizeProductImages(products, optimizationOptions);
1138
+ const normalizedCompanyLogo = companyLogo ? optimizeImageUrl(companyLogo, optimizationOptions, "logo") : companyLogo;
1010
1139
  let TemplateComponent;
1011
1140
  switch (template) {
1012
1141
  case "template1":
@@ -1034,39 +1163,33 @@ async function generateProductCatalog(config) {
1034
1163
  const doc2 = /* @__PURE__ */ jsxRuntime.jsx(renderer.Document, { children: /* @__PURE__ */ jsxRuntime.jsx(renderer.Page, { size: "A4", style: styles7.page, children: /* @__PURE__ */ jsxRuntime.jsx(
1035
1164
  Template4_default,
1036
1165
  {
1037
- products,
1038
- companyLogo,
1166
+ products: normalizedProducts,
1167
+ companyLogo: normalizedCompanyLogo,
1039
1168
  companyName
1040
1169
  }
1041
1170
  ) }) });
1042
- const blob2 = await renderer.pdf(doc2).toBlob();
1043
- const arrayBuffer2 = await blob2.arrayBuffer();
1044
- return new Uint8Array(arrayBuffer2);
1171
+ return renderPdfToUint8Array(doc2);
1045
1172
  }
1046
1173
  if (template === "template5" || template === "template6") {
1047
1174
  const Template = template === "template5" ? Template5_default : Template6_default;
1048
1175
  const doc2 = /* @__PURE__ */ jsxRuntime.jsx(renderer.Document, { children: /* @__PURE__ */ jsxRuntime.jsx(renderer.Page, { size: "A4", orientation: "landscape", style: styles7.page, children: /* @__PURE__ */ jsxRuntime.jsx(
1049
1176
  Template,
1050
1177
  {
1051
- products,
1052
- companyLogo,
1178
+ products: normalizedProducts,
1179
+ companyLogo: normalizedCompanyLogo,
1053
1180
  companyName
1054
1181
  }
1055
1182
  ) }) });
1056
- const blob2 = await renderer.pdf(doc2).toBlob();
1057
- const arrayBuffer2 = await blob2.arrayBuffer();
1058
- return new Uint8Array(arrayBuffer2);
1183
+ return renderPdfToUint8Array(doc2);
1059
1184
  }
1060
1185
  const doc = /* @__PURE__ */ jsxRuntime.jsx(renderer.Document, { children: /* @__PURE__ */ jsxRuntime.jsxs(renderer.Page, { size: "A4", style: styles7.page, children: [
1061
1186
  /* @__PURE__ */ jsxRuntime.jsxs(renderer.View, { style: styles7.header, children: [
1062
- companyLogo && /* @__PURE__ */ jsxRuntime.jsx(renderer.View, { style: styles7.logoSection, children: /* @__PURE__ */ jsxRuntime.jsx(renderer.Image, { style: styles7.logo, src: companyLogo }) }),
1187
+ normalizedCompanyLogo && /* @__PURE__ */ jsxRuntime.jsx(renderer.View, { style: styles7.logoSection, children: /* @__PURE__ */ jsxRuntime.jsx(renderer.Image, { style: styles7.logo, src: normalizedCompanyLogo }) }),
1063
1188
  /* @__PURE__ */ jsxRuntime.jsx(renderer.View, { style: styles7.storeNameSection, children: /* @__PURE__ */ jsxRuntime.jsx(renderer.Text, { style: styles7.storeName, children: companyName }) })
1064
1189
  ] }),
1065
- /* @__PURE__ */ jsxRuntime.jsx(TemplateComponent, { products })
1190
+ /* @__PURE__ */ jsxRuntime.jsx(TemplateComponent, { products: normalizedProducts })
1066
1191
  ] }) });
1067
- const blob = await renderer.pdf(doc).toBlob();
1068
- const arrayBuffer = await blob.arrayBuffer();
1069
- return new Uint8Array(arrayBuffer);
1192
+ return renderPdfToUint8Array(doc);
1070
1193
  }
1071
1194
  function parseExcelFile(buffer) {
1072
1195
  const workbook = XLSX__namespace.read(buffer, {