@printwithsynergy/lens-pdf 0.3.0-beta.81
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 +661 -0
- package/README.md +344 -0
- package/dist/browser/codexOverlay.d.ts +109 -0
- package/dist/browser/codexOverlay.d.ts.map +1 -0
- package/dist/browser/codexOverlay.js +256 -0
- package/dist/browser/codexOverlay.js.map +1 -0
- package/dist/browser/constants.d.ts +13 -0
- package/dist/browser/constants.d.ts.map +1 -0
- package/dist/browser/constants.js +13 -0
- package/dist/browser/constants.js.map +1 -0
- package/dist/browser/index.d.ts +211 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +1190 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/pantone-gold.d.ts +59 -0
- package/dist/browser/pantone-gold.d.ts.map +1 -0
- package/dist/browser/pantone-gold.js +237 -0
- package/dist/browser/pantone-gold.js.map +1 -0
- package/dist/components/AnnotationCanvas.d.ts +27 -0
- package/dist/components/AnnotationCanvas.d.ts.map +1 -0
- package/dist/components/AnnotationCanvas.js +401 -0
- package/dist/components/AnnotationCanvas.js.map +1 -0
- package/dist/components/AnnotationNotesPanel.d.ts +15 -0
- package/dist/components/AnnotationNotesPanel.d.ts.map +1 -0
- package/dist/components/AnnotationNotesPanel.js +235 -0
- package/dist/components/AnnotationNotesPanel.js.map +1 -0
- package/dist/components/AnnotationThread.d.ts +18 -0
- package/dist/components/AnnotationThread.d.ts.map +1 -0
- package/dist/components/AnnotationThread.js +163 -0
- package/dist/components/AnnotationThread.js.map +1 -0
- package/dist/components/AnnotationToolbar.d.ts +39 -0
- package/dist/components/AnnotationToolbar.d.ts.map +1 -0
- package/dist/components/AnnotationToolbar.js +258 -0
- package/dist/components/AnnotationToolbar.js.map +1 -0
- package/dist/components/BoxOverlay.d.ts +20 -0
- package/dist/components/BoxOverlay.d.ts.map +1 -0
- package/dist/components/BoxOverlay.js +107 -0
- package/dist/components/BoxOverlay.js.map +1 -0
- package/dist/components/ColorPickerTool.d.ts +11 -0
- package/dist/components/ColorPickerTool.d.ts.map +1 -0
- package/dist/components/ColorPickerTool.js +220 -0
- package/dist/components/ColorPickerTool.js.map +1 -0
- package/dist/components/DensitometerTool.d.ts +25 -0
- package/dist/components/DensitometerTool.d.ts.map +1 -0
- package/dist/components/DensitometerTool.js +246 -0
- package/dist/components/DensitometerTool.js.map +1 -0
- package/dist/components/DielineInfoPanel.d.ts +27 -0
- package/dist/components/DielineInfoPanel.d.ts.map +1 -0
- package/dist/components/DielineInfoPanel.js +23 -0
- package/dist/components/DielineInfoPanel.js.map +1 -0
- package/dist/components/DielineOverlay.d.ts +10 -0
- package/dist/components/DielineOverlay.d.ts.map +1 -0
- package/dist/components/DielineOverlay.js +57 -0
- package/dist/components/DielineOverlay.js.map +1 -0
- package/dist/components/FindingsSidebar.d.ts +50 -0
- package/dist/components/FindingsSidebar.d.ts.map +1 -0
- package/dist/components/FindingsSidebar.js +78 -0
- package/dist/components/FindingsSidebar.js.map +1 -0
- package/dist/components/LayerCanvas.d.ts +30 -0
- package/dist/components/LayerCanvas.d.ts.map +1 -0
- package/dist/components/LayerCanvas.js +84 -0
- package/dist/components/LayerCanvas.js.map +1 -0
- package/dist/components/LayerPanel.d.ts +9 -0
- package/dist/components/LayerPanel.d.ts.map +1 -0
- package/dist/components/LayerPanel.js +144 -0
- package/dist/components/LayerPanel.js.map +1 -0
- package/dist/components/LensPDF.d.ts +61 -0
- package/dist/components/LensPDF.d.ts.map +1 -0
- package/dist/components/LensPDF.js +49 -0
- package/dist/components/LensPDF.js.map +1 -0
- package/dist/components/LensPDFDemo.d.ts +160 -0
- package/dist/components/LensPDFDemo.d.ts.map +1 -0
- package/dist/components/LensPDFDemo.js +1060 -0
- package/dist/components/LensPDFDemo.js.map +1 -0
- package/dist/components/LensPDFDemo.styles.d.ts +38 -0
- package/dist/components/LensPDFDemo.styles.d.ts.map +1 -0
- package/dist/components/LensPDFDemo.styles.js +282 -0
- package/dist/components/LensPDFDemo.styles.js.map +1 -0
- package/dist/components/LensPDFViewer.d.ts +79 -0
- package/dist/components/LensPDFViewer.d.ts.map +1 -0
- package/dist/components/LensPDFViewer.js +254 -0
- package/dist/components/LensPDFViewer.js.map +1 -0
- package/dist/components/MeasureTool.d.ts +16 -0
- package/dist/components/MeasureTool.d.ts.map +1 -0
- package/dist/components/MeasureTool.js +137 -0
- package/dist/components/MeasureTool.js.map +1 -0
- package/dist/components/MobileBottomSheet.d.ts +12 -0
- package/dist/components/MobileBottomSheet.d.ts.map +1 -0
- package/dist/components/MobileBottomSheet.js +113 -0
- package/dist/components/MobileBottomSheet.js.map +1 -0
- package/dist/components/MobileDrawer.d.ts +31 -0
- package/dist/components/MobileDrawer.d.ts.map +1 -0
- package/dist/components/MobileDrawer.js +67 -0
- package/dist/components/MobileDrawer.js.map +1 -0
- package/dist/components/PageCanvas.d.ts +33 -0
- package/dist/components/PageCanvas.d.ts.map +1 -0
- package/dist/components/PageCanvas.js +385 -0
- package/dist/components/PageCanvas.js.map +1 -0
- package/dist/components/PageNavigator.d.ts +18 -0
- package/dist/components/PageNavigator.d.ts.map +1 -0
- package/dist/components/PageNavigator.js +44 -0
- package/dist/components/PageNavigator.js.map +1 -0
- package/dist/components/SeparationCanvas.d.ts +12 -0
- package/dist/components/SeparationCanvas.d.ts.map +1 -0
- package/dist/components/SeparationCanvas.js +174 -0
- package/dist/components/SeparationCanvas.js.map +1 -0
- package/dist/components/TACHeatmapOverlay.d.ts +17 -0
- package/dist/components/TACHeatmapOverlay.d.ts.map +1 -0
- package/dist/components/TACHeatmapOverlay.js +119 -0
- package/dist/components/TACHeatmapOverlay.js.map +1 -0
- package/dist/components/ZoomControls.d.ts +11 -0
- package/dist/components/ZoomControls.d.ts.map +1 -0
- package/dist/components/ZoomControls.js +26 -0
- package/dist/components/ZoomControls.js.map +1 -0
- package/dist/components/defaultShellPlugins.d.ts +3 -0
- package/dist/components/defaultShellPlugins.d.ts.map +1 -0
- package/dist/components/defaultShellPlugins.js +273 -0
- package/dist/components/defaultShellPlugins.js.map +1 -0
- package/dist/components/index.d.ts +32 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +32 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/presets.d.ts +8 -0
- package/dist/components/presets.d.ts.map +1 -0
- package/dist/components/presets.js +14 -0
- package/dist/components/presets.js.map +1 -0
- package/dist/components/shellPlugins.d.ts +105 -0
- package/dist/components/shellPlugins.d.ts.map +1 -0
- package/dist/components/shellPlugins.js +52 -0
- package/dist/components/shellPlugins.js.map +1 -0
- package/dist/components/useIsMobile.d.ts +16 -0
- package/dist/components/useIsMobile.d.ts.map +1 -0
- package/dist/components/useIsMobile.js +30 -0
- package/dist/components/useIsMobile.js.map +1 -0
- package/dist/fallback-pdfjs/index.d.ts +60 -0
- package/dist/fallback-pdfjs/index.d.ts.map +1 -0
- package/dist/fallback-pdfjs/index.js +163 -0
- package/dist/fallback-pdfjs/index.js.map +1 -0
- package/dist/host/LensPDFProvider.d.ts +36 -0
- package/dist/host/LensPDFProvider.d.ts.map +1 -0
- package/dist/host/LensPDFProvider.js +12 -0
- package/dist/host/LensPDFProvider.js.map +1 -0
- package/dist/host/index.d.ts +167 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +173 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/pdfFallback.d.ts +50 -0
- package/dist/host/pdfFallback.d.ts.map +1 -0
- package/dist/host/pdfFallback.js +171 -0
- package/dist/host/pdfFallback.js.map +1 -0
- package/dist/host/pdfValidation.d.ts +45 -0
- package/dist/host/pdfValidation.d.ts.map +1 -0
- package/dist/host/pdfValidation.js +78 -0
- package/dist/host/pdfValidation.js.map +1 -0
- package/dist/host/shareLink.d.ts +80 -0
- package/dist/host/shareLink.d.ts.map +1 -0
- package/dist/host/shareLink.js +114 -0
- package/dist/host/shareLink.js.map +1 -0
- package/dist/host/useLensPDF.d.ts +73 -0
- package/dist/host/useLensPDF.d.ts.map +1 -0
- package/dist/host/useLensPDF.js +213 -0
- package/dist/host/useLensPDF.js.map +1 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/context.d.ts +70 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +16 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/findings-location.d.ts +53 -0
- package/dist/plugin/findings-location.d.ts.map +1 -0
- package/dist/plugin/findings-location.js +72 -0
- package/dist/plugin/findings-location.js.map +1 -0
- package/dist/plugin/index.d.ts +19 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +16 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/registry.d.ts +61 -0
- package/dist/plugin/registry.d.ts.map +1 -0
- package/dist/plugin/registry.js +102 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/services.d.ts +380 -0
- package/dist/plugin/services.d.ts.map +1 -0
- package/dist/plugin/services.js +104 -0
- package/dist/plugin/services.js.map +1 -0
- package/dist/plugin/types.d.ts +198 -0
- package/dist/plugin/types.d.ts.map +1 -0
- package/dist/plugin/types.js +24 -0
- package/dist/plugin/types.js.map +1 -0
- package/dist/types/index.d.ts +191 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +95 -0
- package/dist/types/index.js.map +1 -0
- package/dist/units/index.d.ts +64 -0
- package/dist/units/index.d.ts.map +1 -0
- package/dist/units/index.js +98 -0
- package/dist/units/index.js.map +1 -0
- package/docs/architecture.md +90 -0
- package/docs/components.md +569 -0
- package/docs/contributing.md +78 -0
- package/docs/fallback.md +174 -0
- package/docs/lens-pdf-viewer.md +128 -0
- package/docs/licensing.md +78 -0
- package/docs/measurement-units.md +87 -0
- package/docs/plugins.md +256 -0
- package/docs/security.md +69 -0
- package/docs/server.md +212 -0
- package/docs/services.md +210 -0
- package/docs/share-links.md +111 -0
- package/docs/theming.md +164 -0
- package/docs/validation.md +83 -0
- package/package.json +139 -0
package/docs/services.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Wiring ViewerServices"
|
|
3
|
+
description: "Implement only the protocols your host supports — page images, layers, separations, TAC heatmaps, color sampling, densitometer, annotations, reports. Unwired services hide their consuming components automatically."
|
|
4
|
+
group: "Reference"
|
|
5
|
+
order: 3
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Wiring `ViewerServices`
|
|
9
|
+
|
|
10
|
+
Every field on `ViewerServices` is independent. Implement the ones your host
|
|
11
|
+
supports; the rest fall through to *unwired* no-op defaults exported from
|
|
12
|
+
`@printwithsynergy/lens-pdf/plugin`. Consuming components detect the unwired
|
|
13
|
+
state and self-hide rather than rendering empty placeholders — see
|
|
14
|
+
[docs/fallback.md](./fallback.md) for the full capability-detection model,
|
|
15
|
+
the in-browser pdf.js fallback, and debug logging.
|
|
16
|
+
|
|
17
|
+
### Quick start with `defaultUnwiredServices`
|
|
18
|
+
|
|
19
|
+
The easiest way to build a partial `ViewerServices` is to spread the
|
|
20
|
+
pre-built defaults and override only the services your host provides:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { defaultUnwiredServices } from "@printwithsynergy/lens-pdf/host";
|
|
24
|
+
|
|
25
|
+
export const services = {
|
|
26
|
+
...defaultUnwiredServices,
|
|
27
|
+
pageImages: {
|
|
28
|
+
getPageImageUrl: ({ pageNum, dpi }) =>
|
|
29
|
+
`/api/pdf/${pageNum}.png?dpi=${dpi}`,
|
|
30
|
+
},
|
|
31
|
+
tokens: { ...defaultUnwiredServices.tokens, accent: "#e50c6a" },
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Everything you don't override stays *unwired* and the consuming
|
|
36
|
+
components self-hide.
|
|
37
|
+
|
|
38
|
+
### Manual approach
|
|
39
|
+
|
|
40
|
+
The full manual approach gives you finer-grained control:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import type { ViewerServices } from "@printwithsynergy/lens-pdf/plugin";
|
|
44
|
+
import {
|
|
45
|
+
defaultThemeTokens,
|
|
46
|
+
noopI18n,
|
|
47
|
+
noopTelemetry,
|
|
48
|
+
} from "@printwithsynergy/lens-pdf/plugin";
|
|
49
|
+
|
|
50
|
+
export const services: ViewerServices = {
|
|
51
|
+
pageImages: {
|
|
52
|
+
getPageImageUrl: ({ pageNum, dpi }) =>
|
|
53
|
+
`/api/pdf/${pageNum}.png?dpi=${dpi}`,
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
layers: {
|
|
57
|
+
getLayerImageUrl: ({ pageNum, layerIndex, dpi }) =>
|
|
58
|
+
`/api/pdf/${pageNum}/layer/${layerIndex}.png?dpi=${dpi}`,
|
|
59
|
+
listLayers: async () => [
|
|
60
|
+
{ name: "Background", ocg_index: 0, default_on: true },
|
|
61
|
+
{ name: "CutContour", ocg_index: 1, default_on: true },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
separations: {
|
|
66
|
+
getChannelImageUrl: ({ pageNum, channelName, dpi }) =>
|
|
67
|
+
`/api/pdf/${pageNum}/channel/${encodeURIComponent(channelName)}.png?dpi=${dpi}`,
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
tacHeatmap: {
|
|
71
|
+
getHeatmapImageUrl: ({ pageNum, dpi, tacLimit }) =>
|
|
72
|
+
`/api/pdf/${pageNum}/tac.png?dpi=${dpi}&limit=${tacLimit}`,
|
|
73
|
+
listRuns: async () => [],
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
colorSample: {
|
|
77
|
+
sampleAt: async ({ pageNum, pdfX, pdfY }) => {
|
|
78
|
+
const r = await fetch(`/api/pdf/${pageNum}/color?x=${pdfX}&y=${pdfY}`);
|
|
79
|
+
return r.ok ? await r.json() : null;
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
densitometer: {
|
|
84
|
+
sampleAt: async (args) => {
|
|
85
|
+
const r = await fetch(`/api/pdf/${args.pageNum}/density`, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: JSON.stringify(args),
|
|
88
|
+
});
|
|
89
|
+
if (!r.ok) {
|
|
90
|
+
if (r.status === 422) throw new Error("No separations available for this page.");
|
|
91
|
+
throw new Error(`Sampling failed (${r.status})`);
|
|
92
|
+
}
|
|
93
|
+
return await r.json();
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
annotations: {
|
|
98
|
+
list: async () => [],
|
|
99
|
+
getForPage: async () => null,
|
|
100
|
+
saveForPage: async () => {},
|
|
101
|
+
remove: async () => {},
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
reports: {
|
|
105
|
+
getHtmlReportUrl: () => "/api/pdf/report.html",
|
|
106
|
+
getPdfDownloadUrl: () => "/api/pdf/report.pdf",
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
telemetry: noopTelemetry,
|
|
110
|
+
i18n: noopI18n,
|
|
111
|
+
tokens: defaultThemeTokens,
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## When you need each field
|
|
116
|
+
|
|
117
|
+
| Service | When you need it |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `pageImages.getPageImageUrl` | Always. Returns the URL of the rendered page tile at a given DPI. |
|
|
120
|
+
| `layers.*` | Mounting `LayerCanvas` or `LayerPanel`. Provides per-OCG isolated tiles + the OCG list. |
|
|
121
|
+
| `separations.getChannelImageUrl` | Mounting `SeparationCanvas`. Returns one tile per ink channel with a transparent background — the canvas composites locally. |
|
|
122
|
+
| `tacHeatmap.*` | Mounting `TACHeatmapOverlay`. Provides a heatmap image plus per-text-run TAC readings for hover tooltips. |
|
|
123
|
+
| `colorSample.sampleAt` | `ColorPickerTool`. Returns RGB + hex + TAC at a PDF point, or `null` on failure. |
|
|
124
|
+
| `densitometer.sampleAt` | `DensitometerTool`. Returns ink-channel percentages + TAC. Throw `Error("No separations available for this page.")` for RGB-only PDFs — the tool surfaces the message verbatim. |
|
|
125
|
+
| `annotations.*` | `AnnotationCanvas`, `AnnotationThread`. Per-page upsert + global list + delete. |
|
|
126
|
+
| `reports.*` | Report-export menu items in `MobileDrawer` or your own toolbar. |
|
|
127
|
+
| `telemetry`, `i18n`, `tokens` | Always present; defaults are safe. Override to plug into your analytics, translation table, or brand palette. |
|
|
128
|
+
|
|
129
|
+
## Notes on each service
|
|
130
|
+
|
|
131
|
+
### `pageImages`
|
|
132
|
+
|
|
133
|
+
URL builders are **synchronous**. If your host needs async signing,
|
|
134
|
+
pre-resolve into a redirect proxy or blob URL upstream. Returning a Promise
|
|
135
|
+
here would force every `<img src={...}>` consumer through `useEffect` +
|
|
136
|
+
state, which doesn't fit the rendering pattern.
|
|
137
|
+
|
|
138
|
+
The viewer caches results internally — your service should not implement
|
|
139
|
+
its own cache.
|
|
140
|
+
|
|
141
|
+
### `layers`
|
|
142
|
+
|
|
143
|
+
`getLayerImageUrl` returns one PNG per OCG with a transparent background
|
|
144
|
+
(typically rendered via Ghostscript's `pngalpha` device with every other
|
|
145
|
+
OCG hidden). The browser composites the active subset locally with
|
|
146
|
+
`source-over` blending, so toggling a layer is just a redraw — no API
|
|
147
|
+
round-trip after the first warm-up.
|
|
148
|
+
|
|
149
|
+
### `separations`
|
|
150
|
+
|
|
151
|
+
Same instant-toggle pattern, but per ink channel. Channel name is a
|
|
152
|
+
process ink (`"Cyan"`, `"Magenta"`, `"Yellow"`, `"Black"`) or a spot ink
|
|
153
|
+
(`"Pantone Reflex Blue C"`, etc.). Your service is responsible for
|
|
154
|
+
percent-encoding the channel name in whatever URL it returns.
|
|
155
|
+
|
|
156
|
+
> Real ink separations require a server-side renderer (Ghostscript,
|
|
157
|
+
> MuPDF, etc.) — the in-browser pdf.js fallback can't produce them.
|
|
158
|
+
> See [server.md](./server.md) for a deployable reference.
|
|
159
|
+
|
|
160
|
+
### `tacHeatmap`
|
|
161
|
+
|
|
162
|
+
`getHeatmapImageUrl` returns a per-pixel RGBA tint over the page.
|
|
163
|
+
`listRuns` returns per-text-run TAC readings used for the hover-tooltip
|
|
164
|
+
layer. Run coordinates use a **top-left origin** to match poppler's
|
|
165
|
+
`pdftotext -bbox` output (the rest of the API uses lower-left).
|
|
166
|
+
|
|
167
|
+
> Like `separations`, the heatmap is computed from per-ink rasters and
|
|
168
|
+
> is server-side only. See [server.md](./server.md).
|
|
169
|
+
|
|
170
|
+
### `colorSample`
|
|
171
|
+
|
|
172
|
+
The tool deliberately swallows errors — return `null` instead of throwing
|
|
173
|
+
so a flaky network doesn't pop a tooltip with a confusing fallback color.
|
|
174
|
+
|
|
175
|
+
### `densitometer`
|
|
176
|
+
|
|
177
|
+
Distinct error messages your `sampleAt` can throw to drive the tool's UI:
|
|
178
|
+
|
|
179
|
+
- `"No separations available for this page."` — engine 422 (RGB-only
|
|
180
|
+
document, no CMYK to split). Surfaces as the friendly amber banner.
|
|
181
|
+
- `"Sampling failed (NNN)"` — engine non-2xx other than 422.
|
|
182
|
+
- `"Network error"` — fetch rejected.
|
|
183
|
+
|
|
184
|
+
The tool reads `Error.message` verbatim — keep messages user-facing.
|
|
185
|
+
|
|
186
|
+
> Server-side only — see [server.md](./server.md).
|
|
187
|
+
|
|
188
|
+
### `annotations`
|
|
189
|
+
|
|
190
|
+
Four concrete methods that match the actual call sites:
|
|
191
|
+
|
|
192
|
+
- `list()` — sidebar thread (every page, every author).
|
|
193
|
+
- `getForPage(pageNum)` — canvas init for the active author.
|
|
194
|
+
- `saveForPage(pageNum, fabricJson)` — canvas autosave (best-effort; the
|
|
195
|
+
canvas swallows network errors so the user can keep drawing).
|
|
196
|
+
- `remove(id)` — sidebar thread.
|
|
197
|
+
|
|
198
|
+
`fabricJson` is an opaque `unknown` — only the host and `AnnotationCanvas`
|
|
199
|
+
interpret it (it's the serialised Fabric.js canvas snapshot).
|
|
200
|
+
|
|
201
|
+
### `reports`
|
|
202
|
+
|
|
203
|
+
Both URL builders are synchronous. Hosts without report exports leave the
|
|
204
|
+
no-op defaults — the consuming menu items (currently `MobileDrawer`'s
|
|
205
|
+
"Share & Export" section) drop the report links entirely rather than
|
|
206
|
+
rendering inert hrefs.
|
|
207
|
+
|
|
208
|
+
### `telemetry`, `i18n`, `tokens`
|
|
209
|
+
|
|
210
|
+
See [theming.md](./theming.md).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Shareable links"
|
|
3
|
+
description: "Generate and parse shareable viewer URLs that pre-load a specific PDF with custom settings — fullscreen, zoom, page, mode, tools, and theme."
|
|
4
|
+
group: "Reference"
|
|
5
|
+
order: 9
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Shareable links
|
|
9
|
+
|
|
10
|
+
`generateShareLink()` builds a URL that opens any LensPDF host page
|
|
11
|
+
with a specific PDF pre-loaded and settings applied.
|
|
12
|
+
`parseShareParams()` reads those query params back into props your
|
|
13
|
+
component can consume.
|
|
14
|
+
|
|
15
|
+
Both are exported from `@printwithsynergy/lens-pdf/host`.
|
|
16
|
+
|
|
17
|
+
## URL format
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
https://lenspdf.com/demo?url=<encoded>&fullscreen=true&zoom=150&page=1&mode=single&theme=dark
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Param | Type | Notes |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `url` | URL-encoded string | PDF URL to pre-load. |
|
|
26
|
+
| `fullscreen` | `"true"` \| `"1"` | Open in fullscreen (no site chrome). |
|
|
27
|
+
| `zoom` | integer | Initial zoom percentage. |
|
|
28
|
+
| `page` | integer | Initial page number (1-indexed). |
|
|
29
|
+
| `mode` | `"scroll"` \| `"single"` | Defaults to `"scroll"` when absent. |
|
|
30
|
+
| `tools` | comma-separated | Subset of tools to enable. |
|
|
31
|
+
| `theme` | `"light"` \| `"dark"` \| JSON | Preset name or inline `ThemeTokens`. |
|
|
32
|
+
|
|
33
|
+
## Generating a link
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { generateShareLink } from "@printwithsynergy/lens-pdf/host";
|
|
37
|
+
|
|
38
|
+
const link = generateShareLink({
|
|
39
|
+
baseUrl: "https://lenspdf.com/demo",
|
|
40
|
+
pdfUrl: "https://cdn.example.com/proof.pdf",
|
|
41
|
+
fullscreen: true,
|
|
42
|
+
zoom: 150,
|
|
43
|
+
page: 2,
|
|
44
|
+
});
|
|
45
|
+
// → "https://lenspdf.com/demo?url=https%3A%2F%2Fcdn.example.com%2Fproof.pdf&fullscreen=true&zoom=150&page=2"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `ShareLinkOptions`
|
|
49
|
+
|
|
50
|
+
| Field | Type | Default | Notes |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| `baseUrl` | `string` | required | Host demo/viewer page URL. |
|
|
53
|
+
| `pdfUrl` | `string` | required | PDF URL to pre-load. |
|
|
54
|
+
| `fullscreen` | `boolean` | `false` | Fixed-position full-viewport. |
|
|
55
|
+
| `zoom` | `number` | _(omitted)_ | Percentage. |
|
|
56
|
+
| `page` | `number` | _(omitted)_ | 1-indexed page. |
|
|
57
|
+
| `mode` | `"scroll" \| "single"` | `"scroll"` | Only serialised when not `"scroll"`. |
|
|
58
|
+
| `tools` | `string[]` | _(omitted)_ | Comma-joined in URL. |
|
|
59
|
+
| `theme` | `"light" \| "dark" \| Partial<ThemeTokens>` | _(omitted)_ | Preset name or JSON-encoded tokens. |
|
|
60
|
+
|
|
61
|
+
## Parsing on the consumer side
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { parseShareParams } from "@printwithsynergy/lens-pdf/host";
|
|
65
|
+
|
|
66
|
+
const params = parseShareParams(new URLSearchParams(window.location.search));
|
|
67
|
+
|
|
68
|
+
// params.pdfUrl → "https://cdn.example.com/proof.pdf"
|
|
69
|
+
// params.fullscreen → true
|
|
70
|
+
// params.zoom → 150
|
|
71
|
+
// params.page → 2
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `ParsedShareParams`
|
|
75
|
+
|
|
76
|
+
| Field | Type | Notes |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `pdfUrl` | `string \| undefined` | |
|
|
79
|
+
| `fullscreen` | `boolean` | Defaults to `false`. |
|
|
80
|
+
| `zoom` | `number \| undefined` | |
|
|
81
|
+
| `page` | `number \| undefined` | |
|
|
82
|
+
| `mode` | `"scroll" \| "single" \| undefined` | |
|
|
83
|
+
| `tools` | `string[] \| undefined` | |
|
|
84
|
+
| `theme` | `"light" \| "dark" \| Partial<ThemeTokens> \| undefined` | |
|
|
85
|
+
|
|
86
|
+
## End-to-end with `<LensPDFDemo>`
|
|
87
|
+
|
|
88
|
+
Wire `parseShareParams` into `<LensPDFDemo>` props:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { LensPDFDemo } from "@printwithsynergy/lens-pdf/components";
|
|
92
|
+
import { parseShareParams } from "@printwithsynergy/lens-pdf/host";
|
|
93
|
+
|
|
94
|
+
export function DemoPage() {
|
|
95
|
+
const params = parseShareParams(new URLSearchParams(window.location.search));
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<LensPDFDemo
|
|
99
|
+
brand="MyApp"
|
|
100
|
+
initialPdfUrl={params.pdfUrl}
|
|
101
|
+
fullscreen={params.fullscreen}
|
|
102
|
+
initialZoom={params.zoom ?? 80}
|
|
103
|
+
initialPage={params.page}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Users can now share links like
|
|
110
|
+
`https://myapp.com/demo?url=https://cdn.example.com/proof.pdf&fullscreen=true`
|
|
111
|
+
that open the viewer full-screen with the PDF pre-loaded.
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Theme, i18n, telemetry, read-only mode"
|
|
3
|
+
description: "Brand tokens, translation strings, analytics hooks, and the read-only flag for share-link viewers. All optional — the no-op defaults keep OSS hosts fast."
|
|
4
|
+
group: "Reference"
|
|
5
|
+
order: 8
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Theme, i18n, telemetry, read-only mode
|
|
9
|
+
|
|
10
|
+
Three of the `ViewerServices` fields are infrastructure rather than data:
|
|
11
|
+
`tokens`, `i18n`, `telemetry`. They always have safe defaults; override
|
|
12
|
+
when you need to plug into your brand palette, translation table, or
|
|
13
|
+
analytics. The `readOnly` flag lives on `ViewerHostContext` and toggles
|
|
14
|
+
write-only UI.
|
|
15
|
+
|
|
16
|
+
## Theme tokens
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
interface ThemeTokens {
|
|
20
|
+
readonly primary: string; // brand primary
|
|
21
|
+
readonly accent: string; // brand accent
|
|
22
|
+
readonly bg: string; // surface background
|
|
23
|
+
readonly fg: string; // foreground / body text
|
|
24
|
+
readonly border: string; // hairline border
|
|
25
|
+
// Optional brand-identity fields read by <LensPDFDemo> as a
|
|
26
|
+
// fallback when the equivalent props (`brand`, `brandLogoUrl`)
|
|
27
|
+
// aren't set. Lets a host bundle palette + logo + label into a
|
|
28
|
+
// single tokens object.
|
|
29
|
+
readonly logoUrl?: string; // brand logo image URL
|
|
30
|
+
readonly logoText?: string; // brand label (default: "LensPDF")
|
|
31
|
+
readonly logoMaxHeight?: number; // pixel cap on logo height (default: 24)
|
|
32
|
+
readonly logoAlt?: string; // alt text for the logo <img>
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`defaultThemeTokens` is a neutral light palette:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defaultThemeTokens } from "@printwithsynergy/lens-pdf/plugin";
|
|
40
|
+
|
|
41
|
+
// {
|
|
42
|
+
// primary: "#0f172a",
|
|
43
|
+
// accent: "#3b82f6",
|
|
44
|
+
// bg: "#ffffff",
|
|
45
|
+
// fg: "#0f172a",
|
|
46
|
+
// border: "#e2e8f0",
|
|
47
|
+
// }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`darkThemeTokens` is a dark palette preset for demo and dark-mode UIs:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { darkThemeTokens } from "@printwithsynergy/lens-pdf/plugin";
|
|
54
|
+
|
|
55
|
+
// {
|
|
56
|
+
// primary: "#0f172a",
|
|
57
|
+
// accent: "#3b82f6",
|
|
58
|
+
// bg: "#0e0a14",
|
|
59
|
+
// fg: "#f5f3f7",
|
|
60
|
+
// border: "#2b2138",
|
|
61
|
+
// }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`<LensPDFDemo>` uses `darkThemeTokens` by default; `<LensPDFViewer>`
|
|
65
|
+
uses `defaultThemeTokens`.
|
|
66
|
+
|
|
67
|
+
Pass your own through `services.tokens`:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
const services: ViewerServices = {
|
|
71
|
+
// …
|
|
72
|
+
tokens: {
|
|
73
|
+
primary: "#1a3a7a",
|
|
74
|
+
accent: "#2563eb",
|
|
75
|
+
bg: "#ffffff",
|
|
76
|
+
fg: "#0f172a",
|
|
77
|
+
border: "#e2e8f0",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Plugins read from `ctx.services.tokens` rather than hardcoding hex
|
|
83
|
+
strings, so swapping a brand palette is a single context-value change.
|
|
84
|
+
|
|
85
|
+
## i18n
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
interface I18nService {
|
|
89
|
+
t(key: string, params?: Record<string, string | number>): string;
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The `noopI18n` default returns the key unchanged with `{param}`
|
|
94
|
+
placeholders substituted. Drop in a real translator as needed:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import type { I18nService } from "@printwithsynergy/lens-pdf/plugin";
|
|
98
|
+
|
|
99
|
+
export const i18n: I18nService = {
|
|
100
|
+
t: (key, params) => translateWithICU(key, params),
|
|
101
|
+
};
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Suitable for English-only environments and tests, the no-op behaves
|
|
105
|
+
like:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
noopI18n.t("hello.name", { name: "Ada" }); // "hello.name"
|
|
109
|
+
// (the key is returned because no entry exists; placeholders still
|
|
110
|
+
// substitute when present in the key text itself)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Telemetry
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
interface TelemetryService {
|
|
117
|
+
track(event: string, properties?: Record<string, unknown>): void;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`noopTelemetry` drops every event on the floor. Wire your analytics by
|
|
122
|
+
overriding:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
import type { TelemetryService } from "@printwithsynergy/lens-pdf/plugin";
|
|
126
|
+
|
|
127
|
+
export const telemetry: TelemetryService = {
|
|
128
|
+
track: (event, props) => window.analytics?.track(event, props),
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
OSS hosts that don't want to ship analytics can leave the no-op default —
|
|
133
|
+
no events will leave the browser.
|
|
134
|
+
|
|
135
|
+
## Read-only mode
|
|
136
|
+
|
|
137
|
+
Set `ViewerHostContext.readOnly` to `true` to suppress write-only UI.
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
<ViewerHostContext.Provider
|
|
141
|
+
value={{
|
|
142
|
+
apiBase: "/api/share/abc123",
|
|
143
|
+
jobApiBase: "/api/share/abc123",
|
|
144
|
+
readOnly: true,
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
…
|
|
148
|
+
</ViewerHostContext.Provider>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
What flips:
|
|
152
|
+
|
|
153
|
+
- `AnnotationCanvas` skips its autosave path entirely (reads still work,
|
|
154
|
+
saves are a no-op).
|
|
155
|
+
- `MobileDrawer` hides annotation, share, and verdict controls based on
|
|
156
|
+
the same flag.
|
|
157
|
+
- Your own host UI should branch on `useViewerHost().readOnly` to hide
|
|
158
|
+
any control that mutates server state.
|
|
159
|
+
|
|
160
|
+
Public-token / share-link viewers typically run with `readOnly: true` and
|
|
161
|
+
a constrained `apiBase` (`/api/share/<token>` etc.), with annotations
|
|
162
|
+
read but not written. The annotation service can be wired to a no-op
|
|
163
|
+
`saveForPage` / `remove` even when `readOnly` is false, but flipping the
|
|
164
|
+
host flag is the standard pattern.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "PDF validation"
|
|
3
|
+
description: "Client-side PDF validation — magic bytes, MIME type, file size — before handing a file to the viewer or pdf.js fallback. Runs entirely in the browser."
|
|
4
|
+
group: "Reference"
|
|
5
|
+
order: 10
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# PDF validation
|
|
9
|
+
|
|
10
|
+
LensPDF ships client-side validation utilities that run entirely in the
|
|
11
|
+
browser. Use them before handing user-supplied files or URLs to the
|
|
12
|
+
viewer.
|
|
13
|
+
|
|
14
|
+
Both are exported from `@printwithsynergy/lens-pdf/host`.
|
|
15
|
+
|
|
16
|
+
## `validatePdfFile(file, maxBytes?)`
|
|
17
|
+
|
|
18
|
+
Async. Checks a `File` object:
|
|
19
|
+
|
|
20
|
+
1. **MIME type** — must be `application/pdf` (or empty — some browsers
|
|
21
|
+
don't populate MIME for drag-dropped files).
|
|
22
|
+
2. **Magic bytes** — first 5 bytes must be `%PDF-` (hex `25 50 44 46 2D`).
|
|
23
|
+
3. **Size** — must be ≤ `maxBytes` (default: 50 MB).
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { validatePdfFile } from "@printwithsynergy/lens-pdf/host";
|
|
27
|
+
|
|
28
|
+
const result = await validatePdfFile(file);
|
|
29
|
+
if (!result.valid) {
|
|
30
|
+
console.error(result.error);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pass a custom limit:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const result = await validatePdfFile(file, 100 * 1024 * 1024); // 100 MB
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## `validatePdfUrl(url)`
|
|
41
|
+
|
|
42
|
+
Synchronous. Validates URL format only — does not fetch or inspect
|
|
43
|
+
headers.
|
|
44
|
+
|
|
45
|
+
Checks:
|
|
46
|
+
1. String is non-empty after trimming.
|
|
47
|
+
2. Parses as a valid `URL`.
|
|
48
|
+
3. Protocol is `http:`, `https:`, or `blob:`.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import { validatePdfUrl } from "@printwithsynergy/lens-pdf/host";
|
|
52
|
+
|
|
53
|
+
const result = validatePdfUrl(draftUrl);
|
|
54
|
+
if (!result.valid) {
|
|
55
|
+
setError(result.error);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## `PdfValidationResult`
|
|
61
|
+
|
|
62
|
+
Both functions return the same shape:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
interface PdfValidationResult {
|
|
66
|
+
valid: boolean;
|
|
67
|
+
error?: string; // Human-readable message when valid is false
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Built-in usage
|
|
72
|
+
|
|
73
|
+
`<LensPDFDemo>` calls both validators automatically — file uploads go
|
|
74
|
+
through `validatePdfFile` and URL submissions go through
|
|
75
|
+
`validatePdfUrl`. The `maxFileSize` prop on `LensPDFDemo` is forwarded
|
|
76
|
+
to `validatePdfFile`.
|
|
77
|
+
|
|
78
|
+
## Security note
|
|
79
|
+
|
|
80
|
+
These checks are **client-side only** and meant to catch user mistakes
|
|
81
|
+
early (wrong file type, oversized files, malformed URLs). They are not
|
|
82
|
+
a security boundary — a determined user can bypass them. Always validate
|
|
83
|
+
and sanitise uploads on the server side when applicable.
|