Ранее я создал flutter_web_twain, чтобы помочь разработчикам создавать веб-приложения для сканирования документов с помощью Flutter и Dynamic Web TWAIN. В этой статье я покажу, как интегрировать Dynamic Web TWAIN в настольное приложение Flutter для Windows с помощью плагина веб-просмотра Flutter Windows.

Раздел 1: Настройка проекта Flutter

  1. Создайте новый проект настольного приложения Flutter
 flutter create web_twain_desktop

2. Установите плагин Flutter webview_windows.

cd web_twain_desktop
flutter pub add webview_windows

3. Создайте папку assets в папке lib и загрузите Dynamic Web TWAIN SDK в папку assets через npm.

cd lib/assets
 npm install dwt

 node_modules:   
 ├── dwt
 │   ├── dist

4. Настройте ресурсы в файле pubspec.yaml.

assets:
     - lib/assets/
     - lib/assets/node_modules/
     - lib/assets/node_modules/dwt/
     - lib/assets/node_modules/dwt/dist/
     - lib/assets/node_modules/dwt/dist/addon/
     - lib/assets/node_modules/dwt/dist/dist/
     - lib/assets/node_modules/dwt/dist/src/
     - lib/assets/node_modules/dwt/dist/types/

Примечание. Поскольку включены только файлы, расположенные непосредственно в каталоге, мы должны добавить все подкаталоги в папке dwt в список ресурсов, чтобы убедиться, что все файлы ресурсов упакованы. Если некоторые файлы ресурсов отсутствуют, Dynamic Web TWAIN может работать неправильно.

Раздел 2: Настройка динамического веб-TWAIN

  1. Dynamic Web TWAIN состоит из сервиса Dynamsoft и библиотек JavaScript. Служба Dynamsoft отвечает за управление связью между JavaScript SDK и сканером. Таким образом, первый шаг — установить node_modules/dwt/dist/dist/DynamsoftServiceSetup.msi в Windows.
  2. Создайте файл index.html в папке с ресурсами.
  3. Включите файл dynamsoft.webtwain.min.js в файл index.html.
<script src="node_modules/dwt/dist/dynamsoft.webtwain.min.js"></script>

4. Подать заявку на получение лицензионного ключа для включения SDK.

Dynamsoft.DWT.ProductKey = "LICENSE-KEY";

5. Укажите путь к ресурсу для SDK.

Dynamsoft.DWT.ResourcesPath = "node_modules/dwt/dist/";

6. Инициализируйте SDK с помощью HTML-элемента div:

<style>
     .container {
         position: absolute;
         top: 10%;
         left: 10;
     }
 </style>
 <div id="document-container" class="container"></div>
 <script>
 var dwtObject = null;
     Dynamsoft.DWT.CreateDWTObjectEx({ "WebTwainId": "container" }, (obj) => {
         dwtObject = obj;

         dwtObject.Viewer.bind(document.getElementById("document-container"));
         dwtObject.Viewer.width = 640;
         dwtObject.Viewer.height = 640;
         dwtObject.Viewer.show();
         onReady();
     }, (errorString) => {
         console.log(errorString);
     });
        
     function onReady() {
         if (dwtObject != null) {

             dwtObject.IfShowUI = false;

             dwtObject.GetDevicesAsync(Dynamsoft.DWT.EnumDWT_DeviceType.TWAINSCANNER | Dynamsoft.DWT.EnumDWT_DeviceType.ESCLSCANNER).then((sources) => {
                 sourceList = sources;

                 for (let i = 0; i < sources.length; i++) {
                     sourceNames.push(sources[i].displayName);
                 }

                 if (sources.length > 0) {
                     if (window.chrome.webview != "undefined") {
                         var param = {
                             "event": "sourceNames",
                             "data": sourceNames
                         }
                         window.chrome.webview.postMessage(param);
                     }
                 }
             });
         }
     }
 </script>

Метод window.chrome.webview.postMessage() используется для отправки сообщений из JavaScript во Flutter. Мы будем использовать этот метод для отправки списка доступных сканеров во Flutter.

7. Добавьте прослушиватель событий для обработки вызовов методов из Flutter.

if (window.chrome.webview != "undefined") {
         window.chrome.webview.addEventListener('message', function (event) {
             let data = JSON.parse(JSON.stringify(event.data));
             if (data.event === "acquire") {
                 acquireImage(data.index);
             }
             else if (data.event === "load") {
                 openImage();
             }
             else if (data.event === "removeAll") {
                 removeAll();
             }
             else if (data.event === "removeSelected") {
                 removeSelected();
             }
             else if (data.event === "download") {
                 downloadDocument();
             }
         });
     }

8. Реализуйте методы в JavaScript:

function removeAll() {
     if (!dwtObject || dwtObject.HowManyImagesInBuffer == 0)
         return;

     dwtObject.RemoveAllImages();
 }

 function removeSelected() {
     if (!dwtObject || dwtObject.HowManyImagesInBuffer == 0)
         return;

     dwtObject.RemoveImage(dwtObject.CurrentImageIndexInBuffer);
 }

 function openImage() {
     if (!dwtObject)
         return;
     dwtObject.Addon.PDF.SetConvertMode(Dynamsoft.DWT.EnumDWT_ConvertMode.CM_RENDERALL);
     let ret = dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL);
 }

 function acquireImage(index) {
     if (!dwtObject)
         return;

     if (sourceList.length > 0) {
         dwtObject.SelectDeviceAsync(sourceList[index]).then(() => {
             return dwtObject.OpenSourceAsync()
         }).then(() => {
             return dwtObject.AcquireImageAsync({
             })
         }).then(() => {
             if (dwtObject) {
                 dwtObject.CloseSource();
             }
         }).catch(
             (e) => {
                 console.error(e)
             }
         )
     }
 }

 function downloadDocument() {
     if (dwtObject) {
         dwtObject.SaveAllAsPDF("DynamicWebTWAIN.pdf");
     }
 }

Раздел 3: Создание пользовательского интерфейса Flutter для сканирования документов

Давайте перейдем к lib/main.dart, чтобы реализовать логику Flutter.

  1. Инициализируйте WebviewController:
function removeAll() {
     if (!dwtObject || dwtObject.HowManyImagesInBuffer == 0)
         return;

     dwtObject.RemoveAllImages();
 }

 function removeSelected() {
     if (!dwtObject || dwtObject.HowManyImagesInBuffer == 0)
         return;

     dwtObject.RemoveImage(dwtObject.CurrentImageIndexInBuffer);
 }

 function openImage() {
     if (!dwtObject)
         return;
     dwtObject.Addon.PDF.SetConvertMode(Dynamsoft.DWT.EnumDWT_ConvertMode.CM_RENDERALL);
     let ret = dwtObject.LoadImageEx("", Dynamsoft.DWT.EnumDWT_ImageType.IT_ALL);
 }

 function acquireImage(index) {
     if (!dwtObject)
         return;

     if (sourceList.length > 0) {
         dwtObject.SelectDeviceAsync(sourceList[index]).then(() => {
             return dwtObject.OpenSourceAsync()
         }).then(() => {
             return dwtObject.AcquireImageAsync({
             })
         }).then(() => {
             if (dwtObject) {
                 dwtObject.CloseSource();
             }
         }).catch(
             (e) => {
                 console.error(e)
             }
         )
     }
 }

 function downloadDocument() {
     if (dwtObject) {
         dwtObject.SaveAllAsPDF("DynamicWebTWAIN.pdf");
     }
 }

2. Найдите путь к файлу index.html и загрузите его в папку WebviewController:

final assetsDirectory = join(dirname(Platform.resolvedExecutable), 'data', 'flutter_assets', "lib/assets/index.html");

 await _controller.loadUrl(Uri.file(assetsDirectory).toString());

3. Добавьте прослушиватель событий для обработки сообщений из JavaScript:

_controller.webMessage.listen((event) {
     if (event['event'] == null) return;

     if (event['event'] == 'sourceNames') {
     _sourceNames.clear();
     for (var item in event['data']) {
         _sourceNames.add(item.toString());
     }

     if (_sourceNames.isNotEmpty) {
         setState(() {
         _selectedItem = _sourceNames[0];
         });
     }
     }
 });

Здесь мы получаем имена физических сканеров из JavaScript и сохраняем их в списке _sourceNames. Мы также устанавливаем первый элемент в списке как элемент, выбранный по умолчанию.

4. Используйте макет Stack, чтобы отобразить веб-представление:

Stack(
     children: [
     Webview(
         _controller,
     ),
     Positioned(
         top: 10,
         left: 10,
         child: Row())]
 )

5. Добавьте DropdownButton, чтобы выбрать физический сканер в Row:

DropdownButton<String>(
     value: _selectedItem,
     items: _sourceNames
         .map<DropdownMenuItem<String>>((String value) {
         return DropdownMenuItem<String>(
         value: value,
         child: Text(value),
         );
     }).toList(),
     onChanged: (String? newValue) {
         if (newValue == null || newValue == '') return;
         setState(() {
         _selectedItem = newValue;
         });
     },
     ),

6. Добавьте пять ElevatedButton для сканирования документа, загрузки изображений, удаления выбранного изображения, удаления всех изображений и сохранения всех полученных изображений в файл PDF.

ElevatedButton(
     onPressed: () async {
     await _controller.postWebMessage(json.encode({
         "event": "acquire",
         "index": _sourceNames.indexOf(_selectedItem)
     }));
     },
     child: const Text("Scan Documents")),
 const SizedBox(
 width: 10,
 height: 10,
 ),
 ElevatedButton(
     onPressed: () async {
     await _controller
         .postWebMessage(json.encode({"event": "load"}));
     },
     child: const Text("Load Documents")),
 const SizedBox(
 width: 10,
 height: 10,
 ),
 ElevatedButton(
     onPressed: () async {
     await _controller.postWebMessage(
         json.encode({"event": "removeSelected"}));
     },
     child: const Text("Remove Selected")),
 const SizedBox(
 height: 10,
 width: 10,
 ),
 ElevatedButton(
     onPressed: () async {
     await _controller.postWebMessage(
         json.encode({"event": "removeAll"}));
     },
     child: const Text("Remove All")),
 const SizedBox(
 width: 10,
 height: 10,
 ),
 ElevatedButton(
     onPressed: () async {
     await _controller
         .postWebMessage(json.encode({"event": "download"}));
     },
     child: const Text("Download Documents"))

7. Запустите настольное приложение Flutter для Windows, чтобы сканировать документы с физического сканера:

flutter run -d windows

Исходный код

https://github.com/yushulx/web-twain-windows-desktop

Первоначально опубликовано на https://www.dynamsoft.com 9 марта 2023 г.