/* GNU Ocrad - Optical Character Recognition program
Copyright (C) 2003, 2004, 2005, 2006, 2007 Antonio Diaz Diaz.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
Return values: 0 for a normal exit, 1 for environmental problems
(file not found, invalid flags, I/O errors, etc), 2 to indicate a
corrupt or invalid input file, 3 for an internal consistency error
(eg, bug) which caused ocrad to panic.
*/
#include
#include
#include
#include
#include
#include
#include "arg_parser.h"
#include "common.h"
#include "rational.h"
#include "rectangle.h"
#include "page_image.h"
#include "textpage.h"
namespace {
const char * invocation_name = 0;
const char * const Program_name = "GNU Ocrad";
const char * const program_name = "ocrad";
const char * const program_year = "2007";
struct Input_control
{
Transformation transformation;
int rindex, layout_level, scale;
Rational threshold, ltrb[4];
bool copy, crop, invert;
Input_control() throw()
: rindex( -1 ), layout_level( 0 ), scale( 0 ), threshold( -1 ),
copy( false ), crop( false ), invert( false ) {}
bool read_crop_rectangle( const char * ptr ) throw();
bool set_threshold( const char * ptr ) throw();
};
void show_error( const char * msg, const int errcode = 0, const bool help = false ) throw()
{
if( msg && msg[0] != 0 )
{
std::fprintf( stderr, "%s: %s", program_name, msg );
if( errcode > 0 ) std::fprintf( stderr, ": %s", strerror( errcode ) );
std::fprintf( stderr, "\n" );
}
if( help && invocation_name && invocation_name[0] != 0 )
std::fprintf( stderr, "Try `%s --help' for more information.\n", invocation_name );
}
bool maybe_less( const Rational a, const Rational b ) throw()
{
if( a >= 0 && a <= 1 && b >= 0 && b <= 1 ) return a < b;
if( a > 1 && b > 1 ) return a < b;
return ( a >= 0 && b > 0 );
}
bool Input_control::read_crop_rectangle( const char * ptr ) throw()
{
int c = ltrb[0].parse( ptr ); // left
if( c && ltrb[0] >= 0 && ptr[c] == ',' )
{
int i = c + 1;
c = ltrb[1].parse( &ptr[i] ); // top
if( c && ltrb[1] >= 0 && ptr[i+c] == ',' )
{
i += c + 1; c = ltrb[2].parse( &ptr[i] ); // right
if( c && maybe_less( ltrb[0], ltrb[2] ) && ptr[i+c] == ',' )
{
i += c + 1; c = ltrb[3].parse( &ptr[i] ); // bottom
if( c && maybe_less( ltrb[1], ltrb[3] ) ) { crop = true; return true; }
}
}
}
show_error( "invalid crop rectangle", 0, true );
return false;
}
bool Input_control::set_threshold( const char * ptr ) throw()
{
Rational tmp;
if( tmp.parse( ptr ) && tmp >= 0 && tmp <= 1 )
{ threshold = tmp; return true; }
show_error( "threshold out of limits (0.0 - 1.0)", 0, true );
return false;
}
void show_help() throw()
{
std::printf( "%s - Optical Character Recognition program.\n", Program_name );
std::printf( "Reads pnm file(s), or standard input, and sends text to standard output.\n" );
std::printf( "\nUsage: %s [options] [files]\n", invocation_name );
std::printf( "Options:\n" );
std::printf( " -h, --help display this help and exit\n" );
std::printf( " -V, --version output version information and exit\n" );
std::printf( " -a, --append append text to output file\n" );
std::printf( " -b, --block= process only the specified text block\n" );
std::printf( " -c, --charset= try `--charset=help' for a list of names\n" );
std::printf( " -e, --filter= try `--filter=help' for a list of names\n" );
std::printf( " -f, --force force overwrite of output file\n" );
std::printf( " -F, --format= output format (byte, utf8)\n" );
std::printf( " -i, --invert invert image levels (white on black)\n" );
std::printf( " -l, --layout= layout analysis, 0=none, 1=column, 2=full\n" );
std::printf( " -o place the output into \n" );
std::printf( " -p, --crop= crop input image by given rectangle\n" );
std::printf( " -s, --scale=[-] scale input image by [1/]\n" );
std::printf( " -t, --transform= try `--transform=help' for a list of names\n" );
std::printf( " -T, --threshold= threshold for binarization (0-100%%)\n" );
std::printf( " -v, --verbose be verbose\n" );
std::printf( " -x export OCR Results File to \n" );
if( Ocrad::verbose )
{
std::printf( " -1..6 pnm output file type (debug)\n" );
std::printf( " -C, --copy 'copy' input to output (debug)\n" );
std::printf( " -D, --debug= (0-100) output intermediate data (debug)\n" );
}
std::printf( "\nReport bugs to bug-ocrad@gnu.org\n" );
}
void show_version() throw()
{
std::printf( "%s %s\n", Program_name, PROGVERSION );
std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year );
std::printf( "License GPLv3+: GNU GPL version 3 or later \n" );
std::printf( "This is free software: you are free to change and redistribute it.\n" );
std::printf( "There is NO WARRANTY, to the extent permitted by law.\n" );
}
const char * my_basename( const char * filename ) throw()
{
const char * c = filename;
while( *c ) { if( *c == '/' ) filename = c + 1; ++c; }
return filename;
}
int process_file( FILE *infile, const char * infile_name,
const Input_control & input_control,
const Control & control ) throw()
{
if( Ocrad::verbose )
std::fprintf( stderr, "processing file `%s'\n", infile_name );
try
{
Page_image page_image( infile, input_control.threshold, input_control.invert );
if( input_control.crop &&
!page_image.crop( input_control.ltrb, input_control.threshold ) )
{
if( Ocrad::verbose )
std::fprintf( stderr, "file `%s' totally cropped out\n", infile_name );
return 1;
}
page_image.transform( input_control.transformation );
page_image.scale( input_control.scale );
page_image.analyse_layout( input_control.layout_level );
if( Ocrad::verbose )
std::fprintf( stderr, "number of text blocks = %d\n", page_image.zones() );
if( input_control.threshold < 0 ) page_image.adapt_thresholds();
if( input_control.rindex >= page_image.zones() )
{
std::fprintf( stderr,"This page has only %d text block(s)\n", page_image.zones() );
return 1;
}
if( input_control.copy )
{
if( control.outfile )
{
if( input_control.layout_level == 0 && page_image.zones() == 1 )
page_image.save( control.outfile, control.filetype );
else
for( int c = 0; c < page_image.zones(); ++c )
if( input_control.rindex < 0 || input_control.rindex == c )
page_image.save( control.outfile, control.filetype, c );
}
return 0;
}
Textpage textpage( page_image, my_basename( infile_name ), control );
}
catch( Page_image::Error e ) { show_error( e.s ); return 2; }
if( Ocrad::verbose ) std::fprintf( stderr, "\n" );
return 0;
}
} // end namespace
bool Ocrad::verbose = false;
void Ocrad::internal_error( const char * msg ) throw()
{
std::string s( "internal error: " ); s += msg;
show_error( s.c_str() );
exit( 3 );
}
// 'infile' contains the scanned image (in pnm format) to be converted
// to text.
// 'outfile' is the destination for the text version of the scanned
// image. (or for a pnm file if debugging).
// 'exportfile' is the Ocr Results File.
//
int main( const int argc, const char * argv[] ) throw()
{
Input_control input_control;
Control control;
const char *outfile_name = 0, *exportfile_name = 0;
bool append = false, force = false;
invocation_name = argv[0];
const Arg_parser::Option options[] =
{
{ '1', 0, Arg_parser::no },
{ '2', 0, Arg_parser::no },
{ '3', 0, Arg_parser::no },
{ '4', 0, Arg_parser::no },
{ '5', 0, Arg_parser::no },
{ '6', 0, Arg_parser::no },
{ 'a', "append", Arg_parser::no },
{ 'b', "block", Arg_parser::yes },
{ 'c', "charset", Arg_parser::yes },
{ 'C', "copy", Arg_parser::no },
{ 'D', "debug", Arg_parser::yes },
{ 'e', "filter", Arg_parser::yes },
{ 'f', "force", Arg_parser::no },
{ 'F', "format", Arg_parser::yes },
{ 'h', "help", Arg_parser::no },
{ 'i', "invert", Arg_parser::no },
{ 'l', "layout", Arg_parser::yes },
{ 'o', 0, Arg_parser::yes },
{ 'p', "crop", Arg_parser::yes },
{ 's', "scale", Arg_parser::yes },
{ 't', "transform", Arg_parser::yes },
{ 'T', "threshold", Arg_parser::yes },
{ 'v', "verbose", Arg_parser::no },
{ 'V', "version", Arg_parser::no },
{ 'x', 0, Arg_parser::yes },
{ 0 , 0, Arg_parser::no } };
Arg_parser parser( argc, argv, options );
if( parser.error().size() ) // bad option
{ show_error( parser.error().c_str(), 0, true ); return 1; }
int argind;
for( argind = 0; argind < parser.arguments(); ++argind )
{
const int code = parser.code( argind );
if( !code ) break; // no more options
const char * arg = parser.argument( argind ).c_str();
switch( code )
{
case '1':
case '2':
case '3':
case '4':
case '5':
case '6': control.filetype = code; break;
case 'C': input_control.copy = true; break;
case 'D': control.debug_level = std::strtol( arg, 0, 0 ); break;
case 'F': if( !control.set_format( arg ) )
{ show_error( "bad output format", 0, true ); return 1; }
break;
case 'T': if( !input_control.set_threshold( arg ) ) return 1; break;
case 'V': show_version(); return 0;
case 'a': append = true; break;
case 'b': input_control.rindex = std::strtol( arg, 0, 0 ) - 1; break;
case 'c': if( !control.charset.enable( arg ) )
{ control.charset.show_error( program_name, arg ); return 1; }
break;
case 'e': if( !control.filter.set( arg ) )
{ control.filter.show_error( program_name, arg ); return 1; }
break;
case 'f': force = true; break;
case 'h': show_help(); return 0;
case 'i': input_control.invert = true; break;
case 'l': input_control.layout_level = std::strtol( arg, 0, 0 ); break;
case 'o': outfile_name = arg; break;
case 'p': if( !input_control.read_crop_rectangle( arg ) ) return 1; break;
case 's': input_control.scale = std::strtol( arg, 0, 0 ); break;
case 't': if( !input_control.transformation.set( arg ) )
{ input_control.transformation.show_error( program_name, arg );
return 1; }
break;
case 'v': Ocrad::verbose = true; break;
case 'x': exportfile_name = arg; break;
default : Ocrad::internal_error( "uncaught option" );
}
} // end process options
if( outfile_name && std::strcmp( outfile_name, "-" ) != 0 )
{
if( append ) control.outfile = std::fopen( outfile_name, "a" );
else if( force ) control.outfile = std::fopen( outfile_name, "w" );
else if( ( control.outfile = std::fopen( outfile_name, "wx" ) ) == 0 )
{
std::fprintf( stderr, "Output file %s already exists.\n", outfile_name );
return 1;
}
if( !control.outfile )
{ std::fprintf( stderr, "Cannot open %s\n", outfile_name ); return 1; }
}
if( exportfile_name && control.debug_level == 0 && !input_control.copy )
{
if( std::strcmp( exportfile_name, "-" ) == 0 )
{ control.exportfile = stdout; if( !outfile_name ) control.outfile = 0; }
else
{
control.exportfile = std::fopen( exportfile_name, "w" );
if( !control.exportfile )
{
std::fprintf( stderr, "Cannot open %s\n", exportfile_name );
return 1;
}
}
std::fprintf( control.exportfile,
"# Ocr Results File. Created by %s version %s\n",
Program_name, PROGVERSION );
}
// process any remaining command line arguments (input files)
FILE *infile = (argind < parser.arguments()) ? 0 : stdin;
const char *infile_name = "-";
int retval = 0;
while( true )
{
while( infile != stdin )
{
if( infile ) std::fclose( infile );
if( argind >= parser.arguments() ) { infile = 0; break; }
infile_name = parser.argument( argind++ ).c_str();
if( std::strcmp( infile_name, "-" ) == 0 ) infile = stdin;
else infile = std::fopen( infile_name, "rb" );
if( infile ) break;
std::fprintf( stderr, "Cannot open %s\n", infile_name );
if( retval == 0 ) retval = 1;
}
if( !infile ) break;
int tmp = process_file( infile, infile_name, input_control, control );
if( infile == stdin )
{
if( tmp <= 1 )
{
int ch;
do ch = std::fgetc( infile ); while( std::isspace( ch ) );
std::ungetc( ch, infile );
}
if( tmp > 1 || std::feof( infile ) || std::ferror( infile ) ) infile = 0;
}
if( tmp > retval ) retval = tmp;
if( control.outfile ) std::fflush( control.outfile );
if( control.exportfile ) std::fflush( control.exportfile );
}
if( control.outfile ) std::fclose( control.outfile );
if( control.exportfile ) std::fclose( control.exportfile );
return retval;
}