How to Build Web Document Scanning Applications with Flutter

Xiao Ling - Nov 16 '22 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

To interop JavaScript with Dart, add the js package as the dependency in pubspec.yaml file:

dependencies:
  ...
  js: ^0.6.3
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Create a new Flutter project and add the flutter_web_twain package.

    flutter create app
    cd app
    flutter pub add flutter_web_twain
    
  2. Download Dynamic Web TWAIN via the npm command.

    npm install dwt
    
  3. Include the JavaScript library in the index.html file.

    <script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>
    
  4. 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, six Radio widgets, and three MaterialButton 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')),
                ]),
              ),
            ])),
      );
    }
    
  5. Edit the main.dart file:

    void main() {
      runApp(const ScannerWidget());
    }
    
  6. Build and launch the web document scanner application:

    # debug
    flutter run -d chrome
    # release
    flutter run -d chrome --release
    

    Angular web document scanner

Source Code

https://github.com/yushulx/flutter_web_twain

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player