@signosoft/tablet-driver
v0.1.1
Published
WebAssembly-based Wacom tablet driver for WebHID signature capture
Readme
WebAssembly Tablet Driver Project
Project Overview
This project implements WebAssembly-based tablet drivers for various Wacom tablets, focusing on secure HID report processing and IP protection through obfuscation techniques.
High-Level Architecture
System Components
1. WASM Module (C++ compiled to WebAssembly)
The core processing engine that handles:
- HID Report Parsing: Extracts pen coordinates, pressure, and status from raw tablet data
- Licence Validation: RSA signature verification for anti-tampering protection
- Device-Specific Drivers: Proprietary parsing constants for different tablet models
- Command Execution: Sends commands to tablets (display images, clear screen, set pen color, etc.)
- Event Emission: Publishes normalized pen data and button events to JavaScript
Key WASM Classes:
WASMTabletDriver: Main interface between JavaScript and C++ (singleton pattern)TabletDriver: Abstract base class for all tablet implementationsDataParser: Extracts pen data from HID reports using device-specific bit offsetsCommand: Handles complex tablet operations (images, multi-step commands, conditionals)EventEmitter: Publishes events from C++ to JavaScript
2. TypeScript Library — Two-Layer Structure
The library is written in TypeScript under src/ and exposes three entry points: src/index.ts (full API), src/index.driver.ts (Layer 1 only), and src/index.signature.ts (Layer 2 only).
Layer 1 — TabletDriver (src/tablet-driver.ts)
Direct WASM bridge that manages:
- WebHID Communication: Requests device access, reads HID reports, sends output reports
- WASM Lifecycle: Initializes WASM module, manages driver connection/disconnection
- Licence Validation: Passes a caller-supplied signed lease to WASM for RSA verification — the library does not fetch leases itself
- Event Distribution: Subscribes to WASM events and routes them to user callbacks (
on(event, callback)) - Command Interface: Translates TypeScript method calls to WASM command execution
TabletDriver is a thin public façade. Its concerns are split across focused collaborators in src/internal/ that share a single DriverState (the canonical reference for module, device, isWasmInitialized, penAspectCorrector):
- internal/driver-state.ts — shared state +
requireInitialized()/isConnected()invariants - internal/wasm-lifecycle.ts —
initialize(): loads WASM, runs RSA licence verification - internal/connection-manager.ts —
connect()/disconnect()/getDeviceInfo(), auto-connect, disconnect detection, pen aspect correction - internal/event-distributor.ts —
on()/off(), master callback wiring, pen-data normalization - internal/command-dispatcher.ts —
sendCommand(),displayImage(),setPenStyle(),getSerialNumber(), etc. - internal/logging-controller.ts — data-log flag controls and convenience modes
Layer 2 — SignatureLayer (src/signature-layer.ts)
Higher-level façade that orchestrates the TabletDriver for signature capture workflows:
- Device-aware signing setup and teardown (
startSigning(),stopSigning()) - Automatic hotspot configuration for OK / Clear / Cancel buttons
- Canvas drawing and pen data collection via
SignatureCapture - Fallback to mouse/pointer input when no tablet is connected
Support modules (src/):
- hotspot-manager.ts: Interactive areas on tablet surface (buttons, drawing zones)
- signature-capture.ts: Canvas drawing and pen data recording during signing
- page-input-controller.ts: Maps pen input to page scroll/click interactions
- signature-device-config.ts: Loads and caches device-specific signing configurations
- canvas-pointer-input.ts: Translates HTML5 pointer events to pen data (mouse fallback)
- image-processor.ts: Converts images (canvas, file, URL, base64) to tablet format
- command-args-builder.ts: Builder pattern for structured command arguments
- aspect-ratio-corrector.ts: Maps tablet coordinates to canvas with correct proportions
- logger.ts: Configurable WASM log levels and data log flags
- wasm-module.ts: WASM module type wrappers
- types.ts: Shared TypeScript types (
PenData,DeviceInfo,SignedLease, etc.) - data/device-configs.ts: Per-device hotspot/image configuration data
- data/device-images.ts: Pre-encoded base64 image assets for tablet screens
Lease fetching is no longer part of the library. Consumers must call their own backend and pass the resulting
SignedLeasetoinitialize(). The demo includes demo/license-utility.ts as a reference helper.
Data Flow: Initialization Workflow
Typical Workflow Using SignatureLayer (Layer 2)
import { SignatureLayer, type SignedLease } from "@signosoft/tablet-driver";
// 1. Create SignatureLayer instance
const layer = new SignatureLayer();
// 2. Obtain a signed lease from your own backend (the library does NOT do this)
const lease: SignedLease = await fetch("/api/v1/driver/leases", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ licenceKey, validityHours: 24 }),
}).then(r => r.json());
// 3. Initialize: loads WASM, verifies RSA licence signature, registers enums.
// The wasm binary (driversWasm.wasm) is a SEPARATE file you host — tell the
// library where it is via wasmUrl (see "Hosting the WASM binary" below).
await layer.initialize(lease, { wasmUrl: "/assets/driversWasm.wasm" });
// 4. Connect: shows browser WebHID device picker (or auto-connects if already granted)
await layer.connect();
// 5. Retrieve tablet metadata (name, aspect ratio, canvas vs monitor style)
const info = layer.getDeviceInfo();
// 6. Start signing: displays signing screen, sets up hotspots, enables drawing
const canvas = document.querySelector("canvas")!;
await layer.startSigning(canvas);
// 7. Subscribe to button events
layer.onOK(async () => {
const { penData, image, canvasMetadata } = await layer.stopSigning();
// submit signature...
});
layer.onClear(() => layer.clearSignature());
layer.onCancel(() => layer.stopSigning());What Happens Internally During Initialization
- WASM load — the library imports the Emscripten loader (
driversWasm.js, an ES module) and calls its factory to create the WebAssembly module. The factory fetches the separatedriversWasm.wasmbinary (see Hosting the WASM binary). - Licence verification — WASM fetches public key from
/api/v1/driver/public-key, verifies RSA-SHA256 signature and expiration - Device connection —
navigator.hid.requestDevice()orgetDevices()opens the HID interface - Driver creation —
TabletDriverFactorymatches VID/PID and creates a device-specific C++ driver instance - Hotspot setup —
SignatureDeviceConfigprovides per-device button layout;HotspotManagerregisters OK / Clear / Cancel regions - Screen display —
DisplayImagecommand sends the idle or signing screen to the tablet
Hosting the WASM binary
This package ships two runtime artifacts: the JavaScript library (dist-driver/) and the WebAssembly binary driversWasm.wasm. The library imports the Emscripten loader for you — there is no <script> tag and no window global — but the .wasm is a separate file your app must serve, and you tell the library where it lives:
await layer.initialize(lease, { wasmUrl: "/assets/driversWasm.wasm" });
// TabletDriver (Layer 1) takes the same option:
await tablet.initialize(lease, { wasmUrl: "/assets/driversWasm.wasm" });How to get driversWasm.wasm to a served URL, depending on your tooling:
- Bundler that handles
import.meta.urlassets (Vite, webpack 5, Parcel): it may emit and serve the.wasmautomatically — tryinitialize(lease)with nowasmUrlfirst. If the wasm 404s, fall back to hosting it explicitly (below). - Explicit hosting (always works): copy
node_modules/@signosoft/tablet-driver/driversWasm.wasminto your served static assets (e.g. anassets//public/folder, or an Angularassetsglob) and pass that URL aswasmUrl. - Advanced: pass
locateFile: (path, dir) => …instead ofwasmUrlfor full control over how the loader resolves the binary.
The package also exposes the raw files via subpath exports —
@signosoft/tablet-driver/driversWasm.wasmand@signosoft/tablet-driver/driversWasm.js— so bundlers that support asset URL imports can resolve the binary directly (e.g.new URL("@signosoft/tablet-driver/driversWasm.wasm", import.meta.url)).
Data Flow: Main Event Loop
HID Input Processing Pipeline
1. Browser HID Event → TypeScript (src/tablet-driver.ts)
device.addEventListener("inputreport", (event) => {
// Pass directly to WASM; the bridge converts the DataView internally
this.module.processHIDReport(event, event.reportId);
});2. TypeScript → WASM Bridge (Driver/Driver/WASMwrapper.cpp)
WASMTabletDriver::processHIDReport(val event, int reportId)- Converts JavaScript typed array to C++
std::vector<uint8_t> - Calls
driver->processHIDReport(data, reportId)
3. HID Report Processing (Driver/Driver/TabletDriver.cpp)
- Log Raw Data: If enabled, logs hex dump of HID report
- Parse Data:
parser->getPenData(parsedData, timeData)extracts and normalizes pen coordinates/pressure- Example:
xParam = {bitOffset: 24, numBits: 16, littleEndian: true} - Reads 16 bits starting at bit 24 to get X coordinate, returns values in 0.0-1.0 range
- Example:
- Log Parsed Data: Shows raw tablet units (e.g.,
x=5000, y=3000, pressure=512) - Button Detection: Checks
inputRegistrationsfor special input patterns (e.g., pen at 0,0 = OK button) - Normalize Data: Normalization to 0.0-1.0 is handled within
getPenData() - Log Normalized Data: Shows final coordinates (e.g.,
x=0.26, y=0.28, pressure=0.5)
4. Event Emission (Driver/Driver/TabletDriver.cpp)
- Creates
InputEvent(type, penData)where type is "Pen", "OK", "Clear", etc. - Calls
inputEmitter.emit(event)to publish to JavaScript
5. WASM → JavaScript Event Bridge (Driver/Driver/WASMwrapper.cpp)
WASMEvent<InputEvent>::emit()converts C++ event to JavaScript object- Calls JavaScript callback with
{type: "Pen", penData: {x, y, pressure, inContact, ...}}
6. TypeScript Event Distribution (src/tablet-driver.ts)
- Master callback receives event from WASM
- Looks up subscribed callbacks in the internal event-callback map
- Executes all registered callbacks for that input type
7. User Callback Execution (consumer code, e.g. demo/driver_simple_impl.ts)
tablet.on("Pen", (penData) => {
drawPenInput(penData); // Draws on canvas
});
tablet.on("OK", () => {
handleOK(); // Returns to home screen
});8. Hotspot System (src/hotspot-manager.ts)
- Subscribes to 'Pen' events automatically
- Checks if pen coordinates fall within hotspot boundaries
- Tracks state per hotspot (isPenInside, isPenDown, isValidForClick)
- Fires hotspot-specific events (enter, leave, down, up, click, move)
Command Execution Flow
Example: tablet.screenClear(imageArgs)
TypeScript Call (src/tablet-driver.ts)
sendCommand("Clear", imageArgs.build())
WASM Command Lookup (Driver/Driver/WASMwrapper.cpp)
WASMTabletDriver::sendCommand("Clear", jsonArgs)- Converts string to
CommandType::Clearenum - Calls
driver->sendCommand(CommandType::Clear, jsonArgs)
Driver Command Execution (Driver/Driver/TabletDriver.cpp)
- Looks up command in
supportedCommandsmap - Executes
command->execute(jsonArgs)
- Looks up command in
Command Implementation (Driver/Driver/Command.cpp)
- SimpleCommand: Sends single HID output report
- ImageCommand: Sends init report → data chunks → completion report
- MultiCommand: Executes sequence of commands
- ConditionalCommand: If/else logic based on JSON arguments
- WrapperCommand: Delegates to another command with static arguments
HID Output Report (Driver/Driver/Utils.cpp)
- Converts
CommandData{reportId, data}to JavaScript - Calls
device.sendReport(reportId, data)via Emscripten
- Converts
Project Structure
Core Components
- Driver/Driver/: C++ source code for the WASM module
- Tablet-specific drivers: DTK1660E.hpp, DTC121.hpp, DTU1031.hpp, DTU1141B.hpp, STU300.hpp, STU430.hpp, STU520.hpp, STU530.hpp, STU540.hpp, STU541.hpp, UG05.hpp, UG0501DL.hpp, UG1070H.hpp (Contains proprietary parsing constants — CRUCIAL IP)
- Core classes: TabletDriver, DataParser, DefaultDriver, InputData, EventEmitter
- WASM interface: WASMwrapper.cpp/hpp
- Utilities: Logger, Utils, JavaScriptUtils, Command
- Driver.cpp: Console application for testing without WASM compilation
- src/: TypeScript source for the driver library (Layers 1 & 2 + utilities)
- demo/: Vite-served TypeScript demo pages plus a few legacy standalone HTML utilities
Build Outputs
- driversWasm.js — produced by
build.bat; the Emscripten loader as an ES module (MODULARIZE+EXPORT_ES6). The TS libraryimports it (it is externalized from the bundle and shipped alongside it). driversWasm.wasm — the separate binary; consumers host it and point the library at it viawasmUrl(see Hosting the WASM binary). - dist/ —
tscoutput, ESM +.d.ts(local/dev convenience) - dist-driver/ — Vite bundle of src/index.ts, minified. This is what
package.jsonexports(and the published package) resolves to; itimportsdriversWasm.jsfrom the package root.
Build Scripts
- build.bat: Compiles C++ to WASM with three profiles:
debug: Fast compilation with full debuggingrelease: Optimized build without debuggingprotected: Maximum optimization and obfuscation
- decompile.bat: Decompiles WASM for reverse engineering analysis
- npm scripts (see
package.json):dev,build,build:driver,build:signature— TypeScript-side only, never invokeemcc
Tools Directory (Not in Repository)
- tools/emsdk/: Emscripten SDK for WebAssembly compilation
- tools/wabt-1.0.37/: WebAssembly Binary Toolkit for decompilation
- tools/ghidra_11.2_PUBLIC/: Reverse engineering analysis tool (optional)
Note: Tools are excluded from repository (.gitignore) due to size - see Prerequisites section for setup instructions.
Build Instructions
Prerequisites
- Windows CMD (not PowerShell - the build scripts use Windows batch syntax)
- Git for cloning dependencies
- curl for downloading header files (included in Windows 10+)
Quick Start Setup
Complete setup from scratch --- run init.bat from project root in Windows CMD
⚠️ Important Note for Build Script Development: When modifying build.bat, do NOT use variable names containing "EMSDK" - they get cleared by emsdk_env.bat. Use alternative names like EMSCRIPTEN_DIR instead.
Required External Dependencies
The C++ WASM module requires several external libraries for image processing and encoding. These must be manually installed before building.
1. STB Image Libraries (Driver/Driver/external/stb/)
STB is a collection of single-file public domain libraries for C/C++. We use:
stb_image.h - Image loading (PNG, JPG, etc.)
stb_image_resize2.h - High-quality image resizing
https://github.com/nothings/stb/blob/master/stb_image.h
https://github.com/nothings/stb/blob/master/stb_image_resize2.h
Place both files in Driver/Driver/external/stb/
2. cppcodec Base64 Library (Driver/Driver/external/cppcodec/)
Header-only C++ library for base64/base32 encoding/decoding.
- https://github.com/tplgy/cppcodec
- Extract to
Driver/Driver/external/cppcodec/
Required Build Tools Setup
The following build tools need to be installed in the tools/ directory. These are excluded from version control due to size.
Emscripten SDK (tools/emsdk/)
- Download from: https://github.com/emscripten-core/emsdk
- Install:
git clone https://github.com/emscripten-core/emsdk.git tools/emsdk - Setup:
cd tools/emsdk && emsdk install latest && emsdk activate latest
WebAssembly Binary Toolkit (tools/wabt-1.0.37/)
- Download from: https://github.com/WebAssembly/wabt/releases
- Extract to:
tools/wabt-1.0.37/(or update version indecompile.bat)
Ghidra (tools/ghidra/) - Optional for reverse engineering analysis
- Download from: https://github.com/NationalSecurityAgency/ghidra/releases
- Extract to:
tools/ghidra_11.2_PUBLIC/ - WASM Plugin Required: Download https://github.com/nneonneo/ghidra-wasm-plugin/releases
- Install plugin in Ghidra for WebAssembly analysis support
Quick Build (WASM)
build.bat protectedBuild Profiles (WASM)
- debug:
-g -O0with assertions and safety checks - release:
-O2 -DNDEBUGoptimized build - protected:
-O3 -DNDEBUGwith maximum obfuscation
TypeScript / npm Scripts
The TS library and the WASM binary are built independently — build.bat produces driversWasm.js (ESM loader) + driversWasm.wasm, and the npm scripts produce the TS bundle that imports the loader. Run build.bat first, then the npm build, when shipping a release.
npm install # one-time install of TS toolchain (Vite, tsc, terser)
npm run dev # Vite dev server on http://localhost:4200, opens demo with HMR
npm run build # tsc → dist/ (ESM + .d.ts; this is the package's published entry)
npm run build:driver # Vite → dist-driver/index.js (Layer 1 only, minified ESM)
npm run build:signature # Vite → dist-signature/index.js (Layer 2 only, minified ESM)
npm run clean # remove dist/The dist/ build is what consumers resolve via import { TabletDriver } from "@signosoft/tablet-driver". The two split bundles (dist-driver/, dist-signature/) exist for consumers who want the smallest possible payload for their use case.
Development & Testing
Console Testing
The project includes a console application (Driver/Driver/Driver.cpp) for testing tablet drivers without WebAssembly compilation.
Web Testing
Run npm run dev to start the Vite dev server on http://localhost:4200 with HMR. WebHID requires a secure context, so the demo must be served over localhost or HTTPS — not opened from file://.
Primary test pages (TypeScript, served via Vite):
- demo/driver_playground.html — Full testing environment for the
TabletDriverlayer (Layer 1). All commands, events, logging controls, and hotspot debugging are accessible directly. Use this when working on the WASM bridge or raw tablet communication. - demo/driver_simple_impl.html — Reference implementation using
SignatureLayer(Layer 2). Shows the complete signing workflow in a realistic setting. Use this when working on or testing the higher-level signing API. - demo/index.html — Landing page with license-validation test buttons.
Legacy standalone utility pages (.js.html suffix, plain-JS — served as static assets by Vite):
- demo/image_to_base64.js.html — Converts images to base64 strings for use in src/data/device-configs.ts and src/data/device-images.ts.
- demo/webHID_exploration.js.html — Low-level WebHID exploration for investigating HID descriptors and reports of new tablet models.
These two
.js.htmlfiles predate the TS rewrite and may reference modules that no longer exist; treat them as scratch tools and migrate or delete as needed.
Reverse Engineering Analysis
Use decompile.bat to analyze WASM obfuscation effectiveness:
decompile.batThis generates driversWasm.wat and driversWasm.c for inspection.
Security & IP Protection
Critical IP Components
The tablet-specific driver classes contain proprietary constants for interpreting HID reports from various Wacom tablets. This parsing logic is the core intellectual property requiring protection.
Obfuscation Techniques
- String encryption and conditional compilation
- Runtime decryption of sensitive parameters
- Build-time obfuscation flags
- Debug string removal in production builds
Production Deployment
Before deployment, ensure Logger.cpp has LogLevel set to Error:
Logger::currentLevel = LogLevel::Error;Architecture
The system processes tablet input through a multi-layer pipeline:
- RAW HID reports from tablet hardware
- PARSED data extraction using tablet-specific constants (C++ WASM)
- NORMALIZED coordinate and pressure values (0.0–1.0 range)
- Layer 1 events —
TabletDriverroutes pen data and button events to JavaScript callbacks - Layer 2 orchestration —
SignatureLayermanages the signing workflow: hotspots, canvas drawing, and pen data collection
System Features & Capabilities
Two-Layer TypeScript API
The library is split into two layers so applications can consume exactly the level of abstraction they need:
TabletDriver(Layer 1) provides the raw WASM bridge: event subscriptions, command execution, and WebHID lifecycle management.SignatureLayer(Layer 2) is a higher-level façade built on top of Layer 1 that handles the full signature capture workflow with a minimal API surface.
Reliable Connection Management
- Automatic USB hardware disconnect detection with proper C++ resource cleanup via
disconnectDriver() - Auto-connect functionality for devices already granted browser permission
- Consistent connection state available via
isConnected()
Multi-Device Support
Supports STU, DTU, DTK, UG, and DTC series tablets with device-specific:
- HID parsing constants (bit offsets for X, Y, pressure, buttons)
- Signing screen layouts and button hotspot positions
- Aspect ratio correction (canvas-style vs monitor-style tablets)
Advanced Hotspot System
Interactive area system supporting multiple region types:
- Static hotspots: Normalized 0–1 coordinates for consistent cross-device positioning
- Element hotspots: DOM element tracking for web page integration
- Page hotspots: Full-screen mapping for monitor-style tablets (DTU/DTK/UG-1070H)
Full web-button event cycle (enter / leave / down / up / click / move) with per-hotspot state tracking.
Image and Command System
- Multi-format image processing pipeline: Canvas, File, HTMLImageElement, base64, URL
- Text-to-image generation for dynamic tablet screen content
- Full command set: DisplayImage, ClearScreen, EnableDrawing, SetPenStyle, GetSerialNumber, and more
Contributing
When adding new tablet support, create a new driver class in the pattern of existing tablet drivers (DTK1660E.hpp, etc.) with the specific parsing constants for that tablet model.
Development Workflow
- Modular Development: Add new modules under src/ as
.tsfiles (kebab-case naming). Re-export public symbols from the relevant entry point (src/index.ts, src/index.driver.ts, or src/index.signature.ts). - Device Testing: Test with both demo/driver_simple_impl.html and demo/driver_playground.html via
npm run dev. - Disconnect Testing: Always verify proper cleanup when devices are unplugged.
- Hotspot Configuration: Ensure new devices have appropriate hotspot layouts in src/data/device-configs.ts.
- Documentation: Update this README and CLAUDE.md for significant changes.
License
Proprietary Commercial License — Copyright © 2026, Signosoft. All rights reserved.
This software is proprietary to Signosoft and is protected by copyright laws. It is not open source.
🔐 Commercial Usage
A valid, paid, and active commercial license agreement with Signosoft is strictly required for the use, operation, reproduction, distribution, or deployment of this software in any environment (including development, testing, staging, and production).
Operation additionally requires a current, valid, signed licence lease obtained from Signosoft at runtime — the software will not function without it.
📜 Key Terms
- Unauthorized Use: Any use without a current, fully paid license is expressly prohibited and constitutes a violation of our intellectual property rights.
- Legal Action: Signosoft reserves the right to pursue legal action to enforce its rights and seek damages for unauthorized use.
- Restrictions: You may not reverse-engineer, decompile, or disassemble this software except as permitted by applicable law.
For information on how to obtain a valid commercial license, pricing, and support, please contact us at [email protected].
See the LICENSE file for the full legal text.
