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