@softwear/latestcollectioncore 1.0.194 → 1.0.196

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.
@@ -61,6 +61,7 @@ const GLOBAL_PATTERNS = [
61
61
  ['colorCodeSupplier|colorSupplier', 'articleCode|articleCodeSupplier'],
62
62
  ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier'],
63
63
  ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier', 'buyPrice|valuePrice'],
64
+ ['brand', 'articleDescription', 'colorSupplier'],
64
65
  ];
65
66
  function prepFilterSkusImages(filteredSkus, merge = true, fileNames) {
66
67
  const skusCopy = (0, deepCopy_1.default)(filteredSkus);
@@ -161,7 +162,25 @@ function checkWithoutLast(fileName) {
161
162
  returnWithoutLast = true;
162
163
  else if (lastToken.length <= 2 && /^[A-Za-zÀ-ÖØ-öø-ÿ0-9]+$/.test(lastToken))
163
164
  returnWithoutLast = true;
165
+ const tokenLength = tokens.length;
164
166
  if (returnWithoutLast) {
167
+ let sliceIndex = -1;
168
+ if (tokenLength > 3 && tokens[tokenLength - 3] == 'press' && ((tokens[tokenLength - 2] == 'pack') || (tokens[tokenLength - 2] == 'model')))
169
+ sliceIndex = -3;
170
+ else if (tokenLength > 2 && tokens[tokenLength - 2] == 'packshot')
171
+ sliceIndex = -2;
172
+ return tokens
173
+ .slice(0, sliceIndex)
174
+ .join('')
175
+ .replace(/[^\p{L}\p{N}]+/gu, '');
176
+ }
177
+ if (tokenLength > 2 && tokens[tokenLength - 2] == 'press' && ((tokens[tokenLength - 1] == 'pack') || (tokens[tokenLength - 1] == 'model'))) {
178
+ return tokens
179
+ .slice(0, -2)
180
+ .join('')
181
+ .replace(/[^\p{L}\p{N}]+/gu, '');
182
+ }
183
+ else if (tokenLength > 1 && tokens[tokenLength - 1] == 'packshot') {
165
184
  return tokens
166
185
  .slice(0, -1)
167
186
  .join('')
package/dist/reports.d.ts CHANGED
@@ -36,6 +36,13 @@ export interface GenPdfOptions {
36
36
  loadFont?: (url: string) => Promise<string>;
37
37
  loadImage?: (url: string) => Promise<PdfImageAsset | undefined>;
38
38
  }
39
+ /** Fit image in layout box preserving aspect ratio (like CSS object-fit: contain). Stretch only when objectFit is fill/stretch. */
40
+ export declare function computeImageDrawRect(x: number, y: number, boxWidth: number, boxHeight: number, imageWidth: number, imageHeight: number, objectFit?: string): {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ };
39
46
  export declare function genPDF(layout: Layout, printBuffer: any, options?: GenPdfOptions): Promise<jsPDF>;
40
47
  declare const _default: {
41
48
  genPDF: typeof genPDF;
package/dist/reports.js CHANGED
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.genPDF = void 0;
15
+ exports.genPDF = exports.computeImageDrawRect = void 0;
16
16
  const jspdf_1 = require("jspdf");
17
17
  const date_fns_1 = require("date-fns");
18
18
  const deepCopy_1 = __importDefault(require("./deepCopy"));
@@ -152,6 +152,23 @@ function getImageFormat(url, contentType, bytes) {
152
152
  function getImageMimeType(format) {
153
153
  return format === 'PNG' ? 'image/png' : 'image/jpeg';
154
154
  }
155
+ /** Fit image in layout box preserving aspect ratio (like CSS object-fit: contain). Stretch only when objectFit is fill/stretch. */
156
+ function computeImageDrawRect(x, y, boxWidth, boxHeight, imageWidth, imageHeight, objectFit) {
157
+ let drawX = x;
158
+ let drawY = y;
159
+ let drawWidth = boxWidth;
160
+ let drawHeight = boxHeight;
161
+ const stretch = objectFit === 'fill' || objectFit === 'stretch';
162
+ if (!stretch && imageWidth > 0 && imageHeight > 0) {
163
+ const scale = Math.min(boxWidth / imageWidth, boxHeight / imageHeight);
164
+ drawWidth = imageWidth * scale;
165
+ drawHeight = imageHeight * scale;
166
+ drawX = x + (boxWidth - drawWidth) / 2;
167
+ drawY = y + (boxHeight - drawHeight) / 2;
168
+ }
169
+ return { x: drawX, y: drawY, width: drawWidth, height: drawHeight };
170
+ }
171
+ exports.computeImageDrawRect = computeImageDrawRect;
155
172
  function getImageDimensions(bytes, format) {
156
173
  if (format === 'PNG' && bytes.length >= 24 && isPng(bytes)) {
157
174
  return { width: readUint32BE(bytes, 16), height: readUint32BE(bytes, 20) };
@@ -270,7 +287,7 @@ function truncateTextToFitWidth(doc, text, maxWidth) {
270
287
  return truncatedText + ELLIPSIS;
271
288
  }
272
289
  function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context) {
273
- var _a, _b;
290
+ var _a, _b, _c, _d;
274
291
  let text = ''; // Will hold text to display for either 'text' or 'field'
275
292
  // Get text from object for text objects
276
293
  if (object.type == 'text') {
@@ -351,18 +368,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
351
368
  const urlLower = String(imgUrl).toLowerCase();
352
369
  const imageAsset = (_a = context.imageAssets) === null || _a === void 0 ? void 0 : _a.get(String(imgUrl));
353
370
  const format = (imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.format) || (urlLower.endsWith('.png') ? 'PNG' : urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg') ? 'JPEG' : 'JPEG');
354
- let drawX = x;
355
- let drawY = y;
356
- let drawWidth = width;
357
- let drawHeight = height;
358
- // Preserve aspect ratio when objectFit is 'contain' and we have dimensions
359
- if (imgObj.objectFit === 'contain' && (imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.width) && (imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.height)) {
360
- const scale = Math.min(width / imageAsset.width, height / imageAsset.height);
361
- drawWidth = imageAsset.width * scale;
362
- drawHeight = imageAsset.height * scale;
363
- drawX = x + (width - drawWidth) / 2;
364
- drawY = y + (height - drawHeight) / 2;
365
- }
371
+ const { x: drawX, y: drawY, width: drawWidth, height: drawHeight } = computeImageDrawRect(x, y, width, height, (_b = imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.width) !== null && _b !== void 0 ? _b : 0, (_c = imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.height) !== null && _c !== void 0 ? _c : 0, imgObj.objectFit);
366
372
  try {
367
373
  doc.addImage((imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.dataUrl) || imgUrl, format, drawX, drawY, drawWidth, drawHeight);
368
374
  }
@@ -388,7 +394,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
388
394
  return;
389
395
  const fontStyleStr = FONT_STYLES[object.fontStyle & 3] || 'normal';
390
396
  // Use object font if it's a custom font; otherwise layout default overrides standard fonts (Helvetica etc.)
391
- const fontFamily = object.fontFamily && ((_b = options.isCustomPdfFont) === null || _b === void 0 ? void 0 : _b.call(options, object.fontFamily)) ? object.fontFamily : context.defaultFontFamily || object.fontFamily || 'Helvetica';
397
+ const fontFamily = object.fontFamily && ((_d = options.isCustomPdfFont) === null || _d === void 0 ? void 0 : _d.call(options, object.fontFamily)) ? object.fontFamily : context.defaultFontFamily || object.fontFamily || 'Helvetica';
392
398
  doc.setFont(fontFamily, fontStyleStr);
393
399
  doc.setFontSize(object.fontSize / 4);
394
400
  doc.text(truncateTextToFitWidth(doc, text, width), x, y, { align: textAlign, baseline: 'hanging' });
@@ -686,7 +692,7 @@ function genPDF(layout, printBuffer, options = {}) {
686
692
  object.url = logoUrl;
687
693
  }
688
694
  });
689
- // Preload image bytes for Node.js and dimensions for objectFit:contain
695
+ // Preload image bytes for Node.js and pixel dimensions for aspect-ratio fitting
690
696
  const imageAssets = new Map();
691
697
  const loadImage = options.loadImage || loadImageForPdf;
692
698
  const urls = collectImageUrlsFromLayout(layout, printBuffer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softwear/latestcollectioncore",
3
- "version": "1.0.194",
3
+ "version": "1.0.196",
4
4
  "description": "Core functions for LatestCollections applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -26,6 +26,7 @@ const GLOBAL_PATTERNS = [
26
26
  ['colorCodeSupplier|colorSupplier', 'articleCode|articleCodeSupplier'],
27
27
  ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier'],
28
28
  ['brand', 'collectionSupplier', 'colorCodeSupplier', 'articleCode|articleCodeSupplier', 'buyPrice|valuePrice'],
29
+ ['brand', 'articleDescription', 'colorSupplier'],
29
30
  ]
30
31
 
31
32
  function prepFilterSkusImages(filteredSkus: SkuI[], merge = true, fileNames: Array<string>) {
@@ -123,7 +124,22 @@ function checkWithoutLast(fileName: string) {
123
124
  else if (Number(lastToken)) returnWithoutLast = true
124
125
  else if (lastToken.length == 1 && /^[A-Za-zÀ-ÖØ-öø-ÿ]+$/.test(lastToken)) returnWithoutLast = true
125
126
  else if (lastToken.length <= 2 && /^[A-Za-zÀ-ÖØ-öø-ÿ0-9]+$/.test(lastToken)) returnWithoutLast = true
127
+ const tokenLength = tokens.length
126
128
  if (returnWithoutLast) {
129
+ let sliceIndex = -1
130
+ if (tokenLength > 3 && tokens[tokenLength - 3] == 'press' && ((tokens[tokenLength - 2] == 'pack') || (tokens[tokenLength - 2] == 'model'))) sliceIndex = -3
131
+ else if (tokenLength > 2 && tokens[tokenLength - 2] == 'packshot') sliceIndex = -2
132
+ return tokens
133
+ .slice(0, sliceIndex)
134
+ .join('')
135
+ .replace(/[^\p{L}\p{N}]+/gu, '')
136
+ }
137
+ if (tokenLength > 2 && tokens[tokenLength - 2] == 'press' && ((tokens[tokenLength - 1] == 'pack') || (tokens[tokenLength - 1] == 'model'))) {
138
+ return tokens
139
+ .slice(0, -2)
140
+ .join('')
141
+ .replace(/[^\p{L}\p{N}]+/gu, '')
142
+ } else if (tokenLength > 1 && tokens[tokenLength - 1] == 'packshot') {
127
143
  return tokens
128
144
  .slice(0, -1)
129
145
  .join('')
package/src/reports.ts CHANGED
@@ -219,6 +219,31 @@ function getImageMimeType(format: 'PNG' | 'JPEG'): string {
219
219
  return format === 'PNG' ? 'image/png' : 'image/jpeg'
220
220
  }
221
221
 
222
+ /** Fit image in layout box preserving aspect ratio (like CSS object-fit: contain). Stretch only when objectFit is fill/stretch. */
223
+ export function computeImageDrawRect(
224
+ x: number,
225
+ y: number,
226
+ boxWidth: number,
227
+ boxHeight: number,
228
+ imageWidth: number,
229
+ imageHeight: number,
230
+ objectFit?: string
231
+ ): { x: number; y: number; width: number; height: number } {
232
+ let drawX = x
233
+ let drawY = y
234
+ let drawWidth = boxWidth
235
+ let drawHeight = boxHeight
236
+ const stretch = objectFit === 'fill' || objectFit === 'stretch'
237
+ if (!stretch && imageWidth > 0 && imageHeight > 0) {
238
+ const scale = Math.min(boxWidth / imageWidth, boxHeight / imageHeight)
239
+ drawWidth = imageWidth * scale
240
+ drawHeight = imageHeight * scale
241
+ drawX = x + (boxWidth - drawWidth) / 2
242
+ drawY = y + (boxHeight - drawHeight) / 2
243
+ }
244
+ return { x: drawX, y: drawY, width: drawWidth, height: drawHeight }
245
+ }
246
+
222
247
  function getImageDimensions(bytes: Uint8Array, format: 'PNG' | 'JPEG'): { width?: number; height?: number } {
223
248
  if (format === 'PNG' && bytes.length >= 24 && isPng(bytes)) {
224
249
  return { width: readUint32BE(bytes, 16), height: readUint32BE(bytes, 20) }
@@ -411,18 +436,15 @@ function drawSimpleObject(x: number, y: number, doc: jsPDF, object: LayoutObject
411
436
  const urlLower = String(imgUrl).toLowerCase()
412
437
  const imageAsset = context.imageAssets?.get(String(imgUrl))
413
438
  const format = imageAsset?.format || (urlLower.endsWith('.png') ? 'PNG' : urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg') ? 'JPEG' : 'JPEG')
414
- let drawX = x
415
- let drawY = y
416
- let drawWidth = width
417
- let drawHeight = height
418
- // Preserve aspect ratio when objectFit is 'contain' and we have dimensions
419
- if (imgObj.objectFit === 'contain' && imageAsset?.width && imageAsset?.height) {
420
- const scale = Math.min(width / imageAsset.width, height / imageAsset.height)
421
- drawWidth = imageAsset.width * scale
422
- drawHeight = imageAsset.height * scale
423
- drawX = x + (width - drawWidth) / 2
424
- drawY = y + (height - drawHeight) / 2
425
- }
439
+ const { x: drawX, y: drawY, width: drawWidth, height: drawHeight } = computeImageDrawRect(
440
+ x,
441
+ y,
442
+ width,
443
+ height,
444
+ imageAsset?.width ?? 0,
445
+ imageAsset?.height ?? 0,
446
+ imgObj.objectFit
447
+ )
426
448
  try {
427
449
  doc.addImage(imageAsset?.dataUrl || imgUrl, format, drawX, drawY, drawWidth, drawHeight)
428
450
  } catch (_e) {
@@ -773,7 +795,7 @@ export async function genPDF(layout: Layout, printBuffer: any, options: GenPdfOp
773
795
  if (logoUrl) object.url = logoUrl
774
796
  }
775
797
  })
776
- // Preload image bytes for Node.js and dimensions for objectFit:contain
798
+ // Preload image bytes for Node.js and pixel dimensions for aspect-ratio fitting
777
799
  const imageAssets = new Map<string, PdfImageAsset>()
778
800
  const loadImage = options.loadImage || loadImageForPdf
779
801
  const urls = collectImageUrlsFromLayout(layout, printBuffer)
@@ -1,7 +1,19 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { genPDF } from '../src/reports'
2
+ import { computeImageDrawRect, genPDF } from '../src/reports'
3
3
  import type { Layout } from '../src/pdf'
4
4
 
5
+ describe('computeImageDrawRect', () => {
6
+ it('preserves aspect ratio by default (object-fit: contain)', () => {
7
+ const rect = computeImageDrawRect(10, 20, 40, 30, 500, 500)
8
+ expect(rect).toEqual({ x: 15, y: 20, width: 30, height: 30 })
9
+ })
10
+
11
+ it('stretches when objectFit is fill', () => {
12
+ const rect = computeImageDrawRect(10, 20, 40, 30, 500, 375, 'fill')
13
+ expect(rect).toEqual({ x: 10, y: 20, width: 40, height: 30 })
14
+ })
15
+ })
16
+
5
17
  /** Tiny valid JPEG as data URL — avoids flaky network-dependent image fetch in CI/sandbox. */
6
18
  function offlineJpegFixture(): string {
7
19
  const b64 =