VDOM Plugin System - Chrome Extension-like plugins for document analysis
npm install @okrapdf/vdom-pluginsChrome Extension-like plugin architecture for VDOM document analysis. Plugins declare capabilities via manifest, host renders UI dynamically.
``bash`
npm install @okrapdf/vdom-pluginsor
pnpm add @okrapdf/vdom-plugins
`typescript
import {
pluginManager,
orphanDetectorPlugin,
type VdomPlugin
} from '@okrapdf/vdom-plugins';
// Register built-in plugin
pluginManager.register(orphanDetectorPlugin);
// Enable it
pluginManager.enable('orphan-detector');
// Run on lifecycle event
const results = await pluginManager.runEvent('interactive', {
document: {
id: 'doc-123',
getAllNodes: () => nodes,
getNode: (id) => nodes.find(n => n.id === id),
pages: [nodes],
},
$: queryEngine,
});
`
Plugins follow a manifest-based pattern inspired by Chrome Extensions:
`typescript`
interface VdomPlugin
name: string;
description: string;
version?: string;
runsOn: VdomLifecycleEvent[]; // When to trigger
handler: (ctx: PluginContext
configSchema?: PluginConfigSchema; // User-configurable options
viewControls?: ViewControlSchema[]; // UI controls (toggles, filters)
defaultEnabled?: boolean;
cleanup?: () => void | Promise
dependencies?: string[]; // Other plugins this depends on
}
Plugins declare which events they respond to:
- loading - Document loading startedready
- - DOM ready, basic structure availableinteractive
- - User can interact, OCR may still be runningcomplete
- - All processing donenodeAdded
- / nodeRemoved / nodeUpdated - Node mutationsselectionChanged
- - User selection changedpageChanged
- - Current page changed
Handlers receive a rich context object:
`typescript`
interface PluginContext
document: {
id: string;
getAllNodes(): VdomNode[];
getNode(id: string): VdomNode | undefined;
pages: VdomNode[][];
};
$: QueryEngine; // jQuery-like selector
event: VdomLifecycleEvent;
pageNumber?: number;
node?: VdomNode; // For node-specific events
config: TConfig; // User configuration
emit: EmitFunction; // Emit overlays, badges, annotations
log: LogFunction;
}
Plugins can emit visual decorations:
- overlays - Bounding box highlights on the PDF viewer
- badges - Labels attached to tree nodes
- annotations - Text annotations (highlight, underline, etc.)
- stats - Computed statistics for display
Detects OCR blocks not covered by semantic entities (tables, figures, etc.).
`typescript
import { orphanDetectorPlugin } from '@okrapdf/vdom-plugins';
pluginManager.register(orphanDetectorPlugin);
pluginManager.setConfig('orphan-detector', {
coverageThreshold: 0.5, // 50% overlap required
showOverlays: true,
showBadges: true,
badgeColor: 'orange',
});
`
`typescript
import type { VdomPlugin, PluginContext, PluginResult } from '@okrapdf/vdom-plugins';
interface MyConfig {
threshold: number;
}
const myPlugin: VdomPlugin
name: 'my-plugin',
description: 'Does something useful',
version: '1.0.0',
runsOn: ['complete'],
defaultEnabled: false,
configSchema: {
threshold: {
type: 'number',
label: 'Threshold',
default: 0.8,
min: 0,
max: 1,
},
},
handler: async (ctx: PluginContext
const { threshold } = ctx.config;
const nodes = ctx.$('table').toArray();
// Process nodes...
ctx.emit('stats', { tablesFound: nodes.length });
return { success: true };
},
};
`
`typescript`
class VdomPluginManager {
register(plugin: VdomPlugin): void;
unregister(name: string): void;
enable(name: string): void;
disable(name: string): void;
isEnabled(name: string): boolean;
setConfig(name: string, config: Record
getConfig(name: string): Record
getPlugins(): VdomPlugin[];
getEnabledPlugins(): string[];
runEvent(event, context): Promise
`typescript
import { overlapRatio, bboxContains } from '@okrapdf/vdom-plugins';
// Calculate overlap between two bounding boxes (0-1)
const ratio = overlapRatio(bbox1, bbox2);
// Check if inner bbox is contained by outer (with threshold)
const contained = bboxContains(outer, inner, 0.5);
``
MIT