How to Create a Flutter Plugin for Passport MRZ Scan on Windows, Linux, Android, iOS, and Web

Given that Dynamsoft Capture Vision offers support for Windows, Linux, Android, iOS and Web, it is feasible to develop a 5 in 1 Flutter plugin for it to encompass the development of desktop, mobile and web platforms. In this article, you will learn how to write an MRZ Flutter plugin by leveraging different platform-specific code, including Java, Swift, Dart, C++ and JavaScript.

Downloading flutter_ocr_sdk from Pub.dev

https://pub.dev/packages/flutter_ocr_sdk

Initializing the Flutter MRZ Detection Plugin for Multiple Platforms

We create a Flutter plugin project for multiple platforms by executing the following command:

flutter create --org com.dynamsoft --template=plugin --platforms=android,ios,windows,linux,web -a java flutter_ocr_sdk

Cross-Platform Library Linking

The ways of linking third-party libraries in a Flutter plugin project differ across multiple platforms.

Windows and Linux (CMake)

Both platforms use CMake for build configuration. Copy the SDK headers, shared libraries, and resources to the target platform folder and then configure the CMakeLists.txt file.

Windows Configuration


cmake_minimum_required(VERSION 3.14)

set(PROJECT_NAME "flutter_ocr_sdk")
project(${PROJECT_NAME} LANGUAGES CXX)

set(PLUGIN_NAME "flutter_ocr_sdk_plugin")

link_directories("${PROJECT_SOURCE_DIR}/lib/") 

list(APPEND PLUGIN_SOURCES
  "flutter_ocr_sdk_plugin.cpp"
  "flutter_ocr_sdk_plugin.h"
)

add_library(${PLUGIN_NAME} SHARED
  "include/flutter_ocr_sdk/flutter_ocr_sdk_plugin_c_api.h"
  "flutter_ocr_sdk_plugin_c_api.cpp"
  ${PLUGIN_SOURCES}
)

apply_standard_settings(${PLUGIN_NAME})

set_target_properties(${PLUGIN_NAME} PROPERTIES
  CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)

target_include_directories(${PLUGIN_NAME} INTERFACE
  "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin "DynamsoftCorex64" "DynamsoftLicensex64" "DynamsoftCaptureVisionRouterx64" "DynamsoftUtilityx64")

set(flutter_ocr_sdk_bundled_libraries
  "${PROJECT_SOURCE_DIR}/bin/"
  PARENT_SCOPE
)

Linux Configuration

cmake_minimum_required(VERSION 3.10)

set(PROJECT_NAME "flutter_ocr_sdk")
project(${PROJECT_NAME} LANGUAGES CXX)

set(PLUGIN_NAME "flutter_ocr_sdk_plugin")

link_directories("${PROJECT_SOURCE_DIR}/lib/") 

add_library(${PLUGIN_NAME} SHARED
  "flutter_ocr_sdk_plugin.cc"
)

apply_standard_settings(${PLUGIN_NAME})

set_target_properties(${PLUGIN_NAME} PROPERTIES
  CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)

target_include_directories(${PLUGIN_NAME} INTERFACE
  "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter "DynamsoftCore" "DynamsoftLicense" "DynamsoftCaptureVisionRouter" "DynamsoftUtility")
target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK)

set(flutter_ocr_sdk_bundled_libraries
  ""
  PARENT_SCOPE
)

install(DIRECTORY 
  "${PROJECT_SOURCE_DIR}/../linux/lib/"
  DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/"
)

Owing to the fact that the size of the resources is excessively large, a extract_resources.dart file should be created to download the resources from the internet.

import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:http/http.dart' as http;
import 'package:archive/archive.dart';

const _zipUrl =
    'https://github.com/yushulx/flutter_ocr_sdk/releases/download/v2.0.0/resources.zip';

const _zipName = 'resources.zip';

Future<void> download() async {
  final execDir = Directory(p.dirname(Platform.resolvedExecutable));

  final targetDir =
      Platform.isWindows ? execDir : Directory(p.join(execDir.path, 'lib'));
  targetDir.createSync(recursive: true);

  final cacheDir =
      Directory(p.join(Directory.systemTemp.path, 'flutter_ocr_sdk'));
  cacheDir.createSync(recursive: true);
  final zipCacheFile = File(p.join(cacheDir.path, _zipName));

  if (!zipCacheFile.existsSync()) {
    stdout.writeln('Downloading resources.zip to ${zipCacheFile.path} …');
    final resp = await http.get(Uri.parse(_zipUrl));
    if (resp.statusCode != 200) {
      stderr.writeln('Failed to download ($_zipUrl): HTTP ${resp.statusCode}');
      exit(1);
    }
    zipCacheFile.writeAsBytesSync(resp.bodyBytes);
    stdout.writeln('✅ Cached zip in temp.');
  } else {
    stdout.writeln('✅ Using cached zip: ${zipCacheFile.path}');
  }

  final bytes = zipCacheFile.readAsBytesSync();
  final archive = ZipDecoder().decodeBytes(bytes);
  for (final file in archive) {
    final outPath = p.join(targetDir.path, file.name);
    if (file.isFile) {
      final outFile = File(outPath);
      outFile.createSync(recursive: true);
      outFile.writeAsBytesSync(file.content as List<int>);
    } else {
      Directory(outPath).createSync(recursive: true);
    }
  }

  stdout.writeln('✅ Resources unpacked to: ${targetDir.path}');
}

Android (Gradle)

Add the Dynamsoft Maven repository and dependency to android/build.gradle:

group 'com.dynamsoft.flutter_ocr_sdk'
version '1.0'

buildscript {
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.2'
    }
}

rootProject.allprojects {
    repositories {
        maven {
            url "https://download2.dynamsoft.com/maven/aar"
        }
        google()
        mavenCentral()
    }
}

apply plugin: 'com.android.library'

android {
    if (project.android.hasProperty('namespace')) {
        namespace 'com.dynamsoft.flutter_ocr_sdk'
    }

    compileSdkVersion 31

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    defaultConfig {
        minSdkVersion 21
    }
}

dependencies {
    implementation 'com.dynamsoft:mrzscannerbundle:3.0.0'
}

iOS (CocoaPods)

Update ios/flutter_ocr_sdk.podspec to include the SDK dependency:

Pod::Spec.new do |s|
  s.name             = 'flutter_ocr_sdk'
  s.version          = '1.0.0'
  s.summary          = 'A wrapper for Dynamsoft OCR SDK, detecting MRZ in passports, travel documents, and ID cards.'
  s.description      = <<-DESC
A wrapper for Dynamsoft OCR SDK, detecting MRZ in passports, travel documents, and ID cards.
                       DESC
  s.homepage         = 'https://github.com/yushulx/flutter_ocr_sdk'
  s.license          = { :file => '../LICENSE' }
  s.author           = { 'yushulx' => 'lingxiao1002@gmail.com' }
  s.source           = { :path => '.' }
  s.source_files = 'Classes/**/*'
  s.dependency 'Flutter'
  s.platform = :ios, '9.0'
  s.dependency 'DynamsoftMRZScannerBundle', '3.0.0'
  # Flutter.framework does not contain a i386 slice.
  s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
  s.swift_version = '5.0'
end

Web (JavaScript)

Include the Dynamsoft Capture Vision JavaScript library in index.html:

<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-bundle@2.6.1000/dist/dcv.bundle.min.js"></script>

Dart API Implementation

Platform Interface Definition

Define the cross-platform interface in flutter_ocr_sdk_platform_interface.dart:

abstract class FlutterOcrSdkPlatform extends PlatformInterface {
  FlutterOcrSdkPlatform() : super(token: _token);

  static final Object _token = Object();

  static FlutterOcrSdkPlatform _instance = MethodChannelFlutterOcrSdk();

  static FlutterOcrSdkPlatform get instance => _instance;

  static set instance(FlutterOcrSdkPlatform instance) {
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  Future<int?> init(String path, String key) {
    throw UnimplementedError('init() has not been implemented.');
  }

  Future<List<List<MrzLine>>?> recognizeByBuffer(
      Uint8List bytes, int width, int height, int stride, int format) {
    throw UnimplementedError('recognizeByBuffer() has not been implemented.');
  }

  Future<List<List<MrzLine>>?> recognizeByFile(String filename) {
    throw UnimplementedError('recognizeByFile() has not been implemented.');
  }

  Future<int?> loadModel() async {
    throw UnimplementedError('loadModel() has not been implemented.');
  }
}

  • init(): initialize the SDK with the license key of Dynamsoft Capture Vision.
  • loadModel(): load the MRZ recognition model.
  • recognizeBuffer(): recognize the MRZ from the image buffer.
  • recognizeFile(): recognize the MRZ from the image file.

The flutter_ocr_sdk_web.dart file implements the methods for web, and the flutter_ocr_sdk_method_channel.dart file implements the methods for Android, iOS, Windows and Linux.

Web Implementation (flutter_ocr_sdk_web.dart)

  1. In the web_dlr_manager.dart file, declare the JavaScript properties and functions to be called:

     @JS('Dynamsoft')
     library dynamsoft;
        
     import 'package:flutter_ocr_sdk/template.dart';
        
     import 'vin_result.dart';
     import 'model_type.dart';
     import 'mrz_result.dart';
     import 'ocr_line.dart';
     import 'dart:async';
     import 'dart:typed_data';
     import 'package:js/js.dart';
     import 'utils.dart';
     import 'dart:js_util';
        
     @JS()
     @anonymous
     class RecognitionResult {
       external String get codeType;
       external String get jsonString;
       external String getFieldValue(String field);
     }
        
     @JS()
     @anonymous
     class CapturedResult {
       external List<CapturedItem> get items;
     }
        
     @JS()
     @anonymous
     class CapturedItem {
       external int get type;
       external String get text;
       external String get formatString;
       external Location get location;
       external int get angle;
       external Uint8List get bytes;
       external int get confidence;
     }
        
     @JS()
     @anonymous
     class Location {
       external List<Point> get points;
     }
        
     @JS()
     @anonymous
     class Point {
       external num get x;
       external num get y;
     }
        
     @JS('License.LicenseManager')
     class LicenseManager {
       external static PromiseJsImpl<void> initLicense(
           String license, bool executeNow);
     }
        
     @JS('Core.CoreModule')
     class CoreModule {
       external static PromiseJsImpl<void> loadWasm(List<String> modules);
     }
        
     @JS('DCP.CodeParserModule')
     class CodeParserModule {
       external static PromiseJsImpl<void> loadSpec(String resource);
     }
        
     @JS('DLR.LabelRecognizerModule')
     class LabelRecognizerModule {
       external static PromiseJsImpl<void> loadRecognitionData(String resource);
     }
        
     @JS('DCP.CodeParser')
     class CodeParser {
       external static PromiseJsImpl<CodeParser> createInstance();
       external PromiseJsImpl<RecognitionResult> parse(String text);
     }
    
  2. Create a CaptureVisionManager class to interoperate with the JavaScript library:

     class CaptureVisionManager {
       CaptureVisionRouter? _cvr;
       CodeParser? _parser;
       String modelName = '';
        
       Future<int> init(String key) async {
         try {
           await handleThenable(LicenseManager.initLicense(key, true));
           await handleThenable(CoreModule.loadWasm(["DLR"]));
           _parser = await handleThenable(CodeParser.createInstance());
        
           await handleThenable(CodeParserModule.loadSpec("VIN"));
           await handleThenable(LabelRecognizerModule.loadRecognitionData("VIN"));
        
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD1_ID"));
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_FRENCH_ID"));
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_ID"));
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD2_VISA"));
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD3_PASSPORT"));
           await handleThenable(CodeParserModule.loadSpec("MRTD_TD3_VISA"));
           await handleThenable(LabelRecognizerModule.loadRecognitionData("MRZ"));
        
           _cvr = await handleThenable(CaptureVisionRouter.createInstance());
        
           await handleThenable(_cvr!.initSettings(template));
         } catch (e) {
           print(e);
           return -1;
         }
        
         return 0;
       }
        
       Future<List<List<OcrLine>>?> recognizeFile(String file) async {
         CapturedResult capturedResult =
             await handleThenable(_cvr!.capture(file, modelName));
        
         return await _resultWrapper(capturedResult.items);
       }
        
       Future<List<List<OcrLine>>?> recognizeBuffer(Uint8List bytes, int width,
           int height, int stride, int format, int rotation) async {
         final dsImage = jsify({
           'bytes': bytes,
           'width': width,
           'height': height,
           'stride': stride,
           'format': format,
           'orientation': rotation
         });
        
         CapturedResult capturedResult =
             await handleThenable(_cvr!.capture(dsImage, modelName));
        
         return await _resultWrapper(capturedResult.items);
       }
        
       Future<int?> loadModel(ModelType modelType) async {
         if (modelType == ModelType.mrz) {
           modelName = "ReadMRZ";
         } else {
           modelName = "ReadVINText";
         }
         return 0;
       }
        
       Future<List<List<OcrLine>>> _resultWrapper(List<dynamic> results) async {
         List<List<OcrLine>> output = [];
         List<OcrLine> lines = [];
         for (CapturedItem result in results) {
           if (result.type != 4) continue;
           OcrLine line = OcrLine();
           line.confidence = result.confidence;
           line.text = result.text;
           line.x1 = result.location.points[0].x.toInt();
           line.y1 = result.location.points[0].y.toInt();
           line.x2 = result.location.points[1].x.toInt();
           line.y2 = result.location.points[1].y.toInt();
           line.x3 = result.location.points[2].x.toInt();
           line.y3 = result.location.points[2].y.toInt();
           line.x4 = result.location.points[3].x.toInt();
           line.y4 = result.location.points[3].y.toInt();
        
           RecognitionResult data =
               await handleThenable(_parser!.parse(result.text));
        
           if (modelName == "ReadMRZ") {
             String type = data.getFieldValue("documentCode");
             String mrzString = line.text;
             String docType = data.codeType;
             String nationality = data.getFieldValue("nationality");
             String surname = data.getFieldValue("primaryIdentifier");
             String givenName = data.getFieldValue("secondaryIdentifier");
             String docNumber = type == "P"
                 ? data.getFieldValue("passportNumber")
                 : data.getFieldValue("documentNumber");
             String issuingCountry = data.getFieldValue("issuingState");
             String birthDate = data.getFieldValue("dateOfBirth");
             String gender = data.getFieldValue("sex");
             String expiration = data.getFieldValue("dateOfExpiry");
        
             MrzResult mrzResult = MrzResult(
                 type: docType,
                 nationality: nationality,
                 surname: surname,
                 givenName: givenName,
                 docNumber: docNumber,
                 issuingCountry: issuingCountry,
                 birthDate: birthDate,
                 gender: gender,
                 expiration: expiration,
                 lines: mrzString);
             line.mrzResult = mrzResult;
           }
        
           lines.add(line);
         }
         output.add(lines);
         return output;
       }
     }
        
    

Mobile/Desktop Implementation (flutter_ocr_sdk_method_channel.dart)

@override
Future<int?> init(String key) async {
  if (Platform.isLinux || Platform.isWindows) {
    await download();
  }
  return await methodChannel.invokeMethod<int>('init', {'key': key});
}

@override
Future<List<List<OcrLine>>?> recognizeBuffer(Uint8List bytes, int width,
    int height, int stride, int format, int rotation) async {
  List<dynamic>? results =
      await methodChannel.invokeMethod('recognizeBuffer', {
    'bytes': bytes,
    'width': width,
    'height': height,
    'stride': stride,
    'format': format,
    'rotation': rotation
  });

  if (results == null || results.isEmpty) return [];

  return _resultWrapper(results);
}

@override
Future<List<List<OcrLine>>?> recognizeFile(String filename) async {
  List<dynamic>? results = await methodChannel.invokeMethod('recognizeFile', {
    'filename': filename,
  });

  if (results == null || results.isEmpty) return [];

  return _resultWrapper(results);
}

List<List<OcrLine>> _resultWrapper(List<dynamic> data) {
  List<List<OcrLine>> results = [];

  for (List<dynamic> area in data) {
    List<OcrLine> lines = [];
    for (int i = 0; i < area.length; i++) {
      OcrLine line = OcrLine();
      Map<dynamic, dynamic> map = area[i];
      if (map.isEmpty) continue;
      line.confidence = map['confidence'];
      line.text = map['text'];
      line.x1 = map['x1'];
      line.y1 = map['y1'];
      line.x2 = map['x2'];
      line.y2 = map['y2'];
      line.x3 = map['x3'];
      line.y3 = map['y3'];
      line.x4 = map['x4'];
      line.y4 = map['y4'];

      if (map['type'] == 'MRZ') {
        String mrzString = map['mrzString'];
        String docType = map["docType"];
        String nationality = map["nationality"];
        String surname = map["surname"];
        String givenName = map["givenName"];
        String docNumber = map["docNumber"];
        String issuingCountry = map["issuingCountry"];
        String birthDate = map["birthDate"];
        String gender = map["gender"];
        String expiration = map["expiration"];

        MrzResult mrzResult = MrzResult(
            type: docType,
            nationality: nationality,
            surname: surname,
            givenName: givenName,
            docNumber: docNumber,
            issuingCountry: issuingCountry,
            birthDate: birthDate,
            gender: gender,
            expiration: expiration,
            lines: mrzString);
        line.mrzResult = mrzResult;
      } else if (map['type'] == 'VIN') {
        String vinString = map['vinString'];
        String wmi = map['wmi'];
        String region = map['region'];
        String vds = map['vds'];
        String checkDigit = map['checkDigit'];
        String modelYear = map['modelYear'];
        String plantCode = map['plantCode'];
        String serialNumber = map['serialNumber'];

        VinResult vinResult = VinResult(
            vinString: vinString,
            wmi: wmi,
            region: region,
            vds: vds,
            checkDigit: checkDigit,
            modelYear: modelYear,
            plantCode: plantCode,
            serialNumber: serialNumber);

        line.vinResult = vinResult;
      }

      lines.add(line);
    }

    results.add(lines);
  }

  return results;
}

@override
Future<int?> loadModel({ModelType modelType = ModelType.mrz}) async {
  String templateName = "ReadPassportAndId";

  if (modelType == ModelType.vin) {
    templateName = "ReadVINText";
  }

  int ret = await methodChannel
      .invokeMethod('loadModel', {'template': templateName});
  return ret;
}

Native Platform Implementations

In the aforementioned section, we have implemented the MRZ recognition logic in Dart. For web applications, the Dart code can be directly utilized. At present, we will write Java, Swift, and C++ code for Android, iOS, Windows, and Linux platforms.

Android (Java)

public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
  switch (call.method) {
    case "init": {
      final String license = call.argument("key");
      mOCRManager.init(license, activity, result);
      break;
    }
    case "recognizeFile": {
      final String filename = call.argument("filename");
      final Result r = result;
      mExecutor.execute(new Runnable() {
        @Override
        public void run() {
          final ArrayList<ArrayList<HashMap<String, Object>>> results = mOCRManager.recognizeFile(filename);
          mHandler.post(new Runnable() {
            @Override
            public void run() {
              r.success(results);
            }
          });

        }
      });
    }
    break;
    case "recognizeBuffer": {
      final byte[] bytes = call.argument("bytes");
      final int width = call.argument("width");
      final int height = call.argument("height");
      final int stride = call.argument("stride");
      final int format = call.argument("format");
      final int rotation = call.argument("rotation");
      final Result r = result;
      mExecutor.execute(new Runnable() {
        @Override
        public void run() {
          final ArrayList<ArrayList<HashMap<String, Object>>> results = mOCRManager.recognizeBuffer(bytes, width, height, stride, format, rotation);
          mHandler.post(new Runnable() {
            @Override
            public void run() {
              r.success(results);
            }
          });

        }
      });
    }
    break;
    case "loadModel": {
      final String name = call.argument("template");
      mOCRManager.loadModel(name);
      result.success(0);
    }
    break;
    default:
      result.notImplemented();
  }
}

iOS (Swift)

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let arguments: NSDictionary = call.arguments as! NSDictionary
    switch call.method {
    case "init":
        completionHandlers.append(result)
        let license: String = arguments.value(forKey: "key") as! String
        LicenseManager.initLicense(license, verificationDelegate: self)
    case "loadModel":
        let name: String = arguments.value(forKey: "template") as! String
        templateName = name
        result(0)
    case "recognizeFile":
        DispatchQueue.global().async {
            let filename: String = arguments.value(forKey: "filename") as! String
            let res = self.cvr.captureFromFile(filename, templateName: self.templateName)
            result(self.wrapResults(result: res))
        }
    case "recognizeBuffer":
        DispatchQueue.global().async {
            let buffer: FlutterStandardTypedData =
                arguments.value(forKey: "bytes") as! FlutterStandardTypedData
            let width: Int = arguments.value(forKey: "width") as! Int
            let height: Int = arguments.value(forKey: "height") as! Int
            let stride: Int = arguments.value(forKey: "stride") as! Int
            let format: Int = arguments.value(forKey: "format") as! Int
            let rotation: Int = arguments.value(forKey: "rotation") as! Int
            let enumImagePixelFormat = ImagePixelFormat(rawValue: format)
            let imageData = ImageData.init()
            imageData.bytes = buffer.data
            imageData.width = UInt(width)
            imageData.height = UInt(height)
            imageData.stride = UInt(stride)
            imageData.format = enumImagePixelFormat!
            imageData.orientation = rotation
            let res = self.cvr.captureFromBuffer(imageData, templateName: self.templateName)
            result(self.wrapResults(result: res))
        }
    default:
        result(.none)
    }
}

Windows(C++)

void FlutterOcrSdkPlugin::HandleMethodCall(
      const flutter::MethodCall<flutter::EncodableValue> &method_call,
      std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result)
{

  const auto *arguments = std::get_if<EncodableMap>(method_call.arguments());

  if (method_call.method_name().compare("init") == 0)
  {
    std::string license;
    int ret = 0;

    if (arguments)
    {
      auto license_it = arguments->find(EncodableValue("key"));
      if (license_it != arguments->end())
      {
        license = std::get<std::string>(license_it->second);
      }
      ret = manager->Init(license.c_str());
    }

    result->Success(EncodableValue(ret));
  }
  else if (method_call.method_name().compare("loadModel") == 0)
  {
    std::string path, params;
    int ret = 0;

    if (arguments)
    {
      auto params_it = arguments->find(EncodableValue("template"));
      if (params_it != arguments->end())
      {
        params = std::get<std::string>(params_it->second);
      }

      ret = manager->LoadModel(params);
    }

    result->Success(EncodableValue(ret));
  }
  else if (method_call.method_name().compare("recognizeFile") == 0)
  {
    std::string filename;
    EncodableList results;

    if (arguments)
    {
      auto filename_it = arguments->find(EncodableValue("filename"));
      if (filename_it != arguments->end())
      {
        filename = std::get<std::string>(filename_it->second);
      }

      manager->RecognizeFile(result, filename.c_str());
    }
  }
  else if (method_call.method_name().compare("recognizeBuffer") == 0)
  {
    EncodableList results;

    std::vector<unsigned char> bytes;
    int width = 0, height = 0, stride = 0, format = 0, rotation = 0;

    if (arguments)
    {
      auto bytes_it = arguments->find(EncodableValue("bytes"));
      if (bytes_it != arguments->end())
      {
        bytes = std::get<vector<unsigned char>>(bytes_it->second);
      }

      auto width_it = arguments->find(EncodableValue("width"));
      if (width_it != arguments->end())
      {
        width = std::get<int>(width_it->second);
      }

      auto height_it = arguments->find(EncodableValue("height"));
      if (height_it != arguments->end())
      {
        height = std::get<int>(height_it->second);
      }

      auto stride_it = arguments->find(EncodableValue("stride"));
      if (stride_it != arguments->end())
      {
        stride = std::get<int>(stride_it->second);
      }

      auto format_it = arguments->find(EncodableValue("format"));
      if (format_it != arguments->end())
      {
        format = std::get<int>(format_it->second);
      }

      auto rotation_it = arguments->find(EncodableValue("rotation"));
      if (rotation_it != arguments->end())
      {
        rotation = std::get<int>(rotation_it->second);
      }
      manager->RecognizeBuffer(result, reinterpret_cast<unsigned char *>(bytes.data()), width, height, stride, format, rotation);
    }
  }
  else
  {
    result->NotImplemented();
  }
}

Linux (C++)

static void flutter_ocr_sdk_plugin_handle_method_call(
    FlutterOcrSdkPlugin *self,
    FlMethodCall *method_call)
{
  bool isAsync = false;
  g_autoptr(FlMethodResponse) response = nullptr;

  const gchar *method = fl_method_call_get_name(method_call);
  FlValue *args = fl_method_call_get_args(method_call);

  if (strcmp(method, "init") == 0)
  {
    if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
    {
      return;
    }

    FlValue *value = fl_value_lookup_string(args, "key");
    if (value == nullptr)
    {
      return;
    }
    const char *license = fl_value_get_string(value);

    int ret = self->manager->Init(license);
    g_autoptr(FlValue) result = fl_value_new_int(ret);
    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  }
  else if (strcmp(method, "loadModel") == 0)
  {
    if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
    {
      return;
    }

    FlValue *value = fl_value_lookup_string(args, "template");
    if (value == nullptr)
    {
      return;
    }
    const char *params = fl_value_get_string(value);

    std::string str(params);
    int ret = self->manager->LoadModel(str);

    g_autoptr(FlValue) result = fl_value_new_int(ret);
    response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
  }
  else if (strcmp(method, "recognizeFile") == 0)
  {
    isAsync = true;
    if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
    {
      return;
    }

    FlValue *value = fl_value_lookup_string(args, "filename");
    if (value == nullptr)
    {
      return;
    }
    const char *filename = fl_value_get_string(value);

    self->manager->RecognizeFile(method_call, filename);
  }
  else if (strcmp(method, "recognizeBuffer") == 0)
  {
    isAsync = true;
    if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP)
    {
      return;
    }

    FlValue *value = fl_value_lookup_string(args, "bytes");
    if (value == nullptr)
    {
      return;
    }
    unsigned char *bytes = (unsigned char *)fl_value_get_uint8_list(value);

    value = fl_value_lookup_string(args, "width");
    if (value == nullptr)
    {
      return;
    }
    int width = fl_value_get_int(value);

    value = fl_value_lookup_string(args, "height");
    if (value == nullptr)
    {
      return;
    }
    int height = fl_value_get_int(value);

    value = fl_value_lookup_string(args, "stride");
    if (value == nullptr)
    {
      return;
    }
    int stride = fl_value_get_int(value);

    value = fl_value_lookup_string(args, "format");
    if (value == nullptr)
    {
      return;
    }
    int format = fl_value_get_int(value);

    value = fl_value_lookup_string(args, "rotation");
    if (value == nullptr)
    {
      return;
    }
    int rotation = fl_value_get_int(value);

    self->manager->RecognizeBuffer(method_call, bytes, width, height, stride, format, stride * height, rotation);
  }
  else
  {
    response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
  }

  if (!isAsync)
  {
    fl_method_call_respond(method_call, response, nullptr);
  }
}

Full-Stack Application Example

  1. Add dependencies to pubspec.yaml:

     dependencies:
       ...
    
       cupertino_icons: ^1.0.2
       image_picker: ^1.0.0
       shared_preferences: ^2.1.1
       camera: 
         path: ./camera/
       camera_windows: 
         path: ./windows_camera/
       share_plus: ^10.0.2
       url_launcher: ^6.1.11
       flutter_exif_rotation: ^0.5.1
       flutter_lite_camera:
    
  2. Create a stateful widget:

     import 'dart:typed_data';
    
     import 'package:file_selector/file_selector.dart';
     import 'package:flutter/material.dart';
     import 'dart:async';
    
     import 'dart:io';
    
     import 'package:flutter_ocr_sdk/flutter_ocr_sdk.dart';
     import 'package:flutter_ocr_sdk/flutter_ocr_sdk_platform_interface.dart';
     import 'package:flutter_ocr_sdk/mrz_line.dart';
     import 'package:flutter_ocr_sdk/mrz_parser.dart';
     import 'package:image_picker/image_picker.dart';
    
     import 'package:flutter/foundation.dart' show kIsWeb;
    
     import 'dart:ui' as ui;
    
     Future<void> main() async {
       runApp(
         MaterialApp(
           title: 'MRZ OCR',
           home: Scaffold(
             appBar: AppBar(
               title: const Text("MRZ OCR"),
             ),
             body: MRZApp(),
           ),
         ),
       );
     }
    
     class MRZApp extends StatefulWidget {
       @override
       MobileState createState() => MobileState();
     }
    
  3. Initialize the MRZ scanner plugin with a valid license key:

     class MobileState extends State<MRZApp> {
       late FlutterOcrSdk _mrzDetector;
       final picker = ImagePicker();
    
       @override
       void initState() {
         super.initState();
    
         initSDK();
       }
    
       Future<int> initSDK() async {
           int? ret = await detector.init(
               "LICENSE-KEY");
            
           if (ret == 0) isLicenseValid = true;
           return await detector.loadModel(modelType: model) ?? -1;
         }
     }
    
  4. Build the UI:

     Widget build(BuildContext context) {
       double width = MediaQuery.of(context).size.width;
       double height = MediaQuery.of(context).size.height;
       double left = 5;
       double mrzHeight = 50;
       double mrzWidth = width - left * 2;
       return Scaffold(
         body: Stack(children: [
           Center(
             child: Column(
                 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                 children: [
                   MaterialButton(
                     textColor: Colors.white,
                     color: Colors.blue,
                     onPressed: () async {
                       showDialog(
                           context: context,
                           builder: (BuildContext context) {
                             return const Center(
                               child: CircularProgressIndicator(),
                             );
                           });
                       pictureScan('gallery');
                     },
                     child: const Text('Pick gallery image'),
                   ),
                   MaterialButton(
                     textColor: Colors.white,
                     color: Colors.blue,
                     onPressed: () async {
                       showDialog(
                           context: context,
                           builder: (BuildContext context) {
                             return const Center(
                               child: CircularProgressIndicator(),
                             );
                           });
                       pictureScan('camera');
                     },
                     child: const Text('Pick camera image'),
                   ),
                 ]),
           )
         ]),
       );
     }
    
  5. Press the button to load an image file from the local file system and then recognize MRZ from the image:

     void pictureScan(String source) async {
       XFile? photo;
       if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
         if (source == 'camera') {
           photo = await picker.pickImage(source: ImageSource.camera);
         } else {
           photo = await picker.pickImage(source: ImageSource.gallery);
         }
       } else if (Platform.isWindows || Platform.isLinux) {
         const XTypeGroup typeGroup = XTypeGroup(
           label: 'images',
           extensions: <String>['jpg', 'png', 'bmp', 'tiff', 'pdf', 'gif'],
         );
         photo = await openFile(acceptedTypeGroups: <XTypeGroup>[typeGroup]);
       }
    
       if (photo == null) {
         if (!mounted) return;
         Navigator.pop(context);
         return;
       }
    
       String information = 'No results';
    
       List<List<MrzLine>>? results =
           await _mrzDetector.recognizeByFile(photo.path);
       if (results != null && results.isNotEmpty) {
         for (List<MrzLine> area in results) {
           if (area.length == 2) {
             information =
                 MRZ.parseTwoLines(area[0].text, area[1].text).toString();
           } else if (area.length == 3) {
             information = MRZ
                 .parseThreeLines(area[0].text, area[1].text, area[2].text)
                 .toString();
           }
         }
       }
    
       if (!mounted) return;
       Navigator.pop(context);
       Navigator.push(
         context,
         MaterialPageRoute(
           builder: (context) => DisplayPictureScreen(
               imagePath: photo!.path, mrzInformation: information),
         ),
       );
     }
    
  6. Display the MRZ information on a stateless widget:

     Image getImage(String imagePath) {
       if (kIsWeb) {
         return Image.network(imagePath);
       } else {
         return Image.file(
           File(imagePath),
           fit: BoxFit.contain,
           height: double.infinity,
           width: double.infinity,
           alignment: Alignment.center,
         );
       }
     }
     class DisplayPictureScreen extends StatelessWidget {
       final String imagePath;
       final String mrzInformation;
    
       const DisplayPictureScreen(
           {Key? key, required this.imagePath, required this.mrzInformation})
           : super(key: key);
    
       @override
       Widget build(BuildContext context) {
         return Scaffold(
           appBar: AppBar(title: const Text('MRZ OCR')),
           body: Stack(
             alignment: const Alignment(0.0, 0.0),
             children: [
               getImage(imagePath),
               Container(
                 decoration: const BoxDecoration(
                   color: Colors.black45,
                 ),
                 child: Text(
                   mrzInformation,
                   style: const TextStyle(
                     fontSize: 14,
                     color: Colors.white,
                   ),
                 ),
               ),
             ],
           ),
         );
       }
     }
    
  7. Run the app on web, Windows, Linux, Android and iOS platforms.

    Web

    Flutter MRZ recognition in web

    Windows and Linux

    Flutter MRZ OCR in Windows

    Android and iOS

    Flutter Passport MRZ recognition

Source Code

https://github.com/yushulx/flutter_ocr_sdk