aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2023-01-06 10:02:49 +0200
committerIgor Pashev <pashev.igor@gmail.com>2023-01-06 10:04:59 +0200
commit1145733c29db0a678537ce99ff60e21613f622a8 (patch)
tree63d5d6c324629d4eef1354db3c97f857d6016a34 /lib
downloadiscan-1145733c29db0a678537ce99ff60e21613f622a8.tar.gz
Import iscan 2.30.4-2
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am61
-rw-r--r--lib/basic-imgstream.cc301
-rw-r--r--lib/basic-imgstream.hh144
-rw-r--r--lib/fax-encoder.cc411
-rw-r--r--lib/fax-encoder.hh58
-rw-r--r--lib/file-opener.cc321
-rw-r--r--lib/file-opener.hh101
-rw-r--r--lib/imgstream.cc142
-rw-r--r--lib/imgstream.hh100
-rw-r--r--lib/jpegstream.cc270
-rw-r--r--lib/jpegstream.hh118
-rw-r--r--lib/pcxstream.cc320
-rw-r--r--lib/pcxstream.hh93
-rw-r--r--lib/pdf/Makefile.am44
-rw-r--r--lib/pdf/array.cc115
-rw-r--r--lib/pdf/array.hh87
-rw-r--r--lib/pdf/dictionary.cc123
-rw-r--r--lib/pdf/dictionary.hh93
-rw-r--r--lib/pdf/object.cc103
-rw-r--r--lib/pdf/object.hh120
-rw-r--r--lib/pdf/primitive.cc100
-rw-r--r--lib/pdf/primitive.hh92
-rw-r--r--lib/pdf/writer.cc236
-rw-r--r--lib/pdf/writer.hh165
-rw-r--r--lib/pdfstream.cc351
-rw-r--r--lib/pdfstream.hh105
-rw-r--r--lib/pngstream.cc257
-rw-r--r--lib/pngstream.hh145
-rw-r--r--lib/pnmstream.cc112
-rw-r--r--lib/pnmstream.hh73
-rw-r--r--lib/tests/Makefile.am46
-rw-r--r--lib/tests/even-width.pbmbin0 -> 2427 bytes
-rw-r--r--lib/tests/even-width.pgmbin0 -> 19343 bytes
-rw-r--r--lib/tests/even-width.ppmbin0 -> 57999 bytes
-rw-r--r--lib/tests/odd-width.pbmbin0 -> 2612 bytes
-rw-r--r--lib/tests/odd-width.pgmbin0 -> 19752 bytes
-rw-r--r--lib/tests/odd-width.ppmbin0 -> 59226 bytes
-rw-r--r--lib/tests/pnm.c141
-rw-r--r--lib/tests/pnm.h62
-rwxr-xr-xlib/tests/run-test-pcx.sh76
-rw-r--r--lib/tests/test-pcx.cc85
-rw-r--r--lib/tiffstream.cc268
-rw-r--r--lib/tiffstream.hh105
43 files changed, 5544 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..922ca44
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,61 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2004 Olaf Meeuwissen
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure 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 2 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 FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+SUBDIRS = \
+ pdf \
+ tests
+
+if ENABLE_FRONTEND
+noinst_LTLIBRARIES = libimage-stream.la
+libimage_stream_la_CPPFLAGS = -I$(top_srcdir)/include
+libimage_stream_la_LDFLAGS = -static
+libimage_stream_la_LIBADD = \
+ $(LIBLTDL) \
+ $(top_builddir)/lib/pdf/libpdf.la
+libimage_stream_la_SOURCES = \
+ $(libimage_stream_la_files)
+endif
+libimage_stream_la_files = \
+ basic-imgstream.cc \
+ basic-imgstream.hh \
+ fax-encoder.cc \
+ fax-encoder.hh \
+ file-opener.cc \
+ file-opener.hh \
+ imgstream.cc \
+ imgstream.hh \
+ jpegstream.cc \
+ jpegstream.hh \
+ pcxstream.cc \
+ pcxstream.hh \
+ pdfstream.cc \
+ pdfstream.hh \
+ pngstream.cc \
+ pngstream.hh \
+ pnmstream.cc \
+ pnmstream.hh \
+ tiffstream.cc \
+ tiffstream.hh
+
+EXTRA_DIST = \
+ $(libimage_stream_la_files)
diff --git a/lib/basic-imgstream.cc b/lib/basic-imgstream.cc
new file mode 100644
index 0000000..9b7aea8
--- /dev/null
+++ b/lib/basic-imgstream.cc
@@ -0,0 +1,301 @@
+// basic-imgstream.cc -- the mother of all image streams
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdlib>
+#include <argz.h>
+
+namespace iscan
+{
+
+#if __GLIBC_PREREQ(2, 10)
+ typedef const dirent** dirtype;
+#else
+ typedef const void* dirtype;
+#endif
+
+ basic_imgstream::basic_imgstream (void)
+ : _h_sz (0), _v_sz (0), _hres (0), _vres (0), _bits (0), _cspc (NONE)
+ {
+ }
+
+ basic_imgstream::~basic_imgstream (void)
+ {
+ }
+
+ basic_imgstream&
+ basic_imgstream::flush (void)
+ {
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::size (size_type h_sz, size_type v_sz)
+ {
+ _h_sz = h_sz;
+ _v_sz = v_sz;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::resolution (size_type hres, size_type vres)
+ {
+ _hres = hres;
+ _vres = vres;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::depth (size_type bits)
+ {
+ _bits = bits;
+ return *this;
+ }
+
+ basic_imgstream&
+ basic_imgstream::colour (colour_space space)
+ {
+ _cspc = space;
+ return *this;
+ }
+
+
+ basic_imgstream::dl_handle
+ basic_imgstream::dlopen (const char *libname,
+ bool (*validate) (lt_dlhandle))
+ {
+ if (0 != lt_dlinit ())
+ {
+ throw runtime_error (lt_dlerror ());
+ }
+
+ dl_handle lib = find_dlopen (libname, validate);
+ if (!lib)
+ {
+ lt_dlexit ();
+ throw runtime_error ("no usable library found");
+ }
+
+ return lib;
+ }
+
+ basic_imgstream::dl_ptr
+ basic_imgstream::dlsym (dl_handle lib, const char *funcname)
+ {
+ return lt_dlsym (lib, funcname);
+ }
+
+ int
+ basic_imgstream::dlclose (dl_handle lib)
+ {
+ return lt_dlclose (lib);
+ }
+
+ // forward declarations
+ static int reversionsort (dirtype, dirtype);
+ int selector (const dirent *);
+
+ //!
+ /*! A number of distributions seems to have switched to a policy where
+ the lib*.so files are provided by their -devel packages. Moreover,
+ the typical workstation install does not include such packages and
+ lt_dlopenext() will understandably have trouble finding your lib*.
+
+ This function is a fallback for such cases. It will look for your
+ library in the exact same places as lt_dlopenext(), but with this
+ difference that it will try to open any file that matches lib*.so,
+ starting with the one with the highest version number.
+
+ Actually, it is just as smart lt_dlopenext() and uses the correct
+ shared library extension for your platform. However, it does not
+ try libtool's .la extension.
+
+ The general policy for memory allocation and access problems is to
+ ignore them and muddle on or return the current result rightaway.
+
+ This function returns NULL if no suitable library could be found
+ and a handle to library otherwise.
+ */
+ basic_imgstream::dl_handle
+ basic_imgstream::find_dlopen (const char *libname,
+ bool (*validate) (lt_dlhandle))
+ {
+ using std::bad_alloc;
+ using std::string;
+
+ dl_handle result = NULL;
+
+ try
+ { // prepping the selector()
+ char *name = new char[strlen (libname)
+ + strlen (LT_MODULE_EXT) + 1];
+ name = strcpy (name, libname);
+ name = strcat (name, LT_MODULE_EXT);
+
+ _libname = name; // deleting _libname below, never mind
+ // that name goes out of scope here
+ }
+ catch (bad_alloc& oops)
+ {
+ return result;
+ }
+
+ char *pathz = NULL;
+ size_t length = 0;
+ bool is_pathz_ok = true;
+ { // set up a library search path like
+ // that used by lt_dlopen()
+ int delimiter = ':';
+
+ const char *path = NULL;
+
+ if ((path = lt_dlgetsearchpath ())
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ((path = getenv ("LTDL_LIBRARY_PATH"))
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ((path = getenv (LT_MODULE_PATH_VAR))
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ if ("x86_64" == string (ISCAN_HOST_CPU)
+ && (path = "/usr/local/lib64:/usr/lib64:/lib64")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ // Kludge for multiarch support introduced in Ubuntu 11.04
+ if ("x86_64" == string (ISCAN_HOST_CPU))
+ {
+ if ((path = "/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+ else
+ {
+ if ((path = "/usr/lib/i386-linux-gnu:/lib/i386-linux-gnu")
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+ if ((path = LT_DLSEARCH_PATH)
+ && 0 != argz_add_sep (&pathz, &length, path, delimiter))
+ {
+ is_pathz_ok = false;
+ }
+ }
+
+ if (is_pathz_ok)
+ { // go fetch!
+ const char *dir_name = NULL;
+ while (!result
+ && (dir_name = argz_next (pathz, length, dir_name)))
+ {
+ struct dirent **match = NULL;
+ int count = scandir (dir_name, &match, selector, reversionsort);
+
+ for (int i = 0; !result && i < count; ++i)
+ {
+
+ const char *file_name = match[i]->d_name;
+ try
+ {
+ char *abs_file_name
+ = new char[strlen (dir_name) + strlen ("/")
+ + strlen (file_name) + 1];
+ strcpy (abs_file_name, dir_name);
+ strcat (abs_file_name, "/");
+ strcat (abs_file_name, file_name);
+
+ result = lt_dlopen (abs_file_name);
+ if (validate && !validate (result))
+ {
+ lt_dlclose (result);
+ result = NULL;
+ }
+ delete [] abs_file_name;
+ }
+ catch (bad_alloc& oops)
+ {
+ // just ignore and continue with the next match
+ }
+ free (match[i]);
+ }
+ if (match) free (match); // malloc'd by scandir()
+ }
+ }
+
+ delete [] _libname; // we new'd a name for our selector()
+ free (pathz); // malloc'd by argz_add_sep()
+
+ return result;
+ }
+
+ //! Library name we are looking for.
+ /*! The scandir() API does not allow for passing arbitrary data to the
+ selector(). We use this variable to work around that limitation.
+
+ Note that this makes users of selector() thread unsafe.
+ */
+ const char *basic_imgstream::_libname = NULL;
+
+ //! Selects relevant library filenames.
+ /*! Returns true if the leading part of the directory entry's file
+ name matches the library name we are looking for. The file name
+ may contain trailing version information which is ignored.
+ */
+ int
+ selector (const dirent *dir)
+ {
+ return (0 == strncmp (dir->d_name, basic_imgstream::_libname,
+ strlen (basic_imgstream::_libname)));
+ }
+
+ //! The C library's versionsort() function in reverse.
+ static int
+ reversionsort (dirtype a, dirtype b)
+ {
+ return versionsort (b, a);
+ }
+
+} // namespace iscan
diff --git a/lib/basic-imgstream.hh b/lib/basic-imgstream.hh
new file mode 100644
index 0000000..f55bc60
--- /dev/null
+++ b/lib/basic-imgstream.hh
@@ -0,0 +1,144 @@
+// basic-imgstream.hh -- the mother of all image streams
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_basic_imgstream_hh_included
+#define iscan_basic_imgstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+
+#ifndef LT_MODULE_EXT
+#define LT_MODULE_EXT LTDL_SHLIB_EXT
+#endif
+
+#ifndef LT_MODULE_PATH_VAR
+#define LT_MODULE_PATH_VAR LTDL_SHLIBPATH_VAR
+#endif
+
+#ifndef LT_DLSEARCH_PATH
+#define LT_DLSEARCH_PATH LTDL_SYSSEARCHPATH
+#endif
+
+#endif
+
+#include <dirent.h>
+#include <ltdl.h>
+#include <stdexcept>
+
+namespace iscan
+{
+ using std::runtime_error;
+
+ enum colour_space
+ {
+ NONE,
+
+ monochrome,
+ mono = monochrome,
+
+ grayscale,
+ greyscale = grayscale,
+ gray = grayscale,
+ grey = grayscale,
+
+ RGB,
+ RGB_alpha
+ };
+
+ class basic_imgstream
+ {
+ public:
+ typedef char byte_type;
+ typedef size_t size_type;
+
+ virtual ~basic_imgstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n) = 0;
+ virtual basic_imgstream& flush (void);
+
+ virtual basic_imgstream& size (size_type h_sz, size_type v_sz);
+ virtual basic_imgstream& resolution (size_type hres, size_type vres);
+ virtual basic_imgstream& depth (size_type bits);
+ virtual basic_imgstream& colour (colour_space space);
+ virtual void rotate_180 (bool yes) {};
+
+ protected:
+ basic_imgstream (void);
+
+ size_type _h_sz;
+ size_type _v_sz;
+ size_type _hres;
+ size_type _vres;
+
+ size_type _bits;
+ colour_space _cspc;
+
+ private: // undefined to prevent copying
+ basic_imgstream (const basic_imgstream&);
+ basic_imgstream& operator= (const basic_imgstream&);
+
+
+ // API to deal with external image format libraries
+
+ protected:
+ typedef lt_dlhandle dl_handle;
+ typedef lt_ptr dl_ptr;
+
+ static dl_handle dlopen (const char *libname,
+ bool (*validate) (dl_handle) = NULL);
+ static dl_ptr dlsym (dl_handle lib, const char *funcname);
+ static int dlclose (dl_handle lib);
+
+ private:
+ static dl_handle find_dlopen (const char *libname,
+ bool (*validate) (dl_handle));
+ static const char *_libname;
+
+ friend int selector (const dirent *);
+
+#ifdef __GNUC__
+#define fundecl(returntype,funcname,arglist...) \
+ typedef returntype (*funcname##_f) (arglist); \
+ funcname##_f funcname;
+#else
+#error "Your compiler is not known to support macros with a variable"
+#error "number of arguments. In case it does, please report this to"
+#error "the library maintainers and include a suitable preprocessor"
+#error "check for them to add. A patch will be most appreciated."
+#endif
+
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_basic_imgstream_hh_included) */
diff --git a/lib/fax-encoder.cc b/lib/fax-encoder.cc
new file mode 100644
index 0000000..90abe8c
--- /dev/null
+++ b/lib/fax-encoder.cc
@@ -0,0 +1,411 @@
+// fax-encoder.cc -- convert scanlines to fascimile format
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fax-encoder.hh"
+
+#include <vector>
+#include <stdint.h>
+
+#define WHITE false
+#define BLACK true
+
+namespace iscan
+{
+ using namespace std;
+
+ struct code {
+ unsigned int bits;
+ unsigned int code;
+ };
+
+ static const size_t g3_make_up_min = 64;
+ static const size_t g3_make_up_inc = 64;
+ static const size_t g3_extra_make_up_min = 1792;
+ static const size_t g3_extra_make_up_max = 2560;
+
+ //! Terminating codes for white runs of less than 64 pixels.
+ static const struct code g3_white_terminal[] =
+ {
+ { 8, 0x35 },
+ { 6, 0x07 },
+ { 4, 0x07 },
+ { 4, 0x08 },
+ { 4, 0x0b },
+ { 4, 0x0c },
+ { 4, 0x0e },
+ { 4, 0x0f },
+ { 5, 0x13 },
+ { 5, 0x14 },
+ { 5, 0x07 },
+ { 5, 0x08 },
+ { 6, 0x08 },
+ { 6, 0x03 },
+ { 6, 0x34 },
+ { 6, 0x35 },
+ { 6, 0x2a },
+ { 6, 0x2b },
+ { 7, 0x27 },
+ { 7, 0x0c },
+ { 7, 0x08 },
+ { 7, 0x17 },
+ { 7, 0x03 },
+ { 7, 0x04 },
+ { 7, 0x28 },
+ { 7, 0x2b },
+ { 7, 0x13 },
+ { 7, 0x24 },
+ { 7, 0x18 },
+ { 8, 0x02 },
+ { 8, 0x03 },
+ { 8, 0x1a },
+ { 8, 0x1b },
+ { 8, 0x12 },
+ { 8, 0x13 },
+ { 8, 0x14 },
+ { 8, 0x15 },
+ { 8, 0x16 },
+ { 8, 0x17 },
+ { 8, 0x28 },
+ { 8, 0x29 },
+ { 8, 0x2a },
+ { 8, 0x2b },
+ { 8, 0x2c },
+ { 8, 0x2d },
+ { 8, 0x04 },
+ { 8, 0x05 },
+ { 8, 0x0a },
+ { 8, 0x0b },
+ { 8, 0x52 },
+ { 8, 0x53 },
+ { 8, 0x54 },
+ { 8, 0x55 },
+ { 8, 0x24 },
+ { 8, 0x25 },
+ { 8, 0x58 },
+ { 8, 0x59 },
+ { 8, 0x5a },
+ { 8, 0x5b },
+ { 8, 0x4a },
+ { 8, 0x4b },
+ { 8, 0x32 },
+ { 8, 0x33 },
+ { 8, 0x34 },
+ };
+
+ //! Terminating codes for black runs of less than 64 pixels.
+ static const struct code g3_black_terminal[] =
+ {
+ { 10, 0x37 },
+ { 3, 0x02 },
+ { 2, 0x03 },
+ { 2, 0x02 },
+ { 3, 0x03 },
+ { 4, 0x03 },
+ { 4, 0x02 },
+ { 5, 0x03 },
+ { 6, 0x05 },
+ { 6, 0x04 },
+ { 7, 0x04 },
+ { 7, 0x05 },
+ { 7, 0x07 },
+ { 8, 0x04 },
+ { 8, 0x07 },
+ { 9, 0x18 },
+ { 10, 0x17 },
+ { 10, 0x18 },
+ { 10, 0x08 },
+ { 11, 0x67 },
+ { 11, 0x68 },
+ { 11, 0x6c },
+ { 11, 0x37 },
+ { 11, 0x28 },
+ { 11, 0x17 },
+ { 11, 0x18 },
+ { 12, 0xca },
+ { 12, 0xcb },
+ { 12, 0xcc },
+ { 12, 0xcd },
+ { 12, 0x68 },
+ { 12, 0x69 },
+ { 12, 0x6a },
+ { 12, 0x6b },
+ { 12, 0xd2 },
+ { 12, 0xd3 },
+ { 12, 0xd4 },
+ { 12, 0xd5 },
+ { 12, 0xd6 },
+ { 12, 0xd7 },
+ { 12, 0x6c },
+ { 12, 0x6d },
+ { 12, 0xda },
+ { 12, 0xdb },
+ { 12, 0x54 },
+ { 12, 0x55 },
+ { 12, 0x56 },
+ { 12, 0x57 },
+ { 12, 0x64 },
+ { 12, 0x65 },
+ { 12, 0x52 },
+ { 12, 0x53 },
+ { 12, 0x24 },
+ { 12, 0x37 },
+ { 12, 0x38 },
+ { 12, 0x27 },
+ { 12, 0x28 },
+ { 12, 0x58 },
+ { 12, 0x59 },
+ { 12, 0x2b },
+ { 12, 0x2c },
+ { 12, 0x5a },
+ { 12, 0x66 },
+ { 12, 0x67 },
+ };
+
+ //! Make up codes for white runs of 64 to 1728 + 63 pixels.
+ static const struct code g3_white_make_up[] =
+ { // runlength 64 + index * 64
+ { 5, 0x1b },
+ { 5, 0x12 },
+ { 6, 0x17 },
+ { 7, 0x37 },
+ { 8, 0x36 },
+ { 8, 0x37 },
+ { 8, 0x64 },
+ { 8, 0x65 },
+ { 8, 0x68 },
+ { 8, 0x67 },
+ { 9, 0xcc },
+ { 9, 0xcd },
+ { 9, 0xd2 },
+ { 9, 0xd3 },
+ { 9, 0xd4 },
+ { 9, 0xd5 },
+ { 9, 0xd6 },
+ { 9, 0xd7 },
+ { 9, 0xd8 },
+ { 9, 0xd9 },
+ { 9, 0xda },
+ { 9, 0xdb },
+ { 9, 0x98 },
+ { 9, 0x99 },
+ { 9, 0x9a },
+ { 6, 0x18 },
+ { 9, 0x9b },
+ };
+
+ //! Make up codes for black runs of 64 to 1728 + 63 pixels.
+ static const struct code g3_black_make_up[] =
+ { // runlength 64 + index * 64
+ { 10, 0x0f },
+ { 12, 0xc8 },
+ { 12, 0xc9 },
+ { 12, 0x5b },
+ { 12, 0x33 },
+ { 12, 0x34 },
+ { 12, 0x35 },
+ { 13, 0x6c },
+ { 13, 0x6d },
+ { 13, 0x4a },
+ { 13, 0x4b },
+ { 13, 0x4c },
+ { 13, 0x4d },
+ { 13, 0x72 },
+ { 13, 0x73 },
+ { 13, 0x74 },
+ { 13, 0x75 },
+ { 13, 0x76 },
+ { 13, 0x77 },
+ { 13, 0x52 },
+ { 13, 0x53 },
+ { 13, 0x54 },
+ { 13, 0x55 },
+ { 13, 0x5a },
+ { 13, 0x5b },
+ { 13, 0x64 },
+ { 13, 0x65 },
+ };
+
+ //! Additional make up codes for run of more than 1792 pixels.
+ static const struct code g3_extra_make_up[] =
+ { // runlength 1792 + index * 64
+ { 11, 0x08 },
+ { 11, 0x0c },
+ { 11, 0x0d },
+ { 12, 0x12 },
+ { 12, 0x13 },
+ { 12, 0x14 },
+ { 12, 0x15 },
+ { 12, 0x16 },
+ { 12, 0x17 },
+ { 12, 0x1c },
+ { 12, 0x1d },
+ { 12, 0x1e },
+ { 12, 0x1f },
+ };
+
+ static string transform (vector<size_t>& runs);
+
+ //! Converts a packed \a line of pixels into FAX G3 encoded scanline.
+ /*! This functions merely collects the run lengths into a vector and
+ passes that vector off to transform().
+ */
+ string
+ fax_encoder::operator() (const byte_type* line, size_type n)
+ {
+ bool colour = WHITE;
+ size_t length = 0;
+ vector<size_t> runs;
+
+ uint8_t bit = 0x80;
+
+ n *= 8;
+
+ while (0 < n--)
+ {
+ if (colour == bool ((*line | ~bit) & bit))
+ {
+ ++length;
+ }
+ else
+ {
+ runs.push_back (length);
+ colour = (WHITE == colour ? BLACK : WHITE);
+ length = 1;
+ }
+
+ bit >>= 1;
+ if (0 == bit)
+ {
+ bit = 0x80;
+ ++line;
+ }
+ }
+ runs.push_back (length);
+
+ return transform (runs);
+ }
+
+ //! Converts a vector of run lengths into a FAX G3 encoded string.
+ /*! The string always starts with an end-of-line marker and will be
+ filled if necessary.
+ */
+ static string
+ transform (vector<size_t>& runs)
+ {
+ vector<size_t>::iterator it = runs.begin ();
+ bool colour = WHITE;
+
+ string result;
+ unsigned char ch = 0x00;
+ size_t i = 0;
+
+ unsigned int mask = 1 << 11;
+ unsigned int code = 1;
+
+ while (mask)
+ {
+ if (code & mask) ch |= (1 << (7 - i % 8));
+ ++i;
+ mask >>= 1;
+ if (0 == i % 8)
+ {
+ result.push_back (ch);
+ ch = 0x00;
+ }
+ }
+
+ while (runs.end () != it)
+ {
+ bool terminal = false;
+ do
+ {
+ const struct code *c = NULL;
+
+ if (g3_extra_make_up_max <= *it)
+ {
+ size_t index = ((g3_extra_make_up_max - g3_extra_make_up_min)
+ / g3_make_up_inc);
+
+ c = g3_extra_make_up + index;
+ *it -= g3_extra_make_up_max;
+ }
+ else if (g3_extra_make_up_min <= *it)
+ {
+ size_t index = (*it - g3_extra_make_up_min) / g3_make_up_inc;
+
+ c = g3_extra_make_up + index;
+ *it -= g3_extra_make_up_min + index * g3_make_up_inc;
+ }
+ else if (g3_make_up_min <= *it)
+ {
+ size_t index = (*it - g3_make_up_min) / g3_make_up_inc;
+
+ c = ((WHITE == colour ? g3_white_make_up : g3_black_make_up)
+ + index);
+ *it -= g3_make_up_min + index * g3_make_up_inc;
+ }
+ else
+ {
+ c = ((WHITE == colour ? g3_white_terminal : g3_black_terminal)
+ + *it);
+ *it = 0;
+ terminal = true;
+ }
+
+ unsigned int mask = 1 << (c->bits - 1);
+ unsigned int code = c->code;
+
+ while (mask)
+ {
+ if (code & mask) ch |= (1 << (7 - i % 8));
+ ++i;
+ mask >>= 1;
+ if (0 == i % 8)
+ {
+ result.push_back (ch);
+ ch = 0x00;
+ }
+ }
+ }
+ while (!terminal);
+
+ ++it;
+ colour = (WHITE == colour ? BLACK : WHITE);
+ }
+ if (0 != i % 8)
+ result.push_back (ch);
+
+ return result;
+ }
+
+} // namespace iscan
diff --git a/lib/fax-encoder.hh b/lib/fax-encoder.hh
new file mode 100644
index 0000000..4ae6f38
--- /dev/null
+++ b/lib/fax-encoder.hh
@@ -0,0 +1,58 @@
+// fax-encoder.hh -- convert scanlines to fascimile format
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_fax_encoder_hh_included
+#define iscan_fax_encoder_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+namespace iscan
+{
+ using std::string;
+
+ class fax_encoder
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ string operator() (const byte_type* line, size_type n);
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_fax_encoder_hh_included) */
diff --git a/lib/file-opener.cc b/lib/file-opener.cc
new file mode 100644
index 0000000..c0ccdd8
--- /dev/null
+++ b/lib/file-opener.cc
@@ -0,0 +1,321 @@
+// file-opener.cc -- dealing with files when doing multi-image scans
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "file-opener.hh"
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <list>
+#include <iomanip>
+#include <sstream>
+#include <stdexcept>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace iscan
+{
+ static string tempfile (const string& dirname = string ());
+
+ //! Opening one or more files in a temporary file location.
+ file_opener::file_opener (bool collate)
+ : _collate (collate), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ }
+
+ //! Opening a file by \a name.
+ file_opener::file_opener (const string& name)
+ : _collate (true), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ common_init (name);
+ }
+
+ //! Opening files following a naming \a pattern.
+ file_opener::file_opener (const string& pattern, unsigned int start_index)
+ : _collate (false), _filename (string ()), _tempfile (string ()),
+ _fp (NULL), _pattern (NULL)
+ {
+ common_init (pattern);
+
+ string::size_type hash = _pattern->basename.find_last_not_of (hash_mark);
+
+ if (string::npos == hash)
+ {
+ delete _pattern; // new'd in common_init()
+ throw std::invalid_argument ("invalid file name pattern");
+ }
+ ++hash; // first hash_mark
+
+ _pattern->digits = _pattern->basename.length () - hash;
+ _pattern->basename = _pattern->basename.substr (0, hash);
+ _pattern->index = start_index;
+ }
+
+ file_opener::~file_opener (void)
+ {
+ if (_fp) close ();
+ if (_tempfile != _filename)
+ rename ();
+ delete _pattern;
+ }
+
+ //! Returns the C \c FILE pointer associated with a name().
+ file_opener::operator FILE * (void)
+ {
+ if (_filename.empty ()) set_names ();
+ if (!_fp) open ();
+ return _fp;
+ }
+
+ //! Returns the pathname of the output destination.
+ const string&
+ file_opener::name (void) const
+ {
+ if (_filename.empty ())
+ const_cast<file_opener *> (this)->set_names ();
+ return _filename;
+ }
+
+ //! Returns the pathname of the interim output destination.
+ /*! \deprecated Flaming hack to quickly fix our TIFF support.
+ */
+ const string&
+ file_opener::temp (void) const
+ {
+ if (_filename.empty ())
+ const_cast<file_opener *> (this)->set_names ();
+ return _tempfile;
+ }
+
+ //! Returns the file's extension (without extension separator).
+ string
+ file_opener::extension (void) const
+ {
+ if (!_pattern) return null_ext;
+ return _pattern->extension.substr (ext_sep.size ());
+ }
+
+ //! Opens the next file, unless the file_opener is_collating().
+ file_opener&
+ file_opener::operator++ (void)
+ {
+ if (_collate) return *this;
+
+ if (_fp) close ();
+ if (_tempfile != _filename)
+ rename ();
+ set_names (true);
+ open ();
+
+ return *this;
+ }
+
+ //! Tells whether or not output will be collated.
+ bool
+ file_opener::is_collating (void) const
+ {
+ return _collate;
+ }
+
+ //! Removes the output destination.
+ void
+ file_opener::remove (void)
+ {
+ if (_fp)
+ {
+ close ();
+ if (0 != ::remove (_tempfile.c_str ()))
+ throw std::ios_base::failure (strerror (errno));
+ }
+ _tempfile = string ();
+ _filename = string ();
+ }
+
+ const string file_opener::dir_sep = "/";
+ const string file_opener::ext_sep = ".";
+ const char file_opener::hash_mark = '#';
+
+ const string file_opener::null_ext = string ();
+
+ //! Handles common part of the constructors taking a string.
+ void
+ file_opener::common_init (const string& s)
+ {
+ string::size_type sep = s.rfind (dir_sep);
+ string::size_type dot = s.rfind (ext_sep);
+
+ if (string::npos != sep && dot < sep)
+ dot = string::npos;
+
+ _pattern = new struct pattern;
+ if (string::npos != sep)
+ {
+ ++sep;
+ _pattern->dirname = s.substr (0, sep);
+ _pattern->basename = s.substr (sep, dot - sep);
+ }
+ else
+ {
+ _pattern->dirname = string ();
+ _pattern->basename = s.substr (0, dot);
+ }
+
+ if (string::npos != dot)
+ {
+ _pattern->extension = s.substr (dot);
+ }
+ else
+ {
+ _pattern->extension = null_ext;
+ }
+ _pattern->digits = 0;
+ _pattern->index = 0;
+ }
+
+ void
+ file_opener::set_names (bool next)
+ {
+ using std::stringstream;
+ using std::setfill;
+ using std::setw;
+
+ if (!_pattern)
+ {
+ _tempfile = tempfile ();
+ _filename = _tempfile;
+ }
+ else
+ {
+ if (next) ++_pattern->index;
+
+ stringstream ss;
+ ss << _pattern->dirname
+ << _pattern->basename;
+ if (_pattern->digits)
+ {
+ ss << setfill ('0')
+ << setw (_pattern->digits)
+ << _pattern->index;
+ }
+ ss << _pattern->extension;
+
+ _filename = ss.str ();
+ _tempfile = (!_pattern->digits
+ ? _filename : tempfile (_pattern->dirname.empty ()
+ ? "." : _pattern->dirname));
+ }
+ }
+
+ //! Error handling wrapper around the C fopen() call.
+ void
+ file_opener::open (void)
+ {
+ _fp = fopen (_tempfile.c_str (), "wb");
+ if (!_fp)
+ throw std::ios_base::failure (strerror (errno));
+ }
+
+ //! Error handling wrapper around the C fclose() call.
+ void
+ file_opener::close (void)
+ {
+ if (!_fp) return;
+
+ int rv = fclose (_fp);
+ _fp = NULL;
+
+ if (0 != rv)
+ throw std::ios_base::failure (strerror (errno));
+ }
+
+ void
+ file_opener::rename (void)
+ {
+ if (0 != ::rename (_tempfile.c_str (), _filename.c_str ()))
+ throw std::ios_base::failure (strerror (errno));
+
+ _tempfile = _filename;
+ }
+
+
+ //! Creates a temporary file in a secure way.
+ static string
+ tempfile (const string& dirname)
+ {
+ using std::list;
+
+ list<string> dirs;
+ if (!dirname.empty ()) dirs.push_back (dirname);
+ if (getenv ("TMPDIR")) dirs.push_back (getenv ("TMPDIR"));
+#ifdef P_tmpdir
+ dirs.push_back (P_tmpdir); // C library default
+#endif
+ dirs.push_back ("/tmp"); // last resort
+
+ string filename;
+ list<string>::iterator it = dirs.begin ();
+
+ while (dirs.end () != it && filename.empty ())
+ {
+ string ts = (*it + file_opener::dir_sep
+ + (!dirname.empty () ? "." : "")
+ + PACKAGE_TARNAME "XXXXXX");
+ char *tc = new char [ts.length() + 1];
+
+ ts.copy (tc, ts.length ());
+ tc[ts.length ()] = '\0';
+
+ mode_t um = umask (0);
+ umask (dirname.empty () ? 0077 : um); // use safe permissions
+ if (0 <= mkstemp (tc))
+ filename = tc;
+ umask (um);
+
+ // mkstemp from glibc 2.0.7 and later always uses 0600, (try
+ // to) revert here
+ if (!dirname.empty ())
+ {
+ chmod (filename.c_str (), ( S_IRUSR | S_IRGRP | S_IROTH
+ | S_IWUSR | S_IWGRP | S_IWOTH) & ~um);
+ }
+
+ delete [] tc;
+ ++it;
+ }
+
+ return filename;
+ }
+
+} // namespace iscan
diff --git a/lib/file-opener.hh b/lib/file-opener.hh
new file mode 100644
index 0000000..70c475c
--- /dev/null
+++ b/lib/file-opener.hh
@@ -0,0 +1,101 @@
+// file-opener.hh -- dealing with files when doing multi-image scans
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_file_opener_hh_included
+#define iscan_file_opener_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class file_opener
+ {
+ public:
+ explicit file_opener (bool collate);
+ explicit file_opener (const string& name);
+ file_opener (const string& pattern, unsigned int start_index);
+ ~file_opener (void);
+
+ operator FILE * (void);
+
+ const string& name (void) const;
+ const string& temp (void) const;
+ string extension (void) const;
+
+ file_opener& operator++ (void);
+
+ bool is_collating (void) const;
+
+ void remove (void);
+
+ static const string dir_sep;
+ static const string ext_sep;
+ static const char hash_mark;
+
+ static const string null_ext;
+
+ private:
+ void common_init (const string& s);
+ void set_names (bool next = false);
+
+ void open (void);
+ void close (void);
+ void rename (void);
+
+ bool _collate;
+
+ string _filename;
+ string _tempfile;
+ FILE *_fp;
+
+ struct pattern
+ {
+ string extension;
+ string basename;
+ string dirname; // includes final dir_sep
+ size_t digits;
+ unsigned int index;
+ };
+ struct pattern *_pattern;
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_file_opener_hh_included) */
diff --git a/lib/imgstream.cc b/lib/imgstream.cc
new file mode 100644
index 0000000..80f87fb
--- /dev/null
+++ b/lib/imgstream.cc
@@ -0,0 +1,142 @@
+// imgstream.cc -- provides an interface to write multiple images
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#include "jpegstream.hh"
+#include "pcxstream.hh"
+#include "pngstream.hh"
+#include "pnmstream.hh"
+#include "pdfstream.hh"
+#include "tiffstream.hh"
+
+namespace iscan
+{
+ imgstream::imgstream (file_opener& opener, file_format format,
+ bool match_direction)
+ : _page (0), _match_direction (match_direction),
+ _opener (&opener), _format (format), _configured (false)
+ {
+ _stream = create_stream ();
+ }
+
+ imgstream::imgstream (void)
+ : _page (0), _match_direction (false), _opener (NULL), _format (NO_FORMAT),
+ _stream (NULL), _configured (false)
+ {
+ }
+
+ imgstream::~imgstream (void)
+ {
+ delete _stream;
+ }
+
+ imgstream&
+ imgstream::write (const byte_type *data, size_type n)
+ {
+ if (!_stream) return *this;
+
+ if (!_configured)
+ {
+ _stream->size (_h_sz, _v_sz);
+ _stream->resolution (_hres, _vres);
+ _stream->colour (_cspc);
+ _stream->depth (_bits);
+ _configured = true;
+ }
+
+ _stream->write (data, n);
+
+ return *this;
+ }
+
+ imgstream&
+ imgstream::flush (void)
+ {
+ if (_stream) _stream->flush ();
+ return *this;
+ }
+
+ void
+ imgstream::next (void)
+ {
+ if (!_configured) return;
+
+ delete _stream;
+ _configured = false;
+
+ ++_page;
+ ++(*_opener);
+ _stream = create_stream ();
+ if (_match_direction) _stream->rotate_180 (is_back (_page));
+ }
+
+ bool
+ imgstream::is_back (unsigned long page)
+ {
+ return 0 == (page+1)%2;
+ }
+
+ bool
+ imgstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream *
+ imgstream::create_stream (void)
+ {
+ if (PCX == _format) return new pcxstream (*_opener);
+ if (PNM == _format) return new pnmstream (*_opener);
+ if (PNG == _format) return new pngstream (*_opener);
+ if (JPG == _format) return new jpegstream (*_opener);
+ if (PDF == _format) return new pdfstream (*_opener);
+ if (TIF == _format) return new tiffstream (*_opener, _opener->temp ());
+
+ throw std::invalid_argument ("unsupported file format");
+ }
+
+ imgstream *
+ create_imgstream (file_opener& opener, file_format format,
+ bool match_direction)
+ {
+ if (opener.is_collating ())
+ {
+ if (PDF == format) return new pdfstream (opener, match_direction);
+ if (TIF == format) return new tiffstream (opener, opener.name ());
+ }
+
+ return new imgstream (opener, format, match_direction);
+ }
+
+} // namespace iscan
diff --git a/lib/imgstream.hh b/lib/imgstream.hh
new file mode 100644
index 0000000..5904049
--- /dev/null
+++ b/lib/imgstream.hh
@@ -0,0 +1,100 @@
+// imgstream.hh -- provides an interface to write multiple images
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_imgstream_hh_included
+#define iscan_imgstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+#include "file-opener.hh"
+
+
+namespace iscan
+{
+ enum file_format
+ {
+ PCX,
+ PNM,
+ PNG,
+ JPG, JPEG = JPG,
+ PDF,
+ TIF, // libtiff uses TIFF as a type already!
+ NO_FORMAT,
+ };
+
+ class imgstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ imgstream (file_opener& opener, file_format format,
+ bool match_direction = false);
+ virtual ~imgstream (void);
+
+ virtual imgstream& write (const byte_type *data, size_type n);
+ virtual imgstream& flush (void);
+
+ virtual void next (void);
+
+ static bool is_usable (void);
+
+ protected:
+ imgstream (void);
+
+ bool is_back (unsigned long); // indicates whether a page is the
+ // back page of a duplex scan
+
+ unsigned long _page;
+ bool _match_direction; // when true, match front and back
+ // orientation for duplex scans
+
+ private:
+ basic_imgstream * create_stream (void);
+
+ file_opener* _opener;
+ file_format _format;
+
+ basic_imgstream *_stream;
+ bool _configured;
+ };
+
+ imgstream *
+ create_imgstream (file_opener& opener, file_format format,
+ bool match_direction = false);
+
+} // namespace iscan
+
+#endif /* iscan_imgstream_hh_included */
diff --git a/lib/jpegstream.cc b/lib/jpegstream.cc
new file mode 100644
index 0000000..66bf92d
--- /dev/null
+++ b/lib/jpegstream.cc
@@ -0,0 +1,270 @@
+// jpegstream.cc -- image streams producing JPEG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "jpegstream.hh"
+
+#include <cstdlib>
+#include <ios>
+
+namespace iscan
+{
+ jpegstream::jpegstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false), _scanline (NULL)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+#if HAVE_JPEGLIB_H
+ init ();
+#endif
+ }
+
+ jpegstream::~jpegstream (void)
+ {
+ delete [] _scanline;
+#if HAVE_JPEGLIB_H
+ if (_header)
+ {
+ if (0 == _v_sz) lib->finish_compress (&_info);
+ lib->destroy_compress (&_info);
+ }
+#endif
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ jpegstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+#if HAVE_JPEGLIB_H
+ if (!_header)
+ {
+ write_header ();
+ }
+ if (!_scanline)
+ {
+ lib->write_scanlines (&_info, (JSAMPLE **) &line, 1);
+ if (0 < _info.err->msg_code)
+ throw std::ios_base::failure ("write error");
+ }
+ else
+ {
+ // FIXME: assumes that _bits == 1, whereas the condition for
+ // _scanline to be true, see write_init (), requires
+ // only that _bits != 8.
+ for (unsigned int i = 0; i < _h_sz; ++i)
+ {
+ div_t index = div (i, 8 * sizeof (JSAMPLE));
+ int offset = 8 * sizeof (JSAMPLE) - 1 - index.rem;
+ _scanline[i] = ((line[index.quot] & (1 << offset))
+ ? 0 : ~0);
+ }
+ lib->write_scanlines (&_info, (JSAMPLE **) &_scanline, 1);
+ if (0 < _info.err->msg_code)
+ throw std::ios_base::failure ("write error");
+ }
+ --_v_sz;
+#endif /* HAVE_JPEGLIB_H */
+ return *this;
+ }
+
+ bool
+ jpegstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) jpeg_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_JPEGLIB_H
+ try
+ {
+ basic_imgstream::dlopen ("libjpeg", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_JPEGLIB_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_JPEGLIB_H
+#define funcsym(name) \
+ lib->name \
+ = ((jpegstream::jpeg_lib_handle::name##_f) \
+ basic_imgstream::dlsym (lib->lib, "jpeg_"#name));
+#endif
+
+ bool
+ jpegstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_JPEGLIB_H
+ lib->lib = h;
+
+# ifndef jpeg_create_compress
+ funcsym (create_compress);
+# else
+ funcsym (CreateCompress);
+# endif
+ funcsym (finish_compress);
+ funcsym (destroy_compress);
+ funcsym (destroy);
+ funcsym (stdio_dest);
+ funcsym (std_error);
+ funcsym (write_scanlines);
+ funcsym (set_defaults);
+ funcsym (start_compress);
+ funcsym (default_qtables);
+
+ // restrict usage of libjpeg to the version range it was compiled against;
+ // either before version 7.0 or 7.0 and later
+ bool is_version_consistent =
+ ((JPEG_LIB_VERSION < 70 && !lib->default_qtables) ||
+ (JPEG_LIB_VERSION >= 70 && lib->default_qtables));
+
+ lib->is_usable = (
+# ifndef jpeg_create_compress
+ lib->create_compress
+# else
+ lib->CreateCompress
+# endif
+ && lib->finish_compress
+ && lib->destroy_compress
+ && lib->destroy
+ && lib->stdio_dest
+ && lib->std_error
+ && lib->write_scanlines
+ && lib->set_defaults
+ && lib->start_compress
+ && is_version_consistent);
+#endif /* HAVE_JPEGLIB_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_JPEGLIB_H
+#undef funcsym
+#endif
+
+ basic_imgstream&
+ jpegstream::write_header (void)
+ {
+ check_consistency ();
+
+#if HAVE_JPEGLIB_H
+
+ _info.image_width = _h_sz;
+ _info.image_height = _v_sz;
+
+ _info.in_color_space = (RGB == _cspc ? JCS_RGB : JCS_GRAYSCALE);
+ _info.input_components = (RGB == _cspc ? 3 : 1);
+
+ lib->set_defaults (&_info);
+
+ size_type density_max = (1 << sizeof (_info.X_density) * 8) - 1;
+ _info.density_unit = 1;
+ _info.X_density = (_hres <= density_max) ? _hres : density_max;
+ _info.Y_density = (_vres <= density_max) ? _vres : density_max;
+
+ lib->start_compress (&_info, true);
+
+ if (mono == _cspc && 8 != _bits)
+ {
+ _scanline = new byte_type[_h_sz];
+ }
+ else
+ {
+ _scanline = NULL;
+ }
+#endif /* HAVE_JPEGLIB_H */
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ jpegstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ }
+
+ void
+ jpegstream::init (void)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_JPEGLIB_H
+ // set up JPEG library default error handlers first, then override
+ // error handling for fatal errors (because the default would just
+ // end up calling exit())
+ _info.err = lib->std_error (&_err);
+ _err.error_exit = error_exit;
+# ifndef jpeg_create_compress
+ lib->create_compress (&_info);
+# else
+ lib->CreateCompress (&_info, JPEG_LIB_VERSION,
+ (size_t) sizeof (struct jpeg_compress_struct));
+# endif
+ lib->stdio_dest (&_info, _stream);
+#endif /* HAVE_JPEGLIB_H */
+ }
+
+ jpegstream::jpeg_lib_handle *jpegstream::lib = NULL;
+
+#if HAVE_JPEGLIB_H
+ void
+ jpegstream::error_exit (jpeg_common_struct *info)
+ {
+ jpegstream::lib->destroy (info);
+ }
+#endif
+
+} // namespace iscan
diff --git a/lib/jpegstream.hh b/lib/jpegstream.hh
new file mode 100644
index 0000000..855341c
--- /dev/null
+++ b/lib/jpegstream.hh
@@ -0,0 +1,118 @@
+// jpegstream.hh -- image streams producing JPEG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_jpegstream_hh_included
+#define iscan_jpegstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+#if HAVE_JPEGLIB_H
+#include <jpeglib.h>
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class jpegstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit jpegstream (FILE *fp, const string& pathname = string ());
+ virtual ~jpegstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ void init (void);
+
+ FILE *_stream;
+ bool _header;
+
+ byte_type *_scanline;
+
+ static bool validate (lt_dlhandle h);
+ struct jpeg_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_JPEGLIB_H
+# ifndef jpeg_create_compress
+ fundecl (void, create_compress, jpeg_compress_struct *);
+# else
+ fundecl (void, CreateCompress, jpeg_compress_struct *, int, size_t);
+# endif
+ fundecl (void, finish_compress, jpeg_compress_struct *);
+ fundecl (void, destroy_compress, jpeg_compress_struct *);
+ fundecl (void, destroy, jpeg_common_struct *);
+
+ fundecl (void, stdio_dest, jpeg_compress_struct *, FILE *);
+ fundecl (struct jpeg_error_mgr *, std_error, jpeg_error_mgr *);
+
+ fundecl (void, write_scanlines, jpeg_compress_struct *, JSAMPLE **, int);
+ fundecl (void, set_defaults, jpeg_compress_struct *);
+ fundecl (void, start_compress, jpeg_compress_struct *, bool);
+
+ // only used for version detection purposes; available since libjpeg 7.0
+ fundecl (void, default_qtables, jpeg_compress_struct *, bool);
+#endif /* HAVE_JPEGLIB_H */
+ };
+ static jpeg_lib_handle *lib;
+
+#if HAVE_JPEGLIB_H
+ static void error_exit (j_common_ptr info);
+
+ struct jpeg_compress_struct _info;
+ struct jpeg_error_mgr _err;
+#endif
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_jpegstream_hh_included) */
diff --git a/lib/pcxstream.cc b/lib/pcxstream.cc
new file mode 100644
index 0000000..722e0b0
--- /dev/null
+++ b/lib/pcxstream.cc
@@ -0,0 +1,320 @@
+// pcxstream.cc -- image streams producing PCX files
+// Copyright (C) 2011 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pcxstream.hh"
+
+#include <ios>
+#include <cstring>
+
+namespace iscan
+{
+ pcxstream::pcxstream (FILE *fp, const string& name)
+ : _stream (fp),
+ _header (false),
+ _footer (false),
+ _bytesperline (0),
+ _row_buf (NULL),
+ _zbuf (NULL)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+
+ memset (_pcx_header, 0, sizeof (_pcx_header));
+ _pcx_header [0] = 0x0a; // Manufacturer
+ _pcx_header [1] = 0x05; // Version
+ _pcx_header [2] = 1; // Encoding
+ }
+
+ pcxstream::~pcxstream (void)
+ {
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ pcxstream::flush (void)
+ {
+ if (!_footer)
+ {
+ if (mono == _cspc || grey == _cspc)
+ {
+ write_palette ();
+ }
+ delete [] _row_buf;
+ delete [] _zbuf;
+ _footer = true;
+ }
+
+ fflush (_stream);
+ return *this;
+ }
+
+ void
+ pcxstream::write_palette (void)
+ {
+ const size_type palette_size = 256 * 3 + 1;
+ byte_type palette [palette_size];
+ byte_type *ptr = palette;
+ *ptr = 0x0c; // set palette identifier
+ ++ptr;
+
+ for (int i=0; 256>i; ++i)
+ {
+ *ptr = *(ptr+1) = *(ptr+2) = i;
+ ptr += 3;
+ }
+ size_t rv = fwrite (palette, sizeof (*palette), palette_size, _stream);
+ if (palette_size != rv)
+ {
+ throw std::ios_base::failure ("write error");
+ }
+ }
+
+ basic_imgstream&
+ pcxstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+ if (!_header)
+ {
+ size_type sz = 0;
+ write_header ();
+ if (mono == _cspc)
+ {
+ sz = n * 8;
+ pwrite = &pcxstream::write_mono;
+ }
+ else if (grey == _cspc)
+ {
+ sz = 1; // dummy
+ pwrite = &pcxstream::write_gray;
+ }
+ else if (RGB == _cspc)
+ {
+ sz = _h_sz * 3; // r + g + b
+ pwrite = &pcxstream::write_color;
+ }
+ _row_buf = new byte_type [sz];
+ _zbuf = new byte_type [_h_sz * 2 + 1]; // assume worst case
+ }
+
+ (this->*pwrite) (line, n);
+
+ return *this;
+ }
+
+ void
+ pcxstream::write_color (const byte_type *line, size_type n)
+ {
+ byte_type *r = _row_buf;
+ byte_type *g = r + _h_sz;
+ byte_type *b = g + _h_sz;
+
+ for (size_type i=0; _h_sz>i; ++i)
+ {
+ r [i] = *line; ++line;
+ g [i] = *line; ++line;
+ b [i] = *line; ++line;
+ }
+ write_row (r, _h_sz);
+ write_row (g, _h_sz);
+ write_row (b, _h_sz);
+ }
+
+ void
+ pcxstream::write_gray (const byte_type *line, size_type n)
+ {
+ write_row (line, n);
+ }
+
+ void
+ pcxstream::write_mono (const byte_type *line, size_type n)
+ {
+ for (size_type i=0; n>i; ++i)
+ {
+ for (size_type j=0; 8>j; ++j)
+ {
+ _row_buf [i*8+7-j] = (line[i] & (0x1 << j)) ? 0x0 : 0xff;
+ }
+ }
+ write_row (_row_buf, _h_sz);
+ }
+
+ void
+ pcxstream::write_row (const byte_type *line, size_type n)
+ {
+ size_type zlen = compress_row (line, n, _zbuf);
+
+ // 2 byte align
+ if (n < _bytesperline)
+ {
+ _zbuf [zlen] = 0x00; // add padding
+ ++zlen;
+ }
+
+ write_bytes (_zbuf, zlen);
+ }
+
+ void
+ pcxstream::write_bytes (const byte_type *bytes, size_type n)
+ {
+ size_type rv;
+ rv = fwrite (bytes, sizeof (*bytes), n, _stream);
+ if (n != rv)
+ {
+ throw std::ios_base::failure ("write error");
+ }
+ }
+
+ basic_imgstream::size_type
+ pcxstream::compress_row (const byte_type *line,
+ const size_type n,
+ byte_type *compressed)
+ {
+ byte_type cnt = 0;
+ byte_type *ptr = compressed;
+ byte_type saved = 0;
+
+ for (size_type i=0; i<n; ++i)
+ {
+ if (0 == cnt)
+ {
+ saved = line[i];
+ cnt = 1;
+ }
+ else if (63 > cnt && saved == line[i])
+ {
+ ++cnt;
+ }
+ else if (63 == cnt || saved != line[i])
+ {
+ if (1 < cnt || 0xc0 == (saved & 0xc0))
+ {
+ *ptr = 0xc0 | cnt;
+ ++ptr;
+ }
+ *ptr = saved;
+ ++ptr;
+ saved = line[i];
+ cnt = 1;
+ }
+ }
+ // put the rest
+ if (1 < cnt || 0xc0 == (saved & 0xc0))
+ {
+ *ptr = 0xc0 | cnt;
+ ++ptr;
+ }
+ *ptr = saved;
+ ++ptr;
+
+ return (ptr - compressed);
+ }
+
+ bool
+ pcxstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream&
+ pcxstream::write_header (void)
+ {
+ check_consistency ();
+
+ // Even monochrome image, BitsPerPixel in PCX header is set to 8
+ // in accordance with the implementation of GIMP.
+ size_type bpp = 8;
+ _pcx_header [3] = bpp; // BitsPerPixel
+
+ set_value_le ((_h_sz-1), &_pcx_header [8], 2); // Window.Xmax
+ set_value_le ((_v_sz-1), &_pcx_header [10], 2); // Window.Ymax
+ set_value_le (_hres, &_pcx_header [12], 2); // HDpi
+ set_value_le (_vres, &_pcx_header [14], 2); // VDpi
+
+ size_type nplanes = 1;
+ size_type paletteinfo = 2;
+ if (RGB == _cspc)
+ {
+ nplanes = 3;
+ paletteinfo = 1;
+ }
+ _pcx_header [65] = nplanes; // NPlanes
+ set_value_le (paletteinfo, &_pcx_header [68], 2); // PaletteInfo
+
+ _bytesperline = (_h_sz * bpp + 7) / 8;
+ _bytesperline += (_bytesperline & 0x1); // 2 byte align
+ set_value_le (_bytesperline, &_pcx_header [66], 2); // BytesPerLine
+
+ write_bytes (_pcx_header, sizeof (_pcx_header));
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ pcxstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (!(_bits == 1 || _bits == 8))
+ {
+ throw std::logic_error ("unsupported bit depth");
+ }
+ size_type max = (1 << 16) - 1;
+ if (_h_sz > max || _v_sz > max)
+ {
+ throw std::logic_error ("maximum image size exceeded");
+ }
+ }
+
+ // set value to array with little endian
+ void
+ pcxstream::set_value_le (const size_type value,
+ byte_type *array,
+ const size_type array_size)
+ {
+ if (!array || 0 >= array_size)
+ {
+ throw std::invalid_argument ("invalid argment");
+ }
+
+ size_type v = value;
+
+ for (size_type i=0; array_size>i; ++i)
+ {
+ array[i] = v & 0xff;
+ v >>= 8;
+ }
+ }
+
+} // namespace iscan
diff --git a/lib/pcxstream.hh b/lib/pcxstream.hh
new file mode 100644
index 0000000..883b0af
--- /dev/null
+++ b/lib/pcxstream.hh
@@ -0,0 +1,93 @@
+// pcxstream.hh -- image streams producing PCX files
+// Copyright (C) 2011 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pcxstream_hh_included
+#define iscan_pcxstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class pcxstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pcxstream (FILE *fp, const string& name = string ());
+ virtual ~pcxstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+ virtual basic_imgstream& flush (void);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void write_color (const byte_type *line, size_type n);
+ void write_gray (const byte_type *line, size_type n);
+ void write_mono (const byte_type *line, size_type n);
+ void write_row (const byte_type *line, size_type n);
+ void write_bytes (const byte_type *bytes, size_type n);
+ void write_palette (void);
+ size_type compress_row (const byte_type *line,
+ const size_type n,
+ byte_type *compressed);
+ void check_consistency (void) const;
+ void set_value_le (const size_type value,
+ byte_type *array,
+ const size_type array_size);
+
+ FILE *_stream;
+ bool _header;
+ bool _footer;
+ byte_type _pcx_header [128];
+ size_type _bytesperline;
+ byte_type *_row_buf;
+ byte_type *_zbuf;
+
+ void (pcxstream::*pwrite) (const byte_type *line, size_type n);
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pcxstream_hh_included) */
diff --git a/lib/pdf/Makefile.am b/lib/pdf/Makefile.am
new file mode 100644
index 0000000..4cc3e95
--- /dev/null
+++ b/lib/pdf/Makefile.am
@@ -0,0 +1,44 @@
+## Makefile.am -- an automake template for a Makefile.in file
+## Copyright (C) 2008 SEIKO EPSON CORPORATION
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure 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 2 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 FITNESS
+## FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+## See the GNU General Public License for more details.
+##
+## You should have received a verbatim copy of the GNU General Public
+## License along with this program; if not, write to:
+##
+## Free Software Foundation, Inc.
+## 59 Temple Place, Suite 330
+## Boston, MA 02111-1307 USA
+
+
+if ENABLE_FRONTEND
+noinst_LTLIBRARIES = libpdf.la
+libpdf_la_LDFLAGS = -static
+libpdf_la_SOURCES = \
+ $(libpdf_la_files)
+endif
+
+libpdf_la_files = \
+ array.cc \
+ array.hh \
+ dictionary.cc \
+ dictionary.hh \
+ object.cc \
+ object.hh \
+ primitive.cc \
+ primitive.hh \
+ writer.cc \
+ writer.hh
+
+EXTRA_DIST = \
+ $(libpdf_la_files)
diff --git a/lib/pdf/array.cc b/lib/pdf/array.cc
new file mode 100644
index 0000000..812ee03
--- /dev/null
+++ b/lib/pdf/array.cc
@@ -0,0 +1,115 @@
+// array.cc -- PDF array objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "array.hh"
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+array::~array ()
+{
+ store_citer it;
+
+ for (it = _mine.begin (); _mine.end () != it; ++it)
+ {
+ object* obj = *it;
+ delete obj;
+ obj = NULL;
+ }
+}
+
+void
+array::insert (object *value)
+{
+ _store.push_back (value);
+}
+
+void
+array::insert (primitive value)
+{
+ primitive *copy = new primitive ();
+
+ *copy = value;
+ _mine.push_back (copy);
+ insert (copy);
+}
+
+void
+array::insert (object value)
+{
+ object *copy = new object ();
+
+ *copy = value;
+ _mine.push_back (copy);
+ insert (copy);
+}
+
+size_t
+array::size () const
+{
+ return _store.size ();
+}
+
+const object *
+array::operator[] (size_t index) const
+{
+ return _store[index];
+}
+
+void
+array::print (FILE* fp) const
+{
+ store_citer it;
+
+ fprintf (fp, "[ ");
+ if (4 < _store.size ())
+ {
+ fprintf (fp, "\n");
+ }
+ for (it = _store.begin (); _store.end () != it; ++it)
+ {
+ (*it)->print (fp);
+ fprintf (fp, " ");
+ if (4 < _store.size ())
+ {
+ fprintf (fp, "\n");
+ }
+ }
+ fprintf (fp, "]");
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/array.hh b/lib/pdf/array.hh
new file mode 100644
index 0000000..7bb5ac5
--- /dev/null
+++ b/lib/pdf/array.hh
@@ -0,0 +1,87 @@
+// array.hh -- PDF array objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_array_hh_included
+#define iscan_pdf_array_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+
+#include <vector>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+/*! Defines a PDF array object [p 58].
+ */
+class array : public object
+{
+private:
+ typedef std::vector<object *> store_type;
+ typedef store_type::iterator store_iter;
+ typedef store_type::const_iterator store_citer;
+
+ store_type _store;
+ store_type _mine;
+
+public:
+ virtual ~array ();
+
+ /*! Insert an object at the end the array
+ */
+ void insert (object* obj);
+
+ void insert (primitive obj);
+ void insert (object obj);
+
+ /*! Count the number of objects in the array
+ */
+ size_t size() const;
+
+ /*! Obtain a reference to an object at a given index
+ */
+ const object* operator[] (size_t index) const;
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_array_hh_included
diff --git a/lib/pdf/dictionary.cc b/lib/pdf/dictionary.cc
new file mode 100644
index 0000000..e6822e7
--- /dev/null
+++ b/lib/pdf/dictionary.cc
@@ -0,0 +1,123 @@
+// dictionary.cc -- PDF dictionaries
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "dictionary.hh"
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+dictionary::~dictionary ()
+{
+ store_citer it;
+
+ for (it = _mine.begin (); _mine.end () != it; ++it)
+ {
+ object* obj = it->second;
+ delete obj;
+ obj = NULL;
+ }
+}
+
+void
+dictionary::insert (const char *key, object *value)
+{
+ if (_mine.end () != _mine.find (key))
+ {
+ delete _mine[key];
+ }
+ _store[key] = value;
+}
+
+void
+dictionary::insert (const char *key, primitive value)
+{
+ primitive *copy = new primitive ();
+
+ *copy = value;
+ insert (key, copy);
+ _mine[key] = copy;
+}
+
+void
+dictionary::insert (const char *key, object value)
+{
+ object *copy = new object ();
+
+ *copy = value;
+ insert (key, copy);
+ _mine[key] = copy;
+}
+
+size_t
+dictionary::size () const
+{
+ return _store.size ();
+}
+
+const object *
+dictionary::operator[] (const char* key) const
+{
+ store_citer it = _store.find (key);
+
+ return (_store.end () != it ? it->second : NULL);
+}
+
+void
+dictionary::print (FILE* fp) const
+{
+ store_citer it;
+
+ if (1 >= _store.size ())
+ {
+ it = _store.begin ();
+ fprintf (fp, "<< /%s ", it->first);
+ it->second->print (fp);
+ fprintf (fp, " >>");
+ return;
+ }
+
+ fprintf (fp, "<<\n");
+ for (it = _store.begin (); _store.end () != it; ++it)
+ {
+ fprintf (fp, "/%s ", it->first);
+ it->second->print (fp);
+ fprintf (fp, "\n");
+ }
+ fprintf (fp, ">>");
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/dictionary.hh b/lib/pdf/dictionary.hh
new file mode 100644
index 0000000..d20e0d2
--- /dev/null
+++ b/lib/pdf/dictionary.hh
@@ -0,0 +1,93 @@
+// dictionary.hh -- PDF dictionaries
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifndef iscan_pdf_dictionary_hh_included
+#define iscan_pdf_dictionary_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+
+#include <map>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+/*! Defines a pdf dictionary object [p 59]
+ */
+class dictionary : public object
+{
+private:
+ typedef std::map<const char *, object *> store_type;
+ typedef store_type::iterator store_iter;
+ typedef store_type::const_iterator store_citer;
+
+ store_type _store;
+ store_type _mine;
+
+public:
+ virtual ~dictionary ();
+
+ /*! Insert a key/value pair into the dictionary
+ *
+ * If the key already exists, its value is replaced with the new one.
+ *
+ * The key is written to the PDF file as a name object as defined in the
+ * PDF spec [p. 59]
+ */
+ void insert (const char *key, object *value);
+
+ void insert (const char *key, primitive value);
+ void insert (const char *key, object value);
+
+ /*! Count the number of objects in the dictionary
+ */
+ size_t size () const;
+
+ /*! Obtain a reference to an object with a given key
+ */
+ const object * operator[] (const char *key) const;
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_dictionary_hh_included
diff --git a/lib/pdf/object.cc b/lib/pdf/object.cc
new file mode 100644
index 0000000..dba25da
--- /dev/null
+++ b/lib/pdf/object.cc
@@ -0,0 +1,103 @@
+// object.cc -- PDF objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+
+#include <stdexcept>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+
+object::object ()
+{
+ _obj_num = 0;
+}
+
+object::object (size_t num)
+{
+ // FIXME: what if num has already been used?
+ _obj_num = num;
+}
+
+object::~object (void)
+{
+}
+
+size_t
+object::obj_num ()
+{
+ if (65535 == next_obj_num)
+ {
+ throw std::runtime_error ("PDF object number overflow");
+ }
+
+ if (is_direct ())
+ {
+ _obj_num = ++next_obj_num;
+ }
+ return _obj_num;
+}
+
+bool
+object::operator== (object& that) const
+{
+ // FIXME: what if one or both instances have not gotten an object
+ // number yet?
+ return _obj_num == that._obj_num;
+}
+
+void
+object::print (FILE* fp) const
+{
+ fprintf (fp, "%zu 0 R", _obj_num);
+}
+
+bool
+object::is_direct () const
+{
+ return (0 == _obj_num);
+}
+
+size_t object::next_obj_num = 0;
+
+void object::reset_object_numbers ()
+{
+ next_obj_num = 0;
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/object.hh b/lib/pdf/object.hh
new file mode 100644
index 0000000..ef38e85
--- /dev/null
+++ b/lib/pdf/object.hh
@@ -0,0 +1,120 @@
+// object.hh -- PDF objects
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_object_hh_included
+#define iscan_pdf_object_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <cstdlib>
+#include <cstdio>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+using namespace std;
+
+/*! A base class for all pdf objects [p 51].
+ *
+ * A pdf::object is also used to pass around object numbers in a transparent
+ * fashion so that object references can be output correctly as elements of
+ * arrays and dictionaries.
+ */
+class object
+{
+private:
+
+ size_t _obj_num;
+ static size_t next_obj_num; // the next free object number
+
+public:
+
+ object ();
+
+ /*! Creates a new pdf::object with its object number set to \a num.
+ *
+ * Constructs an indirect object.
+ */
+ object (size_t num);
+
+ virtual ~object (void);
+
+ /*! Obtain the pdf::object's object number.
+ *
+ * If the object has not been allocated an object number yet, a new one is
+ * allocated and returned.
+ */
+ size_t obj_num ();
+
+ /*! Determine whether the object is direct or indirect [p 63].
+ *
+ * Probably don't need this method.
+ *
+ * @return True if the object is direct, False if it is indirect
+ */
+ bool is_direct () const;
+
+ /*! Output the object contents.
+ *
+ * In the case of pdf::object, this only outputs an indirect reference to
+ * itself [p 64].
+ *
+ * Each subclass re-implements this in order to print its own content.
+ * It should only ever output the object contents, ommitting the object
+ * definition header and footer [p 64]
+ */
+ virtual void print (FILE* fp) const;
+
+ /*! Compare the contents of two pdf::objects.
+ *
+ * Only the object contents are compared, the object number of the two
+ * objects can be different.
+ *
+ * In the case of pdf::object, the object numbers are compared.
+ */
+ virtual bool operator== (object& that) const;
+
+ /*! Reset the current object number to recycle them for new documents
+ */
+ static void reset_object_numbers ();
+
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_object_hh_included
diff --git a/lib/pdf/primitive.cc b/lib/pdf/primitive.cc
new file mode 100644
index 0000000..8a47c14
--- /dev/null
+++ b/lib/pdf/primitive.cc
@@ -0,0 +1,100 @@
+// primitive.cc -- PDF primitives
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "primitive.hh"
+
+#include <sstream>
+
+namespace iscan
+{
+
+namespace pdf
+{
+
+primitive::primitive ()
+ : _str (string ())
+{
+}
+
+primitive::primitive (string value)
+ : _str (value)
+{
+}
+
+primitive::primitive (int value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::primitive (size_t value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::primitive (double value)
+{
+ stringstream ss;
+ ss << value;
+ ss >> _str;
+}
+
+primitive::~primitive (void)
+{
+}
+
+bool
+primitive::operator== (const primitive& that) const
+{
+ return _str == that._str;
+}
+
+void
+primitive::print (FILE* fp) const
+{
+ fprintf (fp, "%s", _str.c_str ());
+}
+
+// FIXME: doesn't do the default assignment just what we want?
+void
+primitive::operator= (const primitive& that)
+{
+ _str = that._str;
+}
+
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/primitive.hh b/lib/pdf/primitive.hh
new file mode 100644
index 0000000..e45adc0
--- /dev/null
+++ b/lib/pdf/primitive.hh
@@ -0,0 +1,92 @@
+// primitive.hh -- PDF primitives
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_primitive_hh_included
+#define iscan_pdf_primitive_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+
+#include <string>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::string;
+
+/*! Defines a primitive pdf object: one of string, name, integer, or real.
+ */
+class primitive : public object
+{
+private:
+ string _str;
+
+public:
+ primitive ();
+
+ /*! Create a new pdf string/name object with the given value.
+ */
+ primitive (string value);
+
+ /*! Create a new pdf integer object
+ */
+ primitive (int value);
+
+ primitive (size_t value);
+
+ /*! Create a new pdf real object
+ */
+ primitive (double value);
+
+ virtual ~primitive (void);
+
+ /*! Compare the contents of two pdf::primitives.
+ *
+ * Only the object contents are compared, the object number of the
+ * two objects may differ.
+ */
+ virtual bool operator== (const primitive& other) const;
+
+ void operator= (const primitive& that);
+
+ virtual void print (FILE* fp) const;
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_primitive_hh_included
diff --git a/lib/pdf/writer.cc b/lib/pdf/writer.cc
new file mode 100644
index 0000000..78a9355
--- /dev/null
+++ b/lib/pdf/writer.cc
@@ -0,0 +1,236 @@
+// writer.cc -- putting PDF object in a file
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "writer.hh"
+
+#include <sstream>
+#include <stdexcept>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::runtime_error;
+ using std::string;
+
+writer::writer (FILE *file)
+ : _xref (xref ()), _file (file)
+{
+ _xref_pos = 0;
+ _last_xref_pos = 0;
+ _saved_pos = 0;
+ _mode = object_mode;
+ _stream_len_obj = NULL;
+}
+
+writer::~writer ()
+{
+ delete _stream_len_obj;
+ _stream_len_obj = NULL;
+}
+
+void
+writer::write (object& obj)
+{
+ if (object_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write (object&)");
+ }
+
+ _xref[obj.obj_num ()] = ftell (_file);
+
+ fprintf (_file, "%zu 0 obj\n", obj.obj_num ());
+ obj.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "endobj\n");
+}
+
+void
+writer::begin_stream (dictionary& dict)
+{
+ if (stream_mode == _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::begin_stream ()");
+ }
+ _mode = stream_mode;
+
+ _stream_len_obj = new primitive ();
+ dict.insert ("Length", object (_stream_len_obj->obj_num ()));
+
+ _xref[dict.obj_num ()] = ftell (_file);
+
+ fprintf (_file, "%zu 0 obj\n", dict.obj_num ());
+ dict.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "stream\n");
+
+ _saved_pos = ftell (_file);
+}
+
+void
+writer::write (const char *buf, size_t n)
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write ()");
+ }
+ size_t rv = fwrite (buf, sizeof (char), n, _file);
+ if (rv != n) throw std::ios_base::failure ("write error");
+}
+
+void
+writer::write (const string& s)
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::write ()");
+ }
+ fprintf (_file, "%s", s.c_str ());
+}
+
+void
+writer::end_stream ()
+{
+ if (stream_mode != _mode)
+ {
+ throw runtime_error ("invalid call to pdf::writer::end_stream ()");
+ }
+ _mode = object_mode;
+
+ size_t pos = ftell (_file);
+ size_t length = pos - _saved_pos;
+
+ fprintf (_file, "\n");
+ fprintf (_file, "endstream");
+ fprintf (_file, "\n");
+ fprintf (_file, "endobj");
+ fprintf (_file, "\n");
+
+ // FIXME: overload the '=' operator in pdf::primitive
+ *_stream_len_obj = primitive (length);
+
+ write (*_stream_len_obj);
+ delete _stream_len_obj;
+ _stream_len_obj = NULL;
+}
+
+void
+writer::header ()
+{
+ if (_mode == stream_mode)
+ {
+ throw runtime_error ("cannot write header in stream mode");
+ }
+ fprintf (_file, "%%PDF-1.0\n");
+}
+
+void
+writer::trailer (dictionary& trailer_dict)
+{
+ if (_mode == stream_mode)
+ {
+ throw runtime_error ("cannot write trailer in stream mode");
+ }
+ write_xref ();
+ write_trailer (trailer_dict);
+}
+
+// FIXME: clean up this kludge
+void
+writer::write_xref ()
+{
+ xref::const_iterator it;
+
+ _last_xref_pos = _xref_pos;
+ _xref_pos = ftell (_file);
+
+ fprintf (_file, "xref\n");
+
+ stringstream ss;
+ size_t start_obj_num = 0;
+ size_t cur_obj_num = 0;
+ size_t last_obj_num = 0;
+
+ ss << "0000000000 65535 f " << endl;
+ for(it = _xref.begin (); _xref.end () != it; ++it)
+ {
+ cur_obj_num = it->first;
+
+ if (cur_obj_num != last_obj_num + 1)
+ {
+ // write out the current xref section and start a new one
+ fprintf (_file, "%zu %zu\n", start_obj_num,
+ last_obj_num + 1 - start_obj_num);
+ fprintf (_file, "%s", ss.str ().c_str ());
+
+ ss.str (""); // flush stream
+ start_obj_num = cur_obj_num;
+ }
+
+ last_obj_num = cur_obj_num;
+
+ ss.width (10);
+ ss.fill ('0');
+ ss << it->second << " 00000 n " << endl;
+ }
+
+ if (!ss.str ().empty ())
+ {
+ fprintf (_file, "%zu %zu\n", start_obj_num,
+ last_obj_num + 1 - start_obj_num);
+ fprintf (_file, "%s", ss.str ().c_str ());
+ }
+}
+
+void
+writer::write_trailer (dictionary& trailer_dict)
+{
+ trailer_dict.insert ("Size", primitive (_xref.size () + 1));
+ if (_last_xref_pos != 0)
+ {
+ trailer_dict.insert ("Prev", primitive (_last_xref_pos));
+ }
+
+ fprintf (_file, "trailer\n");
+ trailer_dict.print (_file);
+ fprintf (_file, "\n");
+ fprintf (_file, "startxref\n");
+ fprintf (_file, "%zu\n", _xref_pos);
+ fprintf (_file, "%%%%EOF\n");
+
+ _xref.clear ();
+}
+
+} // namespace pdf
+} // namespace iscan
diff --git a/lib/pdf/writer.hh b/lib/pdf/writer.hh
new file mode 100644
index 0000000..f62c471
--- /dev/null
+++ b/lib/pdf/writer.hh
@@ -0,0 +1,165 @@
+// writer.hh -- putting PDF objects in a file
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdf_writer_hh_included
+#define iscan_pdf_writer_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "object.hh"
+#include "primitive.hh"
+#include "dictionary.hh"
+
+#include <map>
+
+namespace iscan
+{
+
+namespace pdf
+{
+ using std::string;
+
+/*! Writes PDF objects to a file.
+ * See section 3.4 of the PDF Reference version 1.7 for details on the basic
+ * file structure of a PDF file.
+ *
+ * There are two writing modes: object mode and stream mode.
+ * The default mode is object mode. When begin_stream() is called, the current
+ * mode is set to stream mode. When end_stream() is called, the mode is reset
+ * to object mode.
+ *
+ * In object mode, PDF objects are written all at once. Stream mode allows the
+ * writing of PDF stream objects which can be written incrementally.
+ */
+class writer
+{
+private:
+ typedef std::map<size_t, size_t> xref;
+
+ xref _xref;
+ size_t _xref_pos;
+ size_t _last_xref_pos;
+
+ FILE *_file;
+ size_t _saved_pos;
+
+ primitive* _stream_len_obj;
+
+ typedef enum {
+ object_mode,
+ stream_mode
+ } write_mode;
+
+ write_mode _mode;
+
+public:
+ /*! Creates a new pdf::writer object which will write to the given stream.
+ */
+ writer (FILE *file);
+
+ ~writer ();
+
+ /*! Writes a pdf::object to the file as an indirect object [p 63].
+ *
+ * Recursively writes any child objects to the file.
+ * Can only be called while in object mode, which is the default
+ * mode. If this method is called between calls to begin_stream() and
+ * end_stream() a std::runtime_error exception is thrown.
+ */
+ void write (object& object);
+
+ /*! Initializes a PDF stream [p 60] and sets the current mode to stream mode.
+ *
+ * Pass a pdf::dictionary object, \a dict, defining the stream properties,
+ * omitting the mandatory "Length" property which is automatically
+ * calculated and written to the file. If this method is called when already
+ * in stream mode, a std::runtime_error exception is thrown.
+ */
+ void begin_stream (dictionary& dict);
+
+ /*! Writes \a n bytes from \a buf to the file as part of a PDF stream.
+ *
+ * Can only be called while in stream mode.
+ * Calls to this method must be contained within calls to
+ * begin_stream() and end_stream(). If not, a std::runtime_error
+ * exception is thrown.
+ */
+ void write (const char* buf, size_t n);
+
+ /*! Writes a string \a s to the file as part of a PDF stream.
+ *
+ * Can only be called while in stream mode.
+ * Calls to this method must be contained within calls to
+ * begin_stream() and end_stream(). If not, a std::runtime_error
+ * exception is thrown.
+ */
+ void write (const string& s);
+
+ /*! Finishes writing a PDF stream and sets the current mode to object mode.
+ *
+ * The "Length" property of the stream is written at this point as an
+ * indirect object. Can only be called when in stream mode, if not, a
+ * std::runtime_error exception is thrown.
+ */
+ void end_stream ();
+
+ /*! Writes the PDF header [p 92].
+ *
+ * A std::runtime_error exception is thrown if this method is called while
+ * in stream mode.
+ */
+ void header ();
+
+ /*! Writes the PDF trailer [p 96] and xref table [p 93].
+ *
+ * Also sets the "Prev" entry in \a trailer_dict and clears the internal
+ * xref table. A std::runtime_error exception is thrown if this method is
+ * called while in stream mode.
+ *
+ * \param trailer_dict The trailer entries to be written.
+ */
+ void trailer (dictionary& trailer_dict);
+
+private:
+ // Writes the cross-reference table [p 93].
+ void write_xref ();
+
+ // Writes the file trailer [p 96].
+ void write_trailer (dictionary& trailer_dict);
+};
+
+} // namespace pdf
+} // namespace iscan
+
+#endif // iscan_pdf_writer_hh_included
diff --git a/lib/pdfstream.cc b/lib/pdfstream.cc
new file mode 100644
index 0000000..4875de5
--- /dev/null
+++ b/lib/pdfstream.cc
@@ -0,0 +1,351 @@
+// pdfstream.cc -- image streams producing PDF files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pdfstream.hh"
+#include "jpegstream.hh"
+
+#include <sstream>
+
+namespace iscan
+{
+
+pdfstream::pdfstream (FILE *file, bool match_direction)
+ : imgstream (), // avoid recursion
+ _file (file), _g3 (NULL), _rotate_180 (false)
+{
+ _match_direction = match_direction;
+ init ();
+}
+
+void
+pdfstream::init ()
+{
+ if (!is_usable ())
+ {
+ throw std::runtime_error ("pdf not usable");
+ }
+
+ _page = 0;
+ _row = 0;
+ _need_page_trailer = false;
+ _data_size = 0;
+ _pdf_h_sz = 0;
+ _pdf_v_sz = 0;
+ _do_jpeg = false;
+
+ _doc = NULL;
+ _pages = NULL;
+ _page_list = NULL;
+ _trailer = NULL;
+ _img_height_obj = NULL;
+ _stream = NULL;
+
+ _doc = new pdf::writer (_file);
+
+ pdf::object::reset_object_numbers ();
+}
+
+pdfstream::~pdfstream ()
+{
+ if (_need_page_trailer)
+ {
+ write_page_trailer ();
+ }
+
+ fflush (_file);
+
+ delete _doc;
+ delete _pages;
+ delete _page_list;
+ delete _trailer;
+ delete _img_height_obj;
+
+ delete _stream;
+
+ delete _g3;
+}
+
+bool
+pdfstream::is_usable ()
+{
+ return true;
+}
+
+void
+pdfstream::rotate_180 (bool yes)
+{
+ _rotate_180 = yes;
+}
+
+void
+pdfstream::next ()
+{
+ if (0 == _row)
+ {
+ return;
+ }
+
+ write_page_trailer ();
+ _row = 0;
+ _rotate_180 = _match_direction && is_back (_page);
+}
+
+imgstream&
+pdfstream::write (const byte_type *line, size_type n)
+{
+ if (!line || 0 == n) return *this;
+ if (0 == _page)
+ {
+ write_header ();
+ }
+ if (0 == _row)
+ {
+ // adjust to PDF coordinate system (is this correct?)
+ _pdf_h_sz = (72 * _h_sz) / _hres;
+ _pdf_v_sz = (72 * _v_sz) / _vres;
+
+ write_page_header ();
+ ++_page;
+ }
+
+ if (_stream)
+ {
+ _stream->write (line, n);
+ }
+ else if (_g3)
+ {
+ string buf = (*_g3) (line, n);
+ _doc->write (buf.data (), buf.size ());
+ }
+ else
+ {
+ _doc->write (line, n);
+ }
+ ++_row;
+
+ return *this;
+}
+
+void
+pdfstream::write_header ()
+{
+ _doc->header ();
+
+ delete _pages;
+ _pages = new pdf::dictionary ();
+
+ pdf::dictionary info;
+ info.insert ("Producer", pdf::primitive ("(iscan)"));
+ info.insert ("Creator", pdf::primitive ("(iscan)"));
+ _doc->write (info);
+
+ pdf::dictionary catalog;
+ catalog.insert ("Type", pdf::primitive ("/Catalog"));
+ catalog.insert ("Pages", pdf::object (_pages->obj_num ()));
+ _doc->write (catalog);
+
+ delete _trailer;
+ _trailer = new pdf::dictionary ();
+ _trailer->insert ("Info", pdf::object (info.obj_num ()));
+ _trailer->insert ("Root", pdf::object (catalog.obj_num ()));
+
+ delete _page_list;
+ _page_list = new pdf::array ();
+}
+
+void
+pdfstream::write_page_header ()
+{
+ pdf::dictionary page;
+
+ _page_list->insert (pdf::object (page.obj_num ()));
+
+ _pages->insert ("Type", pdf::primitive ("/Pages"));
+ _pages->insert ("Kids", _page_list);
+ _pages->insert ("Count", pdf::primitive (_page_list->size ()));
+
+ _doc->write (*_pages);
+
+ pdf::dictionary image;
+ pdf::dictionary contents;
+
+ pdf::array mbox;
+ mbox.insert (pdf::primitive (0));
+ mbox.insert (pdf::primitive (0));
+ mbox.insert (pdf::primitive (_pdf_h_sz));
+ mbox.insert (pdf::primitive (_pdf_v_sz));
+
+ std::stringstream ss2;
+ std::string img_name;
+
+ ss2 << "iscanImage" << _page;
+ img_name = ss2.str ();
+
+ pdf::array procset;
+ std::string cproc = "/ImageB";
+ if (RGB == _cspc)
+ {
+ cproc = "/ImageC";
+ }
+ pdf::dictionary tmp;
+ tmp.insert (img_name.c_str (), pdf::object (image.obj_num ()));
+
+ procset.insert (pdf::primitive ("/PDF"));
+ procset.insert (pdf::primitive (cproc));
+
+ pdf::dictionary rsrc;
+ rsrc.insert ("XObject", &tmp);
+ rsrc.insert ("ProcSet", &procset);
+
+ page.insert ("Type", pdf::primitive ("/Page"));
+ page.insert ("Parent", pdf::object (_pages->obj_num ()));
+ page.insert ("Resources", &rsrc);
+ page.insert ("MediaBox", &mbox);
+ page.insert ("Contents", pdf::object (contents.obj_num ()));
+
+ _doc->write (page);
+
+ _doc->begin_stream (contents);
+
+ // transformation matrices must be specified in reverse order
+ std::stringstream ss;
+ ss << "q" << std::endl;
+ ss << _pdf_h_sz << " 0 0 " << _pdf_v_sz << " 0 0 cm" << std::endl;
+ if (_rotate_180)
+ {
+ // undo the translation below
+ ss << "1 0 0 1 0.5 0.5 cm" << std::endl;
+
+ // reflect along x and y axis
+ ss << "-1 0 0 -1 0 0 cm" << std::endl;
+
+ // translate so the image midpoint lies on the origin
+ ss << "1 0 0 1 -0.5 -0.5 cm" << std::endl;
+ }
+ ss << "/" << img_name << " Do" << std::endl;
+ ss << "Q";
+
+ _doc->write (ss.str ());
+ _doc->end_stream ();
+
+ write_image_object (image, img_name);
+
+ _need_page_trailer = true;
+}
+
+void
+pdfstream::write_image_object (pdf::dictionary& image, std::string name)
+{
+ delete _img_height_obj;
+ _img_height_obj = new pdf::primitive ();
+
+ image.insert ("Type", pdf::primitive ("/XObject"));
+ image.insert ("Subtype", pdf::primitive ("/Image"));
+ image.insert ("Width", pdf::primitive (_h_sz));
+ image.insert ("Height", pdf::object (_img_height_obj->obj_num ()));
+
+ pdf::array decode;
+ std::string dev = "/DeviceGray";
+ if (RGB == _cspc) dev = "/DeviceRGB";
+
+ if (monochrome == _cspc)
+ {
+ if (!_g3) _g3 = new fax_encoder;
+ }
+ else
+ {
+ _do_jpeg = iscan::jpegstream::is_usable ();
+ }
+ image.insert ("ColorSpace", pdf::primitive (dev));
+ image.insert ("BitsPerComponent", pdf::primitive (_bits));
+ image.insert ("Interpolate", pdf::primitive ("true"));
+
+ pdf::dictionary parms;
+ if (_do_jpeg)
+ {
+ image.insert ("Filter", pdf::primitive ("/DCTDecode"));
+ }
+ else if (_g3)
+ {
+ image.insert ("Filter", pdf::primitive ("/CCITTFaxDecode"));
+
+ parms.insert ("Columns", pdf::primitive (_h_sz));
+ parms.insert ("Rows", pdf::primitive (_v_sz));
+ parms.insert ("EndOfBlock", pdf::primitive ("false"));
+ parms.insert ("EndOfLine", pdf::primitive ("true"));
+ parms.insert ("EncodedByteAlign", pdf::primitive ("true"));
+ parms.insert ("K", pdf::primitive (0)); // CCITT3 1-D encoding
+ image.insert ("DecodeParms", &parms);
+ }
+
+ // see PDF reference 1.7 p. 342 and p. 1107 # 53
+ image.insert ("Name", pdf::primitive ("/" + name));
+
+ _doc->begin_stream (image);
+
+ if (_do_jpeg)
+ {
+ _stream = new jpegstream (_file);
+ }
+
+ if (_stream)
+ {
+ _stream->size (_h_sz, _v_sz);
+ _stream->depth (_bits);
+ _stream->colour (_cspc);
+ _stream->resolution (_hres, _vres);
+ }
+}
+
+void
+pdfstream::write_page_trailer ()
+{
+ delete _stream;
+ _stream = NULL;
+
+ _doc->end_stream ();
+
+ *_img_height_obj = pdf::primitive (_row);
+ _doc->write (*_img_height_obj);
+
+ _doc->trailer (*_trailer);
+
+ _need_page_trailer = false;
+
+ _pdf_h_sz = 0;
+ _pdf_v_sz = 0;
+
+ _do_jpeg = false;
+}
+
+} // namespace iscan
diff --git a/lib/pdfstream.hh b/lib/pdfstream.hh
new file mode 100644
index 0000000..70f7069
--- /dev/null
+++ b/lib/pdfstream.hh
@@ -0,0 +1,105 @@
+// pdfstream.hh -- image streams producing PDF files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+#ifndef iscan_pdfstream_hh_included
+#define iscan_pdfstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#include "pdf/writer.hh"
+#include "pdf/dictionary.hh"
+#include "pdf/array.hh"
+#include "pdf/primitive.hh"
+#include "fax-encoder.hh"
+
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+class pdfstream : public imgstream
+{
+
+public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+private:
+ bool _need_page_trailer;
+ size_type _data_size;
+ size_type _pdf_h_sz; // scaled size to fit on a page at 72 dpi
+ size_type _pdf_v_sz; // but better to set the dpi in the PDF file,
+ // there is some way to do that, I read it in the spec!
+
+ pdf::writer *_doc;
+ pdf::dictionary *_pages;
+ pdf::array *_page_list;
+ pdf::dictionary *_trailer;
+
+ size_type _row;
+ pdf::primitive *_img_height_obj;
+
+ basic_imgstream *_stream;
+ FILE *_file;
+
+ bool _do_jpeg;
+ fax_encoder *_g3;
+ bool _rotate_180;
+
+public:
+ static bool is_usable ();
+
+ explicit pdfstream (FILE *fp, bool match_direction = false);
+
+ virtual ~pdfstream ();
+
+ virtual imgstream& write (const byte_type *line, size_type n);
+
+ virtual void next ();
+ virtual void rotate_180 (bool yes);
+
+private:
+ void init();
+ void write_header ();
+ void write_page_header ();
+ void write_page_trailer ();
+ void write_image_object (pdf::dictionary& image, std::string name);
+};
+
+} // namespace iscan
+
+#endif /* iscan_pdfstream_hh_included */
diff --git a/lib/pngstream.cc b/lib/pngstream.cc
new file mode 100644
index 0000000..d9344b4
--- /dev/null
+++ b/lib/pngstream.cc
@@ -0,0 +1,257 @@
+// pngstream.cc -- image streams producing PNG files
+// Copyright (C) 2008, 2009, 2016 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pngstream.hh"
+
+#include <iostream>
+
+namespace iscan
+{
+ pngstream::pngstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false), _footer (false)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+#if HAVE_PNG_H
+ init ();
+#endif
+ }
+
+ pngstream::~pngstream (void)
+ {
+ try
+ {
+ flush ();
+ }
+ catch (const std::exception& oops)
+ {
+ std::cerr << oops.what ();
+ }
+
+#if HAVE_PNG_H
+ lib->destroy_write_struct (&_png, &_info);
+#endif
+ }
+
+ basic_imgstream&
+ pngstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+#if HAVE_PNG_H
+ if (!_header)
+ {
+ write_header ();
+ }
+ set_error_handler (_png, _info);
+ lib->write_row (_png, (png_byte *) line);
+#endif
+ return *this;
+ }
+
+ basic_imgstream&
+ pngstream::flush (void)
+ {
+#if HAVE_PNG_H
+ set_error_handler (_png, _info);
+
+ if (_header && !_footer && _png->num_rows == _png->flush_rows)
+ {
+ lib->write_end (_png, _info);
+ _footer = true;
+ }
+#endif
+ fflush (_stream);
+
+ return *this;
+ }
+
+ bool
+ pngstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) png_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_PNG_H
+ try
+ {
+ // Some PNG libraries are *not* linked with libz which leads
+ // to a "file not found" error when calling dlopen. We just
+ // dlopen the libz library explicitly to work around this.
+ basic_imgstream::dlopen ("libz");
+ basic_imgstream::dlopen ("libpng12", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_PNG_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_PNG_H
+#define funcsym(name) \
+ lib->name \
+ = ((pngstream::png_lib_handle::name##_f) \
+ basic_imgstream::dlsym (lib->lib, "png_"#name));
+#endif
+
+ bool
+ pngstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_PNG_H
+ lib->lib = h;
+
+ funcsym (access_version_number);
+ funcsym (create_write_struct);
+ funcsym (create_info_struct);
+ funcsym (destroy_write_struct);
+ funcsym (init_io);
+ funcsym (set_IHDR);
+ funcsym (set_pHYs);
+ funcsym (set_invert_mono);
+ funcsym (write_info);
+ funcsym (write_row);
+ funcsym (write_flush);
+ funcsym (write_end);
+
+ if (lib->access_version_number
+ && lib->create_write_struct
+ && lib->create_info_struct
+ && lib->destroy_write_struct
+ && lib->init_io
+ && lib->set_IHDR
+ && lib->set_pHYs
+ && lib->set_invert_mono
+ && lib->write_info
+ && lib->write_row
+ && lib->write_flush
+ && lib->write_end)
+ {
+ lib->is_usable = (PNG_LIBPNG_VER <= lib->access_version_number ());
+ }
+#endif /* HAVE_PNG_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_PNG_H
+#undef funcsym
+#endif
+
+ basic_imgstream&
+ pngstream::write_header (void)
+ {
+ check_consistency ();
+
+#if HAVE_PNG_H
+ set_error_handler (_png, _info);
+
+ if (mono == _cspc)
+ {
+ _bits = 1;
+ lib->set_invert_mono (_png);
+ }
+
+ lib->init_io (_png, _stream);
+
+ lib->set_IHDR (_png, _info, _h_sz, _v_sz, _bits,
+ (RGB == _cspc
+ ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY),
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ size_type hres = size_type (100 / 2.54 * _hres + 0.5);
+ size_type vres = size_type (100 / 2.54 * _vres + 0.5);
+ lib->set_pHYs (_png, _info, hres, vres, PNG_RESOLUTION_METER);
+
+ lib->write_info (_png, _info);
+#endif /* HAVE_PNG_H */
+
+ _header = true;
+ return *this;
+ }
+
+ void
+ pngstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ }
+
+ void
+ pngstream::init (void)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_PNG_H
+ _png = lib->create_write_struct (version_string,
+ NULL, NULL, NULL);
+ _info = NULL;
+ if (!_png) throw std::bad_alloc ();
+
+ _info = lib->create_info_struct (_png);
+ if (!_info)
+ {
+ lib->destroy_write_struct (&_png, NULL);
+ throw std::bad_alloc ();
+ }
+#endif /* HAVE_PNG_H */
+ }
+
+ pngstream::png_lib_handle *pngstream::lib = NULL;
+
+#if HAVE_PNG_H
+ png_const_charp pngstream::version_string = PNG_LIBPNG_VER_STRING;
+#endif
+
+} // namespace iscan
diff --git a/lib/pngstream.hh b/lib/pngstream.hh
new file mode 100644
index 0000000..77d4f54
--- /dev/null
+++ b/lib/pngstream.hh
@@ -0,0 +1,145 @@
+// pngstream.hh -- image streams producing PNG files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pngstream_hh_included
+#define iscan_pngstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+#include <ios>
+
+#if HAVE_PNG_H
+#include <png.h>
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class pngstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pngstream (FILE *fp, const string& name = string ());
+ virtual ~pngstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+ virtual basic_imgstream& flush (void);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ void init (void);
+
+ FILE *_stream;
+ bool _header;
+ bool _footer;
+
+ static bool validate (lt_dlhandle h);
+ struct png_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_PNG_H
+ fundecl (png_uint_32, access_version_number,
+ void);
+ fundecl (png_structp, create_write_struct,
+ png_const_charp, png_voidp, png_error_ptr, png_error_ptr);
+ fundecl (png_infop , create_info_struct ,
+ png_structp);
+ fundecl (void, destroy_write_struct,
+ png_structpp, png_infopp);
+ fundecl (void, init_io,
+ png_structp, FILE *);
+ fundecl (void, set_IHDR,
+ png_structp, png_infop, png_uint_32, png_uint_32, int,
+ int, int, int, int);
+ fundecl (void, set_pHYs,
+ png_structp, png_infop, png_uint_32, png_uint_32, int);
+ fundecl (void, set_invert_mono, png_structp);
+ fundecl (void, write_info,
+ png_structp, png_infop);
+ fundecl (void, write_row,
+ png_structp, png_bytep);
+ fundecl (void, write_flush,
+ png_structp);
+ fundecl (void, write_end,
+ png_structp, png_infop);
+#endif /* HAVE_PNG_H */
+ };
+ static png_lib_handle *lib;
+
+#if HAVE_PNG_H
+ friend void set_error_handler (png_structp, png_infop);
+
+ png_structp _png;
+ png_infop _info;
+
+ static png_const_charp version_string;
+#endif
+ };
+
+
+// implementation
+
+// This function needs to be expanded verbatim at the location it's
+// invoked for the setjmp call to work as intended.
+#if HAVE_PNG_H
+#define set_error_handler(png, info) \
+ { \
+ if (!png || !info || setjmp (png_jmpbuf (png))) \
+ { \
+ pngstream::lib->destroy_write_struct (&png, &info); \
+ png = NULL; \
+ info = NULL; \
+ throw std::ios_base::failure ("write error"); \
+ } \
+ }
+#endif
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pngstream_hh_included) */
diff --git a/lib/pnmstream.cc b/lib/pnmstream.cc
new file mode 100644
index 0000000..0f4ee6b
--- /dev/null
+++ b/lib/pnmstream.cc
@@ -0,0 +1,112 @@
+// pnmstream.cc -- image streams producing PNM files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "pnmstream.hh"
+
+#include <ios>
+
+namespace iscan
+{
+ pnmstream::pnmstream (FILE *fp, const string& name)
+ : _stream (fp), _header (false)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+ }
+
+ pnmstream::~pnmstream (void)
+ {
+ fflush (_stream);
+ }
+
+ basic_imgstream&
+ pnmstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+ if (!_header)
+ {
+ write_header ();
+ }
+ size_t rv = fwrite (line, sizeof (byte_type), n, _stream);
+ if (n != rv) throw std::ios_base::failure ("write error");
+ return *this;
+ }
+
+ bool
+ pnmstream::is_usable (void)
+ {
+ return true;
+ }
+
+ basic_imgstream&
+ pnmstream::write_header (void)
+ {
+ check_consistency ();
+
+ string magic;
+ if (mono == _cspc) magic = "P4";
+ if (grey == _cspc) magic = "P5";
+ if (RGB == _cspc) magic = "P6";
+
+ int rv;
+ rv = fprintf (_stream, "%s\n%zd %zd\n", magic.c_str (), _h_sz, _v_sz);
+ if (0 > rv) throw std::ios_base::failure ("write error");
+ if ("P4" != magic)
+ {
+ rv = fprintf (_stream, "%d\n", (1 << _bits) - 1);
+ if (0 > rv) throw std::ios_base::failure ("write error");
+ }
+ _header = true;
+ return *this;
+ }
+
+ void
+ pnmstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (_bits > 16)
+ {
+ throw std::logic_error ("maximum bit depth exceeded");
+ }
+ if (grey == _cspc || RGB == _cspc)
+ {
+ if (8 != _bits && 16 != _bits)
+ {
+ throw std::invalid_argument ("bit depth/colour space mismatch");
+ }
+ }
+ }
+
+} // namespace iscan
diff --git a/lib/pnmstream.hh b/lib/pnmstream.hh
new file mode 100644
index 0000000..a2c6905
--- /dev/null
+++ b/lib/pnmstream.hh
@@ -0,0 +1,73 @@
+// pnmstream.hh -- image streams producing PNM files
+// Copyright (C) 2008 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other then esmod.
+
+
+#ifndef iscan_pnmstream_hh_included
+#define iscan_pnmstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "basic-imgstream.hh"
+
+#include <cstdio>
+#include <string>
+
+namespace iscan
+{
+ using std::string;
+
+ class pnmstream : public basic_imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ explicit pnmstream (FILE *fp, const string& name = string ());
+ virtual ~pnmstream (void);
+
+ virtual basic_imgstream& write (const byte_type *line, size_type n);
+
+ static bool is_usable (void);
+
+ private:
+ basic_imgstream& write_header (void);
+ void check_consistency (void) const;
+
+ FILE *_stream;
+ bool _header;
+ };
+
+} // namespace iscan
+
+#endif /* !defined (iscan_pnmstream_hh_included) */
diff --git a/lib/tests/Makefile.am b/lib/tests/Makefile.am
new file mode 100644
index 0000000..c51b8ef
--- /dev/null
+++ b/lib/tests/Makefile.am
@@ -0,0 +1,46 @@
+## Makefile.am -- an -*- automake -*- template for Makefile.in
+## Copyright (C) 2011 SEIKO EPSON CORPORATION
+##
+## License: GPLv2+
+## Authors: AVASYS CORPORATION
+##
+## This file is part of the "Image Scan!" build infra-structure.
+##
+## The "Image Scan!" build infra-structure 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 2 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 ought to have received a copy of the GNU General Public License
+## along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/lib
+
+TESTS = \
+ run-test-pcx.sh
+
+check_PROGRAMS = \
+ test-pcx
+
+test_pcx_LDADD = \
+ ../libimage-stream.la \
+ -lstdc++
+test_pcx_SOURCES = \
+ test-pcx.cc \
+ pnm.c \
+ pnm.h
+
+EXTRA_DIST = \
+ even-width.pbm \
+ even-width.pgm \
+ even-width.ppm \
+ odd-width.pbm \
+ odd-width.pgm \
+ odd-width.ppm \
+ run-test-pcx.sh
diff --git a/lib/tests/even-width.pbm b/lib/tests/even-width.pbm
new file mode 100644
index 0000000..65bf750
--- /dev/null
+++ b/lib/tests/even-width.pbm
Binary files differ
diff --git a/lib/tests/even-width.pgm b/lib/tests/even-width.pgm
new file mode 100644
index 0000000..3b4bf37
--- /dev/null
+++ b/lib/tests/even-width.pgm
Binary files differ
diff --git a/lib/tests/even-width.ppm b/lib/tests/even-width.ppm
new file mode 100644
index 0000000..0179c95
--- /dev/null
+++ b/lib/tests/even-width.ppm
Binary files differ
diff --git a/lib/tests/odd-width.pbm b/lib/tests/odd-width.pbm
new file mode 100644
index 0000000..76a669a
--- /dev/null
+++ b/lib/tests/odd-width.pbm
Binary files differ
diff --git a/lib/tests/odd-width.pgm b/lib/tests/odd-width.pgm
new file mode 100644
index 0000000..9c29d53
--- /dev/null
+++ b/lib/tests/odd-width.pgm
Binary files differ
diff --git a/lib/tests/odd-width.ppm b/lib/tests/odd-width.ppm
new file mode 100644
index 0000000..0e945b9
--- /dev/null
+++ b/lib/tests/odd-width.ppm
Binary files differ
diff --git a/lib/tests/pnm.c b/lib/tests/pnm.c
new file mode 100644
index 0000000..d9ba1b6
--- /dev/null
+++ b/lib/tests/pnm.c
@@ -0,0 +1,141 @@
+/* pnm.c -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pnm.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+pnm *
+read_pnm (const char *file)
+{
+ pnm image, *rv;
+
+ char *line = NULL;
+ size_t sz = 0;
+
+ FILE *fp;
+
+ if (file)
+ fp = fopen (file, "r");
+ else
+ fp = stdin;
+
+ if (!fp) return NULL;
+
+ getline (&line, &sz, fp); /* file magic number */
+ if ('P' != line[0]) return NULL;
+
+ image.depth = 8;
+ /**/ if ('6' == line[1]) image.format = 1;
+ else if ('5' == line[1]) image.format = 0;
+ else if ('4' == line[1]) { image.format = 0; image.depth = 1; }
+ else return NULL;
+
+ getline (&line, &sz, fp);
+ while ('#' == line[0]) /* skip header comments */
+ getline (&line, &sz, fp);
+ sscanf (line, "%d%d", &image.pixels_per_line, &image.lines);
+ if (1 < image.depth)
+ {
+ getline (&line, &sz, fp); /* max color value */
+ sscanf (line, "%d", &image.depth);
+ /**/ if (image.depth < (1 << 1)) image.depth = 1;
+ else if (image.depth < (1 << 8)) image.depth = 8;
+ else if (image.depth < (1 << 16)) image.depth = 16;
+ else return NULL;
+ }
+
+ free (line);
+ sz = 0;
+
+ image.bytes_per_line = (image.pixels_per_line
+ * (image.format ? 3 :1)
+ * (image.depth == 16 ? 2 : 1));
+ if (1 == image.depth)
+ {
+ image.bytes_per_line = (image.bytes_per_line + 7) / 8;
+ }
+ image.size = image.bytes_per_line * image.lines;
+
+ image.buffer = malloc (image.size);
+ if (!image.buffer) return NULL;
+ while (sz < image.size)
+ {
+ size_t s = fread (image.buffer, 1, image.size, fp);
+ if (0 == s)
+ {
+ if (ferror (fp))
+ {
+ fprintf (stderr, "error reading image file\n");
+ free (image.buffer);
+ return NULL;
+ }
+ if (feof (fp))
+ {
+ fprintf (stderr, "premature end of image file\n");
+ free (image.buffer);
+ return NULL;
+ }
+ }
+ sz += s;
+ }
+ fclose (fp);
+
+ rv = (pnm *) malloc (sizeof (pnm));
+ if (!rv)
+ {
+ free (image.buffer);
+ return NULL;
+ }
+ memcpy (rv, &image, sizeof (pnm));
+
+ return rv;
+}
+
+void
+write_pnm (const char *file, const pnm *image, const char *comment)
+{
+ FILE *fp;
+
+ if (file)
+ fp = fopen (file, "w");
+ else
+ fp = stdout;
+
+ if (!fp) return;
+
+ if (1 == image->depth)
+ {
+ fprintf (fp, "P4\n");
+ }
+ else
+ {
+ fprintf (fp, "P%d\n", (image->format ? 6 : 5));
+ }
+ fprintf (fp, "# %s\n", comment);
+ fprintf (fp, "%d %d\n", image->pixels_per_line, image->lines);
+ if (1 != image->depth)
+ {
+ fprintf (fp, "%d\n", (1 << image->depth) - 1);
+ }
+ fwrite (image->buffer, 1, image->bytes_per_line * image->lines, fp);
+ fflush (fp);
+ fclose (fp);
+}
diff --git a/lib/tests/pnm.h b/lib/tests/pnm.h
new file mode 100644
index 0000000..fcc2b9e
--- /dev/null
+++ b/lib/tests/pnm.h
@@ -0,0 +1,62 @@
+/* pnm.c -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+
+#ifndef pnm_h
+#define pnm_h
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct {
+ void *buffer; /*!< image data */
+ size_t size; /*!< number of bytes allocated for buffer */
+ int format; /*!< zero for grayscale, non-zero for RGB */
+ int32_t bytes_per_line;
+ int32_t pixels_per_line;
+ int32_t lines;
+ int32_t depth;
+ } pnm;
+
+ /*! \brief Reads a PNM image from \a file
+ *
+ * Comments in the image file are allowed only between the first
+ * and second non-comment lines. That is, only a single block of
+ * consecutive comment lines after the first line is supported.
+ *
+ * Memory to hold the image data is acquired using malloc() and the
+ * caller is responsible for releasing this resource.
+ *
+ * Passing the \c NULL pointer for \a file will read from \c stdin.
+ */
+ pnm * read_pnm (const char *file);
+
+ /*! \brief Outputs a PNM \a image to \a file
+ *
+ * Files produced are reusable for input.
+ *
+ * Passing the \c NULL pointer for \a file will result in output on
+ * \c stdout.
+ */
+ void write_pnm (const char *file, const pnm *image,
+ const char *comment);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* defined (pnm_h) */
diff --git a/lib/tests/run-test-pcx.sh b/lib/tests/run-test-pcx.sh
new file mode 100755
index 0000000..c186ea2
--- /dev/null
+++ b/lib/tests/run-test-pcx.sh
@@ -0,0 +1,76 @@
+#! /bin/sh
+# run-test-pcx.sh -- unit test for pcxstream class
+# Copyright (C) 2011 SEIKO EPSON CORPORATION
+#
+# License: GPLv2+
+# Authors: AVASYS CORPORATION
+#
+# This file is part of the "Image Scan!" test suite.
+#
+# The "Image Scan!" test suite 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 2 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 ought to have received a copy of the GNU General Public License
+# along with this package. If not, see <http://www.gnu.org/licenses/>.
+
+if ! test -x ./test-pcx; then
+ echo "FAIL: ./test-pcx not found, run make first"
+ exit 1
+fi
+
+DO_COMPARE=`type convert >/dev/null && echo yes`
+if test "xyes" != x$DO_COMPARE; then
+ echo "INFO: compare test is skipped"
+fi
+
+# Compare test is additional.
+compare () {
+ FMT=`echo $SRC | sed 's,.*\.,,'`
+ convert "pcx:$DST" "$FMT:-" 2>/dev/null | cmp "$SRC" -
+ return $?
+}
+
+# Make temporary output in $builddir unless overridden. Only clean up
+# if tests succeed.
+run_test () {
+ SRC="$input"
+ DST=`mktemp ${TMPDIR:=.}/pcx.XXXXXXXX`
+
+ if ! ./test-pcx "$SRC" "$DST"; then
+ echo "FAIL: ./test-pcx $SRC $DST"
+ TEST_RESULT=FAIL
+ return
+ fi
+ if test "xyes" = x$DO_COMPARE; then
+ if ! compare "$SRC" "$DST"; then
+ echo "FAIL: compare $SRC $DST"
+ TEST_RESULT=FAIL
+ return
+ fi
+ fi
+ rm -f "$DST"
+}
+
+# `make check` normally sets $srcdir
+SRCDIR=${srcdir:=.}
+TEST_RESULT=PASS
+for input in \
+ "$SRCDIR/even-width.pbm" \
+ "$SRCDIR/even-width.pgm" \
+ "$SRCDIR/even-width.ppm" \
+ "$SRCDIR/odd-width.pbm" \
+ "$SRCDIR/odd-width.pgm" \
+ "$SRCDIR/odd-width.ppm" \
+ ; do
+ run_test
+done
+
+test "PASS" = "$TEST_RESULT"
+exit $?
diff --git a/lib/tests/test-pcx.cc b/lib/tests/test-pcx.cc
new file mode 100644
index 0000000..86e9a0e
--- /dev/null
+++ b/lib/tests/test-pcx.cc
@@ -0,0 +1,85 @@
+/* test-pcx.cc -- utility functions for testing purposes
+ * Copyright (C) 2019 SEIKO EPSON Corporation
+ *
+ * License: EPSON END USER SOFTWARE LICENSE
+ * Author : SEIKO EPSON Corporation
+ *
+ * This file is part of Image Scan! for Linux.
+ * It is distributed under the terms of the EPSON END USER SOFTWARE LICENSE.
+ *
+ * You should have received a verbatim copy of the EPSON END USER SOFTWARE
+ * LICENSE along with the software.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <iostream>
+#include "file-opener.hh"
+#include "imgstream.hh"
+#include "pnm.h"
+
+int main (int argc, char *argv[])
+{
+ using iscan::file_opener;
+ using iscan::imgstream;
+
+ char *i = NULL;
+ char *o = NULL;
+
+ pnm *img = NULL;
+ int x_res = 0;
+ int y_res = 0;
+
+ if (argc != 3)
+ {
+ std::cerr << "usage: ./test-pcx input.pnm output.pcx"
+ << std::endl;
+ return EXIT_FAILURE;
+ }
+ i = argv[1];
+ o = argv[2];
+
+ img = read_pnm (i);
+ if (!img)
+ return EXIT_FAILURE;
+
+ x_res = 300;
+ y_res = 300;
+
+ file_opener *fo = NULL;
+ imgstream *is = NULL;
+ iscan::file_format format = iscan::PCX;
+ fo = new file_opener (std::string (o));
+ is = create_imgstream (*fo, format, false);
+
+ is->next ();
+ is->size (img->pixels_per_line, img->lines);
+ is->depth (img->depth);
+ iscan::colour_space space;
+ if (1 == img->format)
+ space = iscan::RGB;
+ else if (0 == img->format && 1 == img->depth)
+ space = iscan::mono;
+ else if (0 == img->format)
+ space = iscan::grey;
+ else
+ return EXIT_FAILURE;
+ is->colour (space);
+ is->resolution (x_res, y_res);
+
+ int l;
+ char *ptr = (char *)img->buffer;
+ for (l=0; l<img->lines; ++l)
+ {
+ is->write (ptr, img->bytes_per_line);
+ ptr += img->bytes_per_line;
+ }
+ is->flush ();
+
+ delete is;
+ delete fo;
+
+ return 0;
+}
diff --git a/lib/tiffstream.cc b/lib/tiffstream.cc
new file mode 100644
index 0000000..d9bebf4
--- /dev/null
+++ b/lib/tiffstream.cc
@@ -0,0 +1,268 @@
+// tiffstream.cc -- image streams producing TIFF files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "tiffstream.hh"
+
+#include <cstdlib>
+#include <ios>
+#include <stdexcept>
+
+namespace iscan
+{
+ // Forward declaration of handlers and support functions.
+ static void handle_error (const char *module, const char *fmt, va_list ap);
+ static void handle_warning (const char *module, const char *fmt, va_list ap);
+
+
+ tiffstream::tiffstream (FILE *fp, const string& name)
+ : _stream (fp)
+ {
+ if (!_stream) throw std::invalid_argument ("invalid file handle");
+ init (name); // handles HAVE_TIFFIO_H only stuff
+ }
+
+ tiffstream::~tiffstream (void)
+ {
+#if HAVE_TIFFIO_H
+ lib->Close (_tiff);
+#endif
+ fflush (_stream);
+ }
+
+ imgstream&
+ tiffstream::write (const byte_type *line, size_type n)
+ {
+ if (!line || 0 == n) return *this;
+
+ if (0 == _page)
+ {
+ set_tags ();
+ }
+
+#if HAVE_TIFFIO_H
+ if (1 != lib->WriteScanline (_tiff, const_cast<char *> (line), _row, 1))
+ {
+ throw std::ios_base::failure ("failure writing TIFF scanline");
+ }
+ ++_row;
+#endif /* HAVE_TIFFIO_H */
+ return *this;
+ }
+
+ void
+ tiffstream::next (void)
+ {
+#if HAVE_TIFFIO_H
+ if (0 == _row) return;
+#endif
+
+ if (0 < _page)
+ {
+#if HAVE_TIFFIO_H
+ if (1 != lib->WriteDirectory (_tiff))
+ {
+ throw std::runtime_error ("failure writing TIFF directory");
+ }
+#endif
+ }
+ set_tags ();
+ }
+
+ bool
+ tiffstream::is_usable (void)
+ {
+ if (lib)
+ {
+ return lib->is_usable;
+ }
+
+ lib = new (std::nothrow) tiff_lib_handle ();
+ if (!lib)
+ {
+ return false;
+ }
+
+ lib->is_usable = false;
+ lib->message = string ();
+ lib->lib = NULL;
+#if HAVE_TIFFIO_H
+ try
+ {
+ imgstream::dlopen ("libtiff", validate);
+ }
+ catch (std::runtime_error& e)
+ {
+ lib->message = e.what ();
+ return lib->is_usable;
+ }
+#endif /* HAVE_TIFFIO_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_TIFFIO_H
+#define funcsym(name) \
+ lib->name = ((tiffstream::tiff_lib_handle::name##_f) \
+ imgstream::dlsym (lib->lib, "TIFF"#name));
+#endif
+
+ bool
+ tiffstream::validate (lt_dlhandle h)
+ {
+ if (!h) return false;
+
+#if HAVE_TIFFIO_H
+ lib->lib = h;
+
+ funcsym (Open);
+ funcsym (FdOpen);
+ funcsym (Close);
+ funcsym (WriteDirectory);
+ funcsym (WriteScanline);
+ funcsym (Flush);
+ funcsym (SetField);
+ funcsym (SetErrorHandler);
+ funcsym (SetWarningHandler);
+
+ lib->is_usable = (lib->Open
+ && lib->FdOpen
+ && lib->Close
+ && lib->WriteDirectory
+ && lib->WriteScanline
+ && lib->Flush
+ && lib->SetField
+ && lib->SetErrorHandler
+ && lib->SetWarningHandler);
+
+ if (lib->is_usable)
+ {
+ lib->SetErrorHandler (handle_error);
+ lib->SetWarningHandler (handle_warning);
+ }
+#endif /* HAVE_TIFFIO_H */
+
+ return lib->is_usable;
+ }
+
+#if HAVE_TIFFIO_H
+#undef funcsym
+#endif
+
+ void
+ tiffstream::set_tags (void)
+ {
+ check_consistency ();
+
+#if HAVE_TIFFIO_H
+ lib->SetField (_tiff, TIFFTAG_SAMPLESPERPIXEL, (RGB == _cspc ? 3 : 1));
+
+ uint16 pm;
+ if (mono == _cspc) pm = PHOTOMETRIC_MINISWHITE;
+ if (grey == _cspc) pm = PHOTOMETRIC_MINISBLACK;
+ if (RGB == _cspc) pm = PHOTOMETRIC_RGB;
+ lib->SetField (_tiff, TIFFTAG_PHOTOMETRIC, pm);
+
+ if (RGB == _cspc)
+ lib->SetField (_tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+
+ lib->SetField (_tiff, TIFFTAG_BITSPERSAMPLE, _bits);
+
+ lib->SetField (_tiff, TIFFTAG_IMAGEWIDTH , _h_sz);
+ lib->SetField (_tiff, TIFFTAG_IMAGELENGTH, _v_sz);
+ lib->SetField (_tiff, TIFFTAG_ROWSPERSTRIP, 1);
+
+ if (0 != _hres && 0 != _vres)
+ {
+ lib->SetField (_tiff, TIFFTAG_XRESOLUTION, float (_hres));
+ lib->SetField (_tiff, TIFFTAG_YRESOLUTION, float (_vres));
+ lib->SetField (_tiff, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
+ }
+
+ lib->SetField (_tiff, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
+
+ _row = 0;
+#endif /* HAVE_TIFFIO_H */
+ ++_page;
+
+ return;
+ }
+
+ void
+ tiffstream::check_consistency (void) const
+ {
+ if (!(mono == _cspc || grey == _cspc || RGB == _cspc))
+ {
+ throw std::logic_error ("unsupported colour space");
+ }
+ if (!(1 == _bits || 8 == _bits))
+ {
+ throw std::logic_error ("unsupported bit depth");
+ }
+ }
+
+ void
+ tiffstream::init (const string& name)
+ {
+ if (!is_usable ())
+ {
+ throw std::runtime_error (lib->message);
+ }
+
+#if HAVE_TIFFIO_H
+ _row = 0;
+ // libtiff uses 'b' to signal big-endian, not binary as fopen()!
+ _tiff = lib->Open (name.c_str (), "w");
+ if (!_tiff) throw std::bad_alloc ();
+#endif
+ }
+
+ tiffstream::tiff_lib_handle *tiffstream::lib = NULL;
+
+
+ // Definition of handlers and support functions.
+
+ /*! \todo Implement when debugging framework has been worked out
+ */
+ static void
+ handle_error (const char *module, const char *fmt, va_list ap)
+ {
+ }
+
+ /*! \todo Implement when debugging framework has been worked out
+ */
+ static void
+ handle_warning (const char *module, const char *fmt, va_list ap)
+ {
+ }
+
+} // namespace iscan
diff --git a/lib/tiffstream.hh b/lib/tiffstream.hh
new file mode 100644
index 0000000..657ca73
--- /dev/null
+++ b/lib/tiffstream.hh
@@ -0,0 +1,105 @@
+// tiffstream.hh -- image streams producing TIFF files
+// Copyright (C) 2008, 2009 SEIKO EPSON CORPORATION
+//
+// This file is part of the 'iscan' program.
+//
+// The 'iscan' program is free-ish 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 2 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 FITNESS
+// FOR A PARTICULAR PURPOSE or MERCHANTABILITY.
+// See the GNU General Public License for more details.
+//
+// You should have received a verbatim copy of the GNU General Public
+// License along with this program; if not, write to:
+//
+// Free Software Foundation, Inc.
+// 59 Temple Place, Suite 330
+// Boston, MA 02111-1307 USA
+//
+// As a special exception, the copyright holders give permission
+// to link the code of this program with the esmod library and
+// distribute linked combinations including the two. You must obey
+// the GNU General Public License in all respects for all of the
+// code used other than esmod.
+
+
+#ifndef iscan_tiffstream_hh_included
+#define iscan_tiffstream_hh_included
+
+#ifndef __cplusplus
+#error "This is a C++ header file; use a C++ compiler to compile it."
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imgstream.hh"
+
+#if HAVE_TIFFIO_H
+#include <tiffio.h>
+#include <cstdarg> // for error and warning handlers
+#endif
+
+namespace iscan
+{
+ using std::string;
+
+ class tiffstream : public imgstream
+ {
+ public:
+ typedef basic_imgstream::byte_type byte_type;
+ typedef basic_imgstream::size_type size_type;
+
+ tiffstream (FILE *fp, const string& name);
+ virtual ~tiffstream (void);
+
+ virtual imgstream& write (const byte_type *line, size_type n);
+
+ virtual void next (void);
+
+ static bool is_usable (void);
+
+ private:
+ void set_tags (void);
+ void check_consistency (void) const;
+
+ void init (const string& name);
+
+ FILE *_stream;
+
+ static bool validate (lt_dlhandle h);
+ struct tiff_lib_handle
+ {
+ bool is_usable;
+ string message;
+ lt_dlhandle lib;
+
+#if HAVE_TIFFIO_H
+ fundecl (TIFF *, Open, const char *, const char *);
+ fundecl (TIFF *, FdOpen, const int, const char *, const char *);
+ fundecl (void, Close, TIFF *);
+ fundecl (int, WriteDirectory, TIFF *);
+ fundecl (int, WriteScanline, TIFF *, tdata_t, uint32, tsample_t);
+ fundecl (int, Flush, TIFF *);
+ fundecl (int, SetField, TIFF *, ttag_t, ...);
+ fundecl (TIFFErrorHandler, SetErrorHandler, TIFFErrorHandler);
+ fundecl (TIFFErrorHandler, SetWarningHandler, TIFFErrorHandler);
+#endif /* HAVE_TIFFIO_H */
+ };
+
+ static tiff_lib_handle *lib;
+
+#if HAVE_TIFFIO_H
+ TIFF *_tiff;
+ uint32 _row;
+#endif
+ };
+
+} // namespace iscan
+
+#endif // iscan_tiffstream_hh_included