Design Library Pixelnetica™ Document Scanning SDK for Android

The design library contains a set of ready-to-use UI controls.

Include in the project

Put into your module-level build script:

dependencies {
    implementation("com.pixelnetica.sdk:design:3.0.1")
    .....
}

Or, if you use a Version Catalog:

dependencies {
  implementation(libs.pixelnetica.design)
  .....
}

Usage

To add these controls to your application, follow these steps:

  1. Inherit an activity that contains these controls from AppCompatActivity (or a derived class).
  2. Inherit your application (or activity) theme from Theme.ScanningSdk (see the remark below if it is not possible).
  3. Define the following colors in your theme and change their color values as desired:

    <!-- NOTE: Theme "MyApp" derives "Theme.ScanningSdk" -->
    <style name="Theme.ScanningSdk.MyApp">
     <item name="colorPrimary">#FF616161</item>
     <item name="colorPrimaryContainer">#FF3f51b5</item>
     <item name="colorSecondary">#FF3f51b5</item>
     <item name="android:colorBackground">#FFF5F5F5</item>
     <item name="colorOnBackground">#de000000</item>
     <item name="colorOnSurfaceVariant">#FF616161</item>
    </style>
    
  4. If it is not possible to inherit your theme directly from Theme.ScanningSdk, you can define a theme inherited from Theme.ScanningSdk and reference your main theme as shown below:

    <style name="MyAppTheme" parent="....">
     <!-- Add reference to your ScanningSdk theme -->
     <item name="isScanningSdkTheme">@Theme.ScanningSdk.MyApp</item>
     .....
    </style>
    <style name="Theme.ScanningSdk.MyApp">
     <!-- As shown above -->
     .....
    </style>
    
  5. If you use a Compose application, add the following to your Compose theme to support screen mode changing and dynamic colors:

    @Composable
    fun MyAppTheme(.....) {
     .....
     // Apply dynamic colors to ScanningSDK before changing night mode
     ScanningSdkLibrary.enableDynamicColors(dynamicColor)
    
     // Apply dark theme to XML layouts
     AppCompatDelegate.setDefaultNightMode(
         if (darkTheme) {
             AppCompatDelegate.MODE_NIGHT_YES
         } else {
             AppCompatDelegate.MODE_NIGHT_NO
         }
     )
     .....
    }
    

Using CropFragment with XML Views

This component can be used to display images, manually edit document boundaries, and rotate images using XML Views.

Perform the following:

  1. Insert the fragment into your layout. For example:

    <!-- android:tag is optional. You can use any other method to identify the fragment. -->
    <androidx.fragment.app.FragmentContainerView
         android:id="@+id/fragment_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:name="com.pixelnetica.design.crop.CropFragment"
         android:tag="com.pixelnetica.design.crop.CropFragment"
         ....
     />
    
  2. Prepare a class that derives from CropHandler. The easiest way is to derive your ViewModel from CropHandler. Implement CropHandler methods and properties:

    1. val picture: Flow<ScanPicture?>
      Provides a flow with pictures to show or null to show nothing. The orientation of the picture is ignored. See below.

    2. val orientation: Flow<ScanOrientation?>
      Provides a flow with the current orientation of the picture.

    3. val cutout: Flow<ScanCutout?>
      Provides a flow with the current cutout or null to show nothing.

    4. fun onPictureReady(pictureReady: Boolean)
      This callback is called when the picture is shown on the screen.

    5. fun onCutoutChanged(cutout: ScanCutout?)
      This callback is called when the current cutout has been changed by a user. You need to update the CropHandler.cutout flow.

  3. Attach your CropHandler to the CropFragment by calling CropFragment.setHandler. For example:

    // We suppose that viewModel derives CropHandler.
    // We set the tag in XML.
    (findFragmentByTag("com.pixelnetica.design.crop.CropFragment") as CropFragment).setHandler(viewModel)
    

Using CropPicture with Jetpack Compose

This component can be used to display images, manually edit document boundaries, and rotate images using Jetpack Compose.

You can use the Composable function CropPicture with arguments similar to those described below:

@Composable
fun CropPicture(
    modifier: Modifier = Modifier,
    picture: ScanPicture?,
    orientation: ScanOrientation?,
    cutout: ScanCutout?,
    onPictureReady: (Boolean) -> Unit = { },
    onCutoutChanged: (ScanCutout?) -> Unit
)

Set up the LanguageManager

This section explains how to configure the initial language source (e.g., a server). This setup is required for language-related UI components (LangFragment/LanguagesSelector and ReadFragment/RecognizedText).

Prerequisites: The Document Scanning SDK (DSSDK) doesn’t provide a remote server. You need to manage the server yourself and place the language files provided by the DSSDK. More details on how to set up languages on own server.

Store the URL to your language server as urlToServer.

Before using a language list UI, you need to set up the LanguageManager. You can access the manager by calling:

val languageManager = LanguageManager.getInstance(context)

Next, you need to specify at least one language source:

  1. Load the language list from a remote server urlToServer:

    languageManager.requestServer(urlToServer, 1)
    
  2. Load a language list from application assets. This is useful for the most popular languages, such as English. You can store the language file in the assets of your application:

    languageManager.requestAssets(pathToAsset, 2)
    

The second parameter lets us control the sources is “source code”:

  1. Delete all language archives with specified codes. The method can be used when you want to change the language server, and you need to delete archives that came from the old server:

    languageManager.deleteArchives(code)
    
  2. Keep archives only with specified codes and delete the rest:

    languageManager.ensureArchives(codes)
    
  3. Clean up removed languages to install the predefined (marked with -->) languages again:

    languageManager.deleteRemoved()
    

When you have set up the Language Repository, you can use it with ScanDetector and ScanReader:

  1. Use the detector path when you create an instance of ScanDetector:

    launch {
       val detectorPath = LanguageManager
          .getInstance(context)
          .detectorPath
          .first!!
    
       val orientationDetector = ScanDetector(detectorPath)
       picture.detectOrientation(orientationDetector)
    }
    
  2. Use the LanguageStore (directory plus a set of selected languages) when you create an instance of ScanReader:

    launch {
       val languageStore = LanguageManager
          .getInstance(context)
          .languageStore
          .first
          
       val scanReader = ScanReader(languageStore.directory, languageStore.languages)
    
       picture.read(scanReader) 
    }
    

Using the LangFragment with XML Views

This component can be used to manage and edit language lists using XML Views.

Perform the following:

  1. Insert the fragment in your layout. For example:

    <!-- android:tag is optional. You can use any other method to identify the fragment. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_host"
        android:name="com.pixelnetica.design.lang.ui.LangFragment"
        android:tag="com.pixelnetica.design.lang.ui.LangFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
    
  2. Set up the desired behavior:

    1. Get access to the fragment:

      // We have set the tag in XML.
      languageFragment = findFragmentByTag<LangFragment>("com.pixelnetica.design.lang.ui.LangFragment") as LangFragment
      
    2. You can specify an “exclusive” mode: hide the language list when a user expands archives:

      languageFragment.setExclusive(false)
      
    3. You can show or hide the archive list from the code:

      languageFragment.showArchives(true)
      

Using the LanguagesSelector with Jetpack Compose

This component can be used to manage and edit language lists using Jetpack Compose.

You can use the Composable function LanguagesSelector:

@Composable
fun LanguagesSelector(
    modifier: Modifier = Modifier,
    exclusiveMode: Boolean = false,
)

You can enable exclusiveMode to hide the language list when a user expands archives. Only the user can collapse or expand this list. This cannot be done with the API.

Using the ReadFragment with XML Views

This component enables users to edit recognition results using XML Views.

  1. Insert the fragment into your layout. For example:

    <!-- android:tag is optional. You can use any other method to identify the fragment. -->
    <androidx.fragment.app.FragmentContainerView
         android:id="@+id/fragment_container"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:name="com.pixelnetica.design.read.ReadFragment"
         android:tag="com.pixelnetica.design.read.ReadFragment"
         ....
     />
    
  2. Prepare a class that derives from ReadHandler. The easiest way is to derive your ViewModel from ReadHandler. Implement ReadHandler methods and properties:

    1. val picture: Flow<ScanPicture?>
      Provides a flow with pictures to show or null to show nothing.

    2. val lookupRect: Flow<RectF?>
      Provides the recognition item rectangle or null when recognition is done. You can get this value from com.pixelnetica.scanning.ScanReader.ProgressCallback.onProgress.

    3. val lookupProgress: Flow<Int>
      Provides the recognition state: -1 when the image is analyzed, from 0 to 70 when words are recognized. You can get this value from com.pixelnetica.scanning.ScanReader.ProgressCallback.onProgress.

    4. val originalText: Flow<ScanText>
      Provides the recognition result. This value is used to restore text after user editing.

    5. val modifiedText: Flow<ScanText>
      Provides text modified by the user.

    6. fun onCancel()
      Called when the user presses the X button during recognition.

    7. fun onConfirmRestore(confirmRestore: ConfirmRestore)
      Called when a user wants to restore the text back to the original. You can show a question box to the user.

    8. fun onPictureReady(pictureReady: Boolean)
      Called when the picture is shown on the display.

    9. fun onModifiedTextChanged(modifiedText: ScanText)
      Called when the user edits the recognized text. You need to store this text in your application and return it back through the modifiedText flow.

  3. Attach your ReadHandler to the ReadFragment by calling ReadFragment.setHandler(). For example:

    // We suppose that viewModel derives ReadHandler.
    // We have set the tag in XML.
    (findFragmentByTag("com.pixelnetica.design.read.ReadFragment") as ReadFragment).setHandler(viewModel)
    

Using the RecognizedText with Jetpack Compose

This component enables users to edit recognition results using Jetpack Compose.

You can use the Composable function RecognizedText with arguments similar to those described in the section above:

@Composable
fun RecognizedText(
    modifier: Modifier = Modifier,
    picture: ScanPicture?,
    lookupRect: RectF?,
    lookupProgress: Int,
    originalText: ScanText,
    modifiedText: ScanText,
    onCancel: @Composable () -> Unit = { },
    onConfirmRestore: @Composable ConfirmRestoreScope.(List<CharSequence>) -> Unit = { },
    onPictureReady: @Composable (Boolean) -> Unit = { },
    onModifiedTextChanged: @Composable (ScanText) -> Unit = { },
)
Top