ebta-workflow-prod 1.1.2 → 4.0.1
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/.eslintrc.js +7 -0
- package/.vscode/extensions.json +3 -0
- package/.vscode/settings.json +3 -0
- package/README.md +26 -0
- package/build/2983859c0d77824ec8db.svg +1 -0
- package/build/4168d659d55fd94368bc.svg +1 -0
- package/build/641b2c17e92b0e885b9f.svg +1 -0
- package/build/882445a26a4f3964786f.svg +1 -0
- package/build/activitypack.json +1 -1
- package/build/b280e967312a55101032.svg +1 -0
- package/build/main.js +2 -2
- package/build/main.js.LICENSE.txt +99 -0
- package/certs/localhost+2-key.pem +28 -0
- package/certs/localhost+2.pem +26 -0
- package/index.html +58 -0
- package/package.json +43 -7
- package/public/img/background.svg +3 -3
- package/public/img/compass.svg +3 -3
- package/public/img/grab.svg +3 -3
- package/public/img/grabbing.svg +3 -3
- package/public/img/sprites.svg +14 -14
- package/src/activities/AppendObjects.ts +40 -0
- package/src/activities/Base64ToFile.ts +56 -0
- package/src/activities/CreateGraphicLayer.ts +85 -0
- package/src/activities/DrapGeometry.ts +99 -0
- package/src/activities/GetElementByDataLayoutID.ts +58 -0
- package/src/activities/GetSesionStorage.ts +36 -0
- package/src/activities/GetTaskStatus.ts +56 -0
- package/src/activities/GetUserToken.ts +102 -0
- package/src/activities/GetvGISDetails copy.ts +110 -0
- package/src/activities/GetvGISDetails.ts +110 -0
- package/src/activities/HTMLextractor.ts +188 -0
- package/src/activities/LegendCreator.ts +155 -0
- package/src/activities/MergeObjects.ts +61 -0
- package/src/activities/PDFMapGenerator.ts +729 -0
- package/src/activities/RequestChecker.ts +94 -0
- package/src/activities/Sample.ts +41 -0
- package/src/activities/SetSessionStorage.ts +57 -0
- package/src/activities/SliceLine.ts +61 -0
- package/src/elements/AttributeFilter.tsx +1867 -0
- package/src/elements/CircularSlider.tsx +67 -0
- package/src/elements/CsColorPicker.tsx +140 -0
- package/src/elements/DropDownMenu.tsx +282 -0
- package/src/elements/HeaderwithHelp.tsx +178 -0
- package/src/elements/ItemSearcher.tsx +1035 -0
- package/src/elements/ItemSearcher_v2.tsx +1071 -0
- package/src/elements/LineChartMui.tsx +50 -0
- package/src/elements/MenueBar.tsx +118 -0
- package/src/elements/MultipleAutoCompleteCS.tsx +145 -0
- package/src/elements/SingleAutoCompleteCS.tsx +133 -0
- package/src/elements/TableRadioGoup.tsx +163 -0
- package/src/elements/Timeline.tsx +104 -0
- package/src/elements/ToggleButtonGroup.tsx +129 -0
- package/src/elements/Viewer360.tsx +71 -0
- package/src/elements/modules/AttributeFilterEasyComp.tsx +253 -0
- package/src/index.ts +41 -0
- package/src/main.ts +15 -0
- package/src/styles/imageSlider.css +34 -0
- package/tsconfig.json +12 -0
- package/uuid.js +7 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
4
|
+
/* eslint-disable prefer-const */
|
|
5
|
+
import type { IActivityHandler } from "@vertigis/workflow";
|
|
6
|
+
import { PDFDocument, degrees, rgb, PDFName, PDFString, TextAlignment, PDFArray, StandardFonts,PDFRef } from 'pdf-lib';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/** An interface that defines the inputs of the activity. */
|
|
11
|
+
interface PDFMapGeneratorInputs {
|
|
12
|
+
/**
|
|
13
|
+
* @displayName img Base64 Array
|
|
14
|
+
* @description img Base64 Array
|
|
15
|
+
* @required
|
|
16
|
+
*/
|
|
17
|
+
imgBase64: string[];
|
|
18
|
+
/**
|
|
19
|
+
* @displayName PDF Link
|
|
20
|
+
* @description The link of blank PDF Template.
|
|
21
|
+
*/
|
|
22
|
+
pdfLink: string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @displayName Legend img Base64
|
|
26
|
+
* @description Legend img Base64 created by Legend creator
|
|
27
|
+
* @required
|
|
28
|
+
*/
|
|
29
|
+
LegendImgBase64: string;
|
|
30
|
+
/**
|
|
31
|
+
* @displayName Scale Array
|
|
32
|
+
* @description Array contains the scale numbers catched from each extent
|
|
33
|
+
* @required
|
|
34
|
+
*/
|
|
35
|
+
scaleArray: number[];
|
|
36
|
+
/**
|
|
37
|
+
* @displayName Map Title
|
|
38
|
+
* @description Map Title
|
|
39
|
+
*/
|
|
40
|
+
Title: string;
|
|
41
|
+
/**
|
|
42
|
+
* @displayName Revision Description
|
|
43
|
+
* @description Revision Description
|
|
44
|
+
*/
|
|
45
|
+
Descr: string;
|
|
46
|
+
/**
|
|
47
|
+
* @displayName Note
|
|
48
|
+
* @description Note
|
|
49
|
+
*/
|
|
50
|
+
Note: string;
|
|
51
|
+
/**
|
|
52
|
+
* @displayName Revision
|
|
53
|
+
* @description Revision
|
|
54
|
+
*/
|
|
55
|
+
Rev: string;
|
|
56
|
+
/**
|
|
57
|
+
* @displayName Printer Name
|
|
58
|
+
* @description Printer Name
|
|
59
|
+
*/
|
|
60
|
+
PrintUser: string;
|
|
61
|
+
/**
|
|
62
|
+
* @displayName Rotation angle
|
|
63
|
+
* @description Rotation angle for compass
|
|
64
|
+
*/
|
|
65
|
+
rotation: number[];
|
|
66
|
+
/**
|
|
67
|
+
* @displayName QR Code Url
|
|
68
|
+
* @description QR Code Url
|
|
69
|
+
*/
|
|
70
|
+
QrUrl?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* @displayName link inside the button
|
|
73
|
+
* @description Display button lead to QR URLs rather than QRCodes
|
|
74
|
+
*/
|
|
75
|
+
buttonLink?: string[];
|
|
76
|
+
/**
|
|
77
|
+
* @displayName QR Code image array (url ending with .jpg etc)
|
|
78
|
+
* @description Put QR image url so it will be put in pdf. If this value given, QR URL and button parameters will be ignored.
|
|
79
|
+
*/
|
|
80
|
+
QrCodeImage?: string[];
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @displayName Field View Id
|
|
85
|
+
* @description Field View Id
|
|
86
|
+
*/
|
|
87
|
+
FiewViewId: string[] | number[];
|
|
88
|
+
/**
|
|
89
|
+
* @displayName Array of geocoded addresses
|
|
90
|
+
* @description Array of geocoded addresses
|
|
91
|
+
*/
|
|
92
|
+
addressList: string[];
|
|
93
|
+
}
|
|
94
|
+
/** An interface that defines the outputs of the activity. */
|
|
95
|
+
interface PDFMapGeneratorOutputs {
|
|
96
|
+
/**
|
|
97
|
+
* @description The result of the activity.
|
|
98
|
+
*/
|
|
99
|
+
result: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* @displayName PDF Map Generator
|
|
103
|
+
* @category EBTA Utilities
|
|
104
|
+
* @description An activity which generate the Map PDF using map and legend screenshot
|
|
105
|
+
*/
|
|
106
|
+
export default class PDFMapGeneratorActivity implements IActivityHandler {
|
|
107
|
+
private async fetchData(url: string): Promise<ArrayBuffer> {
|
|
108
|
+
try {
|
|
109
|
+
console.log('EBTA PDFMapGenerator version 1.1')
|
|
110
|
+
if (typeof fetch !== 'undefined') {
|
|
111
|
+
const response = await fetch(url, {
|
|
112
|
+
mode: 'cors',
|
|
113
|
+
credentials: 'omit'
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
117
|
+
}
|
|
118
|
+
return await response.arrayBuffer();
|
|
119
|
+
} else {
|
|
120
|
+
const response = await axios.get(url, {
|
|
121
|
+
responseType: 'arraybuffer',
|
|
122
|
+
withCredentials: false
|
|
123
|
+
});
|
|
124
|
+
return response.data;
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(`Failed to fetch data from ${url}:`, error);
|
|
128
|
+
throw new Error(`Failed to fetch data from ${url}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async reencodeImageToArrayBuffer(arrayBuffer: ArrayBuffer): Promise<ArrayBuffer> {
|
|
133
|
+
const blob = new Blob([arrayBuffer], { type: 'image/jpeg' });
|
|
134
|
+
const img = new Image();
|
|
135
|
+
|
|
136
|
+
// Convert the array buffer to a blob URL
|
|
137
|
+
img.src = URL.createObjectURL(blob);
|
|
138
|
+
|
|
139
|
+
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
140
|
+
img.onload = () => {
|
|
141
|
+
const canvas = document.createElement('canvas');
|
|
142
|
+
canvas.width = img.width;
|
|
143
|
+
canvas.height = img.height;
|
|
144
|
+
|
|
145
|
+
const ctx = canvas.getContext('2d');
|
|
146
|
+
if (!ctx) return reject(new Error('Canvas context could not be created'));
|
|
147
|
+
|
|
148
|
+
ctx.drawImage(img, 0, 0);
|
|
149
|
+
|
|
150
|
+
canvas.toBlob((blob) => {
|
|
151
|
+
if (!blob) return reject(new Error('Failed to convert canvas to Blob'));
|
|
152
|
+
|
|
153
|
+
const reader = new FileReader();
|
|
154
|
+
reader.onloadend = () => {
|
|
155
|
+
if (reader.result instanceof ArrayBuffer) {
|
|
156
|
+
resolve(reader.result);
|
|
157
|
+
} else {
|
|
158
|
+
reject(new Error('Failed to read Blob as ArrayBuffer'));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
reader.readAsArrayBuffer(blob);
|
|
162
|
+
}, 'image/jpeg');
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
img.onerror = (err) => reject(new Error('Image load error: '));
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
private arrayBufferToBase64(buffer: ArrayBuffer): string {
|
|
171
|
+
let binary = '';
|
|
172
|
+
const bytes = new Uint8Array(buffer);
|
|
173
|
+
const len = bytes.byteLength;
|
|
174
|
+
for (let i = 0; i < len; i++) {
|
|
175
|
+
binary += String.fromCharCode(bytes[i]);
|
|
176
|
+
}
|
|
177
|
+
return btoa(binary);
|
|
178
|
+
}
|
|
179
|
+
private createDownloadUrl(base64String: string): string {
|
|
180
|
+
return `data:application/pdf;base64,${base64String}`;
|
|
181
|
+
}
|
|
182
|
+
private base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
183
|
+
const binaryString = atob(base64.split(',')[1]);
|
|
184
|
+
const len = binaryString.length;
|
|
185
|
+
const bytes = new Uint8Array(len);
|
|
186
|
+
for (let i = 0; i < len; i++) {
|
|
187
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
188
|
+
}
|
|
189
|
+
return bytes.buffer;
|
|
190
|
+
}
|
|
191
|
+
private isValidImage(base64: string): boolean {
|
|
192
|
+
return base64.startsWith('data:image/png;base64,') ||
|
|
193
|
+
base64.startsWith('data:image/jpg;base64,') ||
|
|
194
|
+
base64.startsWith('data:image/jpeg;base64,');
|
|
195
|
+
}
|
|
196
|
+
private nearestNiceInteger(num) {
|
|
197
|
+
const changeLimit = num * 0.2;
|
|
198
|
+
const bigInterger = [10000, 5000, 2000, 1000, 500, 200, 100, 50, 20, 10, 5, 2];
|
|
199
|
+
for (const multiple of bigInterger) {
|
|
200
|
+
const rounded = Math.round(num / multiple) * multiple;
|
|
201
|
+
if (Math.abs(rounded - num) <= changeLimit) {
|
|
202
|
+
return rounded;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// If no suitable "nice" integer is found within the 20% range, return the original number
|
|
206
|
+
return num;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
/** Perform the execution logic of the activity. */
|
|
212
|
+
async execute(inputs: PDFMapGeneratorInputs): Promise<PDFMapGeneratorOutputs> {
|
|
213
|
+
const defaulPDF = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/A3Layout_Landscape_General_Form.pdf'
|
|
214
|
+
const pdfURL = inputs.pdfLink ? inputs.pdfLink : defaulPDF
|
|
215
|
+
const imgBase64Array = inputs.imgBase64;
|
|
216
|
+
|
|
217
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
218
|
+
if (!imgBase64Array || imgBase64Array.length === 0 || !imgBase64Array.every(this.isValidImage)) {
|
|
219
|
+
return {
|
|
220
|
+
result: 'Invalid image input',
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const pdfBuffer = await this.fetchData(pdfURL);
|
|
225
|
+
console.log('PDF fetched successfully');
|
|
226
|
+
const originalPdfDoc = await PDFDocument.load(pdfBuffer);
|
|
227
|
+
const newPdfDoc = await PDFDocument.create();
|
|
228
|
+
const [originalPage] = await newPdfDoc.copyPages(originalPdfDoc, [0]);
|
|
229
|
+
const { width, height } = originalPage.getSize();
|
|
230
|
+
const pixel2MeterOnPaper = 1 / (71.42857) * 0.0254
|
|
231
|
+
|
|
232
|
+
const image1url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/caution.png'
|
|
233
|
+
const image2url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/warning.png'
|
|
234
|
+
const image3url = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/dbyd.png'
|
|
235
|
+
const imageBarurl = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/Scalebar.png'
|
|
236
|
+
const imageNorthUrl = 'https://cdn-icons-png.flaticon.com/512/3236/3236586.png'
|
|
237
|
+
const image1Buffer = await this.fetchData(image1url);
|
|
238
|
+
console.log('Image1 fetched successfully');
|
|
239
|
+
const image2Buffer = await this.fetchData(image2url);
|
|
240
|
+
console.log('Image2 fetched successfully');
|
|
241
|
+
const image3Buffer = await this.fetchData(image3url);
|
|
242
|
+
console.log('Image3 fetched successfully');
|
|
243
|
+
const imageBarBuffer = await this.fetchData(imageBarurl);
|
|
244
|
+
console.log('Image scale bar fetched successfully');
|
|
245
|
+
const imageNorthBuffer = await this.fetchData(imageNorthUrl);
|
|
246
|
+
console.log('Image North Arrow fetched successfully');
|
|
247
|
+
const legendBuffer = this.base64ToArrayBuffer(inputs.LegendImgBase64);
|
|
248
|
+
const filledPDFs: Uint8Array[] = [];
|
|
249
|
+
for (let i = 0; i < imgBase64Array.length; i++) {
|
|
250
|
+
let doc = await PDFDocument.load(pdfBuffer);
|
|
251
|
+
let form = doc.getForm();
|
|
252
|
+
let newPage = doc.getPage(0)
|
|
253
|
+
let imgBase64 = imgBase64Array[i]
|
|
254
|
+
let imageBuffer = this.base64ToArrayBuffer(imgBase64);
|
|
255
|
+
console.log(`Image ${i} data processed successfully`);
|
|
256
|
+
let image;
|
|
257
|
+
if (imgBase64.startsWith('data:image/png;base64,')) {
|
|
258
|
+
image = await doc.embedPng(imageBuffer);
|
|
259
|
+
} else if (imgBase64.startsWith('data:image/jpeg;base64,') || imgBase64.startsWith('data:image/jpg;base64,')) {
|
|
260
|
+
image = await doc.embedJpg(imageBuffer);
|
|
261
|
+
}
|
|
262
|
+
const image1 = await doc.embedPng(image1Buffer);
|
|
263
|
+
const image2 = await doc.embedPng(image2Buffer);
|
|
264
|
+
const image3 = await doc.embedPng(image3Buffer);
|
|
265
|
+
const imageBar = await doc.embedPng(imageBarBuffer);
|
|
266
|
+
const imageNorth = await doc.embedPng(imageNorthBuffer);
|
|
267
|
+
let legendImage
|
|
268
|
+
if (inputs.LegendImgBase64.startsWith('data:image/png;base64,')) {
|
|
269
|
+
legendImage = await doc.embedPng(legendBuffer);
|
|
270
|
+
} else if (inputs.LegendImgBase64.startsWith('data:image/jpeg;base64,') || inputs.LegendImgBase64.startsWith('data:image/jpg;base64,')) {
|
|
271
|
+
legendImage = await doc.embedJpg(legendBuffer);
|
|
272
|
+
}
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
274
|
+
newPage.drawImage(image, {
|
|
275
|
+
x: width * 0.0205,
|
|
276
|
+
y: height * (0.1695),
|
|
277
|
+
width: width * 0.8113,
|
|
278
|
+
height: height * 0.8029,
|
|
279
|
+
});
|
|
280
|
+
//Define position & size of legend screeshot, depent on width/height ratio
|
|
281
|
+
if (legendImage.width / legendImage.height > 0.259) {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
283
|
+
newPage.drawImage(legendImage, {
|
|
284
|
+
x: width * 0.83796,
|
|
285
|
+
y: height * (0.94611) - width * 0.14 * (legendImage.height / legendImage.width),
|
|
286
|
+
width: width * 0.14,
|
|
287
|
+
height: width * 0.14 * (legendImage.height / legendImage.width),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
if (legendImage.width / legendImage.height < 0.259 / 1.6) {
|
|
292
|
+
if (legendImage.width / legendImage.height < 0.259 / 3.95) {
|
|
293
|
+
if (legendImage.width / legendImage.height < 0.259 / 5.5) {
|
|
294
|
+
console.log("split legend into 3 columns");
|
|
295
|
+
const fullLegendimg = new Image();
|
|
296
|
+
fullLegendimg.src = inputs.LegendImgBase64;
|
|
297
|
+
fullLegendimg.onload = async () => {
|
|
298
|
+
const neckHeight = Math.floor(legendImage.height * 0.33); // Approximate neck position
|
|
299
|
+
const waistHeight = Math.floor(legendImage.height * 0.66); // Approximate waist position
|
|
300
|
+
// Neck to Top
|
|
301
|
+
const neckUpCanvas = document.createElement('canvas');
|
|
302
|
+
neckUpCanvas.width = legendImage.width;
|
|
303
|
+
neckUpCanvas.height = neckHeight;
|
|
304
|
+
const neckUpCtx = neckUpCanvas.getContext('2d');
|
|
305
|
+
neckUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, neckHeight, 0, 0, legendImage.width, neckHeight);
|
|
306
|
+
// Waist to Neck
|
|
307
|
+
const waistUpCanvas = document.createElement('canvas');
|
|
308
|
+
waistUpCanvas.width = legendImage.width;
|
|
309
|
+
waistUpCanvas.height = waistHeight - neckHeight;
|
|
310
|
+
const waistUpCtx = waistUpCanvas.getContext('2d');
|
|
311
|
+
waistUpCtx!.drawImage(fullLegendimg, 0, neckHeight, legendImage.width, waistHeight - neckHeight, 0, 0, legendImage.width, waistHeight - neckHeight);
|
|
312
|
+
// Bottom to Waist
|
|
313
|
+
const waistDownCanvas = document.createElement('canvas');
|
|
314
|
+
waistDownCanvas.width = legendImage.width;
|
|
315
|
+
waistDownCanvas.height = legendImage.height - waistHeight;
|
|
316
|
+
const waistDownCtx = waistDownCanvas.getContext('2d');
|
|
317
|
+
waistDownCtx!.drawImage(fullLegendimg, 0, waistHeight, legendImage.width, legendImage.height - waistHeight, 0, 0, legendImage.width, legendImage.height - waistHeight);
|
|
318
|
+
const neckUpBase64 = neckUpCanvas.toDataURL('image/jpeg');
|
|
319
|
+
const upBuffer1 = this.base64ToArrayBuffer(neckUpBase64);
|
|
320
|
+
const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg');
|
|
321
|
+
const upBuffer2 = this.base64ToArrayBuffer(waistUpBase64);
|
|
322
|
+
const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg');
|
|
323
|
+
const downBuffer = this.base64ToArrayBuffer(waistDownBase64);
|
|
324
|
+
const LegendNeckUp = await doc.embedJpg(upBuffer1);
|
|
325
|
+
const LegendWaistUp = await doc.embedJpg(upBuffer2);
|
|
326
|
+
const LegendWaistDown = await doc.embedJpg(downBuffer);
|
|
327
|
+
newPage.drawImage(LegendNeckUp, {
|
|
328
|
+
x: width * 0.83796,
|
|
329
|
+
y: height * 0.94611 - width * 0.14 * 0.33 * (LegendNeckUp.height / LegendNeckUp.width),
|
|
330
|
+
width: width * 0.14 * 0.33,
|
|
331
|
+
height: width * 0.14 * 0.33 * (LegendNeckUp.height / LegendNeckUp.width),
|
|
332
|
+
});
|
|
333
|
+
newPage.drawImage(LegendWaistUp, {
|
|
334
|
+
x: width * 0.83796 + width * 0.14 * 0.33,
|
|
335
|
+
y: height * 0.94611 - width * 0.14 * 0.33 * (LegendWaistUp.height / LegendWaistUp.width),
|
|
336
|
+
width: width * 0.14 * 0.33,
|
|
337
|
+
height: width * 0.14 * 0.33 * (LegendWaistUp.height / LegendWaistUp.width),
|
|
338
|
+
});
|
|
339
|
+
newPage.drawImage(LegendWaistDown, {
|
|
340
|
+
x: width * 0.83796 + width * 0.14 * .66,
|
|
341
|
+
y: height * 0.94611 - width * 0.14 * 0.33 * (LegendWaistDown.height / LegendWaistDown.width),
|
|
342
|
+
width: width * 0.14 * 0.33,
|
|
343
|
+
height: width * 0.14 * 0.33 * (LegendWaistDown.height / LegendWaistDown.width)
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
//split into 2 columns & strectch
|
|
349
|
+
console.log("split legend into 2 columns & stretch ")
|
|
350
|
+
const fullLegendimg = new Image();
|
|
351
|
+
fullLegendimg.src = inputs.LegendImgBase64
|
|
352
|
+
fullLegendimg.onload = async () => {
|
|
353
|
+
const halfHeight = Math.floor(legendImage.height / 2)
|
|
354
|
+
const waistUpCanvas = document.createElement('canvas');
|
|
355
|
+
waistUpCanvas.width = legendImage.width;
|
|
356
|
+
waistUpCanvas.height = halfHeight;
|
|
357
|
+
const waistUpCtx = waistUpCanvas.getContext('2d');
|
|
358
|
+
waistUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight);
|
|
359
|
+
const waistDownCanvas = document.createElement('canvas');
|
|
360
|
+
waistDownCanvas.width = legendImage.width;
|
|
361
|
+
waistDownCanvas.height = halfHeight;
|
|
362
|
+
const waistDownCtx = waistDownCanvas.getContext('2d');
|
|
363
|
+
waistDownCtx!.drawImage(fullLegendimg, 0, halfHeight, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight);
|
|
364
|
+
const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg');
|
|
365
|
+
const upBuffer = this.base64ToArrayBuffer(waistUpBase64);
|
|
366
|
+
const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg');
|
|
367
|
+
const downBuffer = this.base64ToArrayBuffer(waistDownBase64);
|
|
368
|
+
const LegendUp = await doc.embedJpg(upBuffer);
|
|
369
|
+
const LegendDown = await doc.embedJpg(downBuffer);
|
|
370
|
+
|
|
371
|
+
newPage.drawImage(LegendUp, {
|
|
372
|
+
x: width * 0.83796,
|
|
373
|
+
y: height * (0.94611 - 0.77),
|
|
374
|
+
width: height * 0.77 * (LegendUp.width / LegendUp.height),
|
|
375
|
+
height: height * 0.77,
|
|
376
|
+
});
|
|
377
|
+
newPage.drawImage(LegendDown, {
|
|
378
|
+
x: width * 0.83796 + height * 0.77 * (LegendDown.width / LegendDown.height),
|
|
379
|
+
y: height * (0.94611 - 0.77),
|
|
380
|
+
width: height * 0.77 * (LegendDown.width / LegendDown.height),
|
|
381
|
+
height: height * 0.77,
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
//split into 2 columns & no strectch
|
|
388
|
+
console.log("split legend into 2 columns & no strectch")
|
|
389
|
+
const fullLegendimg = new Image();
|
|
390
|
+
fullLegendimg.src = inputs.LegendImgBase64
|
|
391
|
+
fullLegendimg.onload = async () => {
|
|
392
|
+
const halfHeight = Math.floor(legendImage.height / 2)
|
|
393
|
+
const waistUpCanvas = document.createElement('canvas');
|
|
394
|
+
waistUpCanvas.width = legendImage.width;
|
|
395
|
+
waistUpCanvas.height = halfHeight;
|
|
396
|
+
const waistUpCtx = waistUpCanvas.getContext('2d');
|
|
397
|
+
waistUpCtx!.drawImage(fullLegendimg, 0, 0, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight);
|
|
398
|
+
const waistDownCanvas = document.createElement('canvas');
|
|
399
|
+
waistDownCanvas.width = legendImage.width;
|
|
400
|
+
waistDownCanvas.height = halfHeight;
|
|
401
|
+
const waistDownCtx = waistDownCanvas.getContext('2d');
|
|
402
|
+
waistDownCtx!.drawImage(fullLegendimg, 0, halfHeight, legendImage.width, halfHeight, 0, 0, legendImage.width, halfHeight);
|
|
403
|
+
const waistUpBase64 = waistUpCanvas.toDataURL('image/jpeg');
|
|
404
|
+
const upBuffer = this.base64ToArrayBuffer(waistUpBase64);
|
|
405
|
+
const waistDownBase64 = waistDownCanvas.toDataURL('image/jpeg');
|
|
406
|
+
const downBuffer = this.base64ToArrayBuffer(waistDownBase64);
|
|
407
|
+
const LegendUp = await doc.embedJpg(upBuffer);
|
|
408
|
+
const LegendDown = await doc.embedJpg(downBuffer);
|
|
409
|
+
newPage.drawImage(LegendUp, {
|
|
410
|
+
x: width * 0.83796,
|
|
411
|
+
y: height * (0.94611) - width * 0.14 * 0.5 * (LegendUp.height / LegendUp.width),
|
|
412
|
+
width: width * 0.14 * 0.5,
|
|
413
|
+
height: width * 0.14 * 0.5 * (LegendUp.height / LegendUp.width),
|
|
414
|
+
});
|
|
415
|
+
newPage.drawImage(LegendDown, {
|
|
416
|
+
x: width * 0.83796 + width * 0.14 * 0.505,
|
|
417
|
+
y: height * (0.94611) - width * 0.14 * 0.5 * (LegendDown.height / LegendDown.width),
|
|
418
|
+
width: width * 0.14 * 0.5,
|
|
419
|
+
height: width * 0.14 * 0.5 * (LegendDown.height / LegendDown.width)
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
// A little bit slim, just stretch
|
|
426
|
+
newPage.drawImage(legendImage, {
|
|
427
|
+
x: width * 0.83796,
|
|
428
|
+
y: height * (0.94611 - 0.77),
|
|
429
|
+
width: height * 0.77 * (legendImage.width / legendImage.height),
|
|
430
|
+
height: height * 0.77,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const lableSize = 100
|
|
435
|
+
newPage.drawImage(image1, {
|
|
436
|
+
x: 28,
|
|
437
|
+
y: 145,
|
|
438
|
+
width: lableSize,
|
|
439
|
+
height: lableSize * (image1.height / image1.width),
|
|
440
|
+
});
|
|
441
|
+
newPage.drawImage(image2, {
|
|
442
|
+
x: 28 + lableSize + 6,
|
|
443
|
+
y: 145,
|
|
444
|
+
width: lableSize,
|
|
445
|
+
height: lableSize * (image2.height / image2.width),
|
|
446
|
+
});
|
|
447
|
+
newPage.drawImage(image3, {
|
|
448
|
+
x: 28 + 2 * lableSize + 6 * 2,
|
|
449
|
+
y: 145,
|
|
450
|
+
width: lableSize * .8,
|
|
451
|
+
height: lableSize * .8 * (image3.height / image3.width),
|
|
452
|
+
});
|
|
453
|
+
console.log(`success in page. ${i} `)
|
|
454
|
+
// north arrow
|
|
455
|
+
const centerX = 40;
|
|
456
|
+
const centerY = 110;
|
|
457
|
+
const halfWidth = 10;
|
|
458
|
+
const halfHeight = 10;
|
|
459
|
+
const angle = inputs.rotation[i]
|
|
460
|
+
const newX = centerX - halfWidth * 1.22 * Math.cos((angle - 35) / 180 * Math.PI);
|
|
461
|
+
const newY = centerY + halfHeight * 1.22 * Math.sin((angle - 35) / 180 * Math.PI);
|
|
462
|
+
newPage.drawImage(imageNorth, {
|
|
463
|
+
x: newX,
|
|
464
|
+
y: newY,
|
|
465
|
+
width: 20,
|
|
466
|
+
height: 20,
|
|
467
|
+
rotate: degrees(-angle),
|
|
468
|
+
});
|
|
469
|
+
// scale bar
|
|
470
|
+
const barScale = 0.5
|
|
471
|
+
const barMaxDistance_nochange = imageBar.width * pixel2MeterOnPaper * inputs.scaleArray[i] * 0.544797456 * barScale
|
|
472
|
+
const niceBar = this.nearestNiceInteger(barMaxDistance_nochange)
|
|
473
|
+
const newBarPixel = imageBar.width * (niceBar / barMaxDistance_nochange) * barScale
|
|
474
|
+
const scalebarX = 59
|
|
475
|
+
newPage.drawImage(imageBar, {
|
|
476
|
+
x: scalebarX,
|
|
477
|
+
y: 93,
|
|
478
|
+
width: newBarPixel,
|
|
479
|
+
height: imageBar.height * barScale,
|
|
480
|
+
});
|
|
481
|
+
// 0
|
|
482
|
+
newPage.drawText('0', {
|
|
483
|
+
x: scalebarX,
|
|
484
|
+
y: 113,
|
|
485
|
+
size: 8
|
|
486
|
+
})
|
|
487
|
+
// half scale bar number
|
|
488
|
+
newPage.drawText(Math.ceil(niceBar / 2).toString(), {
|
|
489
|
+
x: scalebarX + newBarPixel / 2,//dynamic
|
|
490
|
+
y: 113,
|
|
491
|
+
size: 8
|
|
492
|
+
})
|
|
493
|
+
// 100% scale bar number
|
|
494
|
+
newPage.drawText(Math.ceil(niceBar).toString(), {
|
|
495
|
+
x: scalebarX + newBarPixel,//dynamic
|
|
496
|
+
y: 113,
|
|
497
|
+
size: 8
|
|
498
|
+
})
|
|
499
|
+
const font = await doc.embedFont(StandardFonts.Helvetica);
|
|
500
|
+
//title
|
|
501
|
+
const titleField = form.getTextField('Title.' + i)
|
|
502
|
+
titleField.setText(inputs.Title)
|
|
503
|
+
titleField.setAlignment(TextAlignment.Center)
|
|
504
|
+
titleField.updateAppearances(font)
|
|
505
|
+
//printer
|
|
506
|
+
const printerField = form.getTextField('Printer.' + i)
|
|
507
|
+
printerField.setText(inputs.PrintUser)
|
|
508
|
+
printerField.updateAppearances(font)
|
|
509
|
+
//description
|
|
510
|
+
const descrField = form.getTextField('RevDescr.' + i)
|
|
511
|
+
descrField.setText(inputs.Descr)
|
|
512
|
+
descrField.updateAppearances(font)
|
|
513
|
+
//address
|
|
514
|
+
const addrField = form.getTextField('Address.' + i)
|
|
515
|
+
addrField.setText(inputs.addressList[i])
|
|
516
|
+
addrField.updateAppearances(font)
|
|
517
|
+
//scale ratio
|
|
518
|
+
const scaletext = '1:' + Math.ceil(inputs.scaleArray[i] * 0.544797456);
|
|
519
|
+
const scaleNumber = form.getTextField('Scale.' + i)
|
|
520
|
+
scaleNumber.setText(scaletext)
|
|
521
|
+
scaleNumber.enableReadOnly()
|
|
522
|
+
scaleNumber.updateAppearances(font)
|
|
523
|
+
//revision
|
|
524
|
+
const revField = form.getTextField('Rev.' + i)
|
|
525
|
+
revField.setText(inputs.Rev)
|
|
526
|
+
revField.updateAppearances(font)
|
|
527
|
+
//date
|
|
528
|
+
const today = new Date();
|
|
529
|
+
const formattedDate = today.toLocaleDateString('en-AU');
|
|
530
|
+
const dateField1 = form.getTextField('DateA.' + i);
|
|
531
|
+
dateField1.setText(formattedDate);
|
|
532
|
+
dateField1.updateAppearances(font)
|
|
533
|
+
const dateField2 = form.getTextField('DateB.' + i);
|
|
534
|
+
dateField2.setText(formattedDate);
|
|
535
|
+
dateField2.updateAppearances(font)
|
|
536
|
+
//Note, will wrap to new line]
|
|
537
|
+
const noteField = form.getTextField('Note.' + i);
|
|
538
|
+
noteField.setText(inputs.Note);
|
|
539
|
+
noteField.updateAppearances(font)
|
|
540
|
+
console.log('Give values Successful')
|
|
541
|
+
let FieldNames = ['Title', 'Printer', 'RevDescr', 'Address', 'Rev', 'Scale', 'DateA', 'DateB', 'Note']
|
|
542
|
+
//Remove those not used in current page
|
|
543
|
+
for (let k = 0; k < 10; k++) {
|
|
544
|
+
if (k != i) {
|
|
545
|
+
for (let field of FieldNames) {
|
|
546
|
+
let tmp = form.getTextField(field + '.' + k)
|
|
547
|
+
const widget0 = tmp.acroField.getWidgets()[0];
|
|
548
|
+
const AP = widget0.ensureAP();
|
|
549
|
+
AP.set(PDFName.of('N'), PDFRef.of(0, 0));
|
|
550
|
+
form.removeField(tmp);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
console.log('Text Fields removal successful')
|
|
555
|
+
//draw a QR Code image only when Qr URL is given && QR image not given
|
|
556
|
+
|
|
557
|
+
if (inputs.buttonLink && inputs.buttonLink.length>0) {
|
|
558
|
+
const buttonX=width * 0.74
|
|
559
|
+
const buttonY=130
|
|
560
|
+
const buttonW=90
|
|
561
|
+
const buttonH=50
|
|
562
|
+
const buttonBackgroundUrl = 'https://cdn.jsdelivr.net/npm/ebta-resources/resources/prints/gis-button.png'
|
|
563
|
+
const buttonBackgroundBuffer = await this.fetchData(buttonBackgroundUrl);
|
|
564
|
+
const buttonEmbed = await doc.embedPng(buttonBackgroundBuffer);
|
|
565
|
+
newPage.drawImage(buttonEmbed, {
|
|
566
|
+
x: buttonX,
|
|
567
|
+
y: buttonY,
|
|
568
|
+
width: buttonW,
|
|
569
|
+
height: buttonH,
|
|
570
|
+
});
|
|
571
|
+
const boldFont = await doc.embedFont(StandardFonts.HelveticaBold);
|
|
572
|
+
newPage.drawText('Go to EBTA GIS', {
|
|
573
|
+
x: buttonX+5,
|
|
574
|
+
y: buttonY+22,
|
|
575
|
+
size: 10,
|
|
576
|
+
color: rgb(1, 1, 1), // White color
|
|
577
|
+
font: boldFont,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const existingAnnots = newPage.node.lookup(PDFName.of('Annots'));
|
|
581
|
+
let annotationsArray = existingAnnots instanceof PDFArray ? existingAnnots : doc.context.obj([]);
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
const linkAnnotation = doc.context.register(
|
|
585
|
+
doc.context.obj({
|
|
586
|
+
Type: 'Annot',
|
|
587
|
+
Subtype: 'Link',
|
|
588
|
+
Rect: [buttonX, buttonY, buttonX+buttonW,buttonY+buttonH],
|
|
589
|
+
Border: [0, 0, 0],
|
|
590
|
+
A: {
|
|
591
|
+
Type: 'Action',
|
|
592
|
+
S: 'URI',
|
|
593
|
+
URI: PDFString.of((i <= (inputs.buttonLink.length - 1)) ? inputs.buttonLink[i] : inputs.buttonLink[inputs.buttonLink.length - 1]),
|
|
594
|
+
},
|
|
595
|
+
})
|
|
596
|
+
);
|
|
597
|
+
// Append the new annotation to the existing ones
|
|
598
|
+
annotationsArray.push(linkAnnotation);
|
|
599
|
+
// Set the updated annotations array back to the page
|
|
600
|
+
newPage.node.set(PDFName.of('Annots'), annotationsArray);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (inputs.QrUrl && inputs.QrUrl.length > 0 && (inputs.QrCodeImage == undefined)) {
|
|
604
|
+
//QR Code
|
|
605
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
606
|
+
const QRCode = require('qrcode');
|
|
607
|
+
const qrDataUrl = (i <= (inputs.QrUrl.length - 1)) ? await QRCode.toDataURL(inputs.QrUrl[i]) : await QRCode.toDataURL(inputs.QrUrl[inputs.QrUrl.length - 1]);
|
|
608
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
609
|
+
const qrImageBytes = await fetch(qrDataUrl).then(res => res.arrayBuffer());
|
|
610
|
+
const qrImage = await doc.embedPng(qrImageBytes);
|
|
611
|
+
newPage.drawImage(qrImage, {
|
|
612
|
+
x: 210,
|
|
613
|
+
y: 93,
|
|
614
|
+
width: 40,
|
|
615
|
+
height: 40,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const existingAnnots = newPage.node.lookup(PDFName.of('Annots'));
|
|
619
|
+
let annotationsArray = existingAnnots instanceof PDFArray ? existingAnnots : doc.context.obj([]);
|
|
620
|
+
const linkAnnotation = doc.context.register(
|
|
621
|
+
doc.context.obj({
|
|
622
|
+
Type: 'Annot',
|
|
623
|
+
Subtype: 'Link',
|
|
624
|
+
Rect: [210, 93, 250, 93+40],
|
|
625
|
+
Border: [0, 0, 0],
|
|
626
|
+
A: {
|
|
627
|
+
Type: 'Action',
|
|
628
|
+
S: 'URI',
|
|
629
|
+
URI: PDFString.of((i <= (inputs.QrUrl.length - 1)) ? inputs.QrUrl[i] : inputs.QrUrl[inputs.QrUrl.length - 1]),
|
|
630
|
+
},
|
|
631
|
+
})
|
|
632
|
+
);
|
|
633
|
+
// Append the new annotation to the existing ones
|
|
634
|
+
annotationsArray.push(linkAnnotation);
|
|
635
|
+
// Set the updated annotations array back to the page
|
|
636
|
+
newPage.node.set(PDFName.of('Annots'), annotationsArray);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
if (inputs.QrCodeImage && inputs.QrCodeImage.length > 0) {
|
|
641
|
+
const inputQrImageUrl = (i <= (inputs.QrCodeImage.length - 1)) ? inputs.QrCodeImage[i] : inputs.QrCodeImage[inputs.QrCodeImage.length - 1]
|
|
642
|
+
const inputQrBuffer = await this.fetchData(inputQrImageUrl);
|
|
643
|
+
console.log('input QR Code image fetched successfully');
|
|
644
|
+
let inputQRembed
|
|
645
|
+
console.log(inputQrImageUrl);
|
|
646
|
+
console.log(inputQrImageUrl.toLowerCase().includes(".png"));
|
|
647
|
+
console.log(inputQrImageUrl.toLowerCase().includes(".jpg")||inputQrImageUrl.toLowerCase().includes(".jpeg"));
|
|
648
|
+
if (inputQrImageUrl.toLowerCase().includes(".png")){
|
|
649
|
+
inputQRembed = await doc.embedPng(inputQrBuffer);
|
|
650
|
+
console.log('embed QR Code image as png');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
else if (inputQrImageUrl.toLowerCase().includes(".jpg")||inputQrImageUrl.toLowerCase().includes(".jpeg")){
|
|
654
|
+
console.log(inputQrBuffer)
|
|
655
|
+
const dataView = new DataView(inputQrBuffer);
|
|
656
|
+
console.log(dataView)
|
|
657
|
+
// Read the first two bytes (SOI marker)
|
|
658
|
+
const soiMarker = dataView.getUint16(0);
|
|
659
|
+
console.log(soiMarker)
|
|
660
|
+
// Check if the SOI marker is correct
|
|
661
|
+
if (soiMarker === 0xFFD8) {
|
|
662
|
+
console.log('SOI marker found: 0xFFD8, keep going');
|
|
663
|
+
inputQRembed = await doc.embedJpg(inputQrBuffer);
|
|
664
|
+
} else {
|
|
665
|
+
console.log('SOI marker not found but forcing to be correct.');
|
|
666
|
+
let fixedArrayBuffer = await this.reencodeImageToArrayBuffer(inputQrBuffer)
|
|
667
|
+
inputQRembed = await doc.embedJpg(fixedArrayBuffer);
|
|
668
|
+
}
|
|
669
|
+
console.log('embed QR Code image as jpg successful');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
newPage.drawImage(inputQRembed, {
|
|
674
|
+
x: 210,
|
|
675
|
+
y: 93,
|
|
676
|
+
width: 40,
|
|
677
|
+
height: 40,
|
|
678
|
+
});
|
|
679
|
+
console.log('draw input QR Code image finished');
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
// field view ID, only happens when has value
|
|
684
|
+
if (inputs.FiewViewId && inputs.FiewViewId.length > 0) {
|
|
685
|
+
const fieldViewIdField = form.createTextField('FieldViewId' + i);
|
|
686
|
+
fieldViewIdField.setText((i <= (inputs.FiewViewId.length - 1)) ? inputs.FiewViewId[i].toString() : inputs.FiewViewId[inputs.FiewViewId.length - 1].toString());
|
|
687
|
+
fieldViewIdField.addToPage(newPage, {
|
|
688
|
+
x: 213,
|
|
689
|
+
y: 78,
|
|
690
|
+
height: 12,
|
|
691
|
+
width: 40,
|
|
692
|
+
borderWidth: 0,
|
|
693
|
+
textColor: rgb(0, 0, 0),
|
|
694
|
+
backgroundColor: rgb(1, 1, 1),
|
|
695
|
+
borderColor: rgb(1, 1, 1),
|
|
696
|
+
});
|
|
697
|
+
console.log('field View ID text added.');
|
|
698
|
+
}
|
|
699
|
+
filledPDFs.push(await doc.save());
|
|
700
|
+
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const finalPDF = await PDFDocument.create();
|
|
704
|
+
|
|
705
|
+
// Copy pages from all filled PDFs into the final document
|
|
706
|
+
for (const pdfBytes of filledPDFs) {
|
|
707
|
+
const pdf = await PDFDocument.load(pdfBytes);
|
|
708
|
+
const copiedPages = await finalPDF.copyPages(pdf, pdf.getPageIndices());
|
|
709
|
+
copiedPages.forEach(page => finalPDF.addPage(page));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const pdfBytes = await finalPDF.save();
|
|
713
|
+
const base64String = this.arrayBufferToBase64(pdfBytes);
|
|
714
|
+
const downloadUrl = this.createDownloadUrl(base64String);
|
|
715
|
+
console.log('pdf file created and ready to download');
|
|
716
|
+
return {
|
|
717
|
+
/**
|
|
718
|
+
* @description The result pdf created for printing.
|
|
719
|
+
*/
|
|
720
|
+
result: downloadUrl,
|
|
721
|
+
};
|
|
722
|
+
} catch (error) {
|
|
723
|
+
console.error('Error in PDF Map Generator:', error);
|
|
724
|
+
return {
|
|
725
|
+
result: 'ERROR',
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|