PageSourceSearch

https://app.appec.it/files/js/my-pdfmaker.js?v=2.8.2

js appec.it collected 2026-09-24 14:23:55 UTC 50,704 bytes, 1,233 lines download raw bytes

1function convertToDataURLviaCanvas(url, outputFormat, max_size = 100) {
2    return new Promise(function (resolve, rejected) {
3        var imageObj1 = new Image();
4        imageObj1.src = url;
5        imageObj1.onload = function () {
6            var c = document.createElement("canvas");
7            c.width = max_size;
8            c.height = max_size;
9            let width = 0;
10            let height = 0;
11            if (imageObj1.height > imageObj1.width) {
12                height = max_size;
13                width = (imageObj1.width / (imageObj1.height / height));
14            } else {
15                width = max_size;
16                height = (imageObj1.height / (imageObj1.width / width));
17            }
18            var ctx = c.getContext("2d");
19            ctx.drawImage(imageObj1, ((max_size - width) / 2), ((max_size - height) / 2), width, height);
20            const dataUrl = c.toDataURL();
21            resolve(dataUrl);
22        };
23        imageObj1.onerror = async function (e) {
24            resolve(await convertToDataURLviaCanvas("resources/images/default_logo.svg", "data:image/svg+xml"));
25        };
26    });
27}
28
29function svgFileToString(iconpath) {
30    return new Promise(function (resolve, rejected) {
31        fetch(iconpath).then(response => response.text()).then(text => {
32            resolve(text);
33        });
34    });
35}
36
37function getColor(name, type = 'hex') {
38    let colorString = getComputedStyle(document.documentElement).getPropertyValue(name).toString().trim();
39    if (!colorString) {
40        colorString = '#ffffff';
41    }
42    let color = tinycolor(colorString);
43    if (type == 'hex') {
44        return color.toHexString();
45    }
46    if (type == 'rgb') {
47        return color.toRgbString();
48    }
49    if (type == 'rgb-array') {
50        return Object.values(color.toRgb()).slice(0, 3);
51    }
52    return 'rgb(255,255,255)';
53}
54// Bilder fuer den PDF-Export: einmal pro Export laden und klein halten (spart RAM und Dateigroesse)
55const pdfTableImageMaxPx = 96;
56const pdfImageCache = new Map();
57function clearPdfImageCache() {
58    pdfImageCache.clear();
59}
60function cachedPdfImage(key, factory) {
61    if (!pdfImageCache.has(key)) {
62        pdfImageCache.set(key, factory());
63    }
64    return pdfImageCache.get(key);
65}
66function getImageDimensions(file) {
67    return cachedPdfImage('dim|' + file, function () {
68        return new Promise(function (resolve, rejected) {
69            var i = new Image()
70            i.onload = function () {
71                const scale = Math.min(1, pdfTableImageMaxPx / Math.max(i.width, i.height));
72                const canvas = document.createElement('canvas');
73                const ctx = canvas.getContext('2d');
74                canvas.width = Math.max(1, Math.round(i.width * scale));
75                canvas.height = Math.max(1, Math.round(i.height * scale));
76                ctx.drawImage(i, 0, 0, canvas.width, canvas.height);
77                const dataUrl = canvas.toDataURL();
78                resolve({ w: canvas.width, h: canvas.height, data: dataUrl })
79            };
80            i.onerror = async function () {
81                // kaputter/fehlender Bildlink: Standardbild statt endlosem Warten
82                resolve({ w: 100, h: 100, data: await convertToDataURLviaCanvas("resources/images/default_logo.svg", "data:image/svg+xml") });
83            };
84            i.src = file
85        });
86    });
87}
88
89// Grosse Fotos vor dem Einbetten verkleinern. Gibt null zurueck, wenn das Original bleiben kann.
90function downscaleImageToDataUrl(url, maxPx = 1200, quality = 0.85) {
91    return new Promise(function (resolve) {
92        var i = new Image();
93        i.onload = function () {
94            if (Math.max(i.width, i.height) <= maxPx) {
95                resolve(null);
96                return;
97            }
98            const scale = maxPx / Math.max(i.width, i.height);
99            const canvas = document.createElement('canvas');
100            canvas.width = Math.round(i.width * scale);
101            canvas.height = Math.round(i.height * scale);
102            const ctx = canvas.getContext('2d');
103            ctx.fillStyle = '#ffffff';
104            ctx.fillRect(0, 0, canvas.width, canvas.height);
105            ctx.drawImage(i, 0, 0, canvas.width, canvas.height);
106            resolve(canvas.toDataURL('image/jpeg', quality));
107        };
108        i.onerror = function () {
109            resolve(null);
110        };
111        i.src = url;
112    });
113}
114
115function mergeImages(image1, image2) {
116    return new Promise(function (resolve, rejected) {
117        var imageObj1 = new Image();
118        var imageObj2 = new Image();
119        imageObj1.src = image1;
120        imageObj1.onerror = function () {
121            resolve(image1);
122        };
123        imageObj1.onload = function () {
124            imageObj2.src = image2;
125            imageObj2.onerror = function () {
126                resolve(image1);
127            };
128            imageObj2.onload = function () {
129                var c = document.createElement("canvas");
130                c.width = 100;
131                c.height = 100;
132                let width = 0;
133                let height = 0;
134                if (imageObj1.height > imageObj1.width) {
135                    height = 100;
136                    width = (imageObj1.width / (imageObj1.height / height));
137                } else {
138                    width = 100;
139                    height = (imageObj1.height / (imageObj1.width / width));
140                }
141                var ctx = c.getContext("2d");
142                ctx.drawImage(imageObj1, ((100 - width) / 2), ((100 - height) / 2), width, height);
143                ctx.drawImage(imageObj2, 60, 60, 40, 40);
144                const dataUrl = c.toDataURL();
145                resolve(dataUrl);
146            }
147        };
148    });
149}
150
151const pdfMakerPTsPerMM = 2.83465;
152const pdfMakerPrefix = location.protocol + '//' + location.host + $('base').attr('href');
153const pdfMakerPageSize = { a4: { l: { h: 210 * pdfMakerPTsPerMM, w: 297 * pdfMakerPTsPerMM }, p: { w: 210 * pdfMakerPTsPerMM, h: 297 * pdfMakerPTsPerMM } }, a3: { l: { h: 297 * pdfMakerPTsPerMM, w: 410 * pdfMakerPTsPerMM }, p: { w: 297 * pdfMakerPTsPerMM, h: 410 * pdfMakerPTsPerMM } } };
154let pdfMakerSettings = {
155    border: 15 * pdfMakerPTsPerMM,
156    logoAPPECHeight: 7 * pdfMakerPTsPerMM,
157    logoAPPECWidth: 25 * pdfMakerPTsPerMM,
158    borderRightDiffA3: 5 * pdfMakerPTsPerMM,
159    borderRightDiffA4: 15 * pdfMakerPTsPerMM
160};
161// init Fonts
162pdfMake.fonts = {
163    montserrat: {
164        normal: pdfMakerPrefix + 'files/webfonts/montserrat/Montserrat-Regular.ttf',
165        bold: pdfMakerPrefix + 'files/webfonts/montserrat/Montserrat-Bold.ttf',
166        italics: pdfMakerPrefix + 'files/webfonts/montserrat/Montserrat-Italic.ttf',
167        bolditalics: pdfMakerPrefix + 'files/webfonts/montserrat/Montserrat-BoldItalic.ttf'
168    }
169};
170// options: continuation (Folgeteil einer Tabelle: ohne Titel/Logos), onProgress(zeile, gesamt), quiet (Ladeanzeige nicht anfassen), asBytes (Uint8Array statt Data-URL)
171function tableListToPDFMaker(data, accountLogo = null, title = '', ref_date = '', show_ref_date = true, options = {}) {
172    return new Promise(async function (resolve, reject) {
173
174        // init Fixed IMAGES
175        let myImages = {};
176        myImages.logo = {
177            url: pdfMakerPrefix + 'resources/images/logo-pdf.png'
178        };
179        if (accountLogo) {
180            myImages.accountLogo = {
181                url: pdfMakerPrefix + accountLogo
182            };
183        }
184
185        let idxHiddenType = data[0].findIndex(x => x.name == 'hidden_type');
186        let hiddenTypes = [];
187        if (idxHiddenType >= 0) {
188            for (let i = 0; i < data.length; i++) {
189                hiddenTypes.push(data[i].splice(idxHiddenType, 1));
190            }
191        }
192
193        let idxSoonToOld = data[0].findIndex(x => x.name == 'soon_to_old');
194        let soonToOld = [];
195        if (idxSoonToOld >= 0) {
196            for (let i = 0; i < data.length; i++) {
197                soonToOld.push(data[i].splice(idxSoonToOld, 1));
198            }
199        }
200
201        let idxDarwDirLine = data[0].findIndex(x => x.name == 'add_row_line');
202        let drawRowLine = [];
203        if (idxDarwDirLine >= 0) {
204            for (let i = 0; i < data.length; i++) {
205                drawRowLine.push(data[i].splice(idxDarwDirLine, 1));
206            }
207        }
208        let idxDirName = data[0].findIndex(x => x.col_name == 'dir_names');
209        // Manipulate-Data
210        // Column Length of Table
211        let columnCount = 0;
212        let dataForTableBody = [];
213        let myWidths = [];
214
215        for (let i = 0; i < data.length; i++) {
216            columnCount = data[i].length;
217        }
218
219        // Head-Row Color
220        dataForTableBody.push(data[0].map((x) => {
221            myWidths.push(x.width);
222            return { text: x.name, color: getColor('--color-primary-contrast'), bold: true };
223        }));
224        if (!myWidths.includes('*')) {
225            if (myWidths.length > 2) {
226                myWidths[2] = '*';
227            } else if (myWidths.length > 1) {
228                myWidths[1] = '*';
229            }
230        }
231        if (language == 'bz') {
232            dataForTableBody.push(data[0].map((x) => {
233                return { text: otherTranslate['it'][x.const_name], color: getColor('--color-primary-contrast'), bold: true };
234            }));
235        }
236
237        // Generate Body Rows
238        let tmpArr = data.slice(1);
239        for (let i = 0; i < tmpArr.length; i++) {
240            for (let j = 0; j < tmpArr[i].length; j++) {
241                let col = {};
242                if (tmpArr[i][j] == null || tmpArr[i][j] == '') {
243                    col['text'] = '/';
244                } else {
245                    // Change br to nl
246                    tmpArr[i][j] = tmpArr[i][j].toString().replace('<br />', "\n");
247                    // Add Not Ok String
248                    if (tmpArr[i][j].startsWith('EXPORT_DATE_NOT_OK ')) {
249                        col = {
250                            text: tmpArr[i][j].replace('EXPORT_DATE_NOT_OK ', (translate['TEST']['DATE_NOT_OK'] + (language == 'bz' ? "/" + otherTranslate['it']['TEST']['DATE_NOT_OK'] : '') + "\n")),
251                            bold: true
252                        }
253                        // Change Image-Url to Table Image
254                    } else if (tmpArr[i][j].match(/\.(jpeg|jpg|png|svg)$/) != null) {
255                        const imageUrl = tmpArr[i][j];
256                        const isDir = (idxHiddenType >= 0 && hiddenTypes[i + 1] == 'dir');
257                        const isSoonToOld = (idxSoonToOld >= 0 && soonToOld[i + 1] == '1');
258                        // gleiche Bilder (z. B. gleiches Modell) nur einmal laden und verkleinern
259                        let tmpImageData = await cachedPdfImage('row|' + imageUrl + '|' + isDir + '|' + isSoonToOld, async function () {
260                            let imageData = null;
261                            if (imageUrl.match(/\.(svg)$/) != null) {
262                                imageData = await convertToDataURLviaCanvas(imageUrl, "data:image/svg+xml");
263                            } else {
264                                imageData = (await getImageDimensions(imageUrl)).data;
265                            }
266                            if (isDir) {
267                                imageData = await mergeImages(imageData, pdfMakerPrefix + 'resources/images/default_dir.svg');
268                            }
269                            if (isSoonToOld) {
270                                imageData = await mergeImages(imageData, pdfMakerPrefix + 'resources/images/soon_to_old.svg');
271                            }
272                            return imageData;
273                        });
274                        if (options.onProgress) {
275                            options.onProgress(i + 1, tmpArr.length);
276                        } else {
277                            showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED'] + '<br />' + translate['TEST']["FILE_WILL_BE_EXPORTED_STEP_5"].replace('##', (i + 2)).replace('%%', tmpArr.length));
278                        }
279                        col = {
280                            image: tmpImageData,
281                            fit: [40, 40],
282                            metaBackgroundCell: null
283                        }
284                    } else if (tmpArr[i][j].startsWith('STATE_')) {
285                        let stateColor = '--color-font-default';
286                        switch (tmpArr[i][j]) {
287                            case 'STATE_1_TO_OLD':
288                                stateColor = '--color-to-old';
289                                break;
290                            case 'STATE_2_OVERDUE':
291                            case 'STATE_TEST_3_NOT_OK':
292                                stateColor = '--color-danger';
293                                break;
294                            case 'STATE_3_UPCOMING':
295                                stateColor = '--color-warning';
296                                break;
297                            case 'STATE_4_INSPECTED':
298                            case 'STATE_TEST_1_OK':
299                                stateColor = '--color-success';
300                                break;
301                            case 'STATE_5_IN_REPAIR':
302                            case 'STATE_TEST_2_IN_REPAIR':
303                                stateColor = '--color-in-repair';
304                                break;
305                            case 'STATE_6_IN_STOCK':
306                                stateColor = '--color-default-medium';
307                                break;
308                            case 'STATE_7_NOT_BE_CHECKED':
309                                stateColor = '--color-light-default';
310                                break;
311                            case 'STATE_8_NOT_FOUND':
312                                stateColor = '--color-not-found';
313                                break;
314                            case 'STATE_9_DISCARDED':
315                                stateColor = '--color-not-ok';
316                                break;
317                            case 'STATE_10_EMPTY':
318                                stateColor = '--color-font-default';
319                                break;
320                        }
321                        col = { text: getStateTranslate(tmpArr[i][j]), metaBackgroundCell: getColor(stateColor), color: getColor(stateColor + '-contrast'), bold: true/*, fillOpacity: 0.5*/ }
322                    } else {
323                        col['text'] = tmpArr[i][j];
324                    }
325                }
326                tmpArr[i][j] = col;
327            }
328            if (idxDarwDirLine >= 0) {
329                if (drawRowLine[i + 1] == 'ADD_ROW_LINE') {
330                    dataForTableBody.push([{
331                        colSpan: columnCount,
332                        text: tmpArr[i][idxDirName],
333                        border: [false, true, false, true],
334                        alignment: 'center',
335                        bold: true,
336                        style: 'fontSizeNormal',
337                        margin: [0, 5, 0, 5]
338                    }]);
339                }
340            }
341            dataForTableBody.push(tmpArr[i]);
342        }
343        if (!options.quiet) {
344            await hideLoading(true);
345            await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_6']);
346        }
347
348        // Generate Content
349        let myContent = [];
350
351        // Title
352        if (!options.continuation) {
353            myContent.push({
354                text: title,
355                style: 'fontSizeHeading2'
356            });
357        }
358
359        // Ref-Date
360        if (ref_date !== '' && show_ref_date && !options.continuation) {
361            myContent.push({
362                text: (translate['ARTICLE']['REF_DATE'] + (language == 'bz' ? ' - ' + otherTranslate['it']['ARTICLE']['REF_DATE'] : '') + ': ' + ref_date),
363                style: 'fontSizeNormal',
364                absolutePosition: { x: pdfMakerSettings.border, y: 11 * pdfMakerPTsPerMM }
365            });
366        }
367
368        // Logo
369        if (!options.continuation) {
370            myContent.push({
371                image: 'logo',
372                width: pdfMakerSettings.logoAPPECWidth,
373                height: pdfMakerSettings.logoAPPECHeight,
374                absolutePosition: { x: (((columnCount > 9) ? (pdfMakerPageSize.a3.l.w - pdfMakerSettings.borderRightDiffA3) : (pdfMakerPageSize.a4.l.w - pdfMakerSettings.borderRightDiffA4)) - pdfMakerSettings.logoAPPECWidth), y: pdfMakerSettings.border }
375            });
376        }
377
378        // Account-Logo
379        if (accountLogo && !options.continuation) {
380            myContent.push({
381                image: 'accountLogo',
382                fit: [pdfMakerSettings.logoAPPECWidth, pdfMakerSettings.logoAPPECHeight],
383                absolutePosition: { x: (((columnCount > 9) ? (pdfMakerPageSize.a3.l.w - pdfMakerSettings.borderRightDiffA3) : (pdfMakerPageSize.a4.l.w - pdfMakerSettings.borderRightDiffA4)) - 10 - (pdfMakerSettings.logoAPPECWidth * 2)), y: pdfMakerSettings.border }
384            });
385        }
386        // Table
387        myContent.push({
388            style: ((columnCount > 15) ? 'fontSizeMini' : 'fontSizeSmall'),
389            absolutePosition: { x: pdfMakerSettings.border, y: pdfMakerSettings.border + (options.continuation ? 0 : (10 * pdfMakerPTsPerMM)) },
390            alignment: 'left',
391            table: {
392                widths: myWidths,
393                body: dataForTableBody,
394                dontBreakRows: true
395            },
396            layout: {
397                defaultBorder: false,
398                fillColor: function (rowIndex, node, columnIndex) {
399                    if (dataForTableBody[rowIndex][columnIndex]["metaBackgroun
399dCell"] !== undefined) {
400                        return dataForTableBody[rowIndex][columnIndex]["metaBackgroundCell"];
401                    }
402                    if (rowIndex == 0 || (rowIndex == 1 && language == 'bz')) {
403                        return getColor('--color-primary');
404                    } else if (rowIndex % 2 === 0) {
405                        return getColor('--color-light-default');
406                    }
407                    return null;
408                }
409            }
410        });
411
412        // Generate PDF
413        var docDefinition = {
414            pageSize: ((columnCount > 9) ? 'A3' : 'A4'),
415            pageOrientation: 'landscape',
416            compress: true,
417            pageBreakBefore: false,
418            pageMargins: [pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border],
419            defaultStyle: {
420                font: 'montserrat'
421            },
422            images: myImages,
423            styles: {
424                fontSizeMini: {
425                    fontSize: 6,
426                    lineHeight: 1
427                },
428                fontSizeSmall: {
429                    fontSize: 7,
430                    lineHeight: 1
431                },
432                fontSizeNormal: {
433                    fontSize: 9,
434                    lineHeight: 1
435                },
436                fontSizeBig: {
437                    fontSize: 11,
438                    lineHeight: 1
439                },
440                fontSizeHeading2: {
441                    fontSize: 16,
442                    lineHeight: 1
443                },
444                anotherStyle: {
445                    italics: true,
446                    alignment: 'right'
447                }
448            },
449            content: myContent
450        };
451        const pdfDocGenerator = pdfMake.createPdf(docDefinition);
452        if (options.asBytes) {
453            pdfDocGenerator.getBuffer((buffer) => {
454                resolve(buffer);
455            });
456        } else {
457            pdfDocGenerator.getDataUrl((dataUrl) => {
458                resolve(dataUrl);
459            });
460        }
461    });
462}
463// options: asBytes (Uint8Array statt Base64-Data-URL)
464function testToPDF(data, accountLogo = null, with_attachments = false, options = {}) {
465    return new Promise(async function (resolve, reject) {
466
467        // init Fixed IMAGES
468        let myImages = {};
469        myImages.logo = {
470            url: pdfMakerPrefix + 'resources/images/logo-pdf.png'
471        };
472        myImages.questionIcons3 = {
473            url: pdfMakerPrefix + 'resources/images/question-icon-3.png'
474        };
475        myImages.questionIcons2 = {
476            url: pdfMakerPrefix + 'resources/images/question-icon-2.png'
477        };
478        myImages.questionIcons1 = {
479            url: pdfMakerPrefix + 'resources/images/question-icon-1.png'
480        };
481        myImages.questionIcons0 = {
482            url: pdfMakerPrefix + 'resources/images/question-icon-0.png'
483        };
484        if (accountLogo) {
485            myImages.accountLogo = {
486                url: pdfMakerPrefix + accountLogo
487            };
488        }
489        if (data['sign']) {
490            myImages.sign = {
491                url: pdfMakerPrefix + data['sign']
492            };
493        }
494
495        // Generate Content
496        let myContent = [];
497
498        // Title
499        myContent.push({
500            text: translate['TEST']['EXPORT_TEST_TITLE'] + (language == 'bz' ? " - " + otherTranslate['it']['TEST']['EXPORT_TEST_TITLE'] : ''),
501            style: 'fontSizeHeading2'
502        });
503
504        // Logo
505        myContent.push({
506            image: 'logo',
507            width: pdfMakerSettings.logoAPPECWidth,
508            height: pdfMakerSettings.logoAPPECHeight,
509            absolutePosition: { x: ((pdfMakerPageSize.a4.p.w - pdfMakerSettings.borderRightDiffA4) - pdfMakerSettings.logoAPPECWidth), y: pdfMakerSettings.border }
510        });
511
512        // Account-Logo
513        if (accountLogo) {
514            myContent.push({
515                image: 'accountLogo',
516                fit: [pdfMakerSettings.logoAPPECWidth, pdfMakerSettings.logoAPPECHeight],
517                absolutePosition: { x: ((pdfMakerPageSize.a4.p.w - pdfMakerSettings.borderRightDiffA4) - 10 - (pdfMakerSettings.logoAPPECWidth * 2)), y: pdfMakerSettings.border }
518            });
519        }
520        myContent.push({
521            canvas: [
522                {
523                    type: 'line',
524                    x1: 0, y1: (3 * pdfMakerPTsPerMM),
525                    x2: (pdfMakerPageSize.a4.p.w - (pdfMakerSettings.border * 2)), y2: (3 * pdfMakerPTsPerMM),
526                    lineWidth: 1,
527                    lineColor: getColor('--color-primary')
528                }
529            ]
530        });
531        let tableData = [
532            { col1: 'SERIAL_NUMBER', col2: 'article_serial_number' },
533            { col1: 'PERSONAL_ID', col2: 'article_personal_id' },
534            { col1: 'NFC_ID', col2: 'article_nfc_id' },
535            { col1: 'MANUFACTURE', col2: 'article_manufacture_name' },
536            { col1: 'MODEL', col2: 'article_model_name' },
537            { col1: 'MODEL_NR', col2: 'article_model_nr' },
538            { col1: 'MODEL_CATEGORY', col2: 'article_model_category_name' },
539            { col1: 'DATE_OF_MANUFACTURE', col2: 'article_date_of_manufacture' },
540            { col1: 'DATE_OF_PURCHASE', col2: 'article_date_of_purchase' },
541            { col1: 'DATE_FIRST_USE', col2: 'article_date_first_use' },
542
543        ];
544        if (data['article_expire_date'] != '') {
545            tableData.push(
546                { col1: 'EXPIRE_DATE', col2: 'article_expire_date', color: (data['article_to_old'] ? getColor('--color-danger') : (data['article_soon_to_old'] ? getColor('--color-warning-shade') : getColor('--color-font-default'))) }
547            );
548        }
549        tableData.push({ col1: 'DIR', col2: 'article_dir_names' });
550        tableData.push({ col1: 'MODEL_STANDARTS', col2: 'article_model_standarts' });
551
552        let myTableBody = [];
553        for (let i = 0; i < tableData.length; i++) {
554            myTableBody.push([
555                { text: (translate['ARTICLE'][tableData[i].col1] + (language == 'bz' ? "\n" + otherTranslate['it']['ARTICLE'][tableData[i].col1] : '')), bold: true, margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 2.5, 0, 2.5]), color: (tableData[i]['color'] ? tableData[i]['color'] : getColor('--color-font-default')) },
556                { text: data[tableData[i].col2], margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 2.5, 0, 2.5]), color: (tableData[i]['color'] ? tableData[i]['color'] : getColor('--color-font-default')), italics: true }
557            ]);
558        }
559        let myWidths = ['auto', '*'];
560        if (data['image']) {
561            myWidths = ['auto', '*', 'auto'];
562            // grosse Fotos verkleinern, sonst wird pro Pruefung das Original eingebettet
563            myImages.testImage = (await downscaleImageToDataUrl(pdfMakerPrefix + data['image'], 1200)) || {
564                url: pdfMakerPrefix + data['image']
565            };
566            myTableBody[0].push({
567                image: 'testImage',
568                fit: [(85 * pdfMakerPTsPerMM), (85 * pdfMakerPTsPerMM)],
569                rowSpan: myTableBody.length
570            })
571        }
572
573
574        // Table
575        myContent.push({
576            margin: [0, 10, 0, 0],
577            style: (language == 'bz' ? 'fontSizeSmall' : 'fontSizeNormal'),
578            alignment: 'left',
579            table: {
580                widths: myWidths,
581                body: myTableBody
582            },
583            layout: {
584                defaultBorder: false,
585                paddingLeft: function (i, node, colIdx) {
586                    return 0;
587                },
588                paddingRight: function (i, node, colIdx) {
589                    if (i == 0) {
590                        return 5;
591                    }
592                    return 0;
593                }
594            }
595        });
596
597        // Table
598        myContent.push({
599            margin: [0, 0, 0, 10],
600            style: (language == 'bz' ? 'fontSizeSmall' : 'fontSizeNormal'),
601            alignment: 'left',
602            table: {
603                widths: ['*'],
604                body: [
605                    [
606                        { text: (translate['ARTICLE']['COMMENT'] + (language == 'bz' ? " - " + otherTranslate['it']['ARTICLE']['COMMENT'] : '')), bold: true, margin: [0, 2.5, 0, 0] }
607                    ], [
608                        { text: data['article_comment'], margin: [0, 0, 0, 0], italics: true }
609                    ]
610                ]
611
612
613            },
614            layout: {
615                defaultBorder: false,
616                paddingLeft: function (i, node) { return 0; },
617                paddingRight: function (i, node) { return 0; }
618            }
619        });
620
621        // Table States
622        myContent.push({
623            style: 'fontSizeNormal',
624            alignment: 'left',
625            table: {
626                widths: [(5 * pdfMakerPTsPerMM), "*", (5 * pdfMakerPTsPerMM), "*", (5 * pdfMakerPTsPerMM), "*", (5 * pdfMakerPTsPerMM), "*"],
627                body: [
628                    [
629                        {
630                            image: 'questionIcons3',
631                            fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
632                            margin: [0, 4, 0, 4]
633                        },
634                        {
635                            text: translate['TEST']['GOOD_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['GOOD_CONDITION'] : ''),
636                            margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 6, 0, 6])
637                        },
638                        {
639                            image: 'questionIcons2',
640                            fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
641                            margin: [0, 4, 0, 4]
642                        },
643                        {
644                            text: translate['TEST']['MONITOR_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['MONITOR_CONDITION'] : ''),
645                            margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 6, 0, 6])
646                        }, {
647                            image: 'questionIcons1',
648                            fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
649                            margin: [0, 4, 0, 4]
650                        },
651                        {
652                            text: translate['TEST']['BAD_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['BAD_CONDITION'] : ''),
653                            margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 6, 0, 6])
654                        }, {
655                            image: 'questionIcons0',
656                            fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
657                            margin: [0, 4, 0, 4]
658                        },
659                        {
660                            text: translate['TEST']['NOT_APPLICABLE'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['NOT_APPLICABLE'] : ''),
661                            margin: (language == 'bz' ? [0, 0, 0, 0] : [0, 6, 0, 6])
662                        }
663                    ]
664                ],
665                dontBreakRows: true
666            },
667            layout: {
668                hLineWidth: function (i, node) {
669                    return 1;
670                },
671                hLineColor: function (i, node) {
672                    return getColor('--color-default-medium');
673                },
674                vLineWidth: function (i, node) {
675                    return 0;
676                },
677                vLineColor: function (i, node) {
678                    return null;
679                }
680            }
681        });
682        const questionValueText = {
683            3: translate['TEST']['GOOD_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['GOOD_CONDITION'] : ''),
684            2: translate['TEST']['MONITOR_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['MONITOR_CONDITION'] : ''),
685            1: translate['TEST']['BAD_CONDITION'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['BAD_CONDITION'] : ''),
686            0: translate['TEST']['NOT_APPLICABLE'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['NOT_APPLICABLE'] : '')
687        };
688        for (let i = 0; i < data['checklists'].length; i++) {
689            let myChecklistBody = [];
690
691
692            myChecklistBody.push([
693                {
694                    stack:
695                        [
696                            {
697                                table: {
698                                    body: [
699                                        [
700                                            {
701                                                svg: await svgFileToString(pdfMakerPrefix + 'resources/images/question-icon-check.svg'),
702                                                fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)]
703                                            },
704                                            {
705                                                text: data['checklists'][i]['name'],
706                                                style: 'fontSizeBig',
707                                                bold: true
708                                            }
709                                        ]
710                                    ],
711                                    dontBreakRows: true
712                                },
713                                layout: {
714                                    defaultBorder: false,
715                                    margin: false,
716                                    paddingLeft: function (i, node) { return 0; },
717                                    paddingRight: function (i, node) { return 5; },
718                                    paddingTop: function (i, node) { return 0; },
719                                    paddingBottom: function (i, node) { return 0; }
720                                },
721                            }
722                        ],
723                    colSpan: 2,
724                    border: [false, false, false, true],
725                    padding: 0
726                },
727                {
728                }
729            ])
730            if (data['checklists'][i]['checklists_json'].length > 0) {
731                for (let j = 0; j < data['checklists'][i]['checklists_json'].length; j++) {
732                    if (data['checklists'][i]['checklists_json'][j]['type'] == 'text') {
733                        myChecklistBody.push([
734                            {
735                                stack:
736                                    [
737                                        {
738                                            text: data['checklists'][i]['checklists_json'][j]['text'], style: 'fontSizeSmall', margin: [0, 5, 0, 2], bold: true
739                                        }, {
740                                            text: data['checklists'][i]['checklists_json'][j]['value'], style: 'fontSizeSmall', margin: [0, 2, 0, 5], italics: true,
741                                        }
742                                    ],
743                                colSpan: 2
744                            }
745                        ]);
746                    } else {
747                        myChecklistBody.push([
748                            { text: data['checklists'][i]['checklists_json'][j]['text'], style: 'fontSizeSmall', margin: [0, 5, 0, 5] },
749                            {
750                                stack:
751                                    [
752                                        {
753                                            image: 'questionIcons' + (data['checklists'][i]['checklists_json'][j]['value']),
754                                            fit: [(5 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
755                                            alignment: 'center'
756                                        }, {
757                                            text: questionValueText[data['checklists'][i]['checklists_json'][j]['value']],
758                                            margin: [0, 2, 0, 0],
759                                            alignment: 'center'
760                                        }
761                                    ],
762                                margin: [0, 5, 0, 5],
763                                alignment: 'right'
764                            }
765                        ]);
766                    }
767
768                }
769            }
770            else {
771                myChecklistBody.push([
772                    {
773                        text: translate['TEST']['CHECKLIST_HAS_NO_QUESTIONS'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['CHECKLIST_HAS_NO_QUESTIONS'] : ''),
774                        colSpan: 2,
775                        border: [false, false, false, false],
776                        style: 'fontSizeBig',
777                        margin: [0, 5, 0, 5],
778                        alignment: 'center'
779                    }
780                ])
781            }
782            // Checklists
783            myContent.push({
784                style: 'fontSizeSmall',
785                alignment: 'left',
786                margin: [0, 10, 0, 10],
787                table: {
788                    widths: ['*', 70],
789                    body: myChecklistBody,
790                    dontBreakRows: true
791                },
792                layout: {
793                    hLineWidth: function (i, node) {
794                        return 1;
795                    },
796                    hLineColor: function (i, node) {
797                        if (i == 1) {
798                            return getColor('--color-primary');
799                        }
800                        return getColor('--color-default-medium');
801                    },
802                    vLineWidth: function (i, node) {
803                        return 0;
804                    },
805                    vLineColor: function (i, node) {
806                        return null;
807                    },
808                    paddingLeft: function (i, node) { return 0; },
809                    paddingRight: function (i, node) { return 0; }
810                }
811            });
812        }
813
814        // comment Test
815        myContent.push({
816            style: 'fontSizeNormal',
817            alignment: 'left',
818            margin: [0, 10, 0, 10],
819            table: {
820                widths: ['*'],
821                body: [
822                    [
823                        {
824                            text: (translate['TEST']['COMMENT'] + (language == 'bz' ? " - " + otherTranslate['it']['TEST']['COMMENT'] : '')), bold: true, margin: [0, 4, 0, 0], border: [false, true, false, false]
825                        }
826                    ],
827                    [
828                        { text: data['comment'], margin: [0, 0, 0, 4], italics: true, border: [false, false, false, true] }
829                    ]
830                ],
831                dontBreakRows: true
832            },
833            layout: {
834                hLineWidth: function (i, node) {
835                    return 1;
836                },
837                hLineColor: function (i, node) {
838                    return getColor('--color-primary');
839                },
840                vLineWidth: function (i, node) {
841                    return 0;
842                },
843                vLineColor: function (i, node) {
844                    return null;
845                },
846                paddingLeft: function (i, node) { return 0; },
847                paddingRight: function (i, node) { return 0; }
848            }
849        });
850
851        if (data['article_not_ok'] == '1') {
852            myContent.push(
853                { text: translate['TEST']['EXPORT_STATE_TEST_3_NOT_OK'] + (language == 'bz' ? " - " + otherTranslate['it']['TEST']['EXPORT_STATE_TEST_3_NOT_OK'] : ''), margin: [0, 4, 0, 4], bold: true, style: "fontSizeBig", color: getColor('--color-danger'), alignment: 'center' }
854            );
855        } else if (data['article_in_repair'] == '1') {
856            myContent.push(
857                { text: translate['TEST']['EXPORT_STATE_TEST_2_IN_REPAIR'] + (language == 'bz' ? " - " + otherTranslate['it']['TEST']['EXPORT_STATE_TEST_2_IN_REPAIR'] : ''), margin: [0, 4, 0, 4], bold: true, style: "fontSizeBig", color: getColor('--color-in-repair'), alignment: 'center' }
858            );
859        } else {
860            myContent.push(
861                { text: translate['TEST']['EXPORT_STATE_TEST_1_OK'] + (language == 'bz' ? " - " + otherTranslate['it']['TEST']['EXPORT_STATE_TEST_1_OK'] : ''), margin: [0, 4, 0, 4], bold: true, style: "fontSizeBig", color: getColor('--color-success'), alignment: 'center' }
862            );
863        }
864
865        myTableBody = [];
866
867        myTableBody.push([
868            {
869                text: translate['TEST']['EXPORT_TESTED_BY'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['EXPORT_TESTED_BY'] : ''),
870                bold: true,
871                margin: [0, 0, 5, 0]
872            },
873            {
874                text: data['tester_name'],
875                italics: true
876            },
877            {
878                text: translate['TEST']['EXPORT_ACCOUNT_NAME'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['EXPORT_ACCOUNT_NAME'] : ''),
879                bold: true,
880                margin: [0, 0, 5, 0]
881            },
882            {
883                text: data['account_name'],
884                italics: true
885            }
886        ]);
887        if (data['article_not_ok'] == '1') {
888            myTableBody.push([
889                {
890                    text: translate['TEST']['DATE'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['DATE'] : ''),
891                    bold: true,
892                    margin: [0, 0, 5, 0]
893                },
894                {
895                    text: data['date'],
896                    italics: true
897                },
898                {
899                    text: translate['TEST']['DATE_NOT_OK'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['DATE_NOT_OK'] : ''),
900                    bold: true,
901                    color: getColor('--color-danger'),
902                    margin: [0, 0, 5, 0]
903                },
904                {
905                    text: data['article_date_not_ok'],
906                    italics: true,
907                    color: getColor('--color-danger')
908                }
909            ]);
910        } else if (data['article_in_repair'] == '1') {
911            myTableBody.push([
912                {
913                    text: translate['TEST']['DATE'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['DATE'] : ''),
914                    bold: true,
915                    margin: [0, 0, 5, 0]
916                },
917                {
918                    text: data['date'],
919                    italics: true
920                },
921                {
922                    text: "",
923                    bold: true,
924                    color: getColor('--color-danger'),
925                    margin: [0, 0, 5, 0]
926                },
927                {
928                    text: "",
929                    italics: true,
930                    color: getColor('--color-danger')
931                }
932            ]);
933        } else {
934
935            myTableBody.push([
936                {
937                    text: translate['TEST']['DATE'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['DATE'] : ''),
938                    bold: true,
939                    margin: [0, 0, 5, 0]
940                },
941                {
942                    text: data['date'],
943                    italics: true
944                },
945                {
946                    text: translate['TEST']['DATE_NEXT_TEST'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['DATE_NEXT_TEST'] : ''),
947                    bold: true,
948                    margin: [0, 0, 5, 0]
949                },
950                {
951                    text: data['date_next_control'],
952                    italics: true
953                }
954            ]);
955        }
956
957        myContent.push({
958            style: 'fontSizeNormal',
959            alignment: 'left',
960            margin: [0, 10, 0, 10],
961            table: {
962                widths: ['auto', '*', 'auto', '*'],
963                body: myTableBody,
964                dontBreakRows: true
965            },
966            layout: {
967                hLineWidth: function (i, node) {
968                    return 0;
969                },
970                hLineColor: function (i, node) {
971                    return null;
972                },
973                vLineWidth: function (i, node) {
974                    return 0;
975                },
976                vLineColor: function (i, node) {
977                    return null;
978                },
979                paddingLeft: function (i, node) { return 0; },
980                paddingRight: function (i, node) { return 0; }
981            }
982        });
983
984        if (data['sign']) {
985            myContent.push({
986                image: 'sign',
987                fit: [(38 * pdfMakerPTsPerMM), (19 * pdfMakerPTsPerMM)]
988            });
989        } else {
990            myContent.push({
991                text: (translate['TEST']['EXPORT_SIGN'] + (language == 'bz' ? "\n" + otherTranslate['it']['TEST']['EXPORT_SIGN'] : ''))
992            });
993        }
994
995        // Generate PDF
996        var docDefinition = {
997            pageSize: 'A4',
998            pageOrientation: 'portrait',
999            compress: true,
1000            pageBreakBefore: false,
1001            pageMargins: [pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border],
1002            defaultStyle: {
1003                font: 'montserrat'
1004            },
1005            images: myImages,
1006            styles: {
1007                fontSizeMini: {
1008                    fontSize: 6,
1009                    lineHeight: 1
1010                },
1011                fontSizeSmall: {
1012                    fontSize: 7,
1013                    lineHeight: 1
1014                },
1015                fontSizeNormal: {
1016                    fontSize: 9,
1017                    lineHeight: 1
1018                },
1019                fontSizeBig: {
1020                    fontSize: 11,
1021                    lineHeight: 1
1022                },
1023                fontSizeHeading2: {
1024                    fontSize: 16,
1025                    lineHeight: 1
1026                },
1027                anotherStyle: {
1028                    italics: true,
1029                    alignment: 'right'
1030                }
1031            },
1032            content: myContent
1033        };
1034        const pdfDocGenerator = pdfMake.createPdf(docDefinition);
1035
1036        pdfDocGenerator.getBuffer(async (mainPdfBytes) => {
1037            if (!with_attachments)
1038                data['attachments'] = [];
1039
1040            let finalPdf = await mergePdfsAndImages([mainPdfBytes, ...data['attachments']], !!options.asBytes);
1041
1042            resolve(finalPdf);
1043        });
1044    });
1045}
1046async function generateTitlePagePDF(data = {}) {
1047    return new Promise(async function (resolve, reject) {
1048
1049        let myImages = {};
1050        myImages.logo = {
1051            url: pdfMakerPrefix + 'resources/images/logo-pdf.png'
1052        };
1053        if (data['logo']) {
1054            myImages.accountLogo = {
1055                url: pdfMakerPrefix + data['logo']
1056            };
1057        }
1058
1059        // Generate Content
1060        let myContent = [];
1061
1062        // Logo
1063        if (data['logo']) {
1064            myContent.push({
1065                image: 'accountLogo',
1066                fit: [(40 * pdfMakerPTsPerMM), (40 * pdfMakerPTsPerMM)],
1067                alignment: 'center'
1068            });
1069        }
1070
1071        // SubTitle
1072        myContent.push({
1073            margin: [0, 50, 0, 0],
1074            text: data['subtitleMain'],
1075            style: 'fontSizeNormal',
1076            bold: true,
1077            alignment: 'center'
1078        });
1079        myContent.push({
1080            text: data['titleMain'],
1081            style: 'fontSizeBig',
1082            bold: false,
1083            alignment: 'center'
1084        });
1085
1086        if (language == 'bz') {
1087            myContent.push({
1088                margin: [0, 5, 0, 15],
1089                canvas: [
1090                    {
1091                        type: 'line',
1092                        x1: (pdfMakerSettings.border * 2), y1: 0,
1093                        x2: (pdfMakerPageSize.a4.p.w - (pdfMakerSettings.border * 4)), y2: 0,
1094                        lineWidth: 1,
1095                        lineColor: getColor('--color-default-medium')
1096                    }
1097                ]
1098            });
1099            myContent.push({
1100                text: data['subtitleSecond'],
1101                style: 'fontSizeNormal',
1102                bold: true,
1103                alignment: 'center'
1104            });
1105
1106            myContent.push({
1107                text: data['titleSecond'],
1108                style: 'fontSizeBig',
1109                bold: false,
1110                alignment: 'center'
1111            });
1112        }
1113        myContent.push({
1114            text: data['validUnit'],
1115            style: 'fontSizeNormal',
1116            bold: true,
1117            alignment: 'center',
1118            margin: (data['logo'] ? [0, 50, 0, 0] : [0, 90, 0, 0])
1119        });
1120
1121        myContent.push({
1122            style: 'fontSizeNormal',
1123            alignment: 'center',
1124            absolutePosition: { x: pdfMakerSettings.border, y: (195 * pdfMakerPTsPerMM) },
1125            table: {
1126                widths: ['*'],
1127                alignment: 'center',
1128                body: [
1129                    [{
1130                        text: data['subtitleDirs'],
1131                        style: 'fontSizeNormal',
1132                        bold: true,
1133                        alignment: 'center',
1134                    }],
1135                    [{
1136                        text: data['dirText'],
1137                        style: 'fontSizeNormal',
1138                        bold: false,
1139                        alignment: 'center'
1140                    }],
1141                    [{
1142                        canvas: [
1143                            {
1144                                type: 'line',
1145                                x1: (pdfMakerSettings.border * 2), y1: 0,
1146                                x2: (pdfMakerPageSize.a4.p.w - (pdfMakerSettings.border * 4)), y2: 0,
1147                                lineWidth: 1,
1148                                lineColor: getColor('--color-default-medium')
1149                            }
1150                        ]
1151                    }],
1152                    [{
1153                        text: data['subtitleInspector'],
1154                        style: 'fontSizeNormal',
1155                        bold: true,
1156                        alignment: 'center'
1157                    }],
1158                    [{
1159                        text: data['inspectorText'],
1160                        style: 'fontSizeNormal',
1161                        bold: false,
1162                        alignment: 'center'
1163                    }]
1164                ]
1165            },
1166            layout: { defaultBorder: false, paddingLeft: () => 0, paddingRight: () => 0 }
1167        });
1168
1169        myContent.push({
1170            absolutePosition: { x: pdfMakerSettings.border, y: (272 * pdfMakerPTsPerMM) },
1171            columns: [
1172                { width: '*', text: '' },
1173                {
1174                    style: 'fontSizeNormal',
1175                    alignment: 'center',
1176                    width: 'auto',
1177                    table: {
1178                        widths: ['auto', 'auto'],
1179                        alignment: 'center',
1180
1181                        body: [
1182                            [
1183                                {
1184                                    text: (translate['TEST']['TITLEPAGE_CREATED_BY'] + (language == 'bz' ? ' / ' + otherTranslate['it']['TEST']['TITLEPAGE_CREATED_BY'] : '')),
1185                                    alignment: 'right'
1186                                },
1187                                {
1188                                    image: 'logo',
1189                                    fit: [(23 * pdfMakerPTsPerMM), (5 * pdfMakerPTsPerMM)],
1190                                    alignment: 'left'
1191                                }
1192                            ]
1193                        ]
1194                    },
1195                    layout: 'noBorders'
1196                },
1197                { width: '*', text: '' }
1198            ]
1199        });
1200
1201        var docDefinition = {
1202            pageSize: 'A4',
1203            pageOrientation: 'portrait',
1204            compress: true,
1205            pageBreakBefore: true,
1206            pageMargins: [pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border, pdfMakerSettings.border],
1207            defaultStyle: {
1208                font: 'montserrat'
1209            },
1210            images: myImages,
1211            styles: {
1212                fontSizeNormal: {
1213                    fontSize: 12,
1214                    lineHeight: 1.5
1215                },
1216                fontSizeBig: {
1217                    fontSize: 20,
1218                    lineHeight: 1.5
1219                },
1220                anotherStyle: {
1221                    italics: true,
1222                    alignment: 'right'
1223                }
1224            },
1225            content: myContent
1226        };
1227        const pdfDocGenerator = pdfMake.createPdf(docDefinition);
1228        pdfDocGenerator.getDataUrl((dataUrl) => {
1229            resolve(dataUrl);
1230        });
1231    });
1232
1233}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.