@shapediver/viewer.shared.services 3.3.4 → 3.3.7

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": "@shapediver/viewer.shared.services",
3
- "version": "3.3.4",
3
+ "version": "3.3.7",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "Michael Oppitz <michael@shapediver.com>",
@@ -10,11 +10,10 @@
10
10
  "test": "__tests__"
11
11
  },
12
12
  "files": [
13
- "dist",
14
- "src",
15
13
  "package.json",
14
+ "dist/",
16
15
  "README.md",
17
- "tsconfig.json"
16
+ "LICENSE"
18
17
  ],
19
18
  "publishConfig": {
20
19
  "access": "public"
@@ -42,7 +41,7 @@
42
41
  "@ctrl/tinycolor": "^3.4.0",
43
42
  "@shapediver/sdk.geometry-api-sdk-v2": "1.11.0",
44
43
  "@shapediver/viewer.settings": "1.0.2",
45
- "@shapediver/viewer.shared.build-data": "3.3.4",
44
+ "@shapediver/viewer.shared.build-data": "3.3.7",
46
45
  "@types/dompurify": "^2.3.1",
47
46
  "@types/ua-parser-js": "^0.7.36",
48
47
  "@types/uuid": "^9.0.0",
@@ -52,5 +51,5 @@
52
51
  "ua-parser-js": "^0.7.28",
53
52
  "uuid": "^9.0.0"
54
53
  },
55
- "gitHead": "8193da527b4e3fc4d90181018bd60d6ac70be3e8"
54
+ "gitHead": "112787d5c5226cca5e89d08102d0b1a3dd4a1d71"
56
55
  }
@@ -1,407 +0,0 @@
1
- import { ColorInput, TinyColor } from '@ctrl/tinycolor';
2
- import { HttpClient } from '../http-client/HttpClient';
3
- import { HttpResponse } from '../http-client/HttpResponse';
4
- import { vec3 } from 'gl-matrix';
5
- import { atobCustom, btoaCustom } from '../utilities/base64';
6
-
7
- export class Converter {
8
- // #region Properties (2)
9
-
10
- private readonly _httpClient: HttpClient = HttpClient.instance;
11
-
12
- private static _instance: Converter;
13
-
14
- // #endregion Properties (2)
15
-
16
- // #region Public Static Getters And Setters (1)
17
-
18
- public static get instance() {
19
- return this._instance || (this._instance = new this());
20
- }
21
-
22
- /**
23
- * Converts a data URL to a Blob object.
24
- *
25
- * @param dataURL
26
- * @returns
27
- */
28
- public dataURLtoBlob(dataURL: string) {
29
- // Split the data URL to get the base64 data
30
- const arr = dataURL.split(",");
31
- const mime = arr[0].match(/:(.*?);/)![1];
32
- const bstr = window.atob(arr[1]);
33
-
34
- let n = bstr.length;
35
- const u8arr = new Uint8Array(n);
36
-
37
- // Convert the binary string to a Uint8Array
38
- while (n--) u8arr[n] = bstr.charCodeAt(n);
39
-
40
- // Create a Blob object from the Uint8Array
41
- return {
42
- blob: new Blob([u8arr], { type: mime }),
43
- arrayBuffer: u8arr.buffer
44
- };
45
- }
46
-
47
- /**
48
- * Convert the given image to an ArrayBuffer and return the image data.
49
- *
50
- * @param image The image to convert.
51
- * @param arrayBuffer Optional: The ArrayBuffer of the image, if it was already converted.
52
- * @returns
53
- */
54
- public async constructImageData(image: Blob | File, arrayBuffer?: ArrayBuffer): Promise<{
55
- imageData: {
56
- filename?: string,
57
- format: string,
58
- size: number
59
- },
60
- arrayBuffer: ArrayBuffer
61
- }> {
62
- if(image instanceof File) {
63
- return {
64
- imageData: {
65
- filename: image.name,
66
- format: image.type,
67
- size: image.size
68
- },
69
- arrayBuffer: arrayBuffer || await image.arrayBuffer()
70
- };
71
- } else {
72
- return {
73
- imageData: {
74
- format: image.type,
75
- size: image.size
76
- },
77
- arrayBuffer: arrayBuffer || await image.arrayBuffer()
78
- };
79
- }
80
- }
81
-
82
- /**
83
- * Convert the given input to an ArrayBuffer.
84
- *
85
- * @param input
86
- * @returns
87
- */
88
- public async convertToArrayBuffer(input: (() => Promise<ArrayBuffer>) | ArrayBuffer | (() => Promise<Blob>) | Blob | File): Promise<ArrayBuffer> {
89
- if (input instanceof File) {
90
- return await input.arrayBuffer();
91
- } else if (input instanceof Blob) {
92
- return await input.arrayBuffer();
93
- } else if (input instanceof ArrayBuffer) {
94
- return input;
95
- } else {
96
- const result = await input();
97
- if (result instanceof Blob) {
98
- return await result.arrayBuffer();
99
- } else {
100
- return result;
101
- }
102
- }
103
- }
104
-
105
- // #endregion Public Static Getters And Setters (1)
106
-
107
- // #region Public Methods (8)
108
-
109
- public async processSVG(blob: Blob): Promise<HTMLImageElement> {
110
- let data = <string>await new Promise((resolve, reject) => {
111
- const reader = new FileReader();
112
- reader.onloadend = () => resolve(<string>reader.result);
113
- reader.onerror = reject;
114
- reader.readAsDataURL(blob);
115
- });
116
- data = data.replace('data:image/svg+xml;base64,', '');
117
- data = atobCustom(data);
118
-
119
- const svgC = document.createElement('DIV');
120
- svgC.id = 'svgc';
121
- svgC.innerHTML = <string>data;
122
-
123
- // now we can access the svg element as a DOM object
124
- const svgE = svgC.getElementsByTagName('svg');
125
- const childImageURIs: string[] = [];
126
- let styleURIs: string[] = [];
127
-
128
- // collect image urls
129
- for (let i = 0; i < svgE.length; ++i) {
130
- for (let j = 0; j < 2; ++j) {
131
- const childImages = <HTMLCollectionOf<SVGImageElement>>svgE[i].getElementsByTagName(['image', 'img'][j]);
132
- for (let k = 0; k < childImages.length; ++k) {
133
- if (childImages[k].href.baseVal.substring(0, 5) != 'data:') {
134
- childImageURIs.push(childImages[k].href.baseVal);
135
- }
136
- }
137
- }
138
- // collect potential font definitions
139
- // we assume styles are imported using the following syntax:
140
- // @import url(CSS_URL);
141
- const styleElements = <HTMLCollectionOf<HTMLStyleElement>>svgE[i].getElementsByTagName('style');
142
- for (let j = 0; j < styleElements.length; ++j) {
143
- const regex = /@import\x20url\(\s*(.*?)\s*\);/g;
144
- let m;
145
- while ((m = regex.exec(styleElements[j].innerHTML)) !== null) {
146
- styleURIs.push(m[1]);
147
- }
148
- // make unique
149
- styleURIs = styleURIs.filter(
150
- function (value, index, self) {
151
- return self.indexOf(value) === index;
152
- }
153
- );
154
- }
155
- }
156
-
157
- // creating a promise for each image which needs to be converted to a data URI
158
- const replacementPromises = [];
159
- const createImagePromise = async (uri: string) => {
160
- if (uri.length > 0) {
161
- const response = await this._httpClient.get(uri, undefined, true) as HttpResponse<ArrayBuffer>;
162
- const uInt8Array = new Uint8Array(response.data as ArrayBuffer);
163
- let i = uInt8Array.length;
164
- const biStr = []; //new Array(i);
165
- while (i--)
166
- biStr[i] = String.fromCharCode(uInt8Array[i]);
167
-
168
- const base64Data = btoaCustom(biStr.join(''));
169
- const imgDataUrl = 'data:' + response.headers['content-type'] + ';base64,' + base64Data;
170
-
171
- // replace url in SVG string
172
- // CAUTION theoretically this could cause unwanted replacements
173
- data = data.replace(uri, imgDataUrl);
174
- }
175
- };
176
-
177
- for (let i = 0; i < childImageURIs.length; ++i)
178
- replacementPromises.push(createImagePromise(childImageURIs[i]));
179
-
180
- // now we create promises for the google fonts to be imported
181
- const createStylePromise = async (styleUrl: string) => {
182
- const response = await this._httpClient.get(
183
- styleUrl,
184
- { responseType: 'text' }
185
- ) as HttpResponse<ArrayBuffer>;
186
- let cssString = response.data as unknown as string;
187
- // we assume that fonts are imported using the following syntax:
188
- // url(FONT_URI);
189
- const fontURLs = [];
190
- const regex = /url\(\s*(.*?)\s*\)/g;
191
- let m;
192
- while ((m = regex.exec(cssString)) !== null) {
193
- fontURLs.push(m[1]);
194
- }
195
-
196
- const fontPromises = [];
197
- const createFontPromise = async (fUrl: string) => {
198
- const response = await this._httpClient.get(
199
- fUrl,
200
- { responseType: 'arraybuffer' }
201
- ) as HttpResponse<ArrayBuffer>;
202
- const uInt8Array = new Uint8Array(response.data as ArrayBuffer);
203
- let i = uInt8Array.length;
204
- const biStr = []; //new Array(i);
205
- while (i--)
206
- biStr[i] = String.fromCharCode(uInt8Array[i]);
207
-
208
- const base64Data = btoaCustom(biStr.join(''));
209
- const fontDataUrl = 'data:' + response.headers['content-type'] + ';base64,' + base64Data;
210
- if (fUrl.length > 0)
211
- cssString = cssString.replace(fUrl, fontDataUrl);
212
- };
213
-
214
- for (let j = 0; j < fontURLs.length; ++j)
215
- fontPromises.push(createFontPromise(fontURLs[j]));
216
-
217
- await Promise.all(fontPromises);
218
- data = data.replace('@import url(' + styleUrl + ');', cssString);
219
- };
220
-
221
- for (let i = 0; i < styleURIs.length; ++i)
222
- replacementPromises.push(createStylePromise(styleURIs[i]));
223
-
224
- await Promise.all(replacementPromises);
225
-
226
- const du = 'data:image/svg+xml,' + encodeURIComponent(data);
227
- const img = new Image(); // same as document.createElement('img')
228
- img.crossOrigin = 'Anonymous';
229
- const promise = new Promise<void>((resolve, reject) => {
230
- img.onload = () => resolve();
231
- img.onerror = reject;
232
- });
233
- img.src = du;
234
- await promise;
235
- return img;
236
- }
237
-
238
- public async responseToImage(response: HttpResponse<{ buffer: ArrayBuffer, blob: Blob }>): Promise<HTMLImageElement> {
239
- // if we already receive and image, this conversion already happened
240
- if (response.data instanceof HTMLImageElement) return response.data;
241
-
242
- if (response.headers['content-type'] === 'image/svg+xml') {
243
- const img = await this.processSVG(response.data.blob);
244
- return img;
245
- } else {
246
- const img = new Image();
247
- const promise = new Promise<void>((resolve, reject) => {
248
- img.onload = () => resolve();
249
- img.onerror = reject;
250
- });
251
- img.crossOrigin = 'anonymous';
252
- img.src = URL.createObjectURL(response.data.blob);
253
- await promise;
254
- URL.revokeObjectURL(img.src);
255
- return img;
256
- }
257
- }
258
-
259
- public toAlpha(color: unknown): number {
260
- const c = this.toHexColor(color);
261
- if (c.length <= 8) return 1;
262
- return parseInt(c.slice(c.length - 2, c.length), 16) / 255;
263
- }
264
-
265
- public toColorArray(color: unknown): number[] {
266
- if (typeof color !== 'string' || !color.startsWith('#'))
267
- color = this.toHexColor(color);
268
- const tColor = new TinyColor(color as ColorInput | undefined);
269
- const rgb = tColor.toRgb();
270
- return [rgb.r / 255.0, rgb.g / 255.0, rgb.b / 255.0];
271
- }
272
-
273
- /**
274
- * @param color
275
- * @param defColor
276
- */
277
- public toHex8Color(color: unknown, defColorString: string = '#000'): string {
278
- const c = this.toHexColor(color, defColorString);
279
- const tColor = new TinyColor(c);
280
- const cH8 = tColor.toHex8String();
281
- return cH8.replace('#', '0x');
282
- }
283
-
284
- /**
285
- * This color converter is mostly left 'as-is' from viewer v2.
286
- * I didn't want to break something that works.
287
- *
288
- * @param color
289
- * @param defColor
290
- */
291
- public toHexColor(color: unknown, defColorString: string = '#000'): string {
292
- if (!color || color === 'default') return defColorString;
293
-
294
- if ((color as Float32Array).constructor === Float32Array)
295
- color = Array.from(color as Float32Array);
296
-
297
- const tColor = new TinyColor(color as TinyColor | undefined);
298
-
299
- if (color instanceof TinyColor)
300
- return this.tinyColorToString(tColor);
301
-
302
- // check if we got a number
303
- if (typeof color === 'number') {
304
- let cs = color.toString(16);
305
- const cl = cs.length;
306
- if (cl < 3) cs = cs.padStart(3, '0');
307
- else if (cl < 6) cs = cs.padStart(6, '0');
308
- else if (cl < 8) cs = cs.padEnd(8, '0');
309
- const tc = new TinyColor(cs);
310
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
311
- }
312
-
313
- // check if the input is a THREE.Color
314
- if (typeof color === 'object' && 'isColor' in (color as object) && 'getHexString' in (color as object) && typeof (color as { getHexString: unknown }).getHexString === 'function'){
315
- const tc = new TinyColor((color as { getHexString: () => string }).getHexString());
316
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
317
- }
318
-
319
- // check for array of numbers
320
- if (Array.isArray(color) && (color.length == 3 || color.length == 4)) {
321
- let isRGBArray = true;
322
- for (let i = 0; i < 3; ++i) {
323
- color[i] = parseFloat(color[i]);
324
- if (isNaN(color[i])) {
325
- isRGBArray = false;
326
- }
327
- }
328
- if (!isRGBArray)
329
- return defColorString;
330
-
331
- const tc = new TinyColor({
332
- r: Math.max(0, Math.min(color[0], 255)),
333
- g: Math.max(0, Math.min(color[1], 255)),
334
- b: Math.max(0, Math.min(color[2], 255))
335
- });
336
- if (color.length == 4) {
337
- const a = parseFloat(color[3]);
338
- if (!isNaN(a)) {
339
- tc.setAlpha(Math.max(0, Math.min(a, 255)) / 255);
340
- }
341
- }
342
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
343
- }
344
-
345
- // if we got something other than a string, check if
346
- // tinycolor can work with it
347
- if (typeof color !== 'string') {
348
- const tc = new TinyColor(color as string);
349
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
350
- }
351
-
352
- // tinycolor doesn't like 0x
353
- const tmpColor = color.replace('0x', '#');
354
-
355
- // if we got no alpha value, add full opacity
356
- if (tmpColor.match(/^#[a-f0-9]{6}$/i) !== null) {
357
- const tc = new TinyColor(tmpColor + 'ff');
358
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
359
- }
360
-
361
- // standard case
362
- if (tmpColor.match(/^#[a-f0-9]{8}$/i) !== null) {
363
- const tc = new TinyColor(tmpColor);
364
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
365
- }
366
-
367
- // correct number which have the alpha value defined as a single hex digit
368
- if (tmpColor.match(/^#[a-f0-9]{7}$/i) !== null) {
369
- const tc = new TinyColor(tmpColor.slice(0, 7) + '0' + tmpColor.slice(-1));
370
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
371
- }
372
-
373
- // check if tinycolor understands the string
374
- const tc = new TinyColor(tmpColor);
375
- return tc.isValid ? this.tinyColorToString(tc) : defColorString;
376
- }
377
-
378
- public toThreeJsColorInput(color: unknown): string {
379
- const c = this.toHexColor(color);
380
- return c.slice(0, c.length - 2);
381
- }
382
-
383
- public toVec3(point: vec3 | { x: number, y: number, z: number } | { X: number, Y: number, Z: number }): vec3 {
384
- if (Array.isArray(point) && point.length >= 3 && typeof point[0] === 'number' && typeof point[1] === 'number' && typeof point[2] === 'number')
385
- return vec3.fromValues(point[0], point[1], point[2]);
386
-
387
- const pointCast1 = point as { x: number, y: number, z: number };
388
- if (((pointCast1.x || pointCast1.x === 0) && typeof pointCast1.x === 'number') && ((pointCast1.y || pointCast1.y === 0) && typeof pointCast1.y === 'number') && ((pointCast1.z || pointCast1.z === 0) && typeof pointCast1.z === 'number'))
389
- return vec3.fromValues(pointCast1.x, pointCast1.y, pointCast1.z);
390
-
391
- const pointCast2 = point as { X: number, Y: number, Z: number };
392
- if (((pointCast2.X || pointCast2.X === 0) && typeof pointCast2.X === 'number') && ((pointCast2.Y || pointCast2.Y === 0) && typeof pointCast2.Y === 'number') && ((pointCast2.Z || pointCast2.Z === 0) && typeof pointCast2.Z === 'number'))
393
- return vec3.fromValues(pointCast2.X, pointCast2.Y, pointCast2.Z);
394
-
395
- return vec3.create();
396
- }
397
-
398
- // #endregion Public Methods (8)
399
-
400
- // #region Private Methods (1)
401
-
402
- private tinyColorToString(color: TinyColor): string {
403
- return color.toHex8String();
404
- }
405
-
406
- // #endregion Private Methods (1)
407
- }