@measurequick/measurequick-report-generator 1.5.235 → 1.5.236
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@measurequick/measurequick-report-generator",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.236",
|
|
4
4
|
"description": "Generates PDF documents for various measureQuick applications.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"jspdf": "^1.3.5",
|
|
13
13
|
"pako": "^2.0.4",
|
|
14
14
|
"pdf-lib": "^1.16.0",
|
|
15
|
-
"prettier": "^3.5.3"
|
|
15
|
+
"prettier": "^3.5.3",
|
|
16
|
+
"qrcode": "^1.5.4"
|
|
16
17
|
},
|
|
17
18
|
"keywords": [
|
|
18
19
|
"measurequick"
|
|
@@ -7,6 +7,7 @@ import * as photos4nci from '../base-64/photos4nci.js';
|
|
|
7
7
|
import * as photos6nci from '../base-64/photos6nci.js';
|
|
8
8
|
import * as base64 from '../base-64/icons.js';
|
|
9
9
|
import * as util from '../util.js';
|
|
10
|
+
import { getButtonRect, drawVideoQrOverlay } from './video-qr-helper.js';
|
|
10
11
|
|
|
11
12
|
export async function getReport(payload) {
|
|
12
13
|
try {
|
|
@@ -18,9 +19,9 @@ export async function getReport(payload) {
|
|
|
18
19
|
console.log('payload',payload);
|
|
19
20
|
let nci = payload&&payload.test&&payload.test.ductScreeningType;
|
|
20
21
|
console.log('nci',nci);
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
page = nci ? photos6nci : page;
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
if (maxPhotosPerPage == 4) {
|
|
25
26
|
page = nci ? photos4nci : photos4;
|
|
26
27
|
pSize = "Mid";
|
|
@@ -29,6 +30,9 @@ export async function getReport(payload) {
|
|
|
29
30
|
pSize = "Large";
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
// Track video overlays to draw after flattening
|
|
34
|
+
let videoOverlays = [];
|
|
35
|
+
|
|
32
36
|
// Process All Photos
|
|
33
37
|
let docIndex = 0;
|
|
34
38
|
let photos = payload.photos.project;
|
|
@@ -63,7 +67,17 @@ export async function getReport(payload) {
|
|
|
63
67
|
if (photo.base64 && photo.base64.includes("image/jpeg")) imageToSet = await docs[docIndex].embedJpg(util._base64ToArrayBuffer(photo.base64));
|
|
64
68
|
else if (photo.base64 && photo.base64.includes("image/png")) imageToSet = await docs[docIndex].embedPng(util._base64ToArrayBuffer(photo.base64));
|
|
65
69
|
}
|
|
66
|
-
|
|
70
|
+
let fieldName = `photo${pSize}${photoPosition}`;
|
|
71
|
+
if (imageToSet) forms[docIndex].getButton(fieldName).setImage(imageToSet);
|
|
72
|
+
|
|
73
|
+
// Track video for QR overlay
|
|
74
|
+
if (photo.isVideo && photo.viewerUrl) {
|
|
75
|
+
let rect = getButtonRect(forms[docIndex], fieldName);
|
|
76
|
+
if (rect) {
|
|
77
|
+
videoOverlays.push({ docIndex: docIndex, rect: rect, viewerUrl: photo.viewerUrl });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
let caption = photo.section;
|
|
68
82
|
if (prevSection == currSection) caption += photo.notes;
|
|
69
83
|
forms[docIndex].getTextField(`Notes${photoPosition}`).setText(caption);
|
|
@@ -76,6 +90,13 @@ export async function getReport(payload) {
|
|
|
76
90
|
|
|
77
91
|
// Prepare Deliverable
|
|
78
92
|
forms.forEach(form => { form.flatten() });
|
|
93
|
+
|
|
94
|
+
// Draw video QR overlays on flattened pages
|
|
95
|
+
for (let overlay of videoOverlays) {
|
|
96
|
+
let docPage = docs[overlay.docIndex].getPage(0);
|
|
97
|
+
await drawVideoQrOverlay(docs[overlay.docIndex], docPage, overlay.rect, overlay.viewerUrl);
|
|
98
|
+
}
|
|
99
|
+
|
|
79
100
|
let pdfDoc = await PDFDocument.create();
|
|
80
101
|
for (let i = 0; i < docs.length; i++) {
|
|
81
102
|
const [nextPage] = await pdfDoc.copyPages(docs[i], [0]);
|
|
@@ -7,6 +7,7 @@ import * as photos4nci from '../base-64/photos4nci.js';
|
|
|
7
7
|
import * as photos6nci from '../base-64/photos6nci.js';
|
|
8
8
|
import * as base64 from '../base-64/icons.js';
|
|
9
9
|
import * as util from '../util.js';
|
|
10
|
+
import { getButtonRect, drawVideoQrOverlay } from './video-qr-helper.js';
|
|
10
11
|
|
|
11
12
|
// Check if project is CoolMaxx or HeatMaxx (NCI branded)
|
|
12
13
|
function isNCIProject(payload) {
|
|
@@ -50,6 +51,9 @@ export async function getReport(payload) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// Track video overlays to draw after flattening
|
|
55
|
+
let videoOverlays = []; // { docIndex, rect, s3ReferenceId }
|
|
56
|
+
|
|
53
57
|
// Process All Photos
|
|
54
58
|
let canProcessReport = false;
|
|
55
59
|
let previousPhotoSectionName;
|
|
@@ -95,7 +99,17 @@ export async function getReport(payload) {
|
|
|
95
99
|
else if (photo.base64.includes("image/png")) imageToSet = await docs[docIndex].embedPng(util._base64ToArrayBuffer(photo.base64));
|
|
96
100
|
else imageToSet = await docs[docIndex].embedJpg(util._base64ToArrayBuffer(photo.base64));
|
|
97
101
|
}
|
|
98
|
-
|
|
102
|
+
let fieldName = `photo${pSize}${photoPosition}`;
|
|
103
|
+
if (imageToSet) forms[docIndex].getButton(fieldName).setImage(imageToSet);
|
|
104
|
+
|
|
105
|
+
// Track video for QR overlay
|
|
106
|
+
if (photo.isVideo && photo.viewerUrl) {
|
|
107
|
+
let rect = getButtonRect(forms[docIndex], fieldName);
|
|
108
|
+
if (rect) {
|
|
109
|
+
videoOverlays.push({ docIndex: docIndex, rect: rect, viewerUrl: photo.viewerUrl });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
let photoSubText = photoSection.name;
|
|
100
114
|
let isFirstPhotoOfSection = previousPhotoSectionName !== photoSectionName;
|
|
101
115
|
if (isFirstPhotoOfSection && photoSection.hasNotes && photoSection.notes) photoSubText += `: ${photoSection.notes}`;
|
|
@@ -160,7 +174,17 @@ export async function getReport(payload) {
|
|
|
160
174
|
}
|
|
161
175
|
//else imageToSet = await docs[docIndex].embedJpg(util._base64ToArrayBuffer(photo.base64));
|
|
162
176
|
}
|
|
163
|
-
|
|
177
|
+
let fieldName = `photo${pSize}${photoPosition}`;
|
|
178
|
+
if (imageToSet) forms[docIndex].getButton(fieldName).setImage(imageToSet);
|
|
179
|
+
|
|
180
|
+
// Track video for QR overlay
|
|
181
|
+
if (photo.isVideo && photo.viewerUrl) {
|
|
182
|
+
let rect = getButtonRect(forms[docIndex], fieldName);
|
|
183
|
+
if (rect) {
|
|
184
|
+
videoOverlays.push({ docIndex: docIndex, rect: rect, viewerUrl: photo.viewerUrl });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
164
188
|
let photoSubText = photoSection.name;
|
|
165
189
|
let isFirstPhotoOfSection = previousPhotoSectionName !== photoSectionName;
|
|
166
190
|
if (isFirstPhotoOfSection && photoSection.hasNotes && photoSection.notes) photoSubText += `: ${photoSection.notes}`;
|
|
@@ -178,6 +202,13 @@ export async function getReport(payload) {
|
|
|
178
202
|
// Prepare Deliverable
|
|
179
203
|
if (canProcessReport) {
|
|
180
204
|
forms.forEach(form => { form.flatten() });
|
|
205
|
+
|
|
206
|
+
// Draw video QR overlays on flattened pages
|
|
207
|
+
for (let overlay of videoOverlays) {
|
|
208
|
+
let docPage = docs[overlay.docIndex].getPage(0);
|
|
209
|
+
await drawVideoQrOverlay(docs[overlay.docIndex], docPage, overlay.rect, overlay.viewerUrl);
|
|
210
|
+
}
|
|
211
|
+
|
|
181
212
|
let pdfDoc = await PDFDocument.create();
|
|
182
213
|
for (let i = 0; i < docs.length; i++) {
|
|
183
214
|
const [nextPage] = await pdfDoc.copyPages(docs[i], [0]);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import QRCode from 'qrcode';
|
|
2
|
+
import { rgb } from 'pdf-lib';
|
|
3
|
+
import * as util from '../util.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate QR code as PNG bytes for a viewer URL
|
|
7
|
+
*/
|
|
8
|
+
export async function generateQrPngBytes(viewerUrl) {
|
|
9
|
+
const dataUrl = await QRCode.toDataURL(viewerUrl, {
|
|
10
|
+
width: 200,
|
|
11
|
+
margin: 1,
|
|
12
|
+
color: { dark: '#000000', light: '#ffffff' }
|
|
13
|
+
});
|
|
14
|
+
return util._base64ToArrayBuffer(dataUrl);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the rectangle of a form button field
|
|
19
|
+
*/
|
|
20
|
+
export function getButtonRect(form, fieldName) {
|
|
21
|
+
try {
|
|
22
|
+
const button = form.getButton(fieldName);
|
|
23
|
+
const widgets = button.acroField.getWidgets();
|
|
24
|
+
if (widgets.length > 0) {
|
|
25
|
+
return widgets[0].getRectangle();
|
|
26
|
+
}
|
|
27
|
+
} catch (e) {
|
|
28
|
+
// field not found
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Draw QR code overlay with "VIEW VIDEO" text on a PDF page
|
|
35
|
+
* Centered in the given rectangle area
|
|
36
|
+
*/
|
|
37
|
+
export async function drawVideoQrOverlay(doc, page, rect, viewerUrl) {
|
|
38
|
+
if (!rect || !viewerUrl) return;
|
|
39
|
+
|
|
40
|
+
const qrBytes = await generateQrPngBytes(viewerUrl);
|
|
41
|
+
if (!qrBytes) return;
|
|
42
|
+
|
|
43
|
+
const qrImage = await doc.embedPng(qrBytes);
|
|
44
|
+
|
|
45
|
+
// Size the QR code to ~40% of the photo area width, min 50, max 100
|
|
46
|
+
let qrSize = Math.min(Math.max(rect.width * 0.4, 50), 100);
|
|
47
|
+
const padding = 8;
|
|
48
|
+
const textHeight = 12;
|
|
49
|
+
const totalHeight = qrSize + padding * 2 + textHeight + 4;
|
|
50
|
+
const totalWidth = qrSize + padding * 2;
|
|
51
|
+
|
|
52
|
+
// Center the overlay in the photo area
|
|
53
|
+
const boxX = rect.x + (rect.width - totalWidth) / 2;
|
|
54
|
+
const boxY = rect.y + (rect.height - totalHeight) / 2;
|
|
55
|
+
|
|
56
|
+
// White background box
|
|
57
|
+
page.drawRectangle({
|
|
58
|
+
x: boxX,
|
|
59
|
+
y: boxY,
|
|
60
|
+
width: totalWidth,
|
|
61
|
+
height: totalHeight,
|
|
62
|
+
color: rgb(1, 1, 1),
|
|
63
|
+
borderColor: rgb(0.8, 0.8, 0.8),
|
|
64
|
+
borderWidth: 0.5
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// QR code image
|
|
68
|
+
page.drawImage(qrImage, {
|
|
69
|
+
x: boxX + padding,
|
|
70
|
+
y: boxY + textHeight + 4 + padding,
|
|
71
|
+
width: qrSize,
|
|
72
|
+
height: qrSize
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// "VIEW VIDEO" text centered below QR
|
|
76
|
+
const fontSize = 8;
|
|
77
|
+
const textWidth = fontSize * 6.5; // approximate width of "VIEW VIDEO"
|
|
78
|
+
page.drawText('VIEW VIDEO', {
|
|
79
|
+
x: boxX + (totalWidth - textWidth) / 2,
|
|
80
|
+
y: boxY + padding,
|
|
81
|
+
size: fontSize,
|
|
82
|
+
color: rgb(0, 0, 0)
|
|
83
|
+
});
|
|
84
|
+
}
|