omni-viewer-core 0.4.0 → 0.5.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.
@@ -1,4 +1,8 @@
1
- export const PDF_ZOOM_LEVELS = [50, 75, 100, 125, 150, 200, 300];
1
+ export const PDF_ZOOM_LEVELS = [
2
+ 50, 75, 100, 125, 150, 175, 200, 225, 250, 275, 300
3
+ ];
4
+ export const PDF_MIN_ZOOM = 25;
5
+ export const PDF_MAX_ZOOM = 400;
2
6
  /**
3
7
  * PDF.js commonly returns one client rect per text span, even for a single
4
8
  * continuous selection. Join neighbouring spans on the same text line so
@@ -41,8 +45,18 @@ export function mergeHighlightRects(rects) {
41
45
  return merged;
42
46
  });
43
47
  }
44
- function nearestZoom(value) {
45
- return PDF_ZOOM_LEVELS.reduce((best, candidate) => Math.abs(candidate - value) < Math.abs(best - value) ? candidate : best);
48
+ function normalizedZoomOptions(options) {
49
+ const requestedMin = Number.isFinite(options.minZoom) ? Math.round(options.minZoom) : PDF_MIN_ZOOM;
50
+ const requestedMax = Number.isFinite(options.maxZoom) ? Math.round(options.maxZoom) : PDF_MAX_ZOOM;
51
+ const min = Math.max(1, Math.min(requestedMin, requestedMax));
52
+ const max = Math.max(min, Math.max(requestedMin, requestedMax));
53
+ const source = options.zoomLevels ?? PDF_ZOOM_LEVELS;
54
+ const levels = [...new Set(source
55
+ .filter(Number.isFinite)
56
+ .map((level) => Math.round(level))
57
+ .filter((level) => level >= min && level <= max))]
58
+ .sort((a, b) => a - b);
59
+ return { levels: levels.length > 0 ? levels : [min, max], min, max };
46
60
  }
47
61
  /** Highest `pdf-a<n>` suffix among seeded ids, so new ids never collide. */
48
62
  function maxSequence(annotations) {
@@ -59,10 +73,11 @@ function copyAnnotations(annotations) {
59
73
  ? { ...annotation, rects: annotation.rects.map((rect) => ({ ...rect })) }
60
74
  : { ...annotation });
61
75
  }
62
- export function createPdfController(pageCount, seed) {
76
+ export function createPdfController(pageCount, seed, options = {}) {
63
77
  const original = Array.from({ length: Math.max(0, Math.floor(pageCount)) }, (_, i) => i + 1);
64
78
  const listeners = new Set();
65
- let zoom = 100;
79
+ const zoomOptions = normalizedZoomOptions(options);
80
+ let zoom = Math.max(zoomOptions.min, Math.min(zoomOptions.max, 100));
66
81
  // A seeded order may only reference pages that still exist; an empty result
67
82
  // falls back to the natural order so the document is never left blank.
68
83
  const seededOrder = seed?.pageOrder?.filter((page) => page >= 1 && page <= original.length) ?? [];
@@ -95,6 +110,9 @@ export function createPdfController(pageCount, seed) {
95
110
  savedSignature = signature();
96
111
  return {
97
112
  get state() { return state(); },
113
+ zoomLevels: zoomOptions.levels,
114
+ minZoom: zoomOptions.min,
115
+ maxZoom: zoomOptions.max,
98
116
  subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
99
117
  dispatch(action) {
100
118
  if (action.type === 'undo') {
@@ -125,19 +143,19 @@ export function createPdfController(pageCount, seed) {
125
143
  const beforeSignature = tracksHistory ? signature() : undefined;
126
144
  switch (action.type) {
127
145
  case 'zoom-in': {
128
- zoom = PDF_ZOOM_LEVELS.find((level) => level > zoom)
129
- ?? PDF_ZOOM_LEVELS[PDF_ZOOM_LEVELS.length - 1];
146
+ zoom = zoomOptions.levels.find((level) => level > zoom)
147
+ ?? zoomOptions.max;
130
148
  break;
131
149
  }
132
150
  case 'zoom-out': {
133
- zoom = [...PDF_ZOOM_LEVELS].reverse().find((level) => level < zoom)
134
- ?? PDF_ZOOM_LEVELS[0];
151
+ zoom = [...zoomOptions.levels].reverse().find((level) => level < zoom)
152
+ ?? zoomOptions.min;
135
153
  break;
136
154
  }
137
155
  case 'set-zoom': {
138
156
  if (!Number.isFinite(action.zoom))
139
157
  return;
140
- zoom = Math.max(25, Math.min(400, Math.round(action.zoom)));
158
+ zoom = Math.max(zoomOptions.min, Math.min(zoomOptions.max, Math.round(action.zoom)));
141
159
  break;
142
160
  }
143
161
  case 'reorder-pages': {
@@ -1,12 +1,73 @@
1
- import type { FilePickService, FileSaveService, FileWritebackService, HostContext } from '../../host/index.js';
1
+ import type { FilePickService, FileSaveResult, FileSaveService, FileWritebackService, HostContext } from '../../host/index.js';
2
2
  import { type MountOptions, type ViewerHandle, type ViewerInput } from '../types.js';
3
- import { type PdfController } from './controller.js';
3
+ import { type PdfController, type PdfControllerOptions, type PdfViewState } from './controller.js';
4
4
  import { type PdfLibModule } from './editing.js';
5
- export { createPdfController, PDF_ZOOM_LEVELS } from './controller.js';
6
- export type { PdfController, PdfAction, PdfViewState, PdfAnnotation } from './controller.js';
5
+ export { createPdfController, PDF_MAX_ZOOM, PDF_MIN_ZOOM, PDF_ZOOM_LEVELS } from './controller.js';
6
+ export type { PdfController, PdfControllerOptions, PdfAction, PdfViewState, PdfAnnotation } from './controller.js';
7
7
  export { pdfViewerCss } from './styles.js';
8
- export { buildEditedPdf, mergePdfBytes, savedPdfName } from './editing.js';
9
- export type { PdfLibModule } from './editing.js';
8
+ export { buildEditedPdf, buildSavedPdf, mergePdfBytes, parseLayer, savedPdfName, SIDECAR_BASE_NAME, SIDECAR_LAYER_NAME } from './editing.js';
9
+ export type { ParsedLayer, PdfLibModule } from './editing.js';
10
+ /** Asset key passed to `HostContext.assets.resolveAssetUrl` by default. */
11
+ export declare const PDF_WORKER_ASSET_KEY = "assets/pdfjs/pdf.worker.min.mjs";
12
+ export type PdfSaveMode = 'hybrid' | 'flattened';
13
+ export type PdfProcessingMode = 'auto' | 'host' | 'browser';
14
+ export interface PdfToolbarAction {
15
+ id: string;
16
+ label: string;
17
+ title?: string;
18
+ ariaLabel?: string;
19
+ disabled?: boolean | (() => boolean);
20
+ onClick(): void | Promise<void>;
21
+ }
22
+ export interface PdfProcessingControl {
23
+ signal: AbortSignal;
24
+ onProgress(progress: number | undefined): void;
25
+ }
26
+ /** Optional host/Worker implementation for byte-heavy PDF operations. */
27
+ export interface PdfProcessingService {
28
+ /** Request byte arrays are borrowed: implementations must not mutate or
29
+ * detach their buffers. A Worker adapter may copy/transfer internally. */
30
+ buildPdf?(request: {
31
+ source: Uint8Array;
32
+ state: PdfViewState;
33
+ mode: PdfSaveMode;
34
+ }, control: PdfProcessingControl): Promise<Uint8Array>;
35
+ mergePdfs?(request: {
36
+ first: Uint8Array;
37
+ second: Uint8Array;
38
+ }, control: PdfProcessingControl): Promise<Uint8Array>;
39
+ }
40
+ export type PdfOperationState = {
41
+ status: 'idle';
42
+ } | {
43
+ status: 'running';
44
+ kind: 'save' | 'save-as' | 'merge';
45
+ progress?: number;
46
+ } | {
47
+ status: 'succeeded';
48
+ kind: 'save' | 'save-as' | 'merge';
49
+ } | {
50
+ status: 'failed';
51
+ kind: 'save' | 'save-as' | 'merge';
52
+ error: string;
53
+ } | {
54
+ status: 'cancelled';
55
+ kind: 'save' | 'save-as' | 'merge';
56
+ };
57
+ export interface PdfMountOptions extends MountOptions, PdfControllerOptions {
58
+ /** Defaults to `hybrid` for v0.4 compatibility. */
59
+ saveMode?: PdfSaveMode;
60
+ /** `auto` delegates when a service method exists, `browser` always uses pdf-lib. */
61
+ processingMode?: PdfProcessingMode;
62
+ /** Maximum merge input requested from FilePickService. */
63
+ maxMergeBytes?: number;
64
+ toolbarActions?: readonly PdfToolbarAction[];
65
+ /** Safe default for VS Code CSP is false. */
66
+ isEvalSupported?: boolean;
67
+ /** Host-provided worker URL. Omit to resolve {@link PDF_WORKER_ASSET_KEY}. */
68
+ workerSrc?: string;
69
+ onSaveAsComplete?(result: FileSaveResult | void): void | Promise<void>;
70
+ }
10
71
  /** Viewer metadata — single source for the registry codegen (DESIGN.md §7). */
11
72
  export declare const PDF_VIEWER_META: {
12
73
  id: string;
@@ -58,12 +119,13 @@ export interface PdfJsDocument {
58
119
  }
59
120
  export interface PdfJsLoadingTask {
60
121
  promise: Promise<PdfJsDocument>;
61
- destroy(): void;
122
+ destroy(): void | Promise<void>;
62
123
  }
63
124
  export interface PdfJsModule {
64
125
  getDocument(options: {
65
126
  data: Uint8Array;
66
127
  password?: string;
128
+ isEvalSupported?: boolean;
67
129
  }): PdfJsLoadingTask;
68
130
  GlobalWorkerOptions: {
69
131
  workerSrc: string;
@@ -80,9 +142,18 @@ export interface PdfViewerDeps {
80
142
  /** Editing dependency (save / save-as / merge / annotation stamping).
81
143
  * Absent -> editing controls disabled with a reason. */
82
144
  loadPdfLib?(): Promise<PdfLibModule>;
145
+ /** Host/extension-host/Web Worker implementation for large byte operations. */
146
+ processing?: PdfProcessingService;
83
147
  }
84
148
  export interface PdfViewerHandle extends ViewerHandle {
85
149
  readonly controller: PdfController;
150
+ readonly operation: PdfOperationState;
86
151
  isDirty(): boolean;
152
+ cancelOperation(): void;
153
+ refreshToolbarActions(): void;
87
154
  }
88
- export declare function mountPdfViewer(input: ViewerInput, container: HTMLElement, ctx: PdfViewerContext, deps: PdfViewerDeps, options?: MountOptions): Promise<PdfViewerHandle>;
155
+ export declare function containPdfSignatureSize(width: number, height: number, maxWidth?: number, maxHeight?: number): {
156
+ width: number;
157
+ height: number;
158
+ };
159
+ export declare function mountPdfViewer(input: ViewerInput, container: HTMLElement, ctx: PdfViewerContext, deps: PdfViewerDeps, options?: PdfMountOptions): Promise<PdfViewerHandle>;