@pirireis/webglobeplugins 1.4.5 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pirireis/webglobeplugins",
3
- "version": "1.4.5",
3
+ "version": "1.5.1",
4
4
  "main": "index.js",
5
5
  "author": "Toprak Nihat Deniz Ozturk",
6
6
  "license": "MIT",
@@ -0,0 +1,20 @@
1
+ export const bluePrint = new Map([
2
+ ['ship', { x: 0, y: 0, width: 21, height: 31, pixelRatio: 1 }],
3
+ ['flag', {
4
+ x: 2,
5
+ y: 2,
6
+ width: 16,
7
+ height: 9,
8
+ pixelRatio: 1
9
+ }],
10
+ ]);
11
+ export const bluePrint2 = new Map([
12
+ ['ship', { x: 0, y: 0, width: 69, height: 99, pixelRatio: 1 }],
13
+ ['flag', {
14
+ x: 9,
15
+ y: 7,
16
+ width: 49,
17
+ height: 22,
18
+ pixelRatio: 1
19
+ }],
20
+ ]);
@@ -0,0 +1,93 @@
1
+ import { Meta } from "./meta/meta";
2
+ import { SingleIconPrinter } from "./printers/single-icon";
3
+ export class IconFactory {
4
+ combinationMap;
5
+ meta;
6
+ printer;
7
+ atlasCanvas;
8
+ ready = false;
9
+ constructor(parts, bluePrint) {
10
+ this.combinationMap = new Map();
11
+ this.printer = new SingleIconPrinter(parts, bluePrint);
12
+ const baseSize = this.printer.baseSize;
13
+ this.meta = new Meta(baseSize, this.combinationMap);
14
+ this.atlasCanvas = document.createElement('canvas');
15
+ this.atlasCanvas.width = this.meta.getAtlasIconSize().x;
16
+ this.atlasCanvas.height = this.meta.getAtlasIconSize().y;
17
+ }
18
+ async initialize() {
19
+ if (this.ready) {
20
+ return;
21
+ }
22
+ const spriteSheetMap = this.meta.getSpriteSheetMap();
23
+ const ctx = this.atlasCanvas.getContext('2d');
24
+ if (!ctx) {
25
+ throw new Error('Failed to get 2D context from atlas canvas.');
26
+ }
27
+ for (const [key, element] of spriteSheetMap.entries()) {
28
+ const partInput = this.combinationMap.get(key);
29
+ await this.printer.printImage(partInput, this.atlasCanvas, element);
30
+ }
31
+ this.ready = true;
32
+ }
33
+ async getSpriteSheet() {
34
+ await this.initialize();
35
+ return {
36
+ atlas: this.atlasCanvas,
37
+ spriteSheetMap: this.meta.getSpriteSheetMap()
38
+ };
39
+ }
40
+ async getIcon(attribs) {
41
+ const partInputs = new Map();
42
+ partInputs.set('ship', attribs?.ship);
43
+ partInputs.set('flag', attribs?.flag);
44
+ return this.printer.getImage(partInputs);
45
+ }
46
+ static canvasToBlobUrl(canvas) {
47
+ return new Promise((resolve, reject) => {
48
+ if (!canvas) {
49
+ reject(new Error('Atlas canvas is missing.'));
50
+ return;
51
+ }
52
+ if (typeof canvas.toBlob !== 'function') {
53
+ reject(new Error('Canvas toBlob is not supported.'));
54
+ return;
55
+ }
56
+ canvas.toBlob((blob) => {
57
+ if (!blob) {
58
+ reject(new Error('Could not serialize atlas canvas.'));
59
+ return;
60
+ }
61
+ resolve(URL.createObjectURL(blob));
62
+ }, 'image/png');
63
+ });
64
+ }
65
+ static jsonToBlobUrl(payload) {
66
+ const blob = new Blob([JSON.stringify(payload)], { type: 'application/json' });
67
+ return URL.createObjectURL(blob);
68
+ }
69
+ async craeteAndSetToGlobeManagerIconMap(combinationsMap, atlasName, globeManager, callback) {
70
+ if (!(combinationsMap instanceof Map)) {
71
+ throw new Error('combinationsMap must be a Map instance.');
72
+ }
73
+ if (combinationsMap !== this.combinationMap) {
74
+ this.combinationMap = combinationsMap;
75
+ const baseSize = this.printer.baseSize;
76
+ this.meta = new Meta(baseSize, combinationsMap);
77
+ this.atlasCanvas = document.createElement('canvas');
78
+ this.atlasCanvas.width = this.meta.getAtlasIconSize().x;
79
+ this.atlasCanvas.height = this.meta.getAtlasIconSize().y;
80
+ this.ready = false;
81
+ }
82
+ const { atlas, spriteSheetMap } = await this.getSpriteSheet();
83
+ const spriteSheetUrl = await IconFactory.canvasToBlobUrl(atlas);
84
+ const spriteSheetMapUrl = IconFactory.jsonToBlobUrl(Object.fromEntries(spriteSheetMap));
85
+ globeManager.api_AddIconMap(atlasName, spriteSheetUrl, spriteSheetMapUrl, callback);
86
+ return {
87
+ atlas,
88
+ spriteSheetMap,
89
+ spriteSheetUrl,
90
+ spriteSheetMapUrl,
91
+ };
92
+ }
93
+ }
@@ -0,0 +1,23 @@
1
+ import { createSpriteMap } from "./sprite-map";
2
+ export class Meta {
3
+ atlasIconSize;
4
+ spriteSheetMap;
5
+ constructor(sourceIconSize, combinationMap) {
6
+ const { spriteSheetMap, atlasSize } = createSpriteMap(sourceIconSize, combinationMap);
7
+ this.atlasIconSize = atlasSize;
8
+ this.spriteSheetMap = spriteSheetMap;
9
+ const atlasCanvas = document.createElement('canvas');
10
+ atlasCanvas.width = atlasSize.x;
11
+ atlasCanvas.height = atlasSize.y;
12
+ const ctx = atlasCanvas.getContext('2d');
13
+ if (!ctx) {
14
+ throw new Error('Failed to get 2D context from atlas canvas.');
15
+ }
16
+ }
17
+ getSpriteSheetMap() {
18
+ return this.spriteSheetMap;
19
+ }
20
+ getAtlasIconSize() {
21
+ return this.atlasIconSize;
22
+ }
23
+ }
@@ -0,0 +1,42 @@
1
+ export function createSpriteMap(iconSize, combinationMap) {
2
+ const spriteSheetMap = new Map();
3
+ const powerOf2 = (n) => {
4
+ let p = 1;
5
+ while (p < n) {
6
+ p *= 2;
7
+ }
8
+ return p;
9
+ };
10
+ const atlasIconSize = (() => {
11
+ const size = powerOf2(Math.max(iconSize.x, iconSize.y));
12
+ return { x: size, y: size };
13
+ })();
14
+ const atlasUnitSize = Math.ceil(Math.sqrt(combinationMap.size));
15
+ const atlasSize = {
16
+ x: atlasIconSize.x * atlasUnitSize,
17
+ y: atlasIconSize.y * atlasUnitSize
18
+ };
19
+ let currentRow = 0;
20
+ let currentCol = 0;
21
+ // Padding size is space the icon will be moved to the center of the atlas cell. This is needed because the icon size may be smaller than the atlas cell size.
22
+ const paddingSize = {
23
+ x: Math.floor((atlasIconSize.x - iconSize.x) / 2),
24
+ y: Math.floor((atlasIconSize.y - iconSize.y) / 2)
25
+ };
26
+ for (const [key, _] of combinationMap) {
27
+ const spriteSheetElement = {
28
+ x: currentCol * atlasIconSize.x + paddingSize.x,
29
+ y: currentRow * atlasIconSize.y + paddingSize.y,
30
+ width: iconSize.x,
31
+ height: iconSize.y,
32
+ pixelRatio: 1
33
+ };
34
+ spriteSheetMap.set(key, spriteSheetElement);
35
+ currentCol++;
36
+ if (currentCol >= atlasUnitSize) {
37
+ currentCol = 0;
38
+ currentRow++;
39
+ }
40
+ }
41
+ return { spriteSheetMap, atlasSize };
42
+ }
@@ -0,0 +1,52 @@
1
+ export class BlobMapPart {
2
+ blobMap;
3
+ static imageCache = new Map();
4
+ constructor(blobMap) {
5
+ this.blobMap = blobMap;
6
+ }
7
+ getBase64Source(partInput) {
8
+ const source = this.blobMap.get(partInput);
9
+ if (!source) {
10
+ throw new Error(`Base64 image for key ${partInput} not found.`);
11
+ }
12
+ return source;
13
+ }
14
+ toDataUri(base64Source) {
15
+ if (base64Source.trimStart().startsWith("data:")) {
16
+ return base64Source;
17
+ }
18
+ return `data:image/png;base64,${base64Source}`;
19
+ }
20
+ loadImage(base64Source) {
21
+ const src = this.toDataUri(base64Source);
22
+ const cached = BlobMapPart.imageCache.get(src);
23
+ if (cached) {
24
+ return cached;
25
+ }
26
+ const imagePromise = new Promise((resolve, reject) => {
27
+ const img = new Image();
28
+ img.onload = () => resolve(img);
29
+ img.onerror = () => {
30
+ BlobMapPart.imageCache.delete(src);
31
+ reject(new Error(`Failed to load base64 image for src: ${src.slice(0, 64)}...`));
32
+ };
33
+ img.src = src;
34
+ });
35
+ BlobMapPart.imageCache.set(src, imagePromise);
36
+ return imagePromise;
37
+ }
38
+ async preparePart(partInput) {
39
+ const source = this.getBase64Source(partInput);
40
+ await this.loadImage(source);
41
+ }
42
+ async printPart(partInput, targetCanvas, partDestination) {
43
+ const source = this.getBase64Source(partInput);
44
+ const img = await this.loadImage(source);
45
+ const ctx = targetCanvas.getContext("2d");
46
+ if (!ctx) {
47
+ throw new Error("Failed to get 2D context from target canvas.");
48
+ }
49
+ const { x, y, width, height } = partDestination;
50
+ ctx.drawImage(img, 0, 0, img.width, img.height, x, y, width, height);
51
+ }
52
+ }
@@ -0,0 +1,52 @@
1
+ export class PngBlobMapPart {
2
+ pngBlobMap;
3
+ imageCache = new Map();
4
+ constructor(pngBlobMap) {
5
+ this.pngBlobMap = pngBlobMap;
6
+ }
7
+ getBase64Source(partInput) {
8
+ const source = this.pngBlobMap.get(partInput);
9
+ if (!source) {
10
+ throw new Error(`Base64 image for key ${partInput} not found.`);
11
+ }
12
+ return source;
13
+ }
14
+ toDataUri(base64Source) {
15
+ if (base64Source.trimStart().startsWith("data:")) {
16
+ return base64Source;
17
+ }
18
+ return `data:image/png;base64,${base64Source}`;
19
+ }
20
+ loadImage(base64Source) {
21
+ const src = this.toDataUri(base64Source);
22
+ const cached = this.imageCache.get(src);
23
+ if (cached) {
24
+ return cached;
25
+ }
26
+ const imagePromise = new Promise((resolve, reject) => {
27
+ const img = new Image();
28
+ img.onload = () => resolve(img);
29
+ img.onerror = () => {
30
+ this.imageCache.delete(src);
31
+ reject(new Error(`Failed to load base64 image for src: ${src.slice(0, 64)}...`));
32
+ };
33
+ img.src = src;
34
+ });
35
+ this.imageCache.set(src, imagePromise);
36
+ return imagePromise;
37
+ }
38
+ async preparePart(partInput) {
39
+ const source = this.getBase64Source(partInput);
40
+ await this.loadImage(source);
41
+ }
42
+ async printPart(partInput, targetCanvas, partDestination) {
43
+ const source = this.getBase64Source(partInput);
44
+ const img = await this.loadImage(source);
45
+ const ctx = targetCanvas.getContext("2d");
46
+ if (!ctx) {
47
+ throw new Error("Failed to get 2D context from target canvas.");
48
+ }
49
+ const { x, y, width, height } = partDestination;
50
+ ctx.drawImage(img, 0, 0, img.width, img.height, x, y, width, height);
51
+ }
52
+ }
@@ -0,0 +1,27 @@
1
+ export class SpriteSheetPart {
2
+ image;
3
+ spriteJson;
4
+ constructor(image, spriteJson) {
5
+ this.image = image;
6
+ this.spriteJson = spriteJson;
7
+ }
8
+ async printPart(partInput, targetCanvas, partDestination) {
9
+ const symbolInfo = this.spriteJson[partInput];
10
+ if (!symbolInfo) {
11
+ throw new Error(`Symbol with key ${partInput} not found in sprite sheet.`);
12
+ }
13
+ const ctx = targetCanvas.getContext('2d');
14
+ if (!ctx) {
15
+ throw new Error('Failed to get 2D context from target canvas.');
16
+ }
17
+ ctx.imageSmoothingEnabled = false;
18
+ // Calculate the position and size of the symbol in the sprite sheet
19
+ const { x, y, width, height } = symbolInfo;
20
+ // Draw the symbol onto the target canvas
21
+ const { x: destX, y: destY, width: destWidth, height: destHeight } = partDestination;
22
+ ctx.drawImage(this.image, x, y, width, height, // Source rectangle in the sprite sheet
23
+ destX, destY, // Destination start point on the target canvas
24
+ destWidth, destHeight // Destination width and height
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,81 @@
1
+ export class StaticSvgPart {
2
+ svgMap;
3
+ static svgCache = new Map();
4
+ constructor(svgMap) {
5
+ this.svgMap = svgMap;
6
+ }
7
+ getSvgSource(partInput) {
8
+ const svgSource = this.svgMap.get(partInput);
9
+ if (!svgSource) {
10
+ throw new Error(`SVG content for key ${partInput} not found.`);
11
+ }
12
+ return svgSource;
13
+ }
14
+ sourceToImage(svgSource) {
15
+ const cacheKey = svgSource;
16
+ const cached = StaticSvgPart.svgCache.get(cacheKey);
17
+ if (cached) {
18
+ return cached;
19
+ }
20
+ const imagePromise = new Promise((resolve, reject) => {
21
+ try {
22
+ const img = new Image();
23
+ img.onload = () => {
24
+ resolve(img);
25
+ };
26
+ img.onerror = () => {
27
+ StaticSvgPart.svgCache.delete(cacheKey);
28
+ reject(new Error(`Failed to render SVG image.`));
29
+ };
30
+ if (svgSource.trimStart().startsWith("<")) {
31
+ // Inline SVG markup: render through a temporary object URL.
32
+ const blob = new Blob([svgSource], { type: 'image/svg+xml' });
33
+ const objectUrl = URL.createObjectURL(blob);
34
+ const cleanup = () => URL.revokeObjectURL(objectUrl);
35
+ const prevLoad = img.onload;
36
+ const prevError = img.onerror;
37
+ img.onload = (event) => {
38
+ cleanup();
39
+ if (typeof prevLoad === "function") {
40
+ prevLoad.call(img, event);
41
+ }
42
+ };
43
+ img.onerror = (event) => {
44
+ cleanup();
45
+ if (typeof prevError === "function") {
46
+ prevError.call(img, event);
47
+ }
48
+ };
49
+ img.src = objectUrl;
50
+ }
51
+ else {
52
+ // URL/path source from catalog (for example /icon-generator/flags/4x3/tr.svg).
53
+ img.src = svgSource;
54
+ }
55
+ }
56
+ catch (err) {
57
+ reject(new Error(`Failed to convert SVG to image: ${err}`));
58
+ }
59
+ });
60
+ StaticSvgPart.svgCache.set(cacheKey, imagePromise);
61
+ return imagePromise;
62
+ }
63
+ async preparePart(partInput) {
64
+ const svgSource = this.getSvgSource(partInput);
65
+ await this.sourceToImage(svgSource);
66
+ }
67
+ async printPart(partInput, targetCanvas, partDestination) {
68
+ const svgSource = this.getSvgSource(partInput);
69
+ console.log(`Rendering SVG for part input "${partInput}"`);
70
+ const img = await this.sourceToImage(svgSource);
71
+ const ctx = targetCanvas.getContext('2d');
72
+ if (!ctx) {
73
+ throw new Error('Failed to get 2D context from target canvas.');
74
+ }
75
+ const { x, y, width, height } = partDestination;
76
+ ctx.drawImage(img, 0, 0, img.width, img.height, // Source rectangle (entire SVG)
77
+ x, y, // Destination start point on the target canvas
78
+ width, height // Destination width and height
79
+ );
80
+ }
81
+ }
@@ -0,0 +1,48 @@
1
+ export class UrlImagePart {
2
+ urlMap;
3
+ static imageCache = new Map();
4
+ constructor(urlMap) {
5
+ this.urlMap = urlMap;
6
+ }
7
+ getUrl(partInput) {
8
+ const url = this.urlMap.get(partInput);
9
+ if (!url) {
10
+ throw new Error(`URL for key ${partInput} not found.`);
11
+ }
12
+ return url;
13
+ }
14
+ loadImage(url) {
15
+ const cached = UrlImagePart.imageCache.get(url);
16
+ if (cached) {
17
+ return cached;
18
+ }
19
+ const imagePromise = new Promise((resolve, reject) => {
20
+ const img = new Image();
21
+ img.onload = () => resolve(img);
22
+ img.onerror = () => {
23
+ UrlImagePart.imageCache.delete(url);
24
+ reject(new Error(`Failed to load image from URL: ${url}`));
25
+ };
26
+ img.src = url;
27
+ });
28
+ UrlImagePart.imageCache.set(url, imagePromise);
29
+ return imagePromise;
30
+ }
31
+ async preparePart(partInput) {
32
+ const url = this.getUrl(partInput);
33
+ await this.loadImage(url);
34
+ }
35
+ async printPart(partInput, targetCanvas, partDestination) {
36
+ const url = this.getUrl(partInput);
37
+ const img = await this.loadImage(url);
38
+ const ctx = targetCanvas.getContext('2d');
39
+ if (!ctx) {
40
+ throw new Error('Failed to get 2D context from target canvas.');
41
+ }
42
+ const { x, y, width, height } = partDestination;
43
+ ctx.drawImage(img, 0, 0, img.width, img.height, // Source rectangle (entire image)
44
+ x, y, // Destination start point on the target canvas
45
+ width, height // Destination width and height
46
+ );
47
+ }
48
+ }
@@ -0,0 +1,65 @@
1
+ import { calculateMinimumBaseSize } from "./util";
2
+ export class SingleIconPrinter {
3
+ parts;
4
+ bluePrint;
5
+ baseSize;
6
+ constructor(parts, bluePrint) {
7
+ this.parts = parts;
8
+ this.bluePrint = bluePrint;
9
+ this.baseSize = calculateMinimumBaseSize(bluePrint);
10
+ }
11
+ async prefetchParts(partInputs) {
12
+ const prefetchTasks = [];
13
+ for (const [sourceKey] of this.bluePrint.entries()) {
14
+ const partInput = partInputs.get(sourceKey);
15
+ const part = this.parts.get(sourceKey);
16
+ if (!part) {
17
+ throw new Error(`Part with key ${sourceKey} not found.`);
18
+ }
19
+ if (part.preparePart) {
20
+ prefetchTasks.push(part.preparePart(partInput));
21
+ }
22
+ }
23
+ await Promise.all(prefetchTasks);
24
+ }
25
+ async getImage(partInputs) {
26
+ const targetCanvas = document.createElement('canvas');
27
+ targetCanvas.width = this.baseSize.x;
28
+ targetCanvas.height = this.baseSize.y;
29
+ const ctx = targetCanvas.getContext('2d');
30
+ if (!ctx) {
31
+ throw new Error('Failed to get 2D context from target canvas.');
32
+ }
33
+ // Start all network-backed part preloads in parallel.
34
+ await this.prefetchParts(partInputs);
35
+ console.log("All parts prefetched, starting rendering...");
36
+ for (const [sourceKey, source] of this.parts.entries()) {
37
+ console.log("Rendering part with key:", sourceKey);
38
+ const partDestination = this.bluePrint.get(sourceKey);
39
+ const partInput = partInputs.get(sourceKey);
40
+ console.log("Part input:", partInput, "Part destination:", partDestination, "Source ", source);
41
+ if (!partDestination || !source) {
42
+ throw new Error(`Source with key ${sourceKey} not found.`);
43
+ }
44
+ await source.printPart(partInput, targetCanvas, partDestination);
45
+ }
46
+ return targetCanvas;
47
+ }
48
+ async printImage(partInputs, targetCanvas, offset) {
49
+ for (const [partKey, part] of this.parts.entries()) {
50
+ const partInput = partInputs.get(partKey);
51
+ const partDestination = this.bluePrint.get(partKey);
52
+ if (!partDestination || !part) {
53
+ throw new Error(`Part with key ${partKey} not found.`);
54
+ }
55
+ const partDestiantionWithOffset = {
56
+ x: partDestination.x + offset.x,
57
+ y: partDestination.y + offset.y,
58
+ width: partDestination.width,
59
+ height: partDestination.height,
60
+ pixelRatio: partDestination.pixelRatio
61
+ };
62
+ await part.printPart(partInput, targetCanvas, partDestiantionWithOffset);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,16 @@
1
+ export function calculateMinimumBaseSize(bluePrint) {
2
+ let minX = 0;
3
+ let minY = 0;
4
+ bluePrint.forEach((partDestination) => {
5
+ const { x, y, width, height } = partDestination;
6
+ const endX = x + width;
7
+ const endY = y + height;
8
+ if (endX > minX) {
9
+ minX = endX;
10
+ }
11
+ if (endY > minY) {
12
+ minY = endY;
13
+ }
14
+ });
15
+ return { x: minX, y: minY };
16
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // boatIconCode parametresini globe bilecek