@softwear/latestcollectioncore 1.0.191 → 1.0.194
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/dist/index.d.ts +2 -0
- package/dist/index.js +6 -1
- package/dist/layoutFieldFormat.d.ts +16 -0
- package/dist/layoutFieldFormat.js +60 -0
- package/dist/pdf.d.ts +2 -1
- package/dist/reports.d.ts +2 -0
- package/dist/reports.js +16 -31
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/layoutFieldFormat.ts +60 -0
- package/src/pdf.ts +3 -1
- package/src/reports.ts +14 -22
- package/test/layoutFieldFormat.spec.ts +69 -0
- package/test/reports.spec.ts +54 -0
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { default as reports, genPDF } from './reports';
|
|
|
19
19
|
export { default as round2 } from './round2';
|
|
20
20
|
export { default as sizeToMap } from './sizeToMap';
|
|
21
21
|
export { default as transaction } from './transaction';
|
|
22
|
+
export type { LayoutFieldFormat } from './layoutFieldFormat';
|
|
23
|
+
export { isCurrencyFieldFormat, isZeroFieldValue, resolveLayoutFieldText, shouldSuppressZeroField } from './layoutFieldFormat';
|
|
22
24
|
export * from './pdf';
|
|
23
25
|
export type { GenPdfAlert, GenPdfOptions, PdfFontDefinition, PdfFontVariant, PdfImageAsset } from './reports';
|
|
24
26
|
export * from './types';
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.isAxiosError = exports.exponentialDelay = exports.axiosRetry = exports.createAxiosInstance = exports.lcAxios = exports.transaction = exports.sizeToMap = exports.round2 = exports.genPDF = exports.reports = exports.projectObject = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findMetaBrandSetting = exports.findSkuByBarcode = exports.ensureImpliedProperties = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
|
|
20
|
+
exports.isAxiosError = exports.exponentialDelay = exports.axiosRetry = exports.createAxiosInstance = exports.lcAxios = exports.shouldSuppressZeroField = exports.resolveLayoutFieldText = exports.isZeroFieldValue = exports.isCurrencyFieldFormat = exports.transaction = exports.sizeToMap = exports.round2 = exports.genPDF = exports.reports = exports.projectObject = exports.pivotTable = exports.isean13 = exports.imageBinder = exports.hasOnlyDigits = exports.hashBrand = exports.getPreferedPropertyMappings = exports.getBrandName = exports.findMetaBrandSetting = exports.findSkuByBarcode = exports.ensureImpliedProperties = exports.ensureArray = exports.edifact = exports.ean13 = exports.deepCopy = exports.buildPropertyMappingFn = exports.articleStatus = void 0;
|
|
21
21
|
var articleStatus_1 = require("./articleStatus");
|
|
22
22
|
Object.defineProperty(exports, "articleStatus", { enumerable: true, get: function () { return __importDefault(articleStatus_1).default; } });
|
|
23
23
|
var buildPropertyMappingFn_1 = require("./buildPropertyMappingFn");
|
|
@@ -61,6 +61,11 @@ var sizeToMap_1 = require("./sizeToMap");
|
|
|
61
61
|
Object.defineProperty(exports, "sizeToMap", { enumerable: true, get: function () { return __importDefault(sizeToMap_1).default; } });
|
|
62
62
|
var transaction_1 = require("./transaction");
|
|
63
63
|
Object.defineProperty(exports, "transaction", { enumerable: true, get: function () { return __importDefault(transaction_1).default; } });
|
|
64
|
+
var layoutFieldFormat_1 = require("./layoutFieldFormat");
|
|
65
|
+
Object.defineProperty(exports, "isCurrencyFieldFormat", { enumerable: true, get: function () { return layoutFieldFormat_1.isCurrencyFieldFormat; } });
|
|
66
|
+
Object.defineProperty(exports, "isZeroFieldValue", { enumerable: true, get: function () { return layoutFieldFormat_1.isZeroFieldValue; } });
|
|
67
|
+
Object.defineProperty(exports, "resolveLayoutFieldText", { enumerable: true, get: function () { return layoutFieldFormat_1.resolveLayoutFieldText; } });
|
|
68
|
+
Object.defineProperty(exports, "shouldSuppressZeroField", { enumerable: true, get: function () { return layoutFieldFormat_1.shouldSuppressZeroField; } });
|
|
64
69
|
__exportStar(require("./pdf"), exports);
|
|
65
70
|
__exportStar(require("./types"), exports);
|
|
66
71
|
__exportStar(require("./consts"), exports);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type LayoutFieldFormat = 'text' | 'number' | 'date' | 'currency' | 'number-nonzero' | 'currency-nonzero';
|
|
2
|
+
export interface ResolveLayoutFieldTextOptions {
|
|
3
|
+
pageNumber?: number;
|
|
4
|
+
pageCount?: number;
|
|
5
|
+
formatDate: (raw: number | string) => string;
|
|
6
|
+
formatCurrency?: (raw: string) => string;
|
|
7
|
+
onFieldNotFound?: (source: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function isZeroFieldValue(value: unknown): boolean;
|
|
10
|
+
export declare function isCurrencyFieldFormat(format: string | undefined): boolean;
|
|
11
|
+
export declare function shouldSuppressZeroField(format: string | undefined): boolean;
|
|
12
|
+
export declare function resolveLayoutFieldText(object: {
|
|
13
|
+
format?: string;
|
|
14
|
+
source?: string;
|
|
15
|
+
type?: string;
|
|
16
|
+
}, printBuffer: any, getProperty: (propertyName: string, object: any) => any, options: ResolveLayoutFieldTextOptions): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveLayoutFieldText = exports.shouldSuppressZeroField = exports.isCurrencyFieldFormat = exports.isZeroFieldValue = void 0;
|
|
4
|
+
function isZeroFieldValue(value) {
|
|
5
|
+
if (value === 0 || value === '0')
|
|
6
|
+
return true;
|
|
7
|
+
if (typeof value === 'number')
|
|
8
|
+
return value === 0;
|
|
9
|
+
if (typeof value !== 'string')
|
|
10
|
+
return false;
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
if (trimmed === '0')
|
|
13
|
+
return true;
|
|
14
|
+
const normalized = trimmed.replace(',', '.');
|
|
15
|
+
const parsed = parseFloat(normalized);
|
|
16
|
+
return trimmed !== '' && !Number.isNaN(parsed) && parsed === 0;
|
|
17
|
+
}
|
|
18
|
+
exports.isZeroFieldValue = isZeroFieldValue;
|
|
19
|
+
function isCurrencyFieldFormat(format) {
|
|
20
|
+
return format === 'currency' || format === 'currency-nonzero';
|
|
21
|
+
}
|
|
22
|
+
exports.isCurrencyFieldFormat = isCurrencyFieldFormat;
|
|
23
|
+
function shouldSuppressZeroField(format) {
|
|
24
|
+
return format === 'currency-nonzero' || format === 'number-nonzero';
|
|
25
|
+
}
|
|
26
|
+
exports.shouldSuppressZeroField = shouldSuppressZeroField;
|
|
27
|
+
function resolveLayoutFieldText(object, printBuffer, getProperty, options) {
|
|
28
|
+
var _a, _b, _c, _d, _e;
|
|
29
|
+
if (object.type !== 'field')
|
|
30
|
+
return '';
|
|
31
|
+
let raw;
|
|
32
|
+
if (object.source === 'pageNumber')
|
|
33
|
+
raw = String((_a = options.pageNumber) !== null && _a !== void 0 ? _a : '');
|
|
34
|
+
else if (object.source === 'pageCount')
|
|
35
|
+
raw = String((_b = options.pageCount) !== null && _b !== void 0 ? _b : 0);
|
|
36
|
+
else if (object.source === 'page')
|
|
37
|
+
raw = `${(_c = options.pageNumber) !== null && _c !== void 0 ? _c : ''} / ${(_d = options.pageCount) !== null && _d !== void 0 ? _d : 0}`;
|
|
38
|
+
else if (object.source && printBuffer !== undefined && printBuffer !== null)
|
|
39
|
+
raw = getProperty(object.source, printBuffer);
|
|
40
|
+
else
|
|
41
|
+
raw = printBuffer;
|
|
42
|
+
if (shouldSuppressZeroField(object.format) && isZeroFieldValue(raw))
|
|
43
|
+
return '';
|
|
44
|
+
let text;
|
|
45
|
+
if (typeof raw === 'number')
|
|
46
|
+
text = String(raw);
|
|
47
|
+
else if (raw === undefined || raw === null) {
|
|
48
|
+
(_e = options.onFieldNotFound) === null || _e === void 0 ? void 0 : _e.call(options, object.source || '');
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
text = String(raw);
|
|
53
|
+
if (object.format === 'date')
|
|
54
|
+
text = options.formatDate(parseInt(text));
|
|
55
|
+
if (isCurrencyFieldFormat(object.format)) {
|
|
56
|
+
text = options.formatCurrency ? options.formatCurrency(text) : '€ ' + parseFloat(text).toFixed(2);
|
|
57
|
+
}
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
exports.resolveLayoutFieldText = resolveLayoutFieldText;
|
package/dist/pdf.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LayoutFieldFormat } from './layoutFieldFormat';
|
|
1
2
|
export interface PaperSize {
|
|
2
3
|
width: number;
|
|
3
4
|
height: number;
|
|
@@ -28,7 +29,7 @@ export interface TextLayoutObject extends BaseLayoutObject {
|
|
|
28
29
|
export interface FieldLayoutObject extends BaseLayoutObject {
|
|
29
30
|
type: 'field';
|
|
30
31
|
source?: string;
|
|
31
|
-
format?:
|
|
32
|
+
format?: LayoutFieldFormat;
|
|
32
33
|
textAlign?: TextAlign;
|
|
33
34
|
fontFamily: string;
|
|
34
35
|
fontSize: number;
|
package/dist/reports.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface GenPdfOptions {
|
|
|
32
32
|
resolveLogoUrl?: () => string | undefined;
|
|
33
33
|
isCustomPdfFont?: (fontFamily: string) => boolean;
|
|
34
34
|
getPdfFontDefinition?: (fontFamily: string) => PdfFontDefinition | undefined;
|
|
35
|
+
/** Load TTF bytes as base64 (e.g. from disk in Node). Default: fetch variant.url. */
|
|
36
|
+
loadFont?: (url: string) => Promise<string>;
|
|
35
37
|
loadImage?: (url: string) => Promise<PdfImageAsset | undefined>;
|
|
36
38
|
}
|
|
37
39
|
export declare function genPDF(layout: Layout, printBuffer: any, options?: GenPdfOptions): Promise<jsPDF>;
|
package/dist/reports.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.genPDF = 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"));
|
|
19
|
+
const layoutFieldFormat_1 = require("./layoutFieldFormat");
|
|
19
20
|
/** Measurement uses context `currentPageNumber` (1-based). Rendering uses jsPDF’s `getCurrentPageInfo().pageNumber`, which is already 1-based (first page is 1). */
|
|
20
21
|
function getCurrentPageNumber(doc, context) {
|
|
21
22
|
var _a;
|
|
@@ -245,9 +246,10 @@ function embedFontsInDoc(doc, usedFontFamilies, options) {
|
|
|
245
246
|
if (def.variants.length === 0) {
|
|
246
247
|
continue;
|
|
247
248
|
}
|
|
249
|
+
const loadFont = options.loadFont || fetchFontAsBase64;
|
|
248
250
|
for (const variant of def.variants) {
|
|
249
251
|
const vfsName = `${family}-${variant.style}.ttf`;
|
|
250
|
-
const base64 = yield
|
|
252
|
+
const base64 = yield loadFont(variant.url);
|
|
251
253
|
doc.addFileToVFS(vfsName, base64);
|
|
252
254
|
doc.addFont(vfsName, family, variant.style, 'Identity-H');
|
|
253
255
|
}
|
|
@@ -268,7 +270,7 @@ function truncateTextToFitWidth(doc, text, maxWidth) {
|
|
|
268
270
|
return truncatedText + ELLIPSIS;
|
|
269
271
|
}
|
|
270
272
|
function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options, context) {
|
|
271
|
-
var _a, _b
|
|
273
|
+
var _a, _b;
|
|
272
274
|
let text = ''; // Will hold text to display for either 'text' or 'field'
|
|
273
275
|
// Get text from object for text objects
|
|
274
276
|
if (object.type == 'text') {
|
|
@@ -276,33 +278,16 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
|
|
|
276
278
|
}
|
|
277
279
|
// Get text from printBuffer for field objects
|
|
278
280
|
if (object.type == 'field') {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
text = getProperty(object.source, printBuffer);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
text = printBuffer;
|
|
293
|
-
}
|
|
294
|
-
if (typeof text == 'number')
|
|
295
|
-
text = '' + text;
|
|
296
|
-
if (object.format == 'date')
|
|
297
|
-
text = formatDate(parseInt(text));
|
|
298
|
-
if (text === undefined || typeof text != 'string') {
|
|
299
|
-
(_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'fieldnotfound', body: object.source, type: 'warning', timeout: 10000 });
|
|
300
|
-
text = '';
|
|
301
|
-
}
|
|
302
|
-
if (object.format == 'currency') {
|
|
303
|
-
const raw = text;
|
|
304
|
-
text = options.formatCurrency ? options.formatCurrency(raw) : '€ ' + parseFloat(raw).toFixed(2);
|
|
305
|
-
}
|
|
281
|
+
text = (0, layoutFieldFormat_1.resolveLayoutFieldText)(object, printBuffer, getProperty, {
|
|
282
|
+
pageNumber: doc.getNumberOfPages(),
|
|
283
|
+
pageCount: context.pageCount || 0,
|
|
284
|
+
formatDate,
|
|
285
|
+
formatCurrency: options.formatCurrency,
|
|
286
|
+
onFieldNotFound: (source) => {
|
|
287
|
+
var _a;
|
|
288
|
+
(_a = options.onAlert) === null || _a === void 0 ? void 0 : _a.call(options, { header: 'fieldnotfound', body: source, type: 'warning', timeout: 10000 });
|
|
289
|
+
},
|
|
290
|
+
});
|
|
306
291
|
}
|
|
307
292
|
// Manipulate case as specified in object
|
|
308
293
|
if ((object.type === 'text' || object.type === 'field') && object.case === 1)
|
|
@@ -364,7 +349,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
|
|
|
364
349
|
const imgUrl = resolveImageUrl(object, printBuffer);
|
|
365
350
|
if (imgUrl) {
|
|
366
351
|
const urlLower = String(imgUrl).toLowerCase();
|
|
367
|
-
const imageAsset = (
|
|
352
|
+
const imageAsset = (_a = context.imageAssets) === null || _a === void 0 ? void 0 : _a.get(String(imgUrl));
|
|
368
353
|
const format = (imageAsset === null || imageAsset === void 0 ? void 0 : imageAsset.format) || (urlLower.endsWith('.png') ? 'PNG' : urlLower.endsWith('.jpg') || urlLower.endsWith('.jpeg') ? 'JPEG' : 'JPEG');
|
|
369
354
|
let drawX = x;
|
|
370
355
|
let drawY = y;
|
|
@@ -403,7 +388,7 @@ function drawSimpleObject(x, y, doc, object, printBuffer, width, height, options
|
|
|
403
388
|
return;
|
|
404
389
|
const fontStyleStr = FONT_STYLES[object.fontStyle & 3] || 'normal';
|
|
405
390
|
// Use object font if it's a custom font; otherwise layout default overrides standard fonts (Helvetica etc.)
|
|
406
|
-
const fontFamily = object.fontFamily && ((
|
|
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';
|
|
407
392
|
doc.setFont(fontFamily, fontStyleStr);
|
|
408
393
|
doc.setFontSize(object.fontSize / 4);
|
|
409
394
|
doc.text(truncateTextToFitWidth(doc, text, width), x, y, { align: textAlign, baseline: 'hanging' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softwear/latestcollectioncore",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.194",
|
|
4
4
|
"description": "Core functions for LatestCollections applications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc -p tsconfig.json",
|
|
19
19
|
"build:watch": "tsc -p tsconfig.json --watch",
|
|
20
|
-
"test": "vitest run",
|
|
21
|
-
"test:watch": "vitest"
|
|
20
|
+
"test": "TZ=Europe/Amsterdam vitest run",
|
|
21
|
+
"test:watch": "TZ=Europe/Amsterdam vitest"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { default as reports, genPDF } from './reports'
|
|
|
19
19
|
export { default as round2 } from './round2'
|
|
20
20
|
export { default as sizeToMap } from './sizeToMap'
|
|
21
21
|
export { default as transaction } from './transaction'
|
|
22
|
+
export type { LayoutFieldFormat } from './layoutFieldFormat'
|
|
23
|
+
export { isCurrencyFieldFormat, isZeroFieldValue, resolveLayoutFieldText, shouldSuppressZeroField } from './layoutFieldFormat'
|
|
22
24
|
export * from './pdf'
|
|
23
25
|
export type { GenPdfAlert, GenPdfOptions, PdfFontDefinition, PdfFontVariant, PdfImageAsset } from './reports'
|
|
24
26
|
export * from './types'
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type LayoutFieldFormat = 'text' | 'number' | 'date' | 'currency' | 'number-nonzero' | 'currency-nonzero'
|
|
2
|
+
|
|
3
|
+
export interface ResolveLayoutFieldTextOptions {
|
|
4
|
+
pageNumber?: number
|
|
5
|
+
pageCount?: number
|
|
6
|
+
formatDate: (raw: number | string) => string
|
|
7
|
+
formatCurrency?: (raw: string) => string
|
|
8
|
+
onFieldNotFound?: (source: string) => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isZeroFieldValue(value: unknown): boolean {
|
|
12
|
+
if (value === 0 || value === '0') return true
|
|
13
|
+
if (typeof value === 'number') return value === 0
|
|
14
|
+
if (typeof value !== 'string') return false
|
|
15
|
+
const trimmed = value.trim()
|
|
16
|
+
if (trimmed === '0') return true
|
|
17
|
+
const normalized = trimmed.replace(',', '.')
|
|
18
|
+
const parsed = parseFloat(normalized)
|
|
19
|
+
return trimmed !== '' && !Number.isNaN(parsed) && parsed === 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isCurrencyFieldFormat(format: string | undefined): boolean {
|
|
23
|
+
return format === 'currency' || format === 'currency-nonzero'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function shouldSuppressZeroField(format: string | undefined): boolean {
|
|
27
|
+
return format === 'currency-nonzero' || format === 'number-nonzero'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function resolveLayoutFieldText(
|
|
31
|
+
object: { format?: string; source?: string; type?: string },
|
|
32
|
+
printBuffer: any,
|
|
33
|
+
getProperty: (propertyName: string, object: any) => any,
|
|
34
|
+
options: ResolveLayoutFieldTextOptions,
|
|
35
|
+
): string {
|
|
36
|
+
if (object.type !== 'field') return ''
|
|
37
|
+
|
|
38
|
+
let raw: unknown
|
|
39
|
+
if (object.source === 'pageNumber') raw = String(options.pageNumber ?? '')
|
|
40
|
+
else if (object.source === 'pageCount') raw = String(options.pageCount ?? 0)
|
|
41
|
+
else if (object.source === 'page') raw = `${options.pageNumber ?? ''} / ${options.pageCount ?? 0}`
|
|
42
|
+
else if (object.source && printBuffer !== undefined && printBuffer !== null) raw = getProperty(object.source, printBuffer)
|
|
43
|
+
else raw = printBuffer
|
|
44
|
+
|
|
45
|
+
if (shouldSuppressZeroField(object.format) && isZeroFieldValue(raw)) return ''
|
|
46
|
+
|
|
47
|
+
let text: string
|
|
48
|
+
if (typeof raw === 'number') text = String(raw)
|
|
49
|
+
else if (raw === undefined || raw === null) {
|
|
50
|
+
options.onFieldNotFound?.(object.source || '')
|
|
51
|
+
return ''
|
|
52
|
+
} else text = String(raw)
|
|
53
|
+
|
|
54
|
+
if (object.format === 'date') text = options.formatDate(parseInt(text))
|
|
55
|
+
if (isCurrencyFieldFormat(object.format)) {
|
|
56
|
+
text = options.formatCurrency ? options.formatCurrency(text) : '€ ' + parseFloat(text).toFixed(2)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return text
|
|
60
|
+
}
|
package/src/pdf.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LayoutFieldFormat } from './layoutFieldFormat'
|
|
2
|
+
|
|
1
3
|
export interface PaperSize {
|
|
2
4
|
width: number
|
|
3
5
|
height: number
|
|
@@ -32,7 +34,7 @@ export interface TextLayoutObject extends BaseLayoutObject {
|
|
|
32
34
|
export interface FieldLayoutObject extends BaseLayoutObject {
|
|
33
35
|
type: 'field'
|
|
34
36
|
source?: string
|
|
35
|
-
format?:
|
|
37
|
+
format?: LayoutFieldFormat
|
|
36
38
|
textAlign?: TextAlign
|
|
37
39
|
fontFamily: string
|
|
38
40
|
fontSize: number
|
package/src/reports.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsPDF } from 'jspdf'
|
|
2
2
|
import { format } from 'date-fns'
|
|
3
3
|
import deepCopy from './deepCopy'
|
|
4
|
+
import { resolveLayoutFieldText } from './layoutFieldFormat'
|
|
4
5
|
import { PaperSize, Layout, LayoutObject, ContainerLayoutObject } from './pdf'
|
|
5
6
|
|
|
6
7
|
type RenderMode = 'measurement' | 'rendering'
|
|
@@ -41,6 +42,8 @@ export interface GenPdfOptions {
|
|
|
41
42
|
resolveLogoUrl?: () => string | undefined
|
|
42
43
|
isCustomPdfFont?: (fontFamily: string) => boolean
|
|
43
44
|
getPdfFontDefinition?: (fontFamily: string) => PdfFontDefinition | undefined
|
|
45
|
+
/** Load TTF bytes as base64 (e.g. from disk in Node). Default: fetch variant.url. */
|
|
46
|
+
loadFont?: (url: string) => Promise<string>
|
|
44
47
|
loadImage?: (url: string) => Promise<PdfImageAsset | undefined>
|
|
45
48
|
}
|
|
46
49
|
|
|
@@ -311,9 +314,10 @@ async function embedFontsInDoc(doc: jsPDF, usedFontFamilies: Set<string>, option
|
|
|
311
314
|
if (def.variants.length === 0) {
|
|
312
315
|
continue
|
|
313
316
|
}
|
|
317
|
+
const loadFont = options.loadFont || fetchFontAsBase64
|
|
314
318
|
for (const variant of def.variants) {
|
|
315
319
|
const vfsName = `${family}-${variant.style}.ttf`
|
|
316
|
-
const base64 = await
|
|
320
|
+
const base64 = await loadFont(variant.url)
|
|
317
321
|
doc.addFileToVFS(vfsName, base64)
|
|
318
322
|
doc.addFont(vfsName, family, variant.style, 'Identity-H')
|
|
319
323
|
}
|
|
@@ -342,27 +346,15 @@ function drawSimpleObject(x: number, y: number, doc: jsPDF, object: LayoutObject
|
|
|
342
346
|
}
|
|
343
347
|
// Get text from printBuffer for field objects
|
|
344
348
|
if (object.type == 'field') {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
text = printBuffer
|
|
355
|
-
}
|
|
356
|
-
if (typeof text == 'number') text = '' + text
|
|
357
|
-
if (object.format == 'date') text = formatDate(parseInt(text))
|
|
358
|
-
if (text === undefined || typeof text != 'string') {
|
|
359
|
-
options.onAlert?.({ header: 'fieldnotfound', body: object.source, type: 'warning', timeout: 10000 })
|
|
360
|
-
text = ''
|
|
361
|
-
}
|
|
362
|
-
if (object.format == 'currency') {
|
|
363
|
-
const raw = text
|
|
364
|
-
text = options.formatCurrency ? options.formatCurrency(raw) : '€ ' + parseFloat(raw).toFixed(2)
|
|
365
|
-
}
|
|
349
|
+
text = resolveLayoutFieldText(object, printBuffer, getProperty, {
|
|
350
|
+
pageNumber: doc.getNumberOfPages(),
|
|
351
|
+
pageCount: context.pageCount || 0,
|
|
352
|
+
formatDate,
|
|
353
|
+
formatCurrency: options.formatCurrency,
|
|
354
|
+
onFieldNotFound: (source) => {
|
|
355
|
+
options.onAlert?.({ header: 'fieldnotfound', body: source, type: 'warning', timeout: 10000 })
|
|
356
|
+
},
|
|
357
|
+
})
|
|
366
358
|
}
|
|
367
359
|
// Manipulate case as specified in object
|
|
368
360
|
if ((object.type === 'text' || object.type === 'field') && object.case === 1) text = text.toUpperCase()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { isZeroFieldValue, resolveLayoutFieldText } from '../src/layoutFieldFormat'
|
|
3
|
+
|
|
4
|
+
const getProperty = (propertyName: string, object: any) => object[propertyName]
|
|
5
|
+
|
|
6
|
+
describe('isZeroFieldValue', () => {
|
|
7
|
+
it('detects numeric and string zero values', () => {
|
|
8
|
+
expect(isZeroFieldValue(0)).toBe(true)
|
|
9
|
+
expect(isZeroFieldValue('0')).toBe(true)
|
|
10
|
+
expect(isZeroFieldValue('0.00')).toBe(true)
|
|
11
|
+
expect(isZeroFieldValue('0,00')).toBe(true)
|
|
12
|
+
expect(isZeroFieldValue(' 0.0 ')).toBe(true)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('does not treat empty or non-zero values as zero', () => {
|
|
16
|
+
expect(isZeroFieldValue('')).toBe(false)
|
|
17
|
+
expect(isZeroFieldValue(null)).toBe(false)
|
|
18
|
+
expect(isZeroFieldValue(undefined)).toBe(false)
|
|
19
|
+
expect(isZeroFieldValue('10')).toBe(false)
|
|
20
|
+
expect(isZeroFieldValue(0.01)).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('resolveLayoutFieldText', () => {
|
|
25
|
+
const baseOptions = {
|
|
26
|
+
formatDate: (raw: number | string) => `DATE:${raw}`,
|
|
27
|
+
formatCurrency: (raw: string) => `CUR:${raw}`,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
it('suppresses currency-nonzero fields with zero values', () => {
|
|
31
|
+
const text = resolveLayoutFieldText(
|
|
32
|
+
{ type: 'field', source: 'amount', format: 'currency-nonzero' },
|
|
33
|
+
{ amount: '0.00' },
|
|
34
|
+
getProperty,
|
|
35
|
+
baseOptions,
|
|
36
|
+
)
|
|
37
|
+
expect(text).toBe('')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('formats non-zero currency-nonzero fields as currency', () => {
|
|
41
|
+
const text = resolveLayoutFieldText(
|
|
42
|
+
{ type: 'field', source: 'amount', format: 'currency-nonzero' },
|
|
43
|
+
{ amount: '12.5' },
|
|
44
|
+
getProperty,
|
|
45
|
+
baseOptions,
|
|
46
|
+
)
|
|
47
|
+
expect(text).toBe('CUR:12.5')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('suppresses number-nonzero fields with zero values', () => {
|
|
51
|
+
const text = resolveLayoutFieldText(
|
|
52
|
+
{ type: 'field', source: 'qty', format: 'number-nonzero' },
|
|
53
|
+
{ qty: 0 },
|
|
54
|
+
getProperty,
|
|
55
|
+
baseOptions,
|
|
56
|
+
)
|
|
57
|
+
expect(text).toBe('')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('keeps regular number fields at zero', () => {
|
|
61
|
+
const text = resolveLayoutFieldText(
|
|
62
|
+
{ type: 'field', source: 'qty', format: 'number' },
|
|
63
|
+
{ qty: 0 },
|
|
64
|
+
getProperty,
|
|
65
|
+
baseOptions,
|
|
66
|
+
)
|
|
67
|
+
expect(text).toBe('0')
|
|
68
|
+
})
|
|
69
|
+
})
|
package/test/reports.spec.ts
CHANGED
|
@@ -389,4 +389,58 @@ describe('genPDF', () => {
|
|
|
389
389
|
// jsPDF encodes U+20AC as WinAnsi 0x80 in the text operator; still one glyph before digits.
|
|
390
390
|
expect(pdfText).toMatch(/\(\s*.\s*10\.00\) Tj/)
|
|
391
391
|
})
|
|
392
|
+
|
|
393
|
+
it('suppresses currency-nonzero fields when the value is zero', async () => {
|
|
394
|
+
const layout: Layout = {
|
|
395
|
+
name: 'currency-nonzero',
|
|
396
|
+
paperSize: { width: 210, height: 297, footerHeight: 20 },
|
|
397
|
+
objects: [
|
|
398
|
+
{
|
|
399
|
+
type: 'field' as const,
|
|
400
|
+
name: 'discount',
|
|
401
|
+
x: 20,
|
|
402
|
+
y: 20,
|
|
403
|
+
width: 500,
|
|
404
|
+
height: 50,
|
|
405
|
+
active: true,
|
|
406
|
+
source: 'discount',
|
|
407
|
+
format: 'currency-nonzero' as const,
|
|
408
|
+
textAlign: 1 as const,
|
|
409
|
+
fontFamily: 'Helvetica',
|
|
410
|
+
fontSize: 32,
|
|
411
|
+
fontStyle: 0,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
}
|
|
415
|
+
const doc = await genPDF(layout, { discount: '0.00' })
|
|
416
|
+
const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
|
|
417
|
+
expect(pdfText).not.toMatch(/0\.00/)
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it('renders currency-nonzero fields when the value is non-zero', async () => {
|
|
421
|
+
const layout: Layout = {
|
|
422
|
+
name: 'currency-nonzero-value',
|
|
423
|
+
paperSize: { width: 210, height: 297, footerHeight: 20 },
|
|
424
|
+
objects: [
|
|
425
|
+
{
|
|
426
|
+
type: 'field' as const,
|
|
427
|
+
name: 'discount',
|
|
428
|
+
x: 20,
|
|
429
|
+
y: 20,
|
|
430
|
+
width: 500,
|
|
431
|
+
height: 50,
|
|
432
|
+
active: true,
|
|
433
|
+
source: 'discount',
|
|
434
|
+
format: 'currency-nonzero' as const,
|
|
435
|
+
textAlign: 1 as const,
|
|
436
|
+
fontFamily: 'Helvetica',
|
|
437
|
+
fontSize: 32,
|
|
438
|
+
fontStyle: 0,
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
}
|
|
442
|
+
const doc = await genPDF(layout, { discount: '5' })
|
|
443
|
+
const pdfText = Buffer.from(doc.output('arraybuffer')).toString('latin1')
|
|
444
|
+
expect(pdfText).toMatch(/\(\s*.\s*5\.00\) Tj/)
|
|
445
|
+
})
|
|
392
446
|
})
|