-
Notifications
You must be signed in to change notification settings - Fork 2
/
Using FlexiCapture technology API.cs
1385 lines (1173 loc) · 48.9 KB
/
Using FlexiCapture technology API.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// © ABBYY. 2012.
// SAMPLES code is property of ABBYY, exclusive rights are reserved.
// DEVELOPER is allowed to incorporate SAMPLES into his own APPLICATION and modify it
// under the terms of License Agreement between ABBYY and DEVELOPER.
// Product: ABBYY FlexiCapture Engine 10
// Description: Using FlexiCapture technology API
using System;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using FCEngine;
namespace Sample
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Using FlexiCapture technology API
class UsingFlexiCaptureTechnologyAPI : FlexiCaptureEngineSnippets
{
// USE CASE: Recognizing a sequence of image files using FlexiCapture processor
public static void Recognizing_a_sequence_of_image_files_using_FlexiCapture_processor( IEngine engine )
{
trace( "Create an instance of FlexiCapture processor..." );
IFlexiCaptureProcessor processor = engine.CreateFlexiCaptureProcessor();
trace( "Add required Document Definitions..." );
processor.AddDocumentDefinitionFile( SamplesFolder + "\\SampleProject\\Templates\\Invoice_eng.fcdot" );
trace( "Add the image files to recognize..." );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_1.tif" );
//processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_2.tif" );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_3.tif" );
traceBegin( "Run processing loop..." );
int count = 0;
while( true ) {
trace( "Recognize next document..." );
IDocument document = processor.RecognizeNextDocument();
if( document == null ) {
IProcessingError error = processor.GetLastProcessingError();
if( error != null ) {
processError( error, processor, ErrorHandlingStrategy.LogAndContinue );
continue;
} else {
trace( "No more images" );
break;
}
} else if( document.DocumentDefinition == null ) {
processNotMatched( document );
}
trace( "Export recognized document..." );
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "NextDocument_" + count, null );
count++;
}
traceEnd( "OK" );
trace( "Check the results..." );
assert( count == 2 );
}
// USE CASE: Using a custom image source with FlexiCapture processor
public static void Using_a_custom_image_source_with_FlexiCapture_processor( IEngine engine )
{
trace( "Create an instance of FlexiCapture processor..." );
IFlexiCaptureProcessor processor = engine.CreateFlexiCaptureProcessor();
trace( "Add required Document Definitions..." );
processor.AddDocumentDefinitionFile( SamplesFolder + "\\SampleProject\\Templates\\Invoice_eng.fcdot" );
trace( "Set up a custom image source..." );
// Create and configure sample image source (see SampleImageSource class for details)
SampleImageSource imageSource = new SampleImageSource();
// The sample image source will use these files by reference:
imageSource.AddImageFileByRef( SamplesFolder + "\\SampleImages\\Invoices_1.tif" );
imageSource.AddImageFileByRef( SamplesFolder + "\\SampleImages\\Invoices_2.tif" );
imageSource.AddImageFileByRef( SamplesFolder + "\\SampleImages\\Invoices_3.tif" );
// ... these files by value (files in memory):
imageSource.AddImageFileByValue( SamplesFolder + "\\SampleImages\\Invoices_1.tif" );
imageSource.AddImageFileByValue( SamplesFolder + "\\SampleImages\\Invoices_2.tif" );
imageSource.AddImageFileByValue( SamplesFolder + "\\SampleImages\\Invoices_3.tif" );
// Configure the processor to use the new image source
processor.SetCustomImageSource( imageSource );
traceBegin( "Run processing loop..." );
int count = 0;
while( true ) {
trace( "Recognize next document..." );
IDocument document = processor.RecognizeNextDocument();
if( document == null ) {
IProcessingError error = processor.GetLastProcessingError();
if( error != null ) {
processError( error, processor, ErrorHandlingStrategy.LogAndContinue );
continue;
} else {
trace( "No more images" );
break;
}
} else if( document.DocumentDefinition == null ) {
processNotMatched( document );
}
trace( "Export recognized document..." );
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "NextDocument_" + count, null );
count++;
}
traceEnd( "OK" );
trace( "Check the results..." );
assert( count == 4 );
}
// USE CASE: Handling errors in the image queue
public static void Handling_errors_in_the_image_queue( IEngine engine )
{
trace( "Create and configure an instance of FlexiCapture processor..." );
IFlexiCaptureProcessor processor = engine.CreateFlexiCaptureProcessor();
processor.AddDocumentDefinitionFile( SamplesFolder + "\\SampleProject\\Templates\\Invoice_eng.fcdot" );
trace( "Fill the image queue so that errors should occur..." );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_1.tif" );
// ... this file does not exist:
processor.AddImageFile( SamplesFolder + "\\SampleImages\\DOESNOTEXIST.tif" );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_2.tif" );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_3.tif" );
// ... this file exists but will not be accessible:
string accessDeniedPath = SamplesFolder + "\\SampleImages\\ACCESSDENIED.tif";
FileStream accessDeniedStream = File.Open( accessDeniedPath, FileMode.OpenOrCreate, FileAccess.Write );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\ACCESSDENIED.tif" );
processor.AddImageFile( SamplesFolder + "\\SampleImages\\Invoices_1.tif" );
traceBegin( "Run processing loop..." );
int count = 0;
while( true ) {
trace( "Recognize next document..." );
IDocument document = processor.RecognizeNextDocument();
if( document == null ) {
IProcessingError error = processor.GetLastProcessingError();
if( error != null ) {
// Try changing the error handling strategy below (see ErrorHandlingStrategy enum).
// See processError implementation for details
processError( error, processor, ErrorHandlingStrategy.LogAndContinue );
continue;
} else {
trace( "No more images" );
break;
}
} else if( document.DocumentDefinition == null ) {
processNotMatched( document );
}
trace( "Export recognized document..." );
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "NextDocument_" + count, null );
count++;
}
traceEnd( "OK" );
accessDeniedStream.Close();
File.Delete( accessDeniedPath );
trace( "Check the results..." );
assert( count == 3 );
}
// USE CASE: Navigating through the document structure
public static void Navigating_through_the_document_structure( IEngine engine )
{
trace( "Prepare a sample document..." );
IDocument document = PrepareNewRecognizedDocument( engine );
traceBegin( "Build document data tree..." );
// Document is actually the top-level document node. We will use this fact to
// uniformly traverse the tree of document data structure (document nodes or fields)
IField rootNode = document as IField;
recursiveProcessWithChildren( rootNode );
traceEnd( "OK" );
traceBegin( "Build document layout tree..." );
// Again, pages are top-level layout nodes. We will use this to
// uniformly traverse the tree of document geometry (layout blocks)
IPages pages = document.Pages;
for( int i = 0; i < pages.Count; i++ ) {
IPage page = pages[i];
IBlock rootLayoutNode = page as IBlock;
recursiveProcessWithChildren( rootLayoutNode );
}
traceEnd( "OK" );
}
// USE CASE: Using built-in export tools
public static void Exporting_results_using_FlexiCapture_processor( IEngine engine )
{
trace( "Prepare a sample document..." );
IFlexiCaptureProcessor processor;
IDocument document = PrepareNewRecognizedDocument( engine, out processor );
IFileExportParams exportParams = engine.CreateFileExportParams();
if( document.DocumentDefinition != null ) {
// You can export data only in documents of known types
trace( "Export the document using the settings from the Document Definition" );
processor.ExportDocument( document, SamplesFolder + "\\FCEExport" );
traceBegin( "Export the data to all supported file formats..." );
trace( "XML" );
exportParams.FileFormat = FileExportFormatEnum.FEF_XML;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToXML", exportParams );
trace( "XLS" );
exportParams.FileFormat = FileExportFormatEnum.FEF_XLS;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToXLS", exportParams );
trace( "CSV" );
exportParams.FileFormat = FileExportFormatEnum.FEF_CSV;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToCSV", exportParams );
trace( "DBF" );
exportParams.FileFormat = FileExportFormatEnum.FEF_DBF;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToDBF", exportParams );
trace( "TXT" );
exportParams.FileFormat = FileExportFormatEnum.FEF_Text;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToText", exportParams );
traceEnd( "OK" );
} else {
// You can export unrecognized documents to all image formats or PDF (including searchable PDF)
trace( "Unknown document type: Export as searchable PDF" );
IFileExportParams pdfExportParams = engine.CreateFileExportParams();
pdfExportParams.ImageExportParams.Format = ImageFileFormatEnum.IFF_Pdf;
pdfExportParams.ImageExportParams.CreateSearchablePDF = true;
pdfExportParams.ImageExportParams.PDFRecognitionLanguage = "English";
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToSearchablePDF", pdfExportParams );
}
traceBegin( "Export the page images to all supported image file formats..." );
exportParams.FileFormat = FileExportFormatEnum.FEF_XML;
exportParams.ExportOriginalImages = true;
exportParams.ImageExportParams.OverwriteFiles = true;
trace( "TIF" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Tif;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToTIF", exportParams );
trace( "JPG" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Jpg;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_Jpeg;
exportParams.ImageExportParams.ColorType = ImageColorTypeEnum.ICT_Color; // or ICT_Gray
exportParams.ImageExportParams.FileAssemblingRule = ImageFileAssemblingRuleEnum.IFAR_FilePerImage;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToJPG", exportParams );
trace( "PNG" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Png;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_Png;
exportParams.ImageExportParams.FileAssemblingRule = ImageFileAssemblingRuleEnum.IFAR_FilePerImage;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToPNG", exportParams );
trace( "BMP" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Bmp;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_Uncompressed;
exportParams.ImageExportParams.FileAssemblingRule = ImageFileAssemblingRuleEnum.IFAR_FilePerImage;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToBMP", exportParams );
trace( "PDF" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Pdf;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToPDF", exportParams );
trace( "DCX" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Dcx;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_PackBits;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToDCX", exportParams );
trace( "J2K" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_J2k;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_Jpeg;
exportParams.ImageExportParams.ColorType = ImageColorTypeEnum.ICT_Color; // èëè ICT_Gray
exportParams.ImageExportParams.FileAssemblingRule = ImageFileAssemblingRuleEnum.IFAR_FilePerImage;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToJ2K", exportParams );
trace( "PCX" );
exportParams.ImageExportParams.Format = FCEngine.ImageFileFormatEnum.IFF_Pcx;
exportParams.ImageExportParams.CompressionType = ImageCompressionTypeEnum.ICT_PackBits;
exportParams.ImageExportParams.FileAssemblingRule = ImageFileAssemblingRuleEnum.IFAR_FilePerImage;
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ExportToPCX", exportParams );
traceEnd( "OK" );
// NB: By default if the file exists the new data is appended to this file. It works fine in simple
// scenarios but should be avoided if a lot of data is exported or in parallel processing scenarios.
// A better solution to avoid bottlenecks would be to either use custom export or export every
// document to a separate file with a unique name and consolidate data later elsewhere
}
// USE CASE: Implementing custom export
public static void Implementing_custom_export( IEngine engine )
{
trace( "Prepare a sample document..." );
IDocument document = PrepareNewRecognizedDocument( engine );
traceBegin( "Run custom export..." );
// Simple text-based export
SimpleXMLExporter.Export( document, SamplesFolder + "\\FCEExport\\SampleExport.xml" );
// A more sophisticated example based on the standard System.Xml tools
MappedXMLExporter.Export( document, SamplesFolder + "\\FCEExport\\SampleExport.xml" );
// Next two examples show how to export page images
ImageExporter.ExportToPng( document, SamplesFolder + "\\FCEExport" );
ImageExporter.ExportToMultipageTiff( document, SamplesFolder + "\\FCEExport" );
traceEnd( "OK" );
// Show last results in the browser
if( IsInteractive ) {
Process.Start( SamplesFolder + "\\FCEExport\\SampleExport.xml" );
}
}
// USE CASE: Implementing custom storage
public static void Implementing_custom_storage( IEngine engine )
{
trace( "Prepare a sample document..." );
IDocument document = PrepareNewRecognizedDocument( engine );
// If an object supports custom storage, it implements ICustomStorage interface
ICustomStorage customStorage = document as ICustomStorage;
assert( customStorage != null );
// NB. It is the same object but we see it through a different interface
assert( customStorage == document );
trace( "Save the document to a file..." );
customStorage.SaveToFile( SamplesFolder + "\\FCEExport\\Document.mydoc" );
trace( "Save the document to a stream..." );
// The sample write stream is implemented as writing to a file (see SampleWriteStream for details)
SampleWriteStream writeStream = new SampleWriteStream( SamplesFolder + "\\FCEExport\\Document.mystream" );
try {
customStorage.SaveToStream( writeStream );
} finally {
writeStream.Close();
}
IFlexiCaptureProcessor processor = engine.CreateFlexiCaptureProcessor();
trace( "Load a document from file..." );
IDocument newDocument = processor.CreateDocument();
customStorage = newDocument as ICustomStorage;
assert( customStorage != null );
customStorage.LoadFromFile( SamplesFolder + "\\FCEExport\\Document.mydoc" );
processor.ExportDocumentEx( newDocument, SamplesFolder + "\\FCEExport", "LoadedFromFile", null );
trace( "Load a document from stream..." );
newDocument = processor.CreateDocument();
customStorage = newDocument as ICustomStorage;
assert( customStorage != null );
// The sample read stream is implemented as reading from a file (see SampleReadStream for details)
SampleReadStream readStream = new SampleReadStream( SamplesFolder + "\\FCEExport\\Document.mystream" );
customStorage.LoadFromStream( readStream );
processor.ExportDocumentEx( newDocument, SamplesFolder + "\\FCEExport", "LoadedFromStream", null );
traceEnd( "OK" );
}
// USE CASE: Implementing custom reference manager
public static void Implementing_custom_reference_manager( IEngine engine )
{
trace( "Create an instance of FlexiCapture processor..." );
IFlexiCaptureProcessor processor = engine.CreateFlexiCaptureProcessor();
trace( "Configure the processor to use a custom reference manager..." );
// The sample reference manager can resolve "protocol:\\" references (see SampleReferenceManager for details)
processor.SetReferenceManager( new SampleReferenceManager() );
// Now we can pass references starting with "protocol:\\" and they will be correctly resolved:
processor.AddDocumentDefinitionFile( "protocol:\\SampleProject\\Templates\\Invoice_eng.fcdot" );
trace( "Add image files..." );
processor.AddImageFile( "protocol:\\SampleImages\\Invoices_2.tif" );
processor.AddImageFile( "protocol:\\SampleImages\\Invoices_3.tif" );
trace( "Recognize and export..." );
IDocument document = processor.RecognizeNextDocument();
assert( document != null );
assert( document.Pages.Count == 2 );
processor.ExportDocumentEx( document, SamplesFolder + "\\FCEExport", "ReferenceManager", null );
traceEnd( "OK" );
}
// USE CASE: Implementing undo and redo operations for a FlexiCapture Engine object
public static void Implementing_Undo_and_Redo_for_a_document_in_an_editor( IEngine engine )
{
trace( "Prepare a sample document..." );
IDocument document = PrepareNewRecognizedDocument( engine );
// Some FlexiCapture Engine objects (for example IDocument) support undo and redo operations.
// To use this feature in your editor:
// Request IUndoable by casting the document to this interface.
// You should keep the aquired reference as long as you use this
// feature (for example as a member field in your editor class).
// It is important because the internal undo support for the object
// might deinitialize if not used (if all references to IUndoable
// are released)
trace( "Request undo support interface..." );
IUndoable undoable = document as IUndoable;
// Undo support is initially disabled to conserve resources,
// so we must switch it on
assert( undoable.IsUndoRedoEnabled == false );
undoable.EnableUndoRedo( true );
// Initially the object (the document) is not modified
// (the Save button on your editor's toolbar is disabled)
assert( !undoable.IsObjectModified );
// And there is nothing to undo or redo (the Undo/Redo buttons
// on your editor's toolbar are also disabled)
assert( !undoable.CanUndo );
assert( !undoable.CanRedo );
// Let the user of your editor modify the document, use undo/redo
traceBegin( "Modify the document, use Undo Redo..." );
// Take an arbitrary field
IField field = document.Sections[0].Children[0];
string initialValue = field.Value.AsString;
trace( "initialValue = " + initialValue );
// Modify it
trace( "Modify..." );
field.Value.AsText.Delete( 0, 4 );
string modifiedValue = field.Value.AsString;
assert( modifiedValue != initialValue );
trace( "modifiedValue = " + modifiedValue );
// Now we can Undo but cannot Redo (Undo/Redo buttons in your
// editor changed state)
assert( undoable.CanUndo );
assert( !undoable.CanRedo );
// And the object is modified (Save button is enabled)
assert( undoable.IsObjectModified );
// Press Undo button
trace( "Undo..." );
undoable.Undo();
// The field is reverted to initial value
assert( field.Value.AsString == initialValue );
trace( "field.Value = " + field.Value.AsString );
// The document is no longer modified
assert( !undoable.IsObjectModified );
// You cannot Undo, but you can Redo
assert( !undoable.CanUndo );
assert( undoable.CanRedo );
// Press Redo button
trace( "Redo..." );
undoable.Redo();
// The field value changed
assert( field.Value.AsString == modifiedValue );
trace( "field.Value = " + field.Value.AsString );
// The document is modified as before
assert( undoable.IsObjectModified );
// You can Undo, but you cannot Redo
assert( undoable.CanUndo );
assert( !undoable.CanRedo );
traceEnd( "OK" );
// If you want that the considerable resources consumed by the
// undo support be released in timely manner, you should explicitly
// deinitialize undo support as part of your editor's Disposal procedure.
trace( "Deinitialize undo support explicitly..." );
undoable.EnableUndoRedo( false );
}
// USE CASE: Correcting assembly errors
public static void Correcting_assembly_errors( IEngine engine )
{
trace( "Prepare two documents..." );
IDocument document1 = PrepareNewRecognizedDocument( engine );
IDocument document2 = PrepareNewRecognizedDocument( engine );
int totalPagesCount = document1.Pages.Count + document2.Pages.Count;
trace( "Merge the documents..." );
while( document2.Pages.Count > 0 ) {
document1.Pages.Attach( document2.Pages[0], document1.Pages.Count );
}
assert( document2.Pages.Count == 0 );
assert( document1.Pages.Count == totalPagesCount );
trace( "Delete some documents..." );
document1.Pages.DeleteByID( document1.Pages[document1.Pages.Count - 1].ID );
assert( document1.Pages.Count == totalPagesCount - 1 );
traceEnd( "OK" );
}
// USE CASE: Verifying recognized documents
public static void Verifying_recognized_documents( IEngine engine )
{
trace( "Prepare documents and add them to a collection..." );
IDocumentsCollection documentsToVerify = engine.CreateDocumentsCollection();
documentsToVerify.Add( PrepareNewRecognizedDocument( engine ) );
documentsToVerify.Add( PrepareNewRecognizedDocument( engine ) );
traceBegin( "Run verification..." );
IVerificationSession verificationSession = engine.CreateVerificationSession( documentsToVerify );
try {
trace( "Change verification options if required..." );
IVerificationOptions verificationOptions = verificationSession.Options;
verificationOptions.VerifyExtraSymbols = true;
trace( "Open a set of documents (a work set) and collect objects that need to be verified..." );
IVerificationWorkSet verificationWorkSet = verificationSession.NextWorkSet();
while( verificationWorkSet != null ) {
trace( "For each group of objects show the objects to the verification operator for confirmation..." );
IVerificationGroup verificationGroup = verificationWorkSet.NextGroup();
while( verificationGroup != null ) {
trace( "Verification Group: " + verificationGroup.Description + " (confirm all)" );
for( int i = 0; i < verificationGroup.Count; i++ ) {
IVerificationObject verificationObject = verificationGroup.Item( i );
if( verificationObject.Type == VerificationObjectTypeEnum.VOT_Group ) {
verificationObject.State = VerificationObjectStateEnum.VOS_Confirmed;
} else {
IContextVerificationObject contextVerificationObject = verificationObject.AsContextVerificationObject();
// If field value is modified during verification you should recheck rules for
// the corresponding field
contextVerificationObject.CheckRules();
contextVerificationObject.Field.Value.SetVerified();
}
}
verificationGroup = verificationWorkSet.NextGroup();
}
trace( "Save verification results (all documents in the set)..." );
verificationWorkSet.Commit();
trace( "Open the next set of documents..." );
verificationWorkSet = verificationSession.NextWorkSet();
}
trace( "Close the session..." );
}
finally
{
// Verification consumes considerable system resources (many simultaniously
// open and loaded documents and images). So it is VERY important that
// these resources should be released in timely manner and not left for
// garbage collector to manage.
verificationSession.Close();
}
trace( "Check that the documents do not need verification now..." );
for( int i = 0; i < documentsToVerify.Count; i++ ) {
IDocument document = documentsToVerify.Item( i );
recursiveCheckVerified( engine, document.Sections );
}
traceEnd( "OK" );
traceEnd( "OK" );
}
#region Sample Auxiliary Classes and Methods
// Custom Image Source ///////////////////////////////////////////////////////////
class SampleImageSource : IImageSource
{
public SampleImageSource()
{
imageFileAdapters = new Queue();
}
public void AddImageFileByRef( string filePath )
{
imageFileAdapters.Enqueue( new ImageFileAdapter( filePath, false ) );
}
public void AddImageFileByValue( string filePath )
{
imageFileAdapters.Enqueue( new ImageFileAdapter( filePath, true ) );
}
// IImageSource ///////////////////
public string GetName() { return "Sample Image Source"; }
public IFileAdapter GetNextImageFile()
{
// If the image source is accessed from multiple threads (as in the Processors Pool
// sample) this method must be thread-safe. In this sample the lock should be uncommented
// lock( this ) {
if( imageFileAdapters.Count > 0 ) {
return (IFileAdapter)imageFileAdapters.Dequeue();
}
return null;
// }
}
public IImage GetNextImage() { return null; } // this sample source does not use this feature
#region IMPLEMENTATION
class ImageFileAdapter : IFileAdapter
{
public ImageFileAdapter( string _filePath, bool _marshalByValue )
{
filePath = _filePath;
marshalByValue = _marshalByValue;
}
// IFileAdapter ////////////////////
public FileMarshallingTypeEnum GetMarshallingType()
{
if( marshalByValue ) {
return FileMarshallingTypeEnum.FMT_MarshalByValue;
}
return FileMarshallingTypeEnum.FMT_MarshalByReference;
}
public string GetFileReference()
{
if( marshalByValue ) {
// When marshalling by value, this method is allowed to return any string (empty string included).
// If provided, the string is considered to describe the origin of the file and may be used as
// a string in error messages and as part of the image origin info in generated pages.
// These samples are also valid:
// return "";
// return "from: smith@mail.com";
// Here we just choose to use the path to the original file
return filePath;
} else {
// When marshalling by reference (which is the recommended method), the returned value
// must be a reference which can be resolved by the specified reference manager or
// a path in the file system (which is considered as the default reference manager if
// none is explicitly set)
// These samples may be also valid if the reference manager, capable of resolving
// such references is provided:
// return @"http://storage/images?id=123";
// return "1&Jh5K(D:";
// In this sample we use the path in the file system
return filePath;
}
}
public IReadStream GetFileStream()
{
if( marshalByValue ) {
return new ReadStream( filePath );
} else {
// This method is called only if marshalled by value
assert( false );
return null;
}
}
#region IMPLEMENTATION
class ReadStream : IReadStream
{
public ReadStream( string fileName )
{
currentPos = 0;
using( FileStream stream = File.Open( fileName, FileMode.Open ) )
{
int length = (int)stream.Length;
bytes = new byte[length];
stream.Read( bytes, 0, length );
}
}
public int Read( out Array data, int count )
{
// (!) in .NET Framework 3.5 or later you can use Array.BlockCopy to increase performance (!)
if( buf == null || buf.Length < count ) {
buf = new byte[count];
}
int readBytes = 0;
for( int i = 0; i < count && currentPos < bytes.Length; i++, currentPos++, readBytes++ ) {
buf[i] = bytes[currentPos];
}
data = buf;
return readBytes;
}
public void Close()
{
bytes = null;
buf = null;
}
uint currentPos = 0;
byte[] bytes = null;
byte[] buf = null;
}
string filePath;
bool marshalByValue;
#endregion
};
Queue imageFileAdapters;
#endregion
};
// Error Handling ///////////////////////////////////////////////////////////
enum ErrorHandlingStrategy {
LogAndContinue,
LogAndRetry,
ThrowException
};
static void processError( IProcessingError error, IFlexiCaptureProcessor processor, ErrorHandlingStrategy strategy )
{
assert( error != null );
// You can store the problematic image for later analysis
if( error.ImageFile() != null ) {
string imageReference = error.ImageFile().GetFileReference();
}
// And use different strategies to handle the error
switch( strategy ) {
// 1) Log the error and continue
case ErrorHandlingStrategy.LogAndContinue:
trace( "Processing error: " + error.MessageText() );
return;
// 2) Retry
case ErrorHandlingStrategy.LogAndRetry:
trace( "Processing error: " + error.MessageText() );
// Resume processing from the position where error occurred
processor.ResumeProcessing( true );
return;
// 3) Break processing
case ErrorHandlingStrategy.ThrowException:
// This will reset the processing state (image queue, error condition, etc.) but keep
// the processing configuration (loaded templates and processing params). You can omit this
// call if you do not plan to reuse the processor instance or reset the processor elsewhere
processor.ResetProcessing();
throw new Exception( error.MessageText() );
}
}
static void processNotMatched( IDocument document )
{
assert( document != null );
// The document contains the page(s) that could not be matched. Normally this is a single page
// from the image queue. This might not be the case for multipage images with AM_DocumentPerFile
// assembling mode
assert( document.Pages.Count > 0 );
// You can store the problematic image for later analysis, the most straightforward approach being:
string imageReference = document.Pages[0].OriginalImagePath;
// In this sample we expect that all images should be matched. In the real world scenario
// some images will not be matched. This case should be treated according to your requirements
assert( false );
}
// Document Structure ///////////////////////////////////////////////////////////
static void recursiveProcessWithChildren( IField node )
{
traceBegin( node.Name + " [" + node.Type.ToString() + "]" );
recursiveProcessInstancesAndChildren( node );
traceEnd( "" );
}
static void recursiveProcessWithChildren( IField node, int index )
{
// This is an instance of a repeating node
traceBegin( "[" + index.ToString() + "]" );
recursiveProcessInstancesAndChildren( node );
traceEnd( "" );
}
static void recursiveProcessInstancesAndChildren( IField node )
{
if( node.Instances != null ) {
IFieldInstances instances = node.Instances;
for( int i = 0; i < instances.Count; i++ ) {
recursiveProcessWithChildren( instances[i], i );
}
} else if( node.Children != null ) {
IFields children = node.Children;
for( int i = 0; i < children.Count; i++ ) {
recursiveProcessWithChildren( children[i] );
}
}
}
static void recursiveProcessWithChildren( IBlock node )
{
traceBegin( node.Type.ToString() );
if( node.Children != null ) {
IBlocks children = node.Children;
for( int i = 0; i < children.Count; i++ ) {
recursiveProcessWithChildren( children.Item( i ) );
}
}
traceEnd( "" );
}
// Custom Export ///////////////////////////////////////////////////////////
class SimpleXMLExporter
{
public static void Export( IDocument document, string filePath )
{
// Write XML as a simple text file. This sample can be used as a starting point
// for implementing custom export to text-based formats
using( StreamWriter stream = File.CreateText( filePath ) ) {
stream.WriteLine( "<?xml version='1.0' encoding='UTF-8'?>" );
exportDocument( stream, document );
}
}
#region IMPLEMENTATION
static void exportDocument( StreamWriter stream, IDocument document )
{
IDocumentDefinition def = document.DocumentDefinition;
assert( def != null );
stream.WriteLine( "<" + def.Name + ">" );
exportChildren( stream, document.Sections );
stream.WriteLine( "</" + def.Name + ">" );
}
static void exportField( StreamWriter stream, IField field )
{
assert( field != null );
string attributes = "";
if( field.Value != null ) {
attributes = " Value = '" + field.Value.AsString + "'";
}
if( field.Instances != null ) {
exportInstances( stream, field.Instances );
} else if ( field.Children != null ) {
stream.WriteLine( "<" + field.Name + attributes + ">" );
exportChildren( stream, field.Children );
stream.WriteLine( "</" + field.Name + ">" );
} else {
stream.WriteLine( "<" + field.Name + attributes + "/>" );
}
}
static void exportChildren( StreamWriter stream, IFields children )
{
assert( children != null );
for( int i = 0; i < children.Count; i++ ) {
exportField( stream, children[i] );
}
}
static void exportInstances( StreamWriter stream, IFieldInstances instances )
{
assert( instances != null );
for( int i = 0; i < instances.Count; i++ ) {
exportField( stream, instances[i] );
}
}
#endregion
}
class MappedXMLExporter
{
public static void Export( IDocument document, string filePath )
{
// Write XML using standard .NET Framework tools. Maps elements to
// a customized schema
System.Xml.XmlTextWriter xwriter = new System.Xml.XmlTextWriter( filePath, System.Text.Encoding.UTF8 );
try {
xwriter.WriteStartDocument( true );
exportDocument( xwriter, document );
xwriter.WriteEndDocument();
} finally {
xwriter.Close();
}
}
#region THE MAP
// We will map documents of the specified type to a customized schema
static string DocumentType = "Invoice_eng";
static string XmlSchema = "http://www.mycompany.com/Schemas/MyInvoice.xsd";
static string MapName( string name )
{
switch( name ) {
case "Invoice_eng": return "Invoice";
case "Invoice": return "Data";
case "InvoiceNumber": return "Id";
case "InvoiceDate": return "Date";
case "InvoiceTable": return "InvoiceEntry";
default:
return name.Replace( ' ', '_' );
}
}
#endregion
#region IMPLEMENTATION
static void exportDocument( System.Xml.XmlTextWriter xwriter, IDocument document )
{
IDocumentDefinition def = document.DocumentDefinition;
assert( def != null );
assert( def.Name == DocumentType );
xwriter.WriteStartElement( MapName( def.Name ), XmlSchema );
exportChildren( xwriter, document.Sections );
xwriter.WriteEndElement();
}
static void exportField( System.Xml.XmlTextWriter xwriter, IField field )
{
assert( field != null );
if( field.Instances != null ) {
exportInstances( xwriter, field.Instances );
} else {
xwriter.WriteStartElement( MapName( field.Name ) );
if( field.Value != null ) {
xwriter.WriteAttributeString( "Value", field.Value.AsString );
}
if( field.Children != null ) {
exportChildren( xwriter, field.Children );
}
xwriter.WriteEndElement();
}
}
static void exportChildren( System.Xml.XmlTextWriter xwriter, IFields children )
{
assert( children != null );
for( int i = 0; i < children.Count; i++ ) {
exportField( xwriter, children[i] );
}
}
static void exportInstances( System.Xml.XmlTextWriter xwriter, IFieldInstances instances )
{
assert( instances != null );
for( int i = 0; i < instances.Count; i++ ) {
exportField( xwriter, instances[i] );
}
}
#endregion
}
class ImageExporter
{
public static void ExportToPng( IDocument document, string folder )
{
IDocumentDefinition def = document.DocumentDefinition;
assert( def != null );
saveToPng( folder, def.Name, document );
}
public static void ExportToMultipageTiff( IDocument document, string folder )
{
IDocumentDefinition def = document.DocumentDefinition;
assert( def != null );
saveToMultipageTiff( folder, def.Name, document );
}
#region IMPLEMENTATION
static void saveToPng( string folder, string prefix, IDocument document )
{
// Extract all document pages as bitmaps
Bitmap[] pageBitmaps = extractPages( document );
try {
// Save each page to a separate file
for( int i = 0; i < pageBitmaps.Length; i++ ) {
using( FileStream stream = File.Create( folder + "\\" + prefix + "_" + i.ToString() + ".png" ) ) {
pageBitmaps[i].Save( stream, ImageFormat.Png );
}
}
} finally {
// To better manage resources, it is highly recommended that
// all bitmaps should be disposed of explicitly
disposePages( pageBitmaps );
}
}
static void saveToMultipageTiff( string folder, string prefix, IDocument document )
{