@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.
Files changed (213) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +344 -0
  3. package/dist/browser/codexOverlay.d.ts +109 -0
  4. package/dist/browser/codexOverlay.d.ts.map +1 -0
  5. package/dist/browser/codexOverlay.js +256 -0
  6. package/dist/browser/codexOverlay.js.map +1 -0
  7. package/dist/browser/constants.d.ts +13 -0
  8. package/dist/browser/constants.d.ts.map +1 -0
  9. package/dist/browser/constants.js +13 -0
  10. package/dist/browser/constants.js.map +1 -0
  11. package/dist/browser/index.d.ts +211 -0
  12. package/dist/browser/index.d.ts.map +1 -0
  13. package/dist/browser/index.js +1190 -0
  14. package/dist/browser/index.js.map +1 -0
  15. package/dist/browser/pantone-gold.d.ts +59 -0
  16. package/dist/browser/pantone-gold.d.ts.map +1 -0
  17. package/dist/browser/pantone-gold.js +237 -0
  18. package/dist/browser/pantone-gold.js.map +1 -0
  19. package/dist/components/AnnotationCanvas.d.ts +27 -0
  20. package/dist/components/AnnotationCanvas.d.ts.map +1 -0
  21. package/dist/components/AnnotationCanvas.js +401 -0
  22. package/dist/components/AnnotationCanvas.js.map +1 -0
  23. package/dist/components/AnnotationNotesPanel.d.ts +15 -0
  24. package/dist/components/AnnotationNotesPanel.d.ts.map +1 -0
  25. package/dist/components/AnnotationNotesPanel.js +235 -0
  26. package/dist/components/AnnotationNotesPanel.js.map +1 -0
  27. package/dist/components/AnnotationThread.d.ts +18 -0
  28. package/dist/components/AnnotationThread.d.ts.map +1 -0
  29. package/dist/components/AnnotationThread.js +163 -0
  30. package/dist/components/AnnotationThread.js.map +1 -0
  31. package/dist/components/AnnotationToolbar.d.ts +39 -0
  32. package/dist/components/AnnotationToolbar.d.ts.map +1 -0
  33. package/dist/components/AnnotationToolbar.js +258 -0
  34. package/dist/components/AnnotationToolbar.js.map +1 -0
  35. package/dist/components/BoxOverlay.d.ts +20 -0
  36. package/dist/components/BoxOverlay.d.ts.map +1 -0
  37. package/dist/components/BoxOverlay.js +107 -0
  38. package/dist/components/BoxOverlay.js.map +1 -0
  39. package/dist/components/ColorPickerTool.d.ts +11 -0
  40. package/dist/components/ColorPickerTool.d.ts.map +1 -0
  41. package/dist/components/ColorPickerTool.js +220 -0
  42. package/dist/components/ColorPickerTool.js.map +1 -0
  43. package/dist/components/DensitometerTool.d.ts +25 -0
  44. package/dist/components/DensitometerTool.d.ts.map +1 -0
  45. package/dist/components/DensitometerTool.js +246 -0
  46. package/dist/components/DensitometerTool.js.map +1 -0
  47. package/dist/components/DielineInfoPanel.d.ts +27 -0
  48. package/dist/components/DielineInfoPanel.d.ts.map +1 -0
  49. package/dist/components/DielineInfoPanel.js +23 -0
  50. package/dist/components/DielineInfoPanel.js.map +1 -0
  51. package/dist/components/DielineOverlay.d.ts +10 -0
  52. package/dist/components/DielineOverlay.d.ts.map +1 -0
  53. package/dist/components/DielineOverlay.js +57 -0
  54. package/dist/components/DielineOverlay.js.map +1 -0
  55. package/dist/components/FindingsSidebar.d.ts +50 -0
  56. package/dist/components/FindingsSidebar.d.ts.map +1 -0
  57. package/dist/components/FindingsSidebar.js +78 -0
  58. package/dist/components/FindingsSidebar.js.map +1 -0
  59. package/dist/components/LayerCanvas.d.ts +30 -0
  60. package/dist/components/LayerCanvas.d.ts.map +1 -0
  61. package/dist/components/LayerCanvas.js +84 -0
  62. package/dist/components/LayerCanvas.js.map +1 -0
  63. package/dist/components/LayerPanel.d.ts +9 -0
  64. package/dist/components/LayerPanel.d.ts.map +1 -0
  65. package/dist/components/LayerPanel.js +144 -0
  66. package/dist/components/LayerPanel.js.map +1 -0
  67. package/dist/components/LensPDF.d.ts +61 -0
  68. package/dist/components/LensPDF.d.ts.map +1 -0
  69. package/dist/components/LensPDF.js +49 -0
  70. package/dist/components/LensPDF.js.map +1 -0
  71. package/dist/components/LensPDFDemo.d.ts +160 -0
  72. package/dist/components/LensPDFDemo.d.ts.map +1 -0
  73. package/dist/components/LensPDFDemo.js +1060 -0
  74. package/dist/components/LensPDFDemo.js.map +1 -0
  75. package/dist/components/LensPDFDemo.styles.d.ts +38 -0
  76. package/dist/components/LensPDFDemo.styles.d.ts.map +1 -0
  77. package/dist/components/LensPDFDemo.styles.js +282 -0
  78. package/dist/components/LensPDFDemo.styles.js.map +1 -0
  79. package/dist/components/LensPDFViewer.d.ts +79 -0
  80. package/dist/components/LensPDFViewer.d.ts.map +1 -0
  81. package/dist/components/LensPDFViewer.js +254 -0
  82. package/dist/components/LensPDFViewer.js.map +1 -0
  83. package/dist/components/MeasureTool.d.ts +16 -0
  84. package/dist/components/MeasureTool.d.ts.map +1 -0
  85. package/dist/components/MeasureTool.js +137 -0
  86. package/dist/components/MeasureTool.js.map +1 -0
  87. package/dist/components/MobileBottomSheet.d.ts +12 -0
  88. package/dist/components/MobileBottomSheet.d.ts.map +1 -0
  89. package/dist/components/MobileBottomSheet.js +113 -0
  90. package/dist/components/MobileBottomSheet.js.map +1 -0
  91. package/dist/components/MobileDrawer.d.ts +31 -0
  92. package/dist/components/MobileDrawer.d.ts.map +1 -0
  93. package/dist/components/MobileDrawer.js +67 -0
  94. package/dist/components/MobileDrawer.js.map +1 -0
  95. package/dist/components/PageCanvas.d.ts +33 -0
  96. package/dist/components/PageCanvas.d.ts.map +1 -0
  97. package/dist/components/PageCanvas.js +385 -0
  98. package/dist/components/PageCanvas.js.map +1 -0
  99. package/dist/components/PageNavigator.d.ts +18 -0
  100. package/dist/components/PageNavigator.d.ts.map +1 -0
  101. package/dist/components/PageNavigator.js +44 -0
  102. package/dist/components/PageNavigator.js.map +1 -0
  103. package/dist/components/SeparationCanvas.d.ts +12 -0
  104. package/dist/components/SeparationCanvas.d.ts.map +1 -0
  105. package/dist/components/SeparationCanvas.js +174 -0
  106. package/dist/components/SeparationCanvas.js.map +1 -0
  107. package/dist/components/TACHeatmapOverlay.d.ts +17 -0
  108. package/dist/components/TACHeatmapOverlay.d.ts.map +1 -0
  109. package/dist/components/TACHeatmapOverlay.js +119 -0
  110. package/dist/components/TACHeatmapOverlay.js.map +1 -0
  111. package/dist/components/ZoomControls.d.ts +11 -0
  112. package/dist/components/ZoomControls.d.ts.map +1 -0
  113. package/dist/components/ZoomControls.js +26 -0
  114. package/dist/components/ZoomControls.js.map +1 -0
  115. package/dist/components/defaultShellPlugins.d.ts +3 -0
  116. package/dist/components/defaultShellPlugins.d.ts.map +1 -0
  117. package/dist/components/defaultShellPlugins.js +273 -0
  118. package/dist/components/defaultShellPlugins.js.map +1 -0
  119. package/dist/components/index.d.ts +32 -0
  120. package/dist/components/index.d.ts.map +1 -0
  121. package/dist/components/index.js +32 -0
  122. package/dist/components/index.js.map +1 -0
  123. package/dist/components/presets.d.ts +8 -0
  124. package/dist/components/presets.d.ts.map +1 -0
  125. package/dist/components/presets.js +14 -0
  126. package/dist/components/presets.js.map +1 -0
  127. package/dist/components/shellPlugins.d.ts +105 -0
  128. package/dist/components/shellPlugins.d.ts.map +1 -0
  129. package/dist/components/shellPlugins.js +52 -0
  130. package/dist/components/shellPlugins.js.map +1 -0
  131. package/dist/components/useIsMobile.d.ts +16 -0
  132. package/dist/components/useIsMobile.d.ts.map +1 -0
  133. package/dist/components/useIsMobile.js +30 -0
  134. package/dist/components/useIsMobile.js.map +1 -0
  135. package/dist/fallback-pdfjs/index.d.ts +60 -0
  136. package/dist/fallback-pdfjs/index.d.ts.map +1 -0
  137. package/dist/fallback-pdfjs/index.js +163 -0
  138. package/dist/fallback-pdfjs/index.js.map +1 -0
  139. package/dist/host/LensPDFProvider.d.ts +36 -0
  140. package/dist/host/LensPDFProvider.d.ts.map +1 -0
  141. package/dist/host/LensPDFProvider.js +12 -0
  142. package/dist/host/LensPDFProvider.js.map +1 -0
  143. package/dist/host/index.d.ts +167 -0
  144. package/dist/host/index.d.ts.map +1 -0
  145. package/dist/host/index.js +173 -0
  146. package/dist/host/index.js.map +1 -0
  147. package/dist/host/pdfFallback.d.ts +50 -0
  148. package/dist/host/pdfFallback.d.ts.map +1 -0
  149. package/dist/host/pdfFallback.js +171 -0
  150. package/dist/host/pdfFallback.js.map +1 -0
  151. package/dist/host/pdfValidation.d.ts +45 -0
  152. package/dist/host/pdfValidation.d.ts.map +1 -0
  153. package/dist/host/pdfValidation.js +78 -0
  154. package/dist/host/pdfValidation.js.map +1 -0
  155. package/dist/host/shareLink.d.ts +80 -0
  156. package/dist/host/shareLink.d.ts.map +1 -0
  157. package/dist/host/shareLink.js +114 -0
  158. package/dist/host/shareLink.js.map +1 -0
  159. package/dist/host/useLensPDF.d.ts +73 -0
  160. package/dist/host/useLensPDF.d.ts.map +1 -0
  161. package/dist/host/useLensPDF.js +213 -0
  162. package/dist/host/useLensPDF.js.map +1 -0
  163. package/dist/index.d.ts +68 -0
  164. package/dist/index.d.ts.map +1 -0
  165. package/dist/index.js +62 -0
  166. package/dist/index.js.map +1 -0
  167. package/dist/plugin/context.d.ts +70 -0
  168. package/dist/plugin/context.d.ts.map +1 -0
  169. package/dist/plugin/context.js +16 -0
  170. package/dist/plugin/context.js.map +1 -0
  171. package/dist/plugin/findings-location.d.ts +53 -0
  172. package/dist/plugin/findings-location.d.ts.map +1 -0
  173. package/dist/plugin/findings-location.js +72 -0
  174. package/dist/plugin/findings-location.js.map +1 -0
  175. package/dist/plugin/index.d.ts +19 -0
  176. package/dist/plugin/index.d.ts.map +1 -0
  177. package/dist/plugin/index.js +16 -0
  178. package/dist/plugin/index.js.map +1 -0
  179. package/dist/plugin/registry.d.ts +61 -0
  180. package/dist/plugin/registry.d.ts.map +1 -0
  181. package/dist/plugin/registry.js +102 -0
  182. package/dist/plugin/registry.js.map +1 -0
  183. package/dist/plugin/services.d.ts +380 -0
  184. package/dist/plugin/services.d.ts.map +1 -0
  185. package/dist/plugin/services.js +104 -0
  186. package/dist/plugin/services.js.map +1 -0
  187. package/dist/plugin/types.d.ts +198 -0
  188. package/dist/plugin/types.d.ts.map +1 -0
  189. package/dist/plugin/types.js +24 -0
  190. package/dist/plugin/types.js.map +1 -0
  191. package/dist/types/index.d.ts +191 -0
  192. package/dist/types/index.d.ts.map +1 -0
  193. package/dist/types/index.js +95 -0
  194. package/dist/types/index.js.map +1 -0
  195. package/dist/units/index.d.ts +64 -0
  196. package/dist/units/index.d.ts.map +1 -0
  197. package/dist/units/index.js +98 -0
  198. package/dist/units/index.js.map +1 -0
  199. package/docs/architecture.md +90 -0
  200. package/docs/components.md +569 -0
  201. package/docs/contributing.md +78 -0
  202. package/docs/fallback.md +174 -0
  203. package/docs/lens-pdf-viewer.md +128 -0
  204. package/docs/licensing.md +78 -0
  205. package/docs/measurement-units.md +87 -0
  206. package/docs/plugins.md +256 -0
  207. package/docs/security.md +69 -0
  208. package/docs/server.md +212 -0
  209. package/docs/services.md +210 -0
  210. package/docs/share-links.md +111 -0
  211. package/docs/theming.md +164 -0
  212. package/docs/validation.md +83 -0
  213. package/package.json +139 -0
@@ -0,0 +1,569 @@
1
+ ---
2
+ title: "Component reference"
3
+ description: "Per-component props, slots, and usage for every React component the package exports — page canvas, navigator, separations, layers, annotations, mobile chrome."
4
+ group: "Reference"
5
+ order: 5
6
+ ---
7
+
8
+ # Component reference
9
+
10
+ Every component is imported from `@printwithsynergy/lens-pdf/components`
11
+ and reads its data through the contexts described in
12
+ [architecture.md](./architecture.md). Required `ViewerServices` fields are
13
+ called out per component.
14
+
15
+ - [Drop-in viewer](#drop-in-viewer)
16
+ - [Drop-in demo](#drop-in-demo)
17
+ - [Page rendering](#page-rendering)
18
+ - [Print-production overlays](#print-production-overlays)
19
+ - [Sampling tools](#sampling-tools)
20
+ - [Layer & separation modes](#layer--separation-modes)
21
+ - [Annotations](#annotations)
22
+ - [Mobile chrome](#mobile-chrome)
23
+
24
+ ## Drop-in viewer
25
+
26
+ ### `LensPDF`
27
+
28
+ The recommended single-component entry point for production hosts.
29
+ One mount, every viewer-only feature wired to pdf.js out of the box —
30
+ page tile (multi-DPI cache), color picker, densitometer, measure
31
+ tool, TAC heatmap, per-ink separations (CMYK + spots), OCG layers,
32
+ and the annotation toolbar / canvas / thread. No upload chrome — the
33
+ host supplies the URL.
34
+
35
+ ```tsx
36
+ import { LensPDF } from "@printwithsynergy/lens-pdf";
37
+ import pdfWorkerSrc from "pdfjs-dist/build/pdf.worker.mjs?url";
38
+
39
+ export function ProofPage() {
40
+ return <LensPDF pdfUrl="/proofs/abc.pdf" workerSrc={pdfWorkerSrc} />;
41
+ }
42
+ ```
43
+
44
+ #### Props
45
+
46
+ | Prop | Type | Default | Notes |
47
+ | --- | --- | --- | --- |
48
+ | `pdfUrl` | `string` | _(required)_ | PDF the viewer will load. Changing it swaps the document and resets to `initialPage`. |
49
+ | `workerSrc` | `string` | `defaultBrowserWorkerSrc` | Override the pdf.js worker URL. |
50
+ | `services` | `ViewerServices` | _browser services_ | Pass wired services to swap any feature from the in-browser approximation to a backend. |
51
+ | `tools` | `ReadonlyArray<LensPDFDemoTool>` | all | Subset of tools to show in the sidebar. |
52
+ | `initialZoom` | `number` | `80` | Starting zoom percentage. |
53
+ | `initialPage` | `number` | `1` | Starting page (1-indexed). |
54
+ | `tacLimit` | `number` | `300` | TAC limit (in percent) for the heatmap + densitometer. |
55
+ | `tokens` | `Partial<ThemeTokens>` | `darkThemeTokens` | Theme override merged onto the dark palette. Add `logoUrl` / `logoText` / `logoMaxHeight` / `logoAlt` to bundle brand identity into the tokens object. |
56
+ | `brand` / `brandLogoUrl` | `string` | _(none)_ | Optional brand label / logo. Falls back to `tokens.logoText` / `tokens.logoUrl` when the props are unset. |
57
+ | `items` | `OverlayItem[]` | `[]` | Preflight findings (error / warning / advisory bboxes). |
58
+ | `selectedItem` | `OverlayItem \| null` | _(internal)_ | Controlled selection. |
59
+ | `onItemSelect` | `(item) => void` | _(internal)_ | Selection callback. |
60
+ | `forceInspectionPanel` | `boolean` | `false` | Force the Inspection / Findings side panel visible even when `items` is empty (renders a "no findings yet" empty state). Useful for demos that always advertise the panel slot, or for hosts with an in-flight preflight call. When false (default), the panel auto-shows when `items.length > 0` and hides otherwise. |
61
+ | `spotPalette` | `Record<string, string>` | `undefined` | Host-provided spot-colour palette (keyed by spot name). Takes priority over the built-in Pantone Gold library and the PDF's `altRgb` fallback in the separations-panel swatch render. Typical source: codex's `summary.spot_colors.colors[].swatch_hex` or another preflight's swatch hex. |
62
+ | `dieline` | `DielineResult \| null` | _(none)_ | Dieline geometry overlay. |
63
+ | `showBoxOverlays` | `boolean` | `false` | Render trim / bleed / crop popovers. |
64
+ | `cropToTrim` | `boolean` | `false` | Clip the canvas to the page's TrimBox (falls back to BleedBox, then CropBox). |
65
+ | `fullscreen` | `boolean` | `false` | Fixed-position full-viewport mode. |
66
+ | `footer` | `ReactNode` | _(none)_ | Extra content in the footer bar. |
67
+ | `className` | `string` | _(none)_ | Class on the outermost div. |
68
+ | `preset` | `"demo" \| "minimal"` | `"minimal"` | First-party plugin preset baseline. |
69
+ | `plugins` | `ReadonlyArray<LensPDFShellPlugin>` | `[]` | Extra shell plugins; use `replaces` to override built-ins. |
70
+ | `codex` | `MinimalCodexClient` | _(none)_ | Optional codex client; when set, the viewer silently upgrades separations / TAC / layers to Ghostscript-rendered plates as `extractStream` events arrive. |
71
+ | `onPageChange` / `onZoomChange` / `onError` | callbacks | _(none)_ | Lifecycle hooks. |
72
+
73
+ CMYK / TAC are RGB-derived approximations when no backend is wired.
74
+ For ICC-correct readings, deploy the [optional reference server](./server.md)
75
+ and pass its `services` overrides.
76
+
77
+ The `codex` prop accepts any object matching the structural
78
+ `MinimalCodexClient` interface — in practice that means an instance of
79
+ `HttpClient` from `@printwithsynergy/codex-client@^1.8.1`, declared as
80
+ an optional peer dep. Hosts that don't use the codex overlay don't need
81
+ to install it.
82
+
83
+ ## Drop-in demo
84
+
85
+ ### `LensPDFDemo`
86
+
87
+ Marketing / showcase variant of `<LensPDF>` — adds an upload bar,
88
+ URL paste, drag-and-drop, client-side PDF validation, and an
89
+ empty-state UI. Useful for `lenspdf.com`-style demo pages and
90
+ internal sandboxes where users bring their own files. **Most
91
+ consumers should reach for [`<LensPDF>`](#lenspdf) instead.**
92
+
93
+ ```tsx
94
+ import { LensPDFDemo } from "@printwithsynergy/lens-pdf";
95
+
96
+ export function DemoPage() {
97
+ return <LensPDFDemo brand="MyApp" brandLogoUrl="/logo.svg" />;
98
+ }
99
+ ```
100
+
101
+ #### Props
102
+
103
+ | Prop | Type | Default | Notes |
104
+ | --- | --- | --- | --- |
105
+ | `brand` | `string` | `tokens.logoText` ?? `"LensPDF"` | Label in the top bar. |
106
+ | `brandLogoUrl` | `string` | `tokens.logoUrl` | Logo image URL. |
107
+ | `tokens` | `Partial<ThemeTokens>` | `darkThemeTokens` | Merged onto the dark palette. Add `logoUrl` / `logoText` / `logoMaxHeight` / `logoAlt` to bundle brand identity into the tokens object. |
108
+ | `maxFileSize` | `number` | `50 * 1024 * 1024` (50 MB) | Max upload size in bytes. |
109
+ | `services` | `ViewerServices` | _browser services_ | Optional overrides for hosts with a backend. Unwired fields auto-fall through to the in-browser pdf.js services. |
110
+ | `workerSrc` | `string` | `defaultBrowserWorkerSrc` | Override the pdf.js worker URL. |
111
+ | `initialZoom` | `number` | `80` | Starting zoom percentage. |
112
+ | `tacLimit` | `number` | `300` | TAC limit (in percent) for the heatmap + densitometer. |
113
+ | `fullscreen` | `boolean` | `false` | Fixed-position full-viewport mode. |
114
+ | `initialPdfUrl` | `string` | _(none)_ | Pre-loaded PDF URL (e.g. from [share-link params](./share-links.md)). |
115
+ | `initialPage` | `number` | `1` | Starting page (1-indexed). |
116
+ | `footer` | `ReactNode` | _(none)_ | Extra content in the footer bar. |
117
+ | `className` | `string` | _(none)_ | Class on the outermost div. |
118
+ | `tools` | `ReadonlyArray<LensPDFDemoTool>` | all | Feature ids to keep enabled (`color-picker`, `densitometer`, `measure`, `annotate`, `tac-heatmap`, `separations`, `layers`). |
119
+ | `items` / `selectedItem` / `onItemSelect` | preflight props | _(none)_ | Same as on `<LensPDF>` — preflight findings + controlled selection. |
120
+ | `dieline` / `showBoxOverlays` / `cropToTrim` | print-production props | _(off / none)_ | Same as on `<LensPDF>`. |
121
+ | `onPageChange` / `onZoomChange` / `onError` | callbacks | _(none)_ | Lifecycle hooks. |
122
+ | `preset` | `"demo" \| "minimal"` | `"demo"` | First-party plugin preset baseline. `LensPDF` uses `"minimal"`. |
123
+ | `plugins` | `ReadonlyArray<LensPDFShellPlugin>` | `[]` | Extra shell plugins; use `replaces` to override built-ins. |
124
+ | `codex` | `MinimalCodexClient` | _(none)_ | Optional codex client; when set, the viewer silently upgrades separations / TAC / layers to Ghostscript-rendered plates as `extractStream` events arrive. |
125
+
126
+ #### Built-in features
127
+
128
+ - **File upload** — opens a native file picker (`application/pdf`).
129
+ - **URL paste** — form input validates via `validatePdfUrl()`.
130
+ - **Drag-and-drop** — drop anywhere on the component.
131
+ - **Validation** — checks PDF magic bytes (`%PDF-`), MIME type, and
132
+ size. See [validation.md](./validation.md).
133
+ - **Sidebar** — zoom slider, tool toggles, layer panel.
134
+ - **Fullscreen** — `fullscreen` prop renders with `position: fixed; inset: 0`.
135
+ Combine with [shareable links](./share-links.md) for fullscreen share URLs.
136
+ - **Blob lifecycle** — created blob URLs are revoked on PDF change and
137
+ on unmount.
138
+ - **Plugin shell** — left panels + annotation toolbar are mounted from
139
+ slot plugins (`panel.left`, `overlay.toolbar`) via built-in presets.
140
+ - **Inspection / Findings panel** — when the host passes `items` (any
141
+ `OverlayItem[]`), the side drawer leads with an `Inspection (N)`
142
+ section: tier filter chips (errors / warnings / advisories / info)
143
+ + a clickable list that drives `onItemSelect` for canvas highlight
144
+ + page jump. Renders nothing when `items` is empty, so OSS hosts
145
+ without preflight don't see an empty section. Pass
146
+ `forceInspectionPanel` to keep the slot mounted even with no items
147
+ (useful for in-flight preflight calls or demos that advertise the
148
+ feature from the first frame).
149
+ - **Spot-colour palette resolution** — the separations panel resolves
150
+ each spot swatch in this order: host-provided `spotPalette[name]`
151
+ → built-in Pantone Gold library (~85 most-common Coated codes,
152
+ tolerates case + `C` / `U` suffixes + `PMS` prefix) → the PDF
153
+ tint-transform `altRgb` parsed at extraction → neutral grey
154
+ fallback. Hosts with codex output typically pass
155
+ `summary.spot_colors.colors[].swatch_hex` through to `spotPalette`
156
+ for the truest swatch.
157
+
158
+ #### Custom sidebar/menu composition
159
+
160
+ `LensPDFDemo` and `LensPDF` now expose a plugin-first shell for the
161
+ viewer chrome. You can replace built-ins without forking:
162
+
163
+ ```tsx
164
+ import {
165
+ LensPDF,
166
+ type LensPDFShellPlugin,
167
+ } from "@printwithsynergy/lens-pdf/components";
168
+
169
+ const customNotesPanel: LensPDFShellPlugin = {
170
+ id: "acme.panel.notes",
171
+ slot: "panel.left",
172
+ order: 40,
173
+ replaces: "lens.annotations-panel",
174
+ render(ctx) {
175
+ return (
176
+ <section>
177
+ <h2>My Notes</h2>
178
+ <button onClick={() => ctx.setCurrentPage(1)}>Jump to page 1</button>
179
+ </section>
180
+ );
181
+ },
182
+ };
183
+
184
+ export function ProofPage() {
185
+ return (
186
+ <LensPDF
187
+ pdfUrl="/proofs/abc.pdf"
188
+ plugins={[customNotesPanel]}
189
+ />
190
+ );
191
+ }
192
+ ```
193
+
194
+ ## Page rendering
195
+
196
+ ### `PageCanvas`
197
+
198
+ The main page tile. Renders the page image from `services.pageImages`, draws
199
+ bounding boxes for every `OverlayItem`, and fires `onItemClick` when one is
200
+ clicked. Optional `cropToTrim` clips the canvas to the page's trim box
201
+ (falls back to bleed, then crop).
202
+
203
+ ```tsx
204
+ <PageCanvas
205
+ jobId="demo"
206
+ page={page} // PageInfo
207
+ zoom={1} // multiplier; 1.0 = 100%
208
+ items={overlayItems} // readonly OverlayItem[]
209
+ selectedItem={selected} // OverlayItem | null
210
+ onItemClick={setSelected}
211
+ onZoomChange={(z) => setZoom(z * 100)}
212
+ onPageChange={(delta) => setCurrentPage((p) => p + delta)}
213
+ tileDpi={150}
214
+ tileCdnBase={null}
215
+ cropToTrim={false}
216
+ />
217
+ ```
218
+
219
+ Service deps: `pageImages.getPageImageUrl`.
220
+
221
+ ### `PageNavigator`
222
+
223
+ Vertical or horizontal thumbnail strip with per-page overlay-item badges.
224
+ `items` accepts the same `OverlayItem[]` you pass to `PageCanvas`; the
225
+ navigator counts `tier === "error"` and `tier === "warning"` per page and
226
+ draws the appropriate badge.
227
+
228
+ ```tsx
229
+ <PageNavigator
230
+ pages={pages} // PageInfo[]
231
+ currentPage={currentPage}
232
+ items={overlayItems}
233
+ onPageChange={setCurrentPage}
234
+ horizontal={false} // true → strip, false → vertical sidebar
235
+ />
236
+ ```
237
+
238
+ Service deps: `pageImages.getPageImageUrl` (rendered at `THUMBNAIL_DPI = 72`).
239
+
240
+ ### `ZoomControls`
241
+
242
+ `+` / `−` buttons plus a percentage select. `zoom` is a percentage; the
243
+ steps are `[25, 50, 75, 100, 125, 150, 200, 300, 400]`.
244
+
245
+ ```tsx
246
+ <ZoomControls
247
+ zoom={zoom} // number, percent
248
+ onZoomChange={setZoom}
249
+ compact={false} // smaller buttons, no border
250
+ dark={false} // light text on dark bg
251
+ />
252
+ ```
253
+
254
+ ## Print-production overlays
255
+
256
+ ### `BoxOverlay`
257
+
258
+ Trim, Bleed, and Crop box outlines with a clickable info icon per box that
259
+ reveals the dimensions in mm + inches. Pass an optional `dieline` payload
260
+ (`DielineResult`) to also drop a per-region info chip at the centroid of
261
+ each artwork cut area.
262
+
263
+ ```tsx
264
+ <BoxOverlay
265
+ page={page}
266
+ canvasWidth={renderedWidth}
267
+ canvasHeight={renderedHeight}
268
+ dieline={dielineResult} // optional DielineResult | null
269
+ />
270
+ ```
271
+
272
+ ### `DielineOverlay`
273
+
274
+ Standalone dieline-region chips. Renders independently of `BoxOverlay` so
275
+ users can see dieline sizes without enabling the trim/bleed boxes UI.
276
+
277
+ ```tsx
278
+ <DielineOverlay
279
+ page={page}
280
+ canvasWidth={renderedWidth}
281
+ canvasHeight={renderedHeight}
282
+ dieline={dielineResult}
283
+ />
284
+ ```
285
+
286
+ ## Sampling tools
287
+
288
+ ### `ColorPickerTool`
289
+
290
+ Click anywhere on the page to read the rendered RGB + hex + TAC at that
291
+ PDF point. Calls `services.colorSample.sampleAt`. Returns `null` on
292
+ failure; the tool simply displays nothing rather than throwing.
293
+
294
+ ```tsx
295
+ <ColorPickerTool
296
+ jobId="demo"
297
+ pageNum={1}
298
+ pageWidthPts={page.width_pts}
299
+ pageHeightPts={page.height_pts}
300
+ canvasWidth={renderedWidth}
301
+ canvasHeight={renderedHeight}
302
+ />
303
+ ```
304
+
305
+ Service deps: `colorSample.sampleAt`.
306
+
307
+ ### `DensitometerTool`
308
+
309
+ Same shape as `ColorPickerTool`, but reads CMYK + spot-channel percentages
310
+ and Total Area Coverage via `services.densitometer.sampleAt`. Optional
311
+ `tacLimit` (defaults to `300`).
312
+
313
+ ```tsx
314
+ <DensitometerTool
315
+ jobId="demo"
316
+ pageNum={1}
317
+ pageWidthPts={page.width_pts}
318
+ pageHeightPts={page.height_pts}
319
+ canvasWidth={renderedWidth}
320
+ canvasHeight={renderedHeight}
321
+ tacLimit={300}
322
+ />
323
+ ```
324
+
325
+ Service deps: `densitometer.sampleAt`. See
326
+ [services.md](./services.md#densitometer) for the error-message contract.
327
+
328
+ ### `MeasureTool`
329
+
330
+ Click-and-drag a ruler. Reports the distance in PDF points and through each
331
+ unit you supply (defaults to `[mm, in, pt]`).
332
+
333
+ ```tsx
334
+ <MeasureTool
335
+ pageWidthPts={page.width_pts}
336
+ pageHeightPts={page.height_pts}
337
+ canvasWidth={renderedWidth}
338
+ canvasHeight={renderedHeight}
339
+ units={defaultMeasurementUnits}
340
+ />
341
+ ```
342
+
343
+ See [measurement-units.md](./measurement-units.md) for the unit Protocol
344
+ and built-ins.
345
+
346
+ ### `TACHeatmapOverlay`
347
+
348
+ An SVG hover layer over the page tile that places a hit rectangle at each
349
+ text run and shows its mean TAC on hover. Reads both an image URL and the
350
+ per-run list from `services.tacHeatmap`.
351
+
352
+ ```tsx
353
+ <TACHeatmapOverlay
354
+ jobId="demo"
355
+ pageNum={1}
356
+ width={renderedWidth}
357
+ height={renderedHeight}
358
+ pageWidthPts={page.width_pts}
359
+ pageHeightPts={page.height_pts}
360
+ opacity={0.5}
361
+ dpi={150}
362
+ tacLimit={300}
363
+ />
364
+ ```
365
+
366
+ Service deps: `tacHeatmap.getHeatmapImageUrl`, `tacHeatmap.listRuns`.
367
+
368
+ ## Layer & separation modes
369
+
370
+ ### `LayerCanvas`
371
+
372
+ Instant layer toggling via per-OCG isolated tiles. The host renders one
373
+ PNG per layer with a transparent background; the browser composites the
374
+ active subset locally with `source-over` blending. Toggling a layer is a
375
+ redraw, not a network round-trip. The first paint of an unseen layer
376
+ takes 1–3 s (engine + cache write); subsequent toggles hit the cache
377
+ and complete in well under 100 ms.
378
+
379
+ ```tsx
380
+ <LayerCanvas
381
+ jobId="demo"
382
+ pageNum={1}
383
+ enabledLayers={enabled} // Set<number> of OCG indices
384
+ allLayers={allOcgIndices} // number[] in drawing order
385
+ width={renderedWidth}
386
+ height={renderedHeight}
387
+ dpi={DEFAULT_DPI}
388
+ />
389
+ ```
390
+
391
+ Service deps: `layers.getLayerImageUrl`.
392
+
393
+ ### `LayerPanel`
394
+
395
+ Companion UI: a checklist of OCGs with toggle / show-all / hide-all
396
+ controls. Pulls the OCG list from `services.layers.listLayers`.
397
+
398
+ ```tsx
399
+ <LayerPanel
400
+ jobId="demo"
401
+ enabledLayers={enabled}
402
+ onToggleLayer={(idx) => /* … */}
403
+ onSetAllLayers={(on) => /* … */}
404
+ />
405
+ ```
406
+
407
+ Service deps: `layers.listLayers`.
408
+
409
+ ### `SeparationCanvas`
410
+
411
+ Same instant-toggle pattern, but per ink channel (Cyan, Magenta, Yellow,
412
+ Black, plus any spot inks). Uses subtractive multiply blending against a
413
+ white background. Spot inks get a deterministic HSL hue derived from the
414
+ channel name when no engine-provided RGB is available.
415
+
416
+ ```tsx
417
+ <SeparationCanvas
418
+ jobId="demo"
419
+ pageNum={1}
420
+ enabledChannels={enabledChannels} // Set<string>
421
+ allChannels={["Cyan", "Magenta", "Yellow", "Black", "Pantone 185 C"]}
422
+ width={renderedWidth}
423
+ height={renderedHeight}
424
+ dpi={DEFAULT_DPI}
425
+ />
426
+ ```
427
+
428
+ Service deps: `separations.getChannelImageUrl`.
429
+
430
+ ## Annotations
431
+
432
+ The annotation suite needs the optional `fabric@^7` peer dep installed in
433
+ your host app, and respects `ViewerHostContext.readOnly` to suppress
434
+ saves in share-link / public-token modes.
435
+
436
+ ### `AnnotationToolbar`
437
+
438
+ A tool-and-color toolbar. Supported tools are `pointer`, `pen`, `arrow`,
439
+ `rectangle`, `ellipse`, `text`, and `highlight`. The host owns the
440
+ active-tool state and undo/redo stack.
441
+
442
+ ```tsx
443
+ <AnnotationToolbar
444
+ activeTool={tool} // AnnotationTool
445
+ onToolChange={setTool}
446
+ strokeColor={color}
447
+ onStrokeColorChange={setColor}
448
+ onUndo={undo}
449
+ onRedo={redo}
450
+ canUndo={canUndo}
451
+ canRedo={canRedo}
452
+ saving={saving}
453
+ />
454
+ ```
455
+
456
+ ### `AnnotationCanvas`
457
+
458
+ Fabric.js canvas overlay. Autosaves the current page's drawing through
459
+ `services.annotations.saveForPage`. Skips saves when
460
+ `ViewerHostContext.readOnly` is true. Calls `onSavingChange` and
461
+ `onHistoryChange` so a parent toolbar can show a saving spinner and
462
+ disable undo/redo correctly.
463
+
464
+ ```tsx
465
+ <AnnotationCanvas
466
+ jobId="demo"
467
+ pageNum={1}
468
+ width={renderedWidth}
469
+ height={renderedHeight}
470
+ activeTool={tool}
471
+ strokeColor={color}
472
+ onSavingChange={setSaving}
473
+ onHistoryChange={(canUndo, canRedo) => /* … */}
474
+ />
475
+ ```
476
+
477
+ Service deps: `annotations.getForPage`, `annotations.saveForPage`. Renders
478
+ nothing when `services.annotations` is unwired (see
479
+ [fallback.md](./fallback.md)).
480
+
481
+ ### `AnnotationThread`
482
+
483
+ Sidebar list of annotations across every page, loaded from
484
+ `services.annotations.list`. Calls `onJumpToPage` when the user clicks a
485
+ row.
486
+
487
+ ```tsx
488
+ <AnnotationThread
489
+ jobId="demo"
490
+ currentUserEmail="ops@example.com"
491
+ onJumpToPage={setCurrentPage}
492
+ />
493
+ ```
494
+
495
+ Service deps: `annotations.list`, `annotations.remove`. Renders nothing
496
+ when `services.annotations` is unwired (see [fallback.md](./fallback.md)).
497
+
498
+ ## Mobile chrome
499
+
500
+ ### `useIsMobile`
501
+
502
+ Hook that returns `true` when `window.matchMedia("(max-width: 767px)")`
503
+ matches. Used internally by `<LensPDF>` / `<LensPDFDemo>` to switch
504
+ the tools sidebar into a slide-in drawer (anchored to the left edge,
505
+ ~85vw wide, max 320 px) and to switch the color-picker / densitometer
506
+ readouts from floating tooltips to full-width bottom sheets.
507
+
508
+ ```tsx
509
+ import { useIsMobile } from "@printwithsynergy/lens-pdf/components";
510
+
511
+ const isMobile = useIsMobile(); // default 767 px breakpoint
512
+ const isTablet = useIsMobile(1024); // custom breakpoint
513
+ ```
514
+
515
+ ### `MobileDrawer`
516
+
517
+ A slide-out config drawer for phones, mirroring the desktop sidebar.
518
+ Driven by a `ViewerConfig` (`enable_*` capability flags + plan-gate
519
+ booleans), with section toggles for separation / layer / annotation /
520
+ heatmap / box-overlay modes and external links to the HTML report and
521
+ PDF download.
522
+
523
+ `ViewerConfig` is a fairly large shape — see `types/index.ts` for the full
524
+ field list, and `DEFAULT_VIEWER_CONFIG` for sensible defaults.
525
+
526
+ ```tsx
527
+ <MobileDrawer
528
+ isOpen={drawerOpen}
529
+ onClose={() => setDrawerOpen(false)}
530
+ config={config}
531
+ viewerMode={viewerMode} // "normal" | "separation" | "layers" | …
532
+ onToggleMode={setViewerMode}
533
+ measureMode={measureMode} // "none" | "color_picker" | "densitometer" | "ruler"
534
+ onToggleMeasure={setMeasureMode}
535
+ showTacHeatmap={tac}
536
+ onToggleTacHeatmap={() => setTac((v) => !v)}
537
+ showBoxOverlay={boxes}
538
+ onToggleBoxOverlay={() => setBoxes((v) => !v)}
539
+ fileName="design.pdf"
540
+ findingSummary={{ error: 0, warning: 2, advisory: 5 }}
541
+ zoom={zoom}
542
+ onZoomChange={setZoom}
543
+ jobId="demo"
544
+ onExpandSheet={() => sheetRef.current?.expand()}
545
+ onOpenShare={() => /* … */}
546
+ />
547
+ ```
548
+
549
+ Service deps: `reports.getHtmlReportUrl`, `reports.getPdfDownloadUrl`. The
550
+ "View HTML Report" and "Download PDF" items are dropped when
551
+ `services.reports` is unwired even if the matching `config.enable_*` flag
552
+ is on (see [fallback.md](./fallback.md)).
553
+
554
+ ### `MobileBottomSheet`
555
+
556
+ A drag-snap bottom sheet with `collapsed`, `half`, and `full` positions.
557
+ Auto-sizes the `half` position to its content. Accepts `summary` (always
558
+ visible) and `children` (revealed at `half` / `full`). Snap can be
559
+ controlled or uncontrolled.
560
+
561
+ ```tsx
562
+ <MobileBottomSheet
563
+ summary={<FindingSummaryRow />}
564
+ snap={snap}
565
+ onSnapChange={setSnap}
566
+ >
567
+ <FindingDetailList />
568
+ </MobileBottomSheet>
569
+ ```
@@ -0,0 +1,78 @@
1
+ ---
2
+ title: "Contributing"
3
+ description: "Boundary rule, provenance, local development workflow, public-API surface, commit and PR style, and license terms for contributors."
4
+ group: "Project"
5
+ order: 9
6
+ ---
7
+
8
+ # Contributing
9
+
10
+ Notes for contributors to LensPDF itself. End-user / host docs live
11
+ elsewhere in [`docs/`](.).
12
+
13
+ ## Boundary rule
14
+
15
+ The viewer core MUST NOT import:
16
+
17
+ - any host- or product-specific package or path,
18
+ - a literal backend route string anywhere in source — every URL is
19
+ composed by a `ViewerServices` URL builder the host supplies.
20
+
21
+ CI typecheck catches type-level violations (missing types, unresolved
22
+ imports, banned protocol shapes); reviewers enforce the spirit of the
23
+ boundary on every PR. Anything domain-shaped (findings,
24
+ brand-spec violations, audit verdicts, host-specific configuration)
25
+ lives in plugin packs — the core namespace stays unbranded so any
26
+ host can mount it with zero coupling to a particular SaaS.
27
+
28
+ When you add a new feature, ask:
29
+
30
+ - Does it talk to a backend? → It's a `ViewerServices` field.
31
+ - Does it draw on top of the page or in a panel/toolbar? → It's a plugin.
32
+ - Does it depend on a domain shape (a finding, an annotation
33
+ interpretation, a brand spec)? → It does not belong in `core/`.
34
+
35
+ ## Provenance
36
+
37
+ This package was extracted from an upstream SaaS monorepo via
38
+ `git subtree split` over `packages/viewer-shared/src/core/`. History
39
+ is file-scoped; the synthetic root commit (`c77ccc51`) is the start
40
+ of this repo's history. Everything host-specific from the original
41
+ monorepo lives in separate downstream plugin packs — none of it is
42
+ imported here.
43
+
44
+ ## Local development
45
+
46
+ ```sh
47
+ npm install
48
+ npm run typecheck # tsc -p tsconfig.json
49
+ npm test # vitest run
50
+ npm run build # tsc -p tsconfig.build.json
51
+ ```
52
+
53
+ The repo doesn't track a `package-lock.json` (libraries leave that to
54
+ consuming apps). Add tests alongside new components — `vitest` is
55
+ already wired up; see `plugin/services.test.ts` for the existing
56
+ pattern.
57
+
58
+ ## Public API surface
59
+
60
+ Anything exported from a barrel (`index.ts` at the package root, or
61
+ `{components,plugin,host,types,units}/index.ts`) is part of the public
62
+ API and should be considered semver-stable once the package goes public.
63
+ Keep `@public` JSDoc tags accurate when you add or remove exports — they
64
+ double as the contract for downstream consumers.
65
+
66
+ ## Commit / PR style
67
+
68
+ - Conventional-commit prefixes (`feat:`, `fix:`, `docs:`, `chore:`,
69
+ `refactor:`, `test:`, `ci:`, `build:`).
70
+ - Keep PRs small and focused; one feature or one cleanup per branch.
71
+ - Match the existing code's no-comment-by-default policy — write the WHY
72
+ only when the WHY is non-obvious.
73
+
74
+ ## License
75
+
76
+ LensPDF is AGPL-3.0-or-later. By contributing you agree to license your
77
+ contribution under the same terms. See [`LICENSE`](../LICENSE) for the
78
+ full text.