@ktb-tools/scanner
v0.3.0
Published
Embeddable document-scanning workflow built on Scanic, with a customizable React UI, reversible image processing and browser-side image/PDF export
Readme
@ktb-tools/scanner
An embeddable document-scanning workflow for web applications. Built on Scanic, with a customizable React interface, reversible image processing and browser-side image/PDF export.
Integrate a complete scanner and receive user-confirmed files and structured metadata through one API:
- Capture workflow: camera or image import, Auto/Manual capture, stability feedback, independent still-image detection, review, retakes and page management.
- Editing: corner/perspective correction, rotation, output formats and reversible profiles/adjustments with original/before comparison.
- Integration: a framework-independent core, reusable React UI, theme tokens, localization, typed results, cancellation and resource cleanup.
- Consistent output: ordered images, an optional multi-page raster PDF and serializable metadata from the same confirmed page revisions.
Scanic supplies document detection, perspective correction and the corner editor; jsPDF generates the default raster PDFs. ktb.scanner combines these libraries with its session, capture, processing and review workflow. Originals and results remain in memory. There is no built-in upload, and no general detection-quality advantage over Scanic is claimed.
Since 0.3.0, the library provides optional local OCR of confirmed final images through /ocr: per-page text/confidence and a searchable PDF using same-origin assets. Select Paddle or Tesseract; the API retains Tesseract as its default, while the Next.js demo selects Paddle and starts OCR manually. Recognition is serial and abortable, and opt-in RAM reuse skips unchanged page revisions; original images and the existing scanner result stay unchanged. See OCR integration.
This documentation targets library 0.3.0. Check exact registry availability before installation; a locally built package archive provides the same source-version integration. Direct camera entry, preference notifications and the complete theme tokens were introduced in 0.2.0.
Entry points
@ktb-tools/scanner: DOM-free session, types, geometry, profiles and metadata.@ktb-tools/scanner/react:createScanner, themeableScannerand reusableImageViewercomponents.@ktb-tools/scanner/scanic: classic Scanic in an abortable worker.@ktb-tools/scanner/export: serial image and PDF creation.@ktb-tools/scanner/ocr: local OCR since 0.3.0 and searchable PDF creation from confirmed pages.@ktb-tools/scanner/styles.css: scoped styles and semantic theme variables.
For all supported CSS variables/defaults, imperative/React themes, light/dark palettes, locally served fonts, viewer inheritance and styling limits, see the public UI styling guide.
React 19 and React DOM are peers. Build the library and serve the complete dist/assets/ directory at a same-origin URL. Workspace examples do this with pnpm build:library and pnpm assets. No CDN fallback, service worker or global cross-origin isolation is required.
import '@ktb-tools/scanner/styles.css';
async function scan(mount: HTMLElement, initialFiles?: File[]) {
const { createScanner } = await import('@ktb-tools/scanner/react');
const scanner = createScanner({
mount,
assetBaseUrl: '/scanner-assets/',
initialMode: 'auto',
locale: 'en',
autoCaptureStabilityMs: 1000,
imageType: 'image/jpeg',
shutterSound: 'camera', // Use 'off' to avoid audio loading and playback.
createPdf: true,
});
try {
const result = await scanner.open({ initialFiles });
if (result.status === 'confirmed') return result;
} finally {
await scanner.destroy();
}
}scanner.open({ entry: 'camera' }) opens the existing camera UI directly. React uses <Scanner open openOptions={{ entry: 'camera' }} ... />. ScannerEntry is exported from the core and React entries: workspace | camera, default workspace. initialMode still selects Auto/Manual only. Nonempty initialFiles take precedence and open review without requesting a camera; empty arrays follow entry. Invalid entries reject CONFIG_INVALID before opening, even with files. Denial keeps retry/import/cancel available. No camera access occurs from imports or SSR. open({ signal }) supports host cancellation and returns { status: 'cancelled' }. Recoverable processing/export errors keep the session open. destroy() stops owned tracks and workers and releases preview resources. One session per controller; a destroyed controller cannot reopen. The host owns confirmed image/PDF blobs and serializable metadata.
Since 0.3.0, the library offers ScannerOptions.captureWorkflow?: CaptureWorkflow, exported from core and React: review-each (default) or continuous. Set captureWorkflow: 'continuous' in createScanner({...}) or React options, then open with entry: 'camera'. Successful captures show the extracted preview and return to the running camera; Review pages (n) opens the first unreviewed page, otherwise the first accepted page. All candidates still require review or explicit discard before Finish. Retake replaces the current page and returns to its review. Nonempty initial files keep their import path without a camera request. This is a configuration option, not an openOptions field; React captures it on opening. Both reference hosts select continuous capture while preserving a review-each choice. See complete examples and limits.
locale defaults to en; de is built in. Both examples default to English, detect a German primary browser language and honor explicit DE/EN selection. The standalone ImageViewer also accepts locale. createTranslator(locale, texts?) is exported from the core; existing German message-key overrides and English-text overrides remain supported. Error codes are stable while API messages default to English. See the root README for complete demo setup, package installation, asset copying, React/controller integration and result ownership.
onPreferenceChange?: (change: ScannerPreferenceChange) => void was added to ScannerOptions in 0.2.0. Both core and React entry points export the type. It reports only real SDK UI changes as { preference: 'initialMode', value: 'auto' | 'manual' } or { preference: 'imageType', value: 'image/jpeg' | 'image/png' }, immediately and even if the session is subsequently cancelled. Initialization, reapplying options, unchanged values, page edits and ended sessions emit nothing. Host callback failures do not interrupt scanning; React uses the current options.onPreferenceChange handler without reopening. Reapply values with the existing initialMode / imageType options. The host owns validation, migration, persistence and reset; the SDK uses no localStorage or cookies. See host examples. This API is not in npm 0.1.0.
defaultProfile accepts auto, photo, color, grayscale or bw. An open() override takes precedence over controller configuration; without either, Auto chooses and applies Photo or Color immediately. Grayscale and soft monochrome remain manual choices. All edits are recalculated from the original. paperRegion is a weak regional A4/Letter preference, not a GPS request or a size measurement. Manual paper formats and orientation take precedence; paper axes are separate from 90-degree image rotation.
autoCaptureStabilityMs configures the minimum measured stability window, default 1,000 ms. A per-open override takes precedence. Both supplied values must be safe integers from 100 through 5,000. Core users can pass a second argument to new AutoCapture('auto', 600); exported DEFAULT_AUTO_CAPTURE_STABILITY_MS and AUTO_CAPTURE_STABILITY_PRESETS_MS describe the default and reference choices. Fresh distinct observations, geometry and sharpness still govern capture, so sampling/processing can exceed the selected minimum. See integration.
The theme extension in 0.2.0 closes styling gaps in the existing theme option: backdrop/stacking, --scanner-primary-text, --scanner-warning-text/border, camera palette/overlays/focus/shutter, control/panel/mobile radii, spacing, capture chrome and viewer background/radius. An explicit root focus also applies to mobile camera unless a camera-specific focus is supplied. ImageViewer consumes inherited --scanner-font directly, without an additional prop or host font declaration. Existing ten tokens/defaults remain compatible. See the complete public token contract. These additions are not in npm 0.1.0.
SHUTTER_SOUNDS and ShutterSoundId describe camera and reflex (CC0-1.0) plus off. shutterSoundUrl accepts a host-owned recording URL; off suppresses SDK playback even when a custom URL is configured. See capture sounds for integration and credits. ImageViewer accepts an image blob, optional matching unoptimized crop, accessible alternative text and a reset key. It supports fit/100 percent, zoom steps, pinch, pan and held comparison. Since 0.3.0, onPageSwipe?: (direction: 'previous' | 'next') => void opts standalone viewers into one-pointer horizontal page gestures at fit; it never owns page state. SDK review uses the gesture in both capture workflows and for imports. Zoom, pan, pinch, held comparison and editing retain their own interactions. Use the same CSS and bounded container height. The parent owns blobs; the component owns its object URLs.
The public source and complete workspace build inputs are in the project repository. See its integration guide for the declarative React API, theme/text options, limits, assets, HTTPS, downloads and FormData. Verification status distinguishes automated tests from physical-device acceptance. The examples target 0.3.0; verify registry availability or use a built archive from this source. The original 0.1.0 release remains unchanged.
Project source: GNU AGPL version 3 only; see LICENSE. Third-party code and integrated audio retain separate notices under dist/assets/. Audio is supplied as scanner feedback, not a standalone sound collection.
