This article aims to demonstrate how to build a Flutter web plugin with Dynamic Web TWAIN, as well as how to use the plugin to build web document scanning applications.
Flutter Web TWAIN Package
https://pub.dev/packages/flutter_web_twain
Flutter Wrapper for Dynamic Web TWAIN
We use the --template=plugin
and --platforms=web
flags to create a new Flutter plugin project for web development.
flutter create --org com.dynamsoft --template=plugin --platforms=web flutter_web_twain
To interop JavaScript with Dart, add the js package as the dependency in pubspec.yaml
file:
dependencies:
...
js: ^0.6.3
Create a lib/src/web_twain_manager.dart
file, in which we define the JavaScript namespace and classes that we want to use in Dart.
@JS('Dynamsoft')
library dynamsoft;
import 'package:js/js.dart';
import 'dart:html' as html;
import 'utils.dart';
@JS('DWT')
class DWT {
external static set ResourcesPath(String resourcePath);
external static set ProductKey(String productKey);
external static void Load();
external static void Unload();
external static void CreateDWTObjectEx(
dynamic obj, Function success, Function error);
external static set AutoLoad(bool autoLoad);
}
@JS('DWTObject')
class DWTObject {
external set IfShowUI(bool ifShowUI);
external dynamic get Viewer;
external void OpenSource();
external void CloseSource();
external void AcquireImage(dynamic obj, Function success, Function error);
external void LoadImageEx(
String path, int type, Function success, Function error);
external int get HowManyImagesInBuffer;
external set IfShowFileDialog(bool ifShowFileDialog);
external int get CurrentImageIndexInBuffer;
external void SaveAllAsPDF(String path, Function success, Function error);
external void SaveAllAsMultiPageTIFF(
String path, Function success, Function error);
external void SaveAsJPEG(String path, int index);
external int GetImageBitDepth(int index);
external void ConvertToGrayScale(int index);
}
@JS('Viewer')
class Viewer {
external void bind(dynamic container);
external void show();
}
The methods declared above can be used for document scan, load and save, which are a small parts of the whole API. For more advanced functionalities, please refer to Dynamic Web TWAIN API Reference. Note: The Flutter release mode obfuscates the JavaScript code, so it is necessary to use the @JS
annotation to expose all JavaScript properties and methods that will be called in your Flutter project.
Next, we create a WebTwainManager
class and implement the following methods:
-
init()
: Set the Dynamic Web TWAIN resource path and the license key.
class WebTwainManager { DWTObject? _webTwain; String _containerId = ''; html.Element? _container; init(String path, String key) { DWT.ResourcesPath = path; DWT.ProductKey = key; DWT.AutoLoad = false; } }
-
dispose()
: Destroy all web TWAIN instances.
dispose() { DWT.Unload(); }
-
createWebTwain()
: Create a Dynamic Web TWAIN object with an HTML div element.
createContainer(html.DivElement container) { _container = container; _containerId = container.id; var tmp = Map<dynamic, dynamic>(); String jsonStr = '{"WebTwainId":"container"}'; DWT.Unload(); DWT.CreateDWTObjectEx(parse(jsonStr), allowInterop((DWTObject obj) { _webTwain = obj; _webTwain!.IfShowUI = false; Viewer viewer = _webTwain!.Viewer; if (_container != null) { viewer.bind(_container); viewer.show(); } }), allowInterop((error) { print('CreateDWTObjectEx error: $error'); })); }
-
scan()
: Acquire documents from document scanners.
scan(String config) { if (_webTwain != null) { _webTwain!.OpenSource(); _webTwain!.AcquireImage(parse(config), allowInterop(() { _webTwain!.CloseSource(); }), allowInterop((settings, errCode, errString) { _webTwain!.CloseSource(); print(errString); })); } }
-
load()
: Load images from local files.
load() { if (_webTwain != null) { _webTwain!.LoadImageEx("", 5, allowInterop(() { print("Successful!"); }), allowInterop((errorCode, errorString) { alert(errorString); })); } }
-
download()
: Save document images as PDF, TIFF or JPEG files.
download(int type, String filename) { if (_webTwain != null && _webTwain!.HowManyImagesInBuffer > 0) { _webTwain!.IfShowFileDialog = true; switch (type) { case 0: _webTwain!.SaveAllAsPDF( filename, allowInterop(() => {}), allowInterop(() => {})); break; case 1: _webTwain!.SaveAllAsMultiPageTIFF( filename, allowInterop(() => {}), allowInterop(() => {})); break; case 2: if (_webTwain! .GetImageBitDepth(_webTwain!.CurrentImageIndexInBuffer) == 1) { _webTwain!.ConvertToGrayScale(_webTwain!.CurrentImageIndexInBuffer); } _webTwain!.SaveAsJPEG(filename, _webTwain!.CurrentImageIndexInBuffer); } } }
Dynamic Web TWAIN object binds to an HTML div element, and the document images are displayed in the div element. To make the HTML element visible in Flutter, we call ui.platformViewRegistry.registerViewFactory
to register it as a platform view. Create a WebTwainController
class to manage the WebTwainManager
instance and the HTML div element.
import 'dart:async';
import 'dart:html' as html;
import 'shims/dart_ui.dart' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'web_twain_manager.dart';
class WebTwainController {
final WebTwainManager _webTwainManager = WebTwainManager();
final String _webviewId = '${DateTime.now().millisecondsSinceEpoch}';
String get webviewId => _webviewId;
final html.DivElement _twainContainer = html.DivElement();
Future<void> init(String path, String key) async {
ui.platformViewRegistry.registerViewFactory(
_webviewId,
(int id) => _twainContainer
..style.width = '100%'
..style.height = '100%',
);
_webTwainManager.init(path, key);
_twainContainer.id = 'dwtcontrolContainer';
_webTwainManager.createContainer(_twainContainer);
}
Future<void> dispose() async {
_webTwainManager.dispose();
}
Future<void> scan(String config) async {
_webTwainManager.scan(config);
}
Future<void> load() async {
_webTwainManager.load();
}
Future<void> download(int type, String filename) async {
_webTwainManager.download(type, filename);
}
}
Finally, we create a createWebTwainController()
method to instantiate the WebTwainController
and return it to the Flutter app. There are three files need to be modified:
-
flutter_web_twain_platform_interface.dart
Future<WebTwainController?> createWebTwainController() { throw UnimplementedError( 'createWebTwainController() has not been implemented.'); }
-
flutter_web_twain.dart
Future<WebTwainController?> createWebTwainController() { return FlutterWebTwainPlatform.instance.createWebTwainController(); }
-
flutter_web_twain_web.dart
@override Future<WebTwainController> createWebTwainController() { final controller = WebTwainController(); return Future.value(controller); }
Steps to Build Web Document Scanning Applications with Flutter
-
Create a new Flutter project and add the
flutter_web_twain
package.
flutter create app cd app flutter pub add flutter_web_twain
-
Download Dynamic Web TWAIN via the npm command.
npm install dwt
-
Include the JavaScript library in the
index.html
file.
<script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>
-
Create a
ScannerWidget
:Initialize the
WebTwainController
object with the resource path and a valid license key.
import 'package:flutter/services.dart'; import 'package:flutter_web_twain/flutter_web_twain.dart'; import 'package:flutter_web_twain/web_twain_controller.dart'; import 'package:flutter/material.dart'; class ScannerWidget extends StatefulWidget { const ScannerWidget({super.key}); @override State<ScannerWidget> createState() => _ScannerWidgetState(); } class _ScannerWidgetState extends State<ScannerWidget> { final FlutterWebTwain _flutterWebTwain = FlutterWebTwain(); WebTwainController? _controller; PixelFormat? _pixelFormat = PixelFormat.TWPT_BW; FileFormat? _fileFormat = FileFormat.PDF; @override void initState() { super.initState(); _setup(); } @override void dispose() { _controller!.dispose(); super.dispose(); } Future<void> _setup() async { _controller = await _flutterWebTwain.createWebTwainController(); await _controller!.init('node_modules/dwt/dist/', 'LICENSE-KEY'); setState(() {}); } }
Construct the UI, including a
HtmlElementView
widget, sixRadio
widgets, and threeMaterialButton
widgets.
Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Dynamic Web TWAIN for Flutter'), ), body: Column(children: [ Expanded( child: _controller == null ? const CircularProgressIndicator() : HtmlElementView(viewType: _controller!.webviewId), ), SizedBox( child: Row(children: <Widget>[ Radio( value: PixelFormat.TWPT_BW, groupValue: _pixelFormat, onChanged: (PixelFormat? value) { setState(() { _pixelFormat = value; }); }, ), const Text('BW'), Radio( value: PixelFormat.TWPT_GRAY, groupValue: _pixelFormat, onChanged: (PixelFormat? value) { setState(() { _pixelFormat = value; }); }, ), const Text('Gray'), Radio( value: PixelFormat.TWPT_RGB, groupValue: _pixelFormat, onChanged: (PixelFormat? value) { setState(() { _pixelFormat = value; }); }, ), const Text('Color'), const Padding(padding: EdgeInsets.all(10)), MaterialButton( textColor: Colors.white, color: Colors.blue, onPressed: () async { if (_controller != null) { await _controller!.scan( '{"IfShowUI": false, "PixelType": ${_pixelFormat!.index}}'); } }, child: const Text('Scan Documents')), const Padding(padding: EdgeInsets.all(10)), MaterialButton( textColor: Colors.white, color: Colors.blue, onPressed: () async { if (_controller != null) { await _controller!.load(); } }, child: const Text('Load Documents')) ]), ), SizedBox( height: 100, child: Row(children: <Widget>[ Radio( value: FileFormat.PDF, groupValue: _fileFormat, onChanged: (FileFormat? value) { setState(() { _fileFormat = value; }); }, ), const Text('PDF'), Radio( value: FileFormat.TIFF, groupValue: _fileFormat, onChanged: (FileFormat? value) { setState(() { _fileFormat = value; }); }, ), const Text('TIFF'), Radio( value: FileFormat.JPEG, groupValue: _fileFormat, onChanged: (FileFormat? value) { setState(() { _fileFormat = value; }); }, ), const Text('JPEG'), const Padding(padding: EdgeInsets.all(10)), MaterialButton( textColor: Colors.white, color: Colors.blue, onPressed: () async { if (_controller != null) { await _controller! .download(_fileFormat!.index, 'filename'); } }, child: const Text('Download Documents')), ]), ), ])), ); }
-
Edit the
main.dart
file:
void main() { runApp(const ScannerWidget()); }
-
Build and launch the web document scanner application:
# debug flutter run -d chrome # release flutter run -d chrome --release