pdf-mapview 0.1.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/LICENSE +12 -0
- package/README.md +197 -0
- package/dist/client/index.cjs +1155 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +102 -0
- package/dist/client/index.d.ts +102 -0
- package/dist/client/index.js +1149 -0
- package/dist/client/index.js.map +1 -0
- package/dist/ingest/cli.cjs +703 -0
- package/dist/ingest/cli.cjs.map +1 -0
- package/dist/ingest/cli.d.cts +1 -0
- package/dist/ingest/cli.d.ts +1 -0
- package/dist/ingest/cli.js +697 -0
- package/dist/ingest/cli.js.map +1 -0
- package/dist/ingest/index.cjs +751 -0
- package/dist/ingest/index.cjs.map +1 -0
- package/dist/ingest/index.d.cts +33 -0
- package/dist/ingest/index.d.ts +33 -0
- package/dist/ingest/index.js +738 -0
- package/dist/ingest/index.js.map +1 -0
- package/dist/ingest-sfbf6503.d.cts +647 -0
- package/dist/ingest-sfbf6503.d.ts +647 -0
- package/dist/server/index.cjs +751 -0
- package/dist/server/index.cjs.map +1 -0
- package/dist/server/index.d.cts +3 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +738 -0
- package/dist/server/index.js.map +1 -0
- package/dist/shared/index.cjs +238 -0
- package/dist/shared/index.cjs.map +1 -0
- package/dist/shared/index.d.cts +8 -0
- package/dist/shared/index.d.ts +8 -0
- package/dist/shared/index.js +222 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/source-Cb1QZPVw.d.ts +60 -0
- package/dist/source-DHrup45h.d.cts +60 -0
- package/package.json +104 -0
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var promises = require('fs/promises');
|
|
7
|
+
var canvas = require('@napi-rs/canvas');
|
|
8
|
+
var sharp2 = require('sharp');
|
|
9
|
+
|
|
10
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
|
|
12
|
+
var sharp2__default = /*#__PURE__*/_interopDefault(sharp2);
|
|
13
|
+
|
|
14
|
+
// src/shared/manifest.ts
|
|
15
|
+
var normalizedPointSchema = zod.z.object({
|
|
16
|
+
x: zod.z.number().finite().min(0).max(1),
|
|
17
|
+
y: zod.z.number().finite().min(0).max(1)
|
|
18
|
+
});
|
|
19
|
+
var normalizedRectSchema = zod.z.object({
|
|
20
|
+
x: zod.z.number().finite().min(0).max(1),
|
|
21
|
+
y: zod.z.number().finite().min(0).max(1),
|
|
22
|
+
width: zod.z.number().finite().min(0).max(1),
|
|
23
|
+
height: zod.z.number().finite().min(0).max(1)
|
|
24
|
+
});
|
|
25
|
+
var regionFeatureSchema = zod.z.object({
|
|
26
|
+
id: zod.z.string().min(1),
|
|
27
|
+
geometry: zod.z.discriminatedUnion("type", [
|
|
28
|
+
zod.z.object({
|
|
29
|
+
type: zod.z.literal("polygon"),
|
|
30
|
+
points: zod.z.array(normalizedPointSchema).min(3)
|
|
31
|
+
}),
|
|
32
|
+
zod.z.object({
|
|
33
|
+
type: zod.z.literal("rectangle"),
|
|
34
|
+
rect: normalizedRectSchema
|
|
35
|
+
}),
|
|
36
|
+
zod.z.object({
|
|
37
|
+
type: zod.z.literal("point"),
|
|
38
|
+
point: normalizedPointSchema,
|
|
39
|
+
radius: zod.z.number().finite().positive().optional()
|
|
40
|
+
}),
|
|
41
|
+
zod.z.object({
|
|
42
|
+
type: zod.z.literal("label"),
|
|
43
|
+
point: normalizedPointSchema,
|
|
44
|
+
text: zod.z.string().min(1)
|
|
45
|
+
})
|
|
46
|
+
]),
|
|
47
|
+
label: zod.z.string().optional(),
|
|
48
|
+
metadata: zod.z.record(zod.z.unknown()).optional()
|
|
49
|
+
});
|
|
50
|
+
var regionCollectionSchema = zod.z.object({
|
|
51
|
+
type: zod.z.literal("FeatureCollection"),
|
|
52
|
+
regions: zod.z.array(regionFeatureSchema)
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// src/shared/manifest.ts
|
|
56
|
+
var tileLevelSchema = zod.z.object({
|
|
57
|
+
z: zod.z.number().int().min(0),
|
|
58
|
+
width: zod.z.number().int().positive(),
|
|
59
|
+
height: zod.z.number().int().positive(),
|
|
60
|
+
columns: zod.z.number().int().positive(),
|
|
61
|
+
rows: zod.z.number().int().positive(),
|
|
62
|
+
scale: zod.z.number().positive()
|
|
63
|
+
});
|
|
64
|
+
var manifestSchema = zod.z.object({
|
|
65
|
+
version: zod.z.literal(1),
|
|
66
|
+
kind: zod.z.literal("pdf-map"),
|
|
67
|
+
id: zod.z.string().min(1),
|
|
68
|
+
source: zod.z.object({
|
|
69
|
+
type: zod.z.union([zod.z.literal("pdf"), zod.z.literal("image")]),
|
|
70
|
+
originalFilename: zod.z.string().optional(),
|
|
71
|
+
page: zod.z.number().int().positive().optional(),
|
|
72
|
+
width: zod.z.number().int().positive(),
|
|
73
|
+
height: zod.z.number().int().positive(),
|
|
74
|
+
mimeType: zod.z.string().optional()
|
|
75
|
+
}),
|
|
76
|
+
coordinateSpace: zod.z.object({
|
|
77
|
+
normalized: zod.z.literal(true),
|
|
78
|
+
width: zod.z.number().int().positive(),
|
|
79
|
+
height: zod.z.number().int().positive()
|
|
80
|
+
}),
|
|
81
|
+
tiles: zod.z.object({
|
|
82
|
+
tileSize: zod.z.number().int().positive(),
|
|
83
|
+
format: zod.z.union([zod.z.literal("webp"), zod.z.literal("jpeg"), zod.z.literal("png")]),
|
|
84
|
+
minZoom: zod.z.number().int().min(0),
|
|
85
|
+
maxZoom: zod.z.number().int().min(0),
|
|
86
|
+
pathTemplate: zod.z.string().min(1),
|
|
87
|
+
levels: zod.z.array(tileLevelSchema)
|
|
88
|
+
}),
|
|
89
|
+
view: zod.z.object({
|
|
90
|
+
defaultCenter: zod.z.tuple([
|
|
91
|
+
zod.z.number().finite().min(0).max(1),
|
|
92
|
+
zod.z.number().finite().min(0).max(1)
|
|
93
|
+
]),
|
|
94
|
+
defaultZoom: zod.z.number().finite(),
|
|
95
|
+
minZoom: zod.z.number().finite(),
|
|
96
|
+
maxZoom: zod.z.number().finite()
|
|
97
|
+
}),
|
|
98
|
+
overlays: zod.z.object({
|
|
99
|
+
inline: regionCollectionSchema.optional(),
|
|
100
|
+
url: zod.z.string().optional()
|
|
101
|
+
}).optional(),
|
|
102
|
+
assets: zod.z.object({
|
|
103
|
+
preview: zod.z.string().optional()
|
|
104
|
+
}).optional(),
|
|
105
|
+
metadata: zod.z.object({
|
|
106
|
+
title: zod.z.string().optional(),
|
|
107
|
+
createdAt: zod.z.string().optional()
|
|
108
|
+
}).catchall(zod.z.unknown()).optional()
|
|
109
|
+
});
|
|
110
|
+
function createManifest(input) {
|
|
111
|
+
return manifestSchema.parse({
|
|
112
|
+
version: 1,
|
|
113
|
+
kind: "pdf-map",
|
|
114
|
+
...input
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function parseManifest(input) {
|
|
118
|
+
return manifestSchema.parse(input);
|
|
119
|
+
}
|
|
120
|
+
function resolveTileUrl(args) {
|
|
121
|
+
const template = args.overrideTemplate ?? args.manifest.tiles.pathTemplate;
|
|
122
|
+
const relative = template.replaceAll("{z}", String(args.z)).replaceAll("{x}", String(args.x)).replaceAll("{y}", String(args.y));
|
|
123
|
+
if (/^https?:\/\//.test(relative)) {
|
|
124
|
+
return relative;
|
|
125
|
+
}
|
|
126
|
+
if (!args.baseUrl) {
|
|
127
|
+
return relative;
|
|
128
|
+
}
|
|
129
|
+
if (/^https?:\/\//.test(args.baseUrl)) {
|
|
130
|
+
return new URL(relative.replace(/^\//, ""), ensureTrailingSlash(args.baseUrl)).toString();
|
|
131
|
+
}
|
|
132
|
+
return joinRelativeUrl(args.baseUrl, relative);
|
|
133
|
+
}
|
|
134
|
+
function ensureTrailingSlash(value) {
|
|
135
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
136
|
+
}
|
|
137
|
+
function joinRelativeUrl(baseUrl, path) {
|
|
138
|
+
const normalizedBase = ensureLeadingSlash(stripTrailingSlash(baseUrl));
|
|
139
|
+
const normalizedPath = stripLeadingSlash(path);
|
|
140
|
+
if (!normalizedPath) {
|
|
141
|
+
return normalizedBase || "/";
|
|
142
|
+
}
|
|
143
|
+
return normalizedBase ? `${normalizedBase}/${normalizedPath}` : `/${normalizedPath}`;
|
|
144
|
+
}
|
|
145
|
+
function stripLeadingSlash(value) {
|
|
146
|
+
return value.replace(/^\/+/, "");
|
|
147
|
+
}
|
|
148
|
+
function stripTrailingSlash(value) {
|
|
149
|
+
return value.replace(/\/+$/, "");
|
|
150
|
+
}
|
|
151
|
+
function ensureLeadingSlash(value) {
|
|
152
|
+
if (!value) {
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/shared/bytes.ts
|
|
159
|
+
function toUint8Array(input) {
|
|
160
|
+
if (input instanceof ArrayBuffer) {
|
|
161
|
+
return new Uint8Array(input);
|
|
162
|
+
}
|
|
163
|
+
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/ingest/pipeline/inspectInput.ts
|
|
167
|
+
async function inspectInput(input) {
|
|
168
|
+
if (typeof input === "string") {
|
|
169
|
+
const bytes = await promises.readFile(input);
|
|
170
|
+
return {
|
|
171
|
+
bytes: toUint8Array(bytes),
|
|
172
|
+
originalFilename: path.basename(input),
|
|
173
|
+
ext: path.extname(input).slice(1).toLowerCase() || void 0
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
bytes: toUint8Array(input)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function rasterizePdf(options) {
|
|
181
|
+
installNodeCanvasGlobals();
|
|
182
|
+
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
|
183
|
+
const loadingTask = pdfjs.getDocument({
|
|
184
|
+
data: options.bytes,
|
|
185
|
+
useSystemFonts: true
|
|
186
|
+
});
|
|
187
|
+
const pdf = await loadingTask.promise;
|
|
188
|
+
const page = await pdf.getPage(options.page);
|
|
189
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
190
|
+
const scale = resolveScale({
|
|
191
|
+
viewportWidth: viewport.width,
|
|
192
|
+
viewportHeight: viewport.height,
|
|
193
|
+
maxDimension: options.maxDimension,
|
|
194
|
+
rasterDpi: options.rasterDpi
|
|
195
|
+
});
|
|
196
|
+
const scaledViewport = page.getViewport({
|
|
197
|
+
scale: scale <= 0 ? 1 : scale
|
|
198
|
+
});
|
|
199
|
+
const canvas$1 = canvas.createCanvas(Math.ceil(scaledViewport.width), Math.ceil(scaledViewport.height));
|
|
200
|
+
const context = canvas$1.getContext("2d");
|
|
201
|
+
context.fillStyle = options.background;
|
|
202
|
+
context.fillRect(0, 0, canvas$1.width, canvas$1.height);
|
|
203
|
+
await page.render({
|
|
204
|
+
canvasContext: context,
|
|
205
|
+
viewport: scaledViewport
|
|
206
|
+
}).promise;
|
|
207
|
+
const png = await canvas$1.encode("png");
|
|
208
|
+
await page.cleanup();
|
|
209
|
+
await pdf.cleanup();
|
|
210
|
+
await pdf.destroy();
|
|
211
|
+
return {
|
|
212
|
+
bytes: new Uint8Array(png),
|
|
213
|
+
width: canvas$1.width,
|
|
214
|
+
height: canvas$1.height,
|
|
215
|
+
mimeType: "image/png"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function installNodeCanvasGlobals() {
|
|
219
|
+
if (!("DOMMatrix" in globalThis)) {
|
|
220
|
+
globalThis.DOMMatrix = canvas.DOMMatrix;
|
|
221
|
+
}
|
|
222
|
+
if (!("ImageData" in globalThis)) {
|
|
223
|
+
globalThis.ImageData = canvas.ImageData;
|
|
224
|
+
}
|
|
225
|
+
if (!("Path2D" in globalThis)) {
|
|
226
|
+
globalThis.Path2D = canvas.Path2D;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function resolveScale(options) {
|
|
230
|
+
if (options.rasterDpi && Number.isFinite(options.rasterDpi) && options.rasterDpi > 0) {
|
|
231
|
+
return options.rasterDpi / 72;
|
|
232
|
+
}
|
|
233
|
+
return Math.min(
|
|
234
|
+
1,
|
|
235
|
+
options.maxDimension / Math.max(options.viewportWidth, options.viewportHeight)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/ingest/storage/memory.ts
|
|
240
|
+
function memoryStorageAdapter() {
|
|
241
|
+
const artifacts = [];
|
|
242
|
+
const record = (kind, path, contentType, bytes) => {
|
|
243
|
+
const artifact = {
|
|
244
|
+
kind,
|
|
245
|
+
path,
|
|
246
|
+
contentType,
|
|
247
|
+
size: bytes.byteLength
|
|
248
|
+
};
|
|
249
|
+
artifacts.push(artifact);
|
|
250
|
+
return artifact;
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
async writeTile(args) {
|
|
254
|
+
return record("tile", `tiles/${args.z}/${args.x}/${args.y}.${args.ext}`, args.contentType, args.bytes);
|
|
255
|
+
},
|
|
256
|
+
async writeManifest(args) {
|
|
257
|
+
return record("manifest", args.path, args.contentType, args.bytes);
|
|
258
|
+
},
|
|
259
|
+
async writeAsset(args) {
|
|
260
|
+
return record(args.kind, args.path, args.contentType, args.bytes);
|
|
261
|
+
},
|
|
262
|
+
async finalize(_args) {
|
|
263
|
+
return {
|
|
264
|
+
artifacts: [...artifacts]
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function normalizeImage(options) {
|
|
270
|
+
const { data, info } = await sharp2__default.default(options.bytes, { failOn: "none" }).rotate().resize({
|
|
271
|
+
width: options.maxDimension,
|
|
272
|
+
height: options.maxDimension,
|
|
273
|
+
fit: "inside",
|
|
274
|
+
withoutEnlargement: true
|
|
275
|
+
}).flatten({
|
|
276
|
+
background: options.background
|
|
277
|
+
}).png().toBuffer({ resolveWithObject: true });
|
|
278
|
+
return {
|
|
279
|
+
bytes: new Uint8Array(data),
|
|
280
|
+
width: info.width,
|
|
281
|
+
height: info.height,
|
|
282
|
+
mimeType: "image/png"
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async function buildTilePyramid(options) {
|
|
286
|
+
const maxZoom = Math.max(
|
|
287
|
+
0,
|
|
288
|
+
Math.ceil(Math.log2(Math.max(options.width, options.height) / options.tileSize))
|
|
289
|
+
);
|
|
290
|
+
const levels = [];
|
|
291
|
+
const tiles = [];
|
|
292
|
+
const source = Buffer.from(options.image);
|
|
293
|
+
for (let z3 = 0; z3 <= maxZoom; z3 += 1) {
|
|
294
|
+
const scale = 1 / 2 ** (maxZoom - z3);
|
|
295
|
+
const width = Math.max(1, Math.ceil(options.width * scale));
|
|
296
|
+
const height = Math.max(1, Math.ceil(options.height * scale));
|
|
297
|
+
const columns = Math.max(1, Math.ceil(width / options.tileSize));
|
|
298
|
+
const rows = Math.max(1, Math.ceil(height / options.tileSize));
|
|
299
|
+
levels.push({
|
|
300
|
+
z: z3,
|
|
301
|
+
width,
|
|
302
|
+
height,
|
|
303
|
+
columns,
|
|
304
|
+
rows,
|
|
305
|
+
scale
|
|
306
|
+
});
|
|
307
|
+
const levelBuffer = await sharp2__default.default(source, { failOn: "none" }).resize({
|
|
308
|
+
width,
|
|
309
|
+
height,
|
|
310
|
+
fit: "fill"
|
|
311
|
+
}).png().toBuffer();
|
|
312
|
+
for (let y = 0; y < rows; y += 1) {
|
|
313
|
+
for (let x = 0; x < columns; x += 1) {
|
|
314
|
+
const left = x * options.tileSize;
|
|
315
|
+
const top = y * options.tileSize;
|
|
316
|
+
const extractWidth = Math.min(options.tileSize, width - left);
|
|
317
|
+
const extractHeight = Math.min(options.tileSize, height - top);
|
|
318
|
+
const tileBuffer = await sharp2__default.default(levelBuffer, { failOn: "none" }).extract({
|
|
319
|
+
left,
|
|
320
|
+
top,
|
|
321
|
+
width: extractWidth,
|
|
322
|
+
height: extractHeight
|
|
323
|
+
}).toFormat(options.format, formatOptions(options.format, options.quality)).toBuffer();
|
|
324
|
+
tiles.push({
|
|
325
|
+
kind: "tile",
|
|
326
|
+
path: `tiles/${z3}/${x}/${y}.${extensionForFormat(options.format)}`,
|
|
327
|
+
contentType: contentTypeForFormat(options.format),
|
|
328
|
+
bytes: new Uint8Array(tileBuffer)
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const previewBuffer = await sharp2__default.default(source, { failOn: "none" }).resize({
|
|
334
|
+
width: 1024,
|
|
335
|
+
height: 1024,
|
|
336
|
+
fit: "inside",
|
|
337
|
+
withoutEnlargement: true
|
|
338
|
+
}).webp({ quality: 80 }).toBuffer();
|
|
339
|
+
return {
|
|
340
|
+
levels,
|
|
341
|
+
tiles,
|
|
342
|
+
preview: {
|
|
343
|
+
kind: "preview",
|
|
344
|
+
path: "preview.webp",
|
|
345
|
+
contentType: "image/webp",
|
|
346
|
+
bytes: new Uint8Array(previewBuffer)
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function formatOptions(format, quality) {
|
|
351
|
+
switch (format) {
|
|
352
|
+
case "jpeg":
|
|
353
|
+
return { quality };
|
|
354
|
+
case "png":
|
|
355
|
+
return {};
|
|
356
|
+
case "webp":
|
|
357
|
+
default:
|
|
358
|
+
return { quality };
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function extensionForFormat(format) {
|
|
362
|
+
return format === "jpeg" ? "jpg" : format;
|
|
363
|
+
}
|
|
364
|
+
function contentTypeForFormat(format) {
|
|
365
|
+
switch (format) {
|
|
366
|
+
case "jpeg":
|
|
367
|
+
return "image/jpeg";
|
|
368
|
+
case "png":
|
|
369
|
+
return "image/png";
|
|
370
|
+
case "webp":
|
|
371
|
+
default:
|
|
372
|
+
return "image/webp";
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// src/ingest/pipeline/manifestBuilder.ts
|
|
377
|
+
function buildManifest(options) {
|
|
378
|
+
const minZoom = options.levels[0]?.z ?? 0;
|
|
379
|
+
const maxZoom = options.levels[options.levels.length - 1]?.z ?? 0;
|
|
380
|
+
return createManifest({
|
|
381
|
+
id: options.id,
|
|
382
|
+
source: {
|
|
383
|
+
type: options.sourceType,
|
|
384
|
+
originalFilename: options.originalFilename,
|
|
385
|
+
page: options.page,
|
|
386
|
+
width: options.width,
|
|
387
|
+
height: options.height,
|
|
388
|
+
mimeType: options.mimeType
|
|
389
|
+
},
|
|
390
|
+
coordinateSpace: {
|
|
391
|
+
normalized: true,
|
|
392
|
+
width: options.width,
|
|
393
|
+
height: options.height
|
|
394
|
+
},
|
|
395
|
+
tiles: {
|
|
396
|
+
tileSize: options.tileSize,
|
|
397
|
+
format: options.tileFormat,
|
|
398
|
+
minZoom,
|
|
399
|
+
maxZoom,
|
|
400
|
+
pathTemplate: withBaseUrl(options.baseUrl, `tiles/{z}/{x}/{y}.${options.tileFormat === "jpeg" ? "jpg" : options.tileFormat}`),
|
|
401
|
+
levels: options.levels
|
|
402
|
+
},
|
|
403
|
+
view: {
|
|
404
|
+
defaultCenter: [0.5, 0.5],
|
|
405
|
+
defaultZoom: 1,
|
|
406
|
+
minZoom: 0,
|
|
407
|
+
maxZoom: Math.max(maxZoom + 2, 6)
|
|
408
|
+
},
|
|
409
|
+
overlays: options.inlineOverlays || options.overlayUrl ? {
|
|
410
|
+
inline: options.inlineOverlays,
|
|
411
|
+
url: options.overlayUrl ? withBaseUrl(options.baseUrl, options.overlayUrl) : void 0
|
|
412
|
+
} : void 0,
|
|
413
|
+
assets: options.previewPath ? {
|
|
414
|
+
preview: withBaseUrl(options.baseUrl, options.previewPath)
|
|
415
|
+
} : void 0,
|
|
416
|
+
metadata: {
|
|
417
|
+
title: options.title,
|
|
418
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function withBaseUrl(baseUrl, path) {
|
|
423
|
+
if (!baseUrl) {
|
|
424
|
+
return path;
|
|
425
|
+
}
|
|
426
|
+
if (/^https?:\/\//.test(baseUrl)) {
|
|
427
|
+
return new URL(path.replace(/^\//, ""), ensureTrailingSlash2(baseUrl)).toString();
|
|
428
|
+
}
|
|
429
|
+
return joinRelativeUrl2(baseUrl, path);
|
|
430
|
+
}
|
|
431
|
+
function ensureTrailingSlash2(baseUrl) {
|
|
432
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
433
|
+
}
|
|
434
|
+
function joinRelativeUrl2(baseUrl, path) {
|
|
435
|
+
const normalizedBase = ensureLeadingSlash2(stripTrailingSlash2(baseUrl));
|
|
436
|
+
const normalizedPath = stripLeadingSlash2(path);
|
|
437
|
+
if (!normalizedPath) {
|
|
438
|
+
return normalizedBase || "/";
|
|
439
|
+
}
|
|
440
|
+
return normalizedBase ? `${normalizedBase}/${normalizedPath}` : `/${normalizedPath}`;
|
|
441
|
+
}
|
|
442
|
+
function stripLeadingSlash2(value) {
|
|
443
|
+
return value.replace(/^\/+/, "");
|
|
444
|
+
}
|
|
445
|
+
function stripTrailingSlash2(value) {
|
|
446
|
+
return value.replace(/\/+$/, "");
|
|
447
|
+
}
|
|
448
|
+
function ensureLeadingSlash2(value) {
|
|
449
|
+
if (!value) {
|
|
450
|
+
return "";
|
|
451
|
+
}
|
|
452
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/ingest/pipeline/writeArtifacts.ts
|
|
456
|
+
async function writeArtifacts(adapter, manifest, files) {
|
|
457
|
+
const uploaded = [];
|
|
458
|
+
for (const file of files) {
|
|
459
|
+
if (file.kind === "tile") {
|
|
460
|
+
const match = file.path.match(/^tiles\/(\d+)\/(\d+)\/(\d+)\.[^.]+$/);
|
|
461
|
+
if (!match) {
|
|
462
|
+
throw new Error(`Invalid tile path: ${file.path}`);
|
|
463
|
+
}
|
|
464
|
+
uploaded.push(
|
|
465
|
+
await adapter.writeTile({
|
|
466
|
+
z: Number(match[1]),
|
|
467
|
+
x: Number(match[2]),
|
|
468
|
+
y: Number(match[3]),
|
|
469
|
+
ext: file.path.split(".").pop() ?? "bin",
|
|
470
|
+
bytes: file.bytes,
|
|
471
|
+
contentType: file.contentType
|
|
472
|
+
})
|
|
473
|
+
);
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (file.kind === "manifest") {
|
|
477
|
+
uploaded.push(
|
|
478
|
+
await adapter.writeManifest({
|
|
479
|
+
path: file.path,
|
|
480
|
+
bytes: file.bytes,
|
|
481
|
+
contentType: "application/json"
|
|
482
|
+
})
|
|
483
|
+
);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (!adapter.writeAsset) {
|
|
487
|
+
throw new Error(`Storage adapter does not support writing ${file.kind} assets.`);
|
|
488
|
+
}
|
|
489
|
+
uploaded.push(
|
|
490
|
+
await adapter.writeAsset({
|
|
491
|
+
kind: file.kind,
|
|
492
|
+
path: file.path,
|
|
493
|
+
bytes: file.bytes,
|
|
494
|
+
contentType: file.contentType
|
|
495
|
+
})
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
const storage = await adapter.finalize({
|
|
499
|
+
manifest,
|
|
500
|
+
artifacts: uploaded
|
|
501
|
+
});
|
|
502
|
+
return {
|
|
503
|
+
uploaded,
|
|
504
|
+
storage
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/ingest/ingestImage.ts
|
|
509
|
+
async function ingestImage(options) {
|
|
510
|
+
const inspected = await inspectInput(options.input);
|
|
511
|
+
const normalized = await normalizeImage({
|
|
512
|
+
bytes: inspected.bytes,
|
|
513
|
+
maxDimension: options.maxDimension ?? 12288,
|
|
514
|
+
background: options.background ?? "#ffffff"
|
|
515
|
+
});
|
|
516
|
+
return ingestRasterizedImage(normalized, {
|
|
517
|
+
common: options,
|
|
518
|
+
id: options.id ?? defaultId(inspected.originalFilename ?? "image"),
|
|
519
|
+
title: options.title,
|
|
520
|
+
sourceType: "image",
|
|
521
|
+
originalFilename: inspected.originalFilename,
|
|
522
|
+
mimeType: normalized.mimeType
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
async function ingestRasterizedImage(normalized, input) {
|
|
526
|
+
const tileSize = input.common.tileSize ?? 256;
|
|
527
|
+
const tileFormat = input.common.tileFormat ?? "webp";
|
|
528
|
+
const tileQuality = input.common.tileQuality ?? 92;
|
|
529
|
+
const storage = input.common.storage ?? memoryStorageAdapter();
|
|
530
|
+
const pyramid = await buildTilePyramid({
|
|
531
|
+
image: normalized.bytes,
|
|
532
|
+
width: normalized.width,
|
|
533
|
+
height: normalized.height,
|
|
534
|
+
tileSize,
|
|
535
|
+
format: tileFormat,
|
|
536
|
+
quality: tileQuality
|
|
537
|
+
});
|
|
538
|
+
const overlayPayload = await resolveOverlayPayload(input.common.overlays);
|
|
539
|
+
const manifest = buildManifest({
|
|
540
|
+
id: input.id,
|
|
541
|
+
title: input.title,
|
|
542
|
+
sourceType: input.sourceType,
|
|
543
|
+
originalFilename: input.originalFilename,
|
|
544
|
+
page: input.page,
|
|
545
|
+
width: normalized.width,
|
|
546
|
+
height: normalized.height,
|
|
547
|
+
mimeType: input.mimeType ?? normalized.mimeType,
|
|
548
|
+
tileSize,
|
|
549
|
+
tileFormat,
|
|
550
|
+
levels: pyramid.levels,
|
|
551
|
+
baseUrl: input.common.baseUrl,
|
|
552
|
+
inlineOverlays: overlayPayload.inline,
|
|
553
|
+
overlayUrl: overlayPayload.url,
|
|
554
|
+
previewPath: pyramid.preview.path
|
|
555
|
+
});
|
|
556
|
+
const files = [...pyramid.tiles, pyramid.preview];
|
|
557
|
+
if (overlayPayload.file) {
|
|
558
|
+
files.push(overlayPayload.file);
|
|
559
|
+
}
|
|
560
|
+
files.push({
|
|
561
|
+
kind: "manifest",
|
|
562
|
+
path: "manifest.json",
|
|
563
|
+
contentType: "application/json",
|
|
564
|
+
bytes: new TextEncoder().encode(JSON.stringify(manifest, null, 2))
|
|
565
|
+
});
|
|
566
|
+
const { uploaded, storage: storageResult } = await writeArtifacts(storage, manifest, files);
|
|
567
|
+
return {
|
|
568
|
+
manifest,
|
|
569
|
+
width: normalized.width,
|
|
570
|
+
height: normalized.height,
|
|
571
|
+
tileCount: pyramid.tiles.length,
|
|
572
|
+
files,
|
|
573
|
+
uploaded,
|
|
574
|
+
warnings: [],
|
|
575
|
+
storage: storageResult
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
async function resolveOverlayPayload(input) {
|
|
579
|
+
if (!input) {
|
|
580
|
+
return {};
|
|
581
|
+
}
|
|
582
|
+
if (typeof input === "string") {
|
|
583
|
+
return {
|
|
584
|
+
url: input
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const bytes = new TextEncoder().encode(JSON.stringify(input, null, 2));
|
|
588
|
+
if (bytes.byteLength < 64 * 1024) {
|
|
589
|
+
return {
|
|
590
|
+
inline: input
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
url: "regions.json",
|
|
595
|
+
file: {
|
|
596
|
+
kind: "overlay",
|
|
597
|
+
path: "regions.json",
|
|
598
|
+
contentType: "application/json",
|
|
599
|
+
bytes
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function defaultId(seed) {
|
|
604
|
+
const safe = seed.replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
605
|
+
return safe || crypto.randomUUID();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/ingest/ingestPdf.ts
|
|
609
|
+
async function ingestPdf(options) {
|
|
610
|
+
const inspected = await inspectInput(options.input);
|
|
611
|
+
const page = options.page ?? 1;
|
|
612
|
+
const rasterized = await rasterizePdf({
|
|
613
|
+
bytes: inspected.bytes,
|
|
614
|
+
page,
|
|
615
|
+
maxDimension: options.maxDimension ?? 12288,
|
|
616
|
+
rasterDpi: options.rasterDpi,
|
|
617
|
+
background: options.background ?? "#ffffff"
|
|
618
|
+
});
|
|
619
|
+
return ingestRasterizedImage(rasterized, {
|
|
620
|
+
common: options,
|
|
621
|
+
id: options.id ?? defaultId2(inspected.originalFilename ?? "pdf"),
|
|
622
|
+
title: options.title,
|
|
623
|
+
sourceType: "pdf",
|
|
624
|
+
originalFilename: inspected.originalFilename,
|
|
625
|
+
mimeType: "application/pdf",
|
|
626
|
+
page
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function defaultId2(seed) {
|
|
630
|
+
const safe = seed.replace(/\.[^.]+$/, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
631
|
+
return safe || crypto.randomUUID();
|
|
632
|
+
}
|
|
633
|
+
function localStorageAdapter(options) {
|
|
634
|
+
const baseDir = path.resolve(options.baseDir);
|
|
635
|
+
const manifestName = options.manifestName ?? "manifest.json";
|
|
636
|
+
const artifacts = [];
|
|
637
|
+
let cleaned = false;
|
|
638
|
+
const ensureParent = async (filePath) => {
|
|
639
|
+
await promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
640
|
+
};
|
|
641
|
+
const ensureClean = async () => {
|
|
642
|
+
if (!options.clean || cleaned) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
cleaned = true;
|
|
646
|
+
await promises.rm(baseDir, { recursive: true, force: true });
|
|
647
|
+
};
|
|
648
|
+
const write = async (kind, relativePath, contentType, bytes) => {
|
|
649
|
+
await ensureClean();
|
|
650
|
+
const path$1 = path.join(baseDir, relativePath);
|
|
651
|
+
await ensureParent(path$1);
|
|
652
|
+
await promises.writeFile(path$1, bytes);
|
|
653
|
+
const artifact = {
|
|
654
|
+
kind,
|
|
655
|
+
path: path$1,
|
|
656
|
+
contentType,
|
|
657
|
+
size: bytes.byteLength
|
|
658
|
+
};
|
|
659
|
+
artifacts.push(artifact);
|
|
660
|
+
return artifact;
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
async writeTile(args) {
|
|
664
|
+
return write(
|
|
665
|
+
"tile",
|
|
666
|
+
`tiles/${args.z}/${args.x}/${args.y}.${args.ext}`,
|
|
667
|
+
args.contentType,
|
|
668
|
+
args.bytes
|
|
669
|
+
);
|
|
670
|
+
},
|
|
671
|
+
async writeManifest(args) {
|
|
672
|
+
return write("manifest", manifestName, args.contentType, args.bytes);
|
|
673
|
+
},
|
|
674
|
+
async writeAsset(args) {
|
|
675
|
+
return write(args.kind, args.path, args.contentType, args.bytes);
|
|
676
|
+
},
|
|
677
|
+
async finalize(_args) {
|
|
678
|
+
return {
|
|
679
|
+
artifacts: [...artifacts]
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/ingest/storage/s3Compatible.ts
|
|
686
|
+
function s3CompatibleStorageAdapter(options) {
|
|
687
|
+
const prefix = normalizePrefix(options.prefix);
|
|
688
|
+
const artifacts = [];
|
|
689
|
+
const upload = async (kind, relativePath, contentType, bytes, cacheControl) => {
|
|
690
|
+
const key = `${prefix}${relativePath}`;
|
|
691
|
+
const result = await options.putObject({
|
|
692
|
+
key,
|
|
693
|
+
body: bytes,
|
|
694
|
+
contentType,
|
|
695
|
+
cacheControl
|
|
696
|
+
});
|
|
697
|
+
const artifact = {
|
|
698
|
+
kind,
|
|
699
|
+
path: key,
|
|
700
|
+
contentType,
|
|
701
|
+
size: bytes.byteLength,
|
|
702
|
+
url: result?.url ?? (options.baseUrl ? `${normalizeBaseUrl(options.baseUrl)}${key}` : void 0)
|
|
703
|
+
};
|
|
704
|
+
artifacts.push(artifact);
|
|
705
|
+
return artifact;
|
|
706
|
+
};
|
|
707
|
+
return {
|
|
708
|
+
async writeTile(args) {
|
|
709
|
+
return upload(
|
|
710
|
+
"tile",
|
|
711
|
+
`tiles/${args.z}/${args.x}/${args.y}.${args.ext}`,
|
|
712
|
+
args.contentType,
|
|
713
|
+
args.bytes,
|
|
714
|
+
"public, max-age=31536000, immutable"
|
|
715
|
+
);
|
|
716
|
+
},
|
|
717
|
+
async writeManifest(args) {
|
|
718
|
+
return upload("manifest", args.path, args.contentType, args.bytes, "public, max-age=60");
|
|
719
|
+
},
|
|
720
|
+
async writeAsset(args) {
|
|
721
|
+
const cacheControl = args.kind === "preview" ? "public, max-age=31536000, immutable" : "public, max-age=60";
|
|
722
|
+
return upload(args.kind, args.path, args.contentType, args.bytes, cacheControl);
|
|
723
|
+
},
|
|
724
|
+
async finalize(_args) {
|
|
725
|
+
return {
|
|
726
|
+
artifacts: [...artifacts],
|
|
727
|
+
baseUrl: options.baseUrl
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function normalizePrefix(prefix) {
|
|
733
|
+
if (!prefix) {
|
|
734
|
+
return "";
|
|
735
|
+
}
|
|
736
|
+
return prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
737
|
+
}
|
|
738
|
+
function normalizeBaseUrl(baseUrl) {
|
|
739
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
exports.createManifest = createManifest;
|
|
743
|
+
exports.ingestImage = ingestImage;
|
|
744
|
+
exports.ingestPdf = ingestPdf;
|
|
745
|
+
exports.localStorageAdapter = localStorageAdapter;
|
|
746
|
+
exports.memoryStorageAdapter = memoryStorageAdapter;
|
|
747
|
+
exports.parseManifest = parseManifest;
|
|
748
|
+
exports.resolveTileUrl = resolveTileUrl;
|
|
749
|
+
exports.s3CompatibleStorageAdapter = s3CompatibleStorageAdapter;
|
|
750
|
+
//# sourceMappingURL=index.cjs.map
|
|
751
|
+
//# sourceMappingURL=index.cjs.map
|