@versatiles/svg-renderer 0.8.0 → 1.0.0
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/README.md +42 -32
- package/dist/index.cjs +30 -8
- package/dist/index.js +30 -8
- package/dist/{maplibre.d.ts → maplibre-svg-export.d.ts} +2 -0
- package/dist/{maplibre.js → maplibre-svg-export.js} +46 -16
- package/dist/{maplibre.umd.js → maplibre-svg-export.umd.js} +46 -16
- package/package.json +8 -12
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/maplibre.cjs +0 -10984
- package/dist/maplibre.cjs.map +0 -1
- package/dist/maplibre.js.map +0 -1
- package/dist/maplibre.umd.js.map +0 -1
package/README.md
CHANGED
|
@@ -64,13 +64,23 @@ document.body.innerHTML = svg;
|
|
|
64
64
|
|
|
65
65
|
The package includes an `SVGExportControl` that adds an export button to any MapLibre GL JS map.
|
|
66
66
|
|
|
67
|
+
With a bundler, import it from the `/maplibre` subpath:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { SVGExportControl } from '@versatiles/svg-renderer/maplibre';
|
|
71
|
+
|
|
72
|
+
map.addControl(new SVGExportControl(), 'top-right');
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or load it directly in the browser via the UMD bundle, which exposes a `VersaTilesSVG` global:
|
|
76
|
+
|
|
67
77
|
```html
|
|
68
78
|
<!DOCTYPE html>
|
|
69
79
|
<html>
|
|
70
80
|
<head>
|
|
71
81
|
<link rel="stylesheet" href="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.css" />
|
|
72
82
|
<script src="https://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
|
|
73
|
-
<script src="https://unpkg.com/@versatiles/svg-renderer/dist/maplibre.umd.js"></script>
|
|
83
|
+
<script src="https://unpkg.com/@versatiles/svg-renderer/dist/maplibre-svg-export.umd.js"></script>
|
|
74
84
|
</head>
|
|
75
85
|
<body>
|
|
76
86
|
<div id="map"></div>
|
|
@@ -87,7 +97,7 @@ The package includes an `SVGExportControl` that adds an export button to any Map
|
|
|
87
97
|
</html>
|
|
88
98
|
```
|
|
89
99
|
|
|
90
|
-
The control opens a panel where the user can set width
|
|
100
|
+
The control opens a panel where the user can set width and height, preview the SVG, download it, or open it in a new tab. Map interactions are disabled while the panel is open.
|
|
91
101
|
|
|
92
102
|
Options:
|
|
93
103
|
|
|
@@ -95,7 +105,6 @@ Options:
|
|
|
95
105
|
new SVGExportControl({
|
|
96
106
|
defaultWidth: 1024, // default: 1024
|
|
97
107
|
defaultHeight: 1024, // default: 1024
|
|
98
|
-
defaultScale: 1, // default: 1
|
|
99
108
|
});
|
|
100
109
|
```
|
|
101
110
|
|
|
@@ -145,53 +154,54 @@ subgraph 0["src"]
|
|
|
145
154
|
2["index.ts"]
|
|
146
155
|
subgraph 3["pipeline"]
|
|
147
156
|
4["render.ts"]
|
|
148
|
-
|
|
157
|
+
E["style_layer.ts"]
|
|
149
158
|
end
|
|
150
159
|
subgraph 5["sources"]
|
|
151
160
|
6["index.ts"]
|
|
152
161
|
7["geojson.ts"]
|
|
153
162
|
9["raster.ts"]
|
|
154
|
-
A["
|
|
155
|
-
B["
|
|
156
|
-
C["
|
|
157
|
-
|
|
163
|
+
A["base64.ts"]
|
|
164
|
+
B["tiles.ts"]
|
|
165
|
+
C["sprite.ts"]
|
|
166
|
+
D["vector.ts"]
|
|
158
167
|
end
|
|
159
168
|
8["geometry.ts"]
|
|
160
|
-
subgraph
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
169
|
+
subgraph F["renderer"]
|
|
170
|
+
G["svg.ts"]
|
|
171
|
+
H["color.ts"]
|
|
172
|
+
I["svg_path.ts"]
|
|
173
|
+
N["types.ts"]
|
|
165
174
|
end
|
|
166
|
-
subgraph
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
subgraph J["maplibre"]
|
|
176
|
+
K["control.ts"]
|
|
177
|
+
L["panel_css.ts"]
|
|
178
|
+
M["index.ts"]
|
|
170
179
|
end
|
|
171
180
|
end
|
|
172
181
|
1-->2
|
|
173
182
|
2-->4
|
|
174
|
-
2-->
|
|
183
|
+
2-->G
|
|
175
184
|
4-->6
|
|
176
|
-
4-->
|
|
177
|
-
4-->
|
|
185
|
+
4-->C
|
|
186
|
+
4-->E
|
|
178
187
|
6-->7
|
|
179
188
|
6-->9
|
|
180
|
-
6-->B
|
|
181
189
|
6-->C
|
|
190
|
+
6-->D
|
|
182
191
|
7-->8
|
|
183
192
|
9-->A
|
|
184
|
-
|
|
185
|
-
|
|
193
|
+
9-->B
|
|
194
|
+
B-->8
|
|
186
195
|
C-->A
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
L
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
D-->8
|
|
197
|
+
D-->B
|
|
198
|
+
G-->H
|
|
199
|
+
G-->I
|
|
200
|
+
K-->2
|
|
201
|
+
K-->L
|
|
202
|
+
M-->2
|
|
203
|
+
M-->K
|
|
204
|
+
|
|
205
|
+
class 0,3,5,F,J subgraphs;
|
|
196
206
|
classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
|
|
197
207
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -10027,6 +10027,34 @@ function loadGeoJSONSource(options) {
|
|
|
10027
10027
|
}
|
|
10028
10028
|
}
|
|
10029
10029
|
|
|
10030
|
+
/**
|
|
10031
|
+
* Base64-encode an ArrayBuffer (standard alphabet, with padding).
|
|
10032
|
+
*
|
|
10033
|
+
* Three paths, all producing identical output:
|
|
10034
|
+
* 1. `Uint8Array.prototype.toBase64` — the native method (Node 22+, modern
|
|
10035
|
+
* browsers). Fastest and simplest; used when present.
|
|
10036
|
+
* 2. Node's `Buffer` — for Node versions without the native method.
|
|
10037
|
+
* 3. Chunked `btoa(String.fromCharCode(...))` — the browser fallback. It must
|
|
10038
|
+
* chunk: spreading a whole tile/sprite PNG at once passes one argument per
|
|
10039
|
+
* byte, overflowing the engine's argument-count limit (Safari ~64K, V8
|
|
10040
|
+
* higher but finite) and throwing `RangeError: Maximum call stack size
|
|
10041
|
+
* exceeded` for buffers above ~64 KB. 32 KB slices stay well under the limit.
|
|
10042
|
+
*/
|
|
10043
|
+
function arrayBufferToBase64(buffer) {
|
|
10044
|
+
const bytes = new Uint8Array(buffer);
|
|
10045
|
+
const toBase64 = bytes.toBase64;
|
|
10046
|
+
if (typeof toBase64 === 'function')
|
|
10047
|
+
return toBase64.call(bytes);
|
|
10048
|
+
if (typeof Buffer !== 'undefined')
|
|
10049
|
+
return Buffer.from(buffer).toString('base64');
|
|
10050
|
+
const CHUNK_SIZE = 0x8000; // 32 KB, well below the argument-count limit
|
|
10051
|
+
let binary = '';
|
|
10052
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
10053
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
|
|
10054
|
+
}
|
|
10055
|
+
return btoa(binary);
|
|
10056
|
+
}
|
|
10057
|
+
|
|
10030
10058
|
async function getRasterTiles(job, sourceName) {
|
|
10031
10059
|
const { width, height } = job.renderer;
|
|
10032
10060
|
const { zoom, center } = job.view;
|
|
@@ -10040,10 +10068,7 @@ async function getRasterTiles(job, sourceName) {
|
|
|
10040
10068
|
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
10041
10069
|
if (!tile)
|
|
10042
10070
|
return null;
|
|
10043
|
-
const
|
|
10044
|
-
? Buffer.from(tile.buffer).toString('base64')
|
|
10045
|
-
: btoa(String.fromCharCode(...new Uint8Array(tile.buffer)));
|
|
10046
|
-
const dataUri = `data:${tile.contentType};base64,${base64}`;
|
|
10071
|
+
const dataUri = `data:${tile.contentType};base64,${arrayBufferToBase64(tile.buffer)}`;
|
|
10047
10072
|
return {
|
|
10048
10073
|
x: offsetX,
|
|
10049
10074
|
y: offsetY,
|
|
@@ -10089,10 +10114,7 @@ async function loadSpriteAtlas(style) {
|
|
|
10089
10114
|
const { jsonResponse, imageResponse } = spritePair;
|
|
10090
10115
|
const json = (await jsonResponse.json());
|
|
10091
10116
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
10092
|
-
const
|
|
10093
|
-
? Buffer.from(imageBuffer).toString('base64')
|
|
10094
|
-
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
10095
|
-
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
10117
|
+
const sheetDataUri = `data:image/png;base64,${arrayBufferToBase64(imageBuffer)}`;
|
|
10096
10118
|
// Estimate sheet dimensions from sprite entries
|
|
10097
10119
|
let sheetWidth = 0;
|
|
10098
10120
|
let sheetHeight = 0;
|
package/dist/index.js
CHANGED
|
@@ -10025,6 +10025,34 @@ function loadGeoJSONSource(options) {
|
|
|
10025
10025
|
}
|
|
10026
10026
|
}
|
|
10027
10027
|
|
|
10028
|
+
/**
|
|
10029
|
+
* Base64-encode an ArrayBuffer (standard alphabet, with padding).
|
|
10030
|
+
*
|
|
10031
|
+
* Three paths, all producing identical output:
|
|
10032
|
+
* 1. `Uint8Array.prototype.toBase64` — the native method (Node 22+, modern
|
|
10033
|
+
* browsers). Fastest and simplest; used when present.
|
|
10034
|
+
* 2. Node's `Buffer` — for Node versions without the native method.
|
|
10035
|
+
* 3. Chunked `btoa(String.fromCharCode(...))` — the browser fallback. It must
|
|
10036
|
+
* chunk: spreading a whole tile/sprite PNG at once passes one argument per
|
|
10037
|
+
* byte, overflowing the engine's argument-count limit (Safari ~64K, V8
|
|
10038
|
+
* higher but finite) and throwing `RangeError: Maximum call stack size
|
|
10039
|
+
* exceeded` for buffers above ~64 KB. 32 KB slices stay well under the limit.
|
|
10040
|
+
*/
|
|
10041
|
+
function arrayBufferToBase64(buffer) {
|
|
10042
|
+
const bytes = new Uint8Array(buffer);
|
|
10043
|
+
const toBase64 = bytes.toBase64;
|
|
10044
|
+
if (typeof toBase64 === 'function')
|
|
10045
|
+
return toBase64.call(bytes);
|
|
10046
|
+
if (typeof Buffer !== 'undefined')
|
|
10047
|
+
return Buffer.from(buffer).toString('base64');
|
|
10048
|
+
const CHUNK_SIZE = 0x8000; // 32 KB, well below the argument-count limit
|
|
10049
|
+
let binary = '';
|
|
10050
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
10051
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
|
|
10052
|
+
}
|
|
10053
|
+
return btoa(binary);
|
|
10054
|
+
}
|
|
10055
|
+
|
|
10028
10056
|
async function getRasterTiles(job, sourceName) {
|
|
10029
10057
|
const { width, height } = job.renderer;
|
|
10030
10058
|
const { zoom, center } = job.view;
|
|
@@ -10038,10 +10066,7 @@ async function getRasterTiles(job, sourceName) {
|
|
|
10038
10066
|
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
10039
10067
|
if (!tile)
|
|
10040
10068
|
return null;
|
|
10041
|
-
const
|
|
10042
|
-
? Buffer.from(tile.buffer).toString('base64')
|
|
10043
|
-
: btoa(String.fromCharCode(...new Uint8Array(tile.buffer)));
|
|
10044
|
-
const dataUri = `data:${tile.contentType};base64,${base64}`;
|
|
10069
|
+
const dataUri = `data:${tile.contentType};base64,${arrayBufferToBase64(tile.buffer)}`;
|
|
10045
10070
|
return {
|
|
10046
10071
|
x: offsetX,
|
|
10047
10072
|
y: offsetY,
|
|
@@ -10087,10 +10112,7 @@ async function loadSpriteAtlas(style) {
|
|
|
10087
10112
|
const { jsonResponse, imageResponse } = spritePair;
|
|
10088
10113
|
const json = (await jsonResponse.json());
|
|
10089
10114
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
10090
|
-
const
|
|
10091
|
-
? Buffer.from(imageBuffer).toString('base64')
|
|
10092
|
-
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
10093
|
-
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
10115
|
+
const sheetDataUri = `data:image/png;base64,${arrayBufferToBase64(imageBuffer)}`;
|
|
10094
10116
|
// Estimate sheet dimensions from sprite entries
|
|
10095
10117
|
let sheetWidth = 0;
|
|
10096
10118
|
let sheetHeight = 0;
|
|
@@ -2,7 +2,9 @@ import { IControl, Map } from 'maplibre-gl';
|
|
|
2
2
|
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec';
|
|
3
3
|
|
|
4
4
|
interface SVGExportControlOptions {
|
|
5
|
+
/** Initial export width in px. Defaults to the current map viewport width. */
|
|
5
6
|
defaultWidth?: number;
|
|
7
|
+
/** Initial export height in px. Defaults to the current map viewport height. */
|
|
6
8
|
defaultHeight?: number;
|
|
7
9
|
}
|
|
8
10
|
declare class SVGExportControl implements IControl {
|
|
@@ -10206,6 +10206,34 @@ function loadGeoJSONSource(options) {
|
|
|
10206
10206
|
}
|
|
10207
10207
|
}
|
|
10208
10208
|
|
|
10209
|
+
/**
|
|
10210
|
+
* Base64-encode an ArrayBuffer (standard alphabet, with padding).
|
|
10211
|
+
*
|
|
10212
|
+
* Three paths, all producing identical output:
|
|
10213
|
+
* 1. `Uint8Array.prototype.toBase64` — the native method (Node 22+, modern
|
|
10214
|
+
* browsers). Fastest and simplest; used when present.
|
|
10215
|
+
* 2. Node's `Buffer` — for Node versions without the native method.
|
|
10216
|
+
* 3. Chunked `btoa(String.fromCharCode(...))` — the browser fallback. It must
|
|
10217
|
+
* chunk: spreading a whole tile/sprite PNG at once passes one argument per
|
|
10218
|
+
* byte, overflowing the engine's argument-count limit (Safari ~64K, V8
|
|
10219
|
+
* higher but finite) and throwing `RangeError: Maximum call stack size
|
|
10220
|
+
* exceeded` for buffers above ~64 KB. 32 KB slices stay well under the limit.
|
|
10221
|
+
*/
|
|
10222
|
+
function arrayBufferToBase64(buffer) {
|
|
10223
|
+
const bytes = new Uint8Array(buffer);
|
|
10224
|
+
const toBase64 = bytes.toBase64;
|
|
10225
|
+
if (typeof toBase64 === 'function')
|
|
10226
|
+
return toBase64.call(bytes);
|
|
10227
|
+
if (typeof Buffer !== 'undefined')
|
|
10228
|
+
return Buffer.from(buffer).toString('base64');
|
|
10229
|
+
const CHUNK_SIZE = 0x8000; // 32 KB, well below the argument-count limit
|
|
10230
|
+
let binary = '';
|
|
10231
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
10232
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
|
|
10233
|
+
}
|
|
10234
|
+
return btoa(binary);
|
|
10235
|
+
}
|
|
10236
|
+
|
|
10209
10237
|
async function getRasterTiles(job, sourceName) {
|
|
10210
10238
|
const { width, height } = job.renderer;
|
|
10211
10239
|
const { zoom, center } = job.view;
|
|
@@ -10219,10 +10247,7 @@ async function getRasterTiles(job, sourceName) {
|
|
|
10219
10247
|
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
10220
10248
|
if (!tile)
|
|
10221
10249
|
return null;
|
|
10222
|
-
const
|
|
10223
|
-
? Buffer.from(tile.buffer).toString('base64')
|
|
10224
|
-
: btoa(String.fromCharCode(...new Uint8Array(tile.buffer)));
|
|
10225
|
-
const dataUri = `data:${tile.contentType};base64,${base64}`;
|
|
10250
|
+
const dataUri = `data:${tile.contentType};base64,${arrayBufferToBase64(tile.buffer)}`;
|
|
10226
10251
|
return {
|
|
10227
10252
|
x: offsetX,
|
|
10228
10253
|
y: offsetY,
|
|
@@ -10268,10 +10293,7 @@ async function loadSpriteAtlas(style) {
|
|
|
10268
10293
|
const { jsonResponse, imageResponse } = spritePair;
|
|
10269
10294
|
const json = (await jsonResponse.json());
|
|
10270
10295
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
10271
|
-
const
|
|
10272
|
-
? Buffer.from(imageBuffer).toString('base64')
|
|
10273
|
-
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
10274
|
-
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
10296
|
+
const sheetDataUri = `data:image/png;base64,${arrayBufferToBase64(imageBuffer)}`;
|
|
10275
10297
|
// Estimate sheet dimensions from sprite entries
|
|
10276
10298
|
let sheetWidth = 0;
|
|
10277
10299
|
let sheetHeight = 0;
|
|
@@ -10725,7 +10747,12 @@ const ALLOWED_TAGS = new Set(['a', 'b', 'i', 'em', 'strong', 'span']);
|
|
|
10725
10747
|
function sanitizeHTML(html) {
|
|
10726
10748
|
const parser = new DOMParser();
|
|
10727
10749
|
const doc = parser.parseFromString(html, 'text/html');
|
|
10728
|
-
|
|
10750
|
+
// Serialize the sanitized tree back to HTML so allowed tags and safe links
|
|
10751
|
+
// survive. The result is later assigned via innerHTML; every text node is
|
|
10752
|
+
// re-escaped by the serializer, so it is safe to concatenate and inject.
|
|
10753
|
+
const container = document.createElement('div');
|
|
10754
|
+
container.append(sanitizeNode(doc.body));
|
|
10755
|
+
return container.innerHTML;
|
|
10729
10756
|
}
|
|
10730
10757
|
function sanitizeNode(node) {
|
|
10731
10758
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -10764,10 +10791,7 @@ class SVGExportControl {
|
|
|
10764
10791
|
renderGeneration = 0;
|
|
10765
10792
|
options;
|
|
10766
10793
|
constructor(options) {
|
|
10767
|
-
this.options = {
|
|
10768
|
-
defaultWidth: options?.defaultWidth ?? 1024,
|
|
10769
|
-
defaultHeight: options?.defaultHeight ?? 1024,
|
|
10770
|
-
};
|
|
10794
|
+
this.options = { ...options };
|
|
10771
10795
|
}
|
|
10772
10796
|
onAdd(map) {
|
|
10773
10797
|
this.map = map;
|
|
@@ -10797,6 +10821,12 @@ class SVGExportControl {
|
|
|
10797
10821
|
if (this.panel || !this.map)
|
|
10798
10822
|
return;
|
|
10799
10823
|
const mapContainer = this.map.getContainer();
|
|
10824
|
+
// Seed the size inputs from the current map viewport so the default export
|
|
10825
|
+
// matches what the user sees on screen. Explicit constructor options win;
|
|
10826
|
+
// the 1024 fallback covers environments that report no layout size
|
|
10827
|
+
// (clientWidth 0). Inputs stay independent — the user can change either.
|
|
10828
|
+
const defaultWidth = this.options.defaultWidth ?? (mapContainer.clientWidth || 1024);
|
|
10829
|
+
const defaultHeight = this.options.defaultHeight ?? (mapContainer.clientHeight || 1024);
|
|
10800
10830
|
this.panel = document.createElement('div');
|
|
10801
10831
|
this.panel.className = 'svg-export-panel';
|
|
10802
10832
|
this.panel.innerHTML = `
|
|
@@ -10811,8 +10841,8 @@ class SVGExportControl {
|
|
|
10811
10841
|
</div>
|
|
10812
10842
|
<div class="panel-inputs">
|
|
10813
10843
|
<div class="grid">
|
|
10814
|
-
<label>Width<input type="number" class="input-width" value="${String(
|
|
10815
|
-
<label>Height<input type="number" class="input-height" value="${String(
|
|
10844
|
+
<label>Width<input type="number" class="input-width" value="${String(defaultWidth)}" min="1" max="8192"></label>
|
|
10845
|
+
<label>Height<input type="number" class="input-height" value="${String(defaultHeight)}" min="1" max="8192"></label>
|
|
10816
10846
|
</div>
|
|
10817
10847
|
<label class="label-checkbox"><input type="checkbox" class="input-labels"> Include labels and icons (buggy)</label>
|
|
10818
10848
|
</div>
|
|
@@ -10978,4 +11008,4 @@ class SVGExportControl {
|
|
|
10978
11008
|
}
|
|
10979
11009
|
|
|
10980
11010
|
export { SVGExportControl, renderToSVG };
|
|
10981
|
-
//# sourceMappingURL=maplibre.js.map
|
|
11011
|
+
//# sourceMappingURL=maplibre-svg-export.js.map
|
|
@@ -10212,6 +10212,34 @@
|
|
|
10212
10212
|
}
|
|
10213
10213
|
}
|
|
10214
10214
|
|
|
10215
|
+
/**
|
|
10216
|
+
* Base64-encode an ArrayBuffer (standard alphabet, with padding).
|
|
10217
|
+
*
|
|
10218
|
+
* Three paths, all producing identical output:
|
|
10219
|
+
* 1. `Uint8Array.prototype.toBase64` — the native method (Node 22+, modern
|
|
10220
|
+
* browsers). Fastest and simplest; used when present.
|
|
10221
|
+
* 2. Node's `Buffer` — for Node versions without the native method.
|
|
10222
|
+
* 3. Chunked `btoa(String.fromCharCode(...))` — the browser fallback. It must
|
|
10223
|
+
* chunk: spreading a whole tile/sprite PNG at once passes one argument per
|
|
10224
|
+
* byte, overflowing the engine's argument-count limit (Safari ~64K, V8
|
|
10225
|
+
* higher but finite) and throwing `RangeError: Maximum call stack size
|
|
10226
|
+
* exceeded` for buffers above ~64 KB. 32 KB slices stay well under the limit.
|
|
10227
|
+
*/
|
|
10228
|
+
function arrayBufferToBase64(buffer) {
|
|
10229
|
+
const bytes = new Uint8Array(buffer);
|
|
10230
|
+
const toBase64 = bytes.toBase64;
|
|
10231
|
+
if (typeof toBase64 === 'function')
|
|
10232
|
+
return toBase64.call(bytes);
|
|
10233
|
+
if (typeof Buffer !== 'undefined')
|
|
10234
|
+
return Buffer.from(buffer).toString('base64');
|
|
10235
|
+
const CHUNK_SIZE = 0x8000; // 32 KB, well below the argument-count limit
|
|
10236
|
+
let binary = '';
|
|
10237
|
+
for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {
|
|
10238
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE));
|
|
10239
|
+
}
|
|
10240
|
+
return btoa(binary);
|
|
10241
|
+
}
|
|
10242
|
+
|
|
10215
10243
|
async function getRasterTiles(job, sourceName) {
|
|
10216
10244
|
const { width, height } = job.renderer;
|
|
10217
10245
|
const { zoom, center } = job.view;
|
|
@@ -10225,10 +10253,7 @@
|
|
|
10225
10253
|
const tile = await getTile(sourceUrl, zoomLevel, x, y);
|
|
10226
10254
|
if (!tile)
|
|
10227
10255
|
return null;
|
|
10228
|
-
const
|
|
10229
|
-
? Buffer.from(tile.buffer).toString('base64')
|
|
10230
|
-
: btoa(String.fromCharCode(...new Uint8Array(tile.buffer)));
|
|
10231
|
-
const dataUri = `data:${tile.contentType};base64,${base64}`;
|
|
10256
|
+
const dataUri = `data:${tile.contentType};base64,${arrayBufferToBase64(tile.buffer)}`;
|
|
10232
10257
|
return {
|
|
10233
10258
|
x: offsetX,
|
|
10234
10259
|
y: offsetY,
|
|
@@ -10274,10 +10299,7 @@
|
|
|
10274
10299
|
const { jsonResponse, imageResponse } = spritePair;
|
|
10275
10300
|
const json = (await jsonResponse.json());
|
|
10276
10301
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
10277
|
-
const
|
|
10278
|
-
? Buffer.from(imageBuffer).toString('base64')
|
|
10279
|
-
: btoa(String.fromCharCode(...new Uint8Array(imageBuffer)));
|
|
10280
|
-
const sheetDataUri = `data:image/png;base64,${base64}`;
|
|
10302
|
+
const sheetDataUri = `data:image/png;base64,${arrayBufferToBase64(imageBuffer)}`;
|
|
10281
10303
|
// Estimate sheet dimensions from sprite entries
|
|
10282
10304
|
let sheetWidth = 0;
|
|
10283
10305
|
let sheetHeight = 0;
|
|
@@ -10731,7 +10753,12 @@
|
|
|
10731
10753
|
function sanitizeHTML(html) {
|
|
10732
10754
|
const parser = new DOMParser();
|
|
10733
10755
|
const doc = parser.parseFromString(html, 'text/html');
|
|
10734
|
-
|
|
10756
|
+
// Serialize the sanitized tree back to HTML so allowed tags and safe links
|
|
10757
|
+
// survive. The result is later assigned via innerHTML; every text node is
|
|
10758
|
+
// re-escaped by the serializer, so it is safe to concatenate and inject.
|
|
10759
|
+
const container = document.createElement('div');
|
|
10760
|
+
container.append(sanitizeNode(doc.body));
|
|
10761
|
+
return container.innerHTML;
|
|
10735
10762
|
}
|
|
10736
10763
|
function sanitizeNode(node) {
|
|
10737
10764
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
@@ -10770,10 +10797,7 @@
|
|
|
10770
10797
|
renderGeneration = 0;
|
|
10771
10798
|
options;
|
|
10772
10799
|
constructor(options) {
|
|
10773
|
-
this.options = {
|
|
10774
|
-
defaultWidth: options?.defaultWidth ?? 1024,
|
|
10775
|
-
defaultHeight: options?.defaultHeight ?? 1024,
|
|
10776
|
-
};
|
|
10800
|
+
this.options = { ...options };
|
|
10777
10801
|
}
|
|
10778
10802
|
onAdd(map) {
|
|
10779
10803
|
this.map = map;
|
|
@@ -10803,6 +10827,12 @@
|
|
|
10803
10827
|
if (this.panel || !this.map)
|
|
10804
10828
|
return;
|
|
10805
10829
|
const mapContainer = this.map.getContainer();
|
|
10830
|
+
// Seed the size inputs from the current map viewport so the default export
|
|
10831
|
+
// matches what the user sees on screen. Explicit constructor options win;
|
|
10832
|
+
// the 1024 fallback covers environments that report no layout size
|
|
10833
|
+
// (clientWidth 0). Inputs stay independent — the user can change either.
|
|
10834
|
+
const defaultWidth = this.options.defaultWidth ?? (mapContainer.clientWidth || 1024);
|
|
10835
|
+
const defaultHeight = this.options.defaultHeight ?? (mapContainer.clientHeight || 1024);
|
|
10806
10836
|
this.panel = document.createElement('div');
|
|
10807
10837
|
this.panel.className = 'svg-export-panel';
|
|
10808
10838
|
this.panel.innerHTML = `
|
|
@@ -10817,8 +10847,8 @@
|
|
|
10817
10847
|
</div>
|
|
10818
10848
|
<div class="panel-inputs">
|
|
10819
10849
|
<div class="grid">
|
|
10820
|
-
<label>Width<input type="number" class="input-width" value="${String(
|
|
10821
|
-
<label>Height<input type="number" class="input-height" value="${String(
|
|
10850
|
+
<label>Width<input type="number" class="input-width" value="${String(defaultWidth)}" min="1" max="8192"></label>
|
|
10851
|
+
<label>Height<input type="number" class="input-height" value="${String(defaultHeight)}" min="1" max="8192"></label>
|
|
10822
10852
|
</div>
|
|
10823
10853
|
<label class="label-checkbox"><input type="checkbox" class="input-labels"> Include labels and icons (buggy)</label>
|
|
10824
10854
|
</div>
|
|
@@ -10987,4 +11017,4 @@
|
|
|
10987
11017
|
exports.renderToSVG = renderToSVG;
|
|
10988
11018
|
|
|
10989
11019
|
}));
|
|
10990
|
-
//# sourceMappingURL=maplibre.umd.js.map
|
|
11020
|
+
//# sourceMappingURL=maplibre-svg-export.umd.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/svg-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Renders vector maps as SVG.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svg",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"main": "dist/index.cjs",
|
|
19
19
|
"module": "dist/index.js",
|
|
20
20
|
"types": "dist/index.d.ts",
|
|
21
|
-
"unpkg": "dist/maplibre.umd.js",
|
|
22
|
-
"jsdelivr": "dist/maplibre.umd.js",
|
|
21
|
+
"unpkg": "dist/maplibre-svg-export.umd.js",
|
|
22
|
+
"jsdelivr": "dist/maplibre-svg-export.umd.js",
|
|
23
23
|
"exports": {
|
|
24
24
|
".": {
|
|
25
25
|
"import": {
|
|
@@ -32,19 +32,16 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"./maplibre": {
|
|
35
|
-
"script": "./dist/maplibre.umd.js",
|
|
35
|
+
"script": "./dist/maplibre-svg-export.umd.js",
|
|
36
36
|
"import": {
|
|
37
|
-
"types": "./dist/maplibre.d.ts",
|
|
38
|
-
"default": "./dist/maplibre.js"
|
|
39
|
-
},
|
|
40
|
-
"require": {
|
|
41
|
-
"types": "./dist/maplibre.d.ts",
|
|
42
|
-
"default": "./dist/maplibre.cjs"
|
|
37
|
+
"types": "./dist/maplibre-svg-export.d.ts",
|
|
38
|
+
"default": "./dist/maplibre-svg-export.js"
|
|
43
39
|
}
|
|
44
40
|
}
|
|
45
41
|
},
|
|
46
42
|
"files": [
|
|
47
|
-
"dist"
|
|
43
|
+
"dist",
|
|
44
|
+
"!dist/**/*.map"
|
|
48
45
|
],
|
|
49
46
|
"scripts": {
|
|
50
47
|
"build": "rm -rf dist && rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript && rm -rf dist/types",
|
|
@@ -89,7 +86,6 @@
|
|
|
89
86
|
"@maplibre/maplibre-gl-style-spec": "^26.1.0",
|
|
90
87
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
91
88
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
92
|
-
"@turf/union": "^7.3.5",
|
|
93
89
|
"@types/node": "^26.1.1",
|
|
94
90
|
"@types/pbf": "^3.0.5",
|
|
95
91
|
"@types/pngjs": "^6.0.5",
|