Blame tools/csepdjvu.cpp

Packit df99a1
//C-  -*- C++ -*-
Packit df99a1
//C- -------------------------------------------------------------------
Packit df99a1
//C- DjVuLibre-3.5
Packit df99a1
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
Packit df99a1
//C- Copyright (c) 2001  AT&T
Packit df99a1
//C-
Packit df99a1
//C- This software is subject to, and may be distributed under, the
Packit df99a1
//C- GNU General Public License, either Version 2 of the license,
Packit df99a1
//C- or (at your option) any later version. The license should have
Packit df99a1
//C- accompanied the software or you may obtain a copy of the license
Packit df99a1
//C- from the Free Software Foundation at http://www.fsf.org .
Packit df99a1
//C-
Packit df99a1
//C- This program is distributed in the hope that it will be useful,
Packit df99a1
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit df99a1
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit df99a1
//C- GNU General Public License for more details.
Packit df99a1
//C- 
Packit df99a1
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library from
Packit df99a1
//C- Lizardtech Software.  Lizardtech Software has authorized us to
Packit df99a1
//C- replace the original DjVu(r) Reference Library notice by the following
Packit df99a1
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.djvu):
Packit df99a1
//C-
Packit df99a1
//C-  ------------------------------------------------------------------
Packit df99a1
//C- | DjVu (r) Reference Library (v. 3.5)
Packit df99a1
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
Packit df99a1
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
Packit df99a1
//C- | 6,058,214 and patents pending.
Packit df99a1
//C- |
Packit df99a1
//C- | This software is subject to, and may be distributed under, the
Packit df99a1
//C- | GNU General Public License, either Version 2 of the license,
Packit df99a1
//C- | or (at your option) any later version. The license should have
Packit df99a1
//C- | accompanied the software or you may obtain a copy of the license
Packit df99a1
//C- | from the Free Software Foundation at http://www.fsf.org .
Packit df99a1
//C- |
Packit df99a1
//C- | The computer code originally released by LizardTech under this
Packit df99a1
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
Packit df99a1
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
Packit df99a1
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
Packit df99a1
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
Packit df99a1
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
Packit df99a1
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
Packit df99a1
//C- | General Public License.   This grant only confers the right to 
Packit df99a1
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
Packit df99a1
//C- | the extent such infringement is reasonably necessary to enable 
Packit df99a1
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
Packit df99a1
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
Packit df99a1
//C- | any greater extent that may be necessary to utilize further 
Packit df99a1
//C- | modifications or combinations.
Packit df99a1
//C- |
Packit df99a1
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
Packit df99a1
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
Packit df99a1
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
Packit df99a1
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Packit df99a1
//C- +------------------------------------------------------------------
Packit df99a1
Packit df99a1
#ifdef HAVE_CONFIG_H
Packit df99a1
# include "config.h"
Packit df99a1
#endif
Packit df99a1
#if NEED_GNUG_PRAGMAS
Packit df99a1
# pragma implementation
Packit df99a1
#endif
Packit df99a1
Packit df99a1
/** @name csepdjvu
Packit df99a1
Packit df99a1
    {\bf Synopsis}
Packit df99a1
    \begin{verbatim}
Packit df99a1
        csepdjvu <....options-or-separated_files...> <outputdjvufile>
Packit df99a1
    \end{verbatim}
Packit df99a1
Packit df99a1
    {\bf Description}
Packit df99a1
    
Packit df99a1
    File #"csepdjvu.cpp"# demonstrates a complete back-end encoder that takes
Packit df99a1
    {\em separated files} as input and produces a djvu file as output.
Packit df99a1
    Each {\em separated file} contains a concatenation of pages. 
Packit df99a1
    Each page contains the following components:
Packit df99a1
    \begin{itemize}
Packit df99a1
    \item A run-length encoded file representing the foreground.
Packit df99a1
          Two kind of run-length encoded files are accepted.
Packit df99a1
          The Black-And-White RLE format is described in section
Packit df99a1
          \Ref{PNM and RLE file formats}.  The Color RLE format
Packit df99a1
          is described below.
Packit df99a1
    \item An optional PPM image representing the background.
Packit df99a1
          The size (width and height) of the PPM image must be obtained by
Packit df99a1
          rounding up the quotient of the foreground image size by
Packit df99a1
          an integer reduction factor ranging from 1 to 12.
Packit df99a1
    \item An arbitrary number of comment lines starting with 
Packit df99a1
          character '#'.
Packit df99a1
    \end{itemize}
Packit df99a1
    All the provided pages will be converted to Compound DjVu Images.
Packit df99a1
    Foreground colors will be encoded using a single solid color per component
Packit df99a1
    (see \Ref{DjVu Image files}).  Multiple pages will be gathered into a
Packit df99a1
    single bundled file.  Use \Ref{djvmcvt} or \Ref{djvujoin} to create an
Packit df99a1
    indirect file.
Packit df99a1
    
Packit df99a1
    {\bf Options}
Packit df99a1
Packit df99a1
    \begin{description}
Packit df99a1
    \item[-d n] Resolution written into the output file (default: 300).
Packit df99a1
    \item[-q <spec>] Quality level for background (default: 72+11+10+10).
Packit df99a1
                     See option #"-slice"# in program \Ref{c44} for details.
Packit df99a1
    \item[-v] Displays a brief message per page.
Packit df99a1
    \item[-vv] Displays lots of additional messages.
Packit df99a1
    \end{description}
Packit df99a1
          
Packit df99a1
    {\bf Color RLE Images}
Packit df99a1
Packit df99a1
    The Color RLE file format is a simple run-length encoding scheme for color
Packit df99a1
    images with a limited number of colors. Color RLE files always begin with
Packit df99a1
    a text header composed of:
Packit df99a1
    - the two characters #"R6"#,
Packit df99a1
    - the number of columns in decimal,\\
Packit df99a1
    - the number of rows in decimal,\\
Packit df99a1
    - the number of palette entries in decimal.\\
Packit df99a1
    These four items are separated by blank characters (space, tab, cr, or nl)
Packit df99a1
    or by comment lines introduced by character ``\#''.  The last number is
Packit df99a1
    followed by exactly one character (usually a nl character).  This header
Packit df99a1
    is followed by a palette containing three bytes per color.  The bytes
Packit df99a1
    represent the red, green, and blue components of the color.  
Packit df99a1
Packit df99a1
    The palette is followed by four bytes integers (MSB first) representing
Packit df99a1
    runs.  The twelve upper bits of this integer indicate the index of the run
Packit df99a1
    color in the palette entry.  The twenty lower bits of the integer indicate
Packit df99a1
    the run length.  Color indices greater than 0xff0 are reserved for pixels
Packit df99a1
    belonging to the background layer.  Color index 0xfff is used for
Packit df99a1
    transparent runs. Color index 0xffe is used for don't-care runs
Packit df99a1
    (i.e. pixels whose values should be taken by smoothly completing the
Packit df99a1
    background using the wavelet masking algorithm).  Each row is represented
Packit df99a1
    by a sequence of runs whose lengths add up to the image width.  Rows are
Packit df99a1
    encoded starting with the top row and progressing towards the bottom row.
Packit df99a1
Packit df99a1
    @memo
Packit df99a1
    Creates DjVu files from Separated files.
Packit df99a1
    @author
Packit df99a1
    L\'eon Bottou <leonb@research.att.com>
Packit df99a1
*/
Packit df99a1
//@{
Packit df99a1
//@}
Packit df99a1
Packit df99a1
Packit df99a1
#include "DjVuGlobal.h"
Packit df99a1
#include "GException.h"
Packit df99a1
#include "GSmartPointer.h"
Packit df99a1
#include "GContainer.h"
Packit df99a1
#include "ByteStream.h"
Packit df99a1
#include "IFFByteStream.h"
Packit df99a1
#include "GRect.h"
Packit df99a1
#include "GBitmap.h"
Packit df99a1
#include "JB2Image.h"
Packit df99a1
#include "DjVuPalette.h"
Packit df99a1
#include "IW44Image.h"
Packit df99a1
#include "DjVuInfo.h"
Packit df99a1
#include "DjVmDoc.h"
Packit df99a1
#include "DjVmNav.h"
Packit df99a1
#include "GOS.h"
Packit df99a1
#include "GURL.h"
Packit df99a1
#include "DjVuMessage.h"
Packit df99a1
#include "DjVuText.h"
Packit df99a1
#include "BSByteStream.h"
Packit df99a1
Packit df99a1
#include "miniexp.h"
Packit df99a1
#include "jb2tune.h"
Packit df99a1
#include "common.h"
Packit df99a1
Packit df99a1
#undef MIN
Packit df99a1
#undef MAX
Packit df99a1
inline int MIN(int a, int b) { return ( a
Packit df99a1
inline int MAX(int a, int b) { return ( a>b ?a :b); }
Packit df99a1
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// OPTIONS
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
struct csepdjvuopts
Packit df99a1
{
Packit df99a1
  int dpi;                  // resolution
Packit df99a1
  int verbose;              // verbosity level
Packit df99a1
  DjVuTXT::ZoneType text;   // level of text detail
Packit df99a1
  unsigned char slice[16];  // background quality spec
Packit df99a1
  csepdjvuopts();
Packit df99a1
};
Packit df99a1
Packit df99a1
csepdjvuopts::csepdjvuopts()
Packit df99a1
{
Packit df99a1
      dpi = 300;
Packit df99a1
      verbose = 0;
Packit df99a1
      text = DjVuTXT::WORD;
Packit df99a1
      slice[0] =  72;
Packit df99a1
      slice[1] =  83;      
Packit df99a1
      slice[2] =  93;
Packit df99a1
      slice[3] = 103;
Packit df99a1
      slice[4] =   0;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// BUFFERED BYTESTREAM
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
// -- A bytestream that performs buffering and 
Packit df99a1
//    offers a stdio-like interface for parsing files.
Packit df99a1
Packit df99a1
class BufferByteStream : public ByteStream 
Packit df99a1
{
Packit df99a1
public:
Packit df99a1
  enum {bufsize=512};
Packit df99a1
private:
Packit df99a1
  ByteStream &bs;
Packit df99a1
  unsigned char buffer[bufsize];
Packit df99a1
  int bufpos;
Packit df99a1
  int bufend;
Packit df99a1
public:
Packit df99a1
  BufferByteStream(ByteStream &lbs;;
Packit df99a1
  size_t read(void *buffer, size_t size);
Packit df99a1
  size_t write(const void *buffer, size_t size);
Packit df99a1
  virtual long tell(void) const;
Packit df99a1
  int eof(void);
Packit df99a1
  int unget(int c);
Packit df99a1
  inline int get(void);
Packit df99a1
  // parsing helpers
Packit df99a1
  bool skip(const char *s = " \t\n\r");
Packit df99a1
  bool expect(int &c, const char *s); 
Packit df99a1
  bool read_integer(int &x);
Packit df99a1
  bool read_pair(int &x, int &y);
Packit df99a1
  bool read_geometry(GRect &r);
Packit df99a1
  bool read_ps_string(GUTF8String &s);
Packit df99a1
};
Packit df99a1
Packit df99a1
BufferByteStream::BufferByteStream(ByteStream &bs)
Packit df99a1
  : bs(bs), bufpos(1), bufend(1)
Packit df99a1
{ 
Packit df99a1
}
Packit df99a1
Packit df99a1
int 
Packit df99a1
BufferByteStream::eof(void) // aka. feof
Packit df99a1
{
Packit df99a1
  if (bufpos < bufend) 
Packit df99a1
    return false;
Packit df99a1
  bufend = bufpos = 1;
Packit df99a1
  bufend += bs.read(buffer+bufend, bufsize-bufend);
Packit df99a1
  return (bufend == bufpos);
Packit df99a1
}
Packit df99a1
Packit df99a1
size_t 
Packit df99a1
BufferByteStream::read(void *buf, size_t size)
Packit df99a1
{
Packit df99a1
  if (size < 1)
Packit df99a1
    return 0;
Packit df99a1
  if (bufend == bufpos) 
Packit df99a1
    {
Packit df99a1
      if (size >= bufsize)
Packit df99a1
        return bs.read(buf, size);
Packit df99a1
      if (eof())
Packit df99a1
        return 0;
Packit df99a1
    }
Packit df99a1
  if (bufpos + (int)size > bufend)
Packit df99a1
    size = bufend - bufpos;
Packit df99a1
  memcpy(buf, buffer+bufpos, size);
Packit df99a1
  bufpos += size;
Packit df99a1
  return size;
Packit df99a1
}
Packit df99a1
Packit df99a1
size_t 
Packit df99a1
BufferByteStream::write(const void *, size_t )
Packit df99a1
{
Packit df99a1
  G_THROW("Cannot write into a BufferByteStream");
Packit df99a1
  return 0;
Packit df99a1
}
Packit df99a1
Packit df99a1
long 
Packit df99a1
BufferByteStream::tell(void) const
Packit df99a1
{ 
Packit df99a1
  return bs.tell() + bufpos - bufend; 
Packit df99a1
}
Packit df99a1
    
Packit df99a1
inline int 
Packit df99a1
BufferByteStream::get(void) // aka. getc()
Packit df99a1
{
Packit df99a1
  if (bufpos < bufend || !eof())
Packit df99a1
    return buffer[bufpos++];
Packit df99a1
  return EOF;
Packit df99a1
}
Packit df99a1
Packit df99a1
int  
Packit df99a1
BufferByteStream::unget(int c) // aka. ungetc()
Packit df99a1
{
Packit df99a1
  if (bufpos > 0 && c != EOF) 
Packit df99a1
    return buffer[--bufpos] = (unsigned char)c;
Packit df99a1
  return EOF;
Packit df99a1
}
Packit df99a1
Packit df99a1
bool 
Packit df99a1
BufferByteStream::expect(int &c, const char *s)
Packit df99a1
{
Packit df99a1
  c = get();
Packit df99a1
  if (strchr(s, c))
Packit df99a1
    return true;
Packit df99a1
  unget(c);
Packit df99a1
  return false;
Packit df99a1
}
Packit df99a1
Packit df99a1
bool
Packit df99a1
BufferByteStream::skip(const char *s)
Packit df99a1
{
Packit df99a1
  int c;
Packit df99a1
  while (expect(c, s)) { }
Packit df99a1
  return true;
Packit df99a1
}
Packit df99a1
Packit df99a1
bool 
Packit df99a1
BufferByteStream::read_integer(int &x)
Packit df99a1
{
Packit df99a1
  x = 0;
Packit df99a1
  int c = get();
Packit df99a1
  if (c<'0' || c>'9')
Packit df99a1
    return false;
Packit df99a1
  while (c>='0' && c<='9') 
Packit df99a1
    {
Packit df99a1
      x = x*10 + c - '0';
Packit df99a1
      c = get();
Packit df99a1
    }
Packit df99a1
  unget(c);
Packit df99a1
  return true;
Packit df99a1
}
Packit df99a1
Packit df99a1
bool 
Packit df99a1
BufferByteStream::read_pair(int &x, int &y)
Packit df99a1
{
Packit df99a1
  int c;
Packit df99a1
  x = y = 0;
Packit df99a1
  expect(c, "-");
Packit df99a1
  if (! read_integer(x)) 
Packit df99a1
    return false;
Packit df99a1
  if (c == '-') 
Packit df99a1
    x = -x;
Packit df99a1
  if (! expect(c, ":"))
Packit df99a1
    return false;
Packit df99a1
  expect(c, "-");
Packit df99a1
  if (! read_integer(y)) 
Packit df99a1
    return false;
Packit df99a1
  if (c == '-')
Packit df99a1
    y = -y;
Packit df99a1
  return true;
Packit df99a1
}
Packit df99a1
Packit df99a1
bool 
Packit df99a1
BufferByteStream::read_geometry(GRect &r)
Packit df99a1
{
Packit df99a1
  int x,y,w,h,c;
Packit df99a1
  x = y = w = h = 0;
Packit df99a1
  if (read_integer(w) && expect(c, "x") && read_integer(h))
Packit df99a1
    {
Packit df99a1
      if (expect(c,"+-"))
Packit df99a1
        {
Packit df99a1
          if (c == '+')
Packit df99a1
            expect(c,"-");
Packit df99a1
          if (! read_integer(x))
Packit df99a1
            return false;
Packit df99a1
          if (c == '-')
Packit df99a1
            x = -x;
Packit df99a1
        }
Packit df99a1
      if (expect(c,"+-"))
Packit df99a1
        {
Packit df99a1
          if (c == '+')
Packit df99a1
            expect(c,"-");
Packit df99a1
          if (! read_integer(y))
Packit df99a1
            return false;
Packit df99a1
          if (c == '-')
Packit df99a1
            y = -y;
Packit df99a1
        }
Packit df99a1
      r = GRect(x,y,w,h);
Packit df99a1
      return true;
Packit df99a1
    }
Packit df99a1
  return false;
Packit df99a1
}
Packit df99a1
Packit df99a1
static void
Packit df99a1
add_to_string(GUTF8String &s, char *buffer, int len, int &bom)
Packit df99a1
{
Packit df99a1
  if (!s && !bom && len>=2)
Packit df99a1
    {
Packit df99a1
      if (buffer[0]==(char)0xfe && buffer[1]==(char)0xff)
Packit df99a1
        bom = 0xfeff;
Packit df99a1
      if (buffer[0]==(char)0xff && buffer[1]==(char)0xfe)
Packit df99a1
        bom = 0xfffe;
Packit df99a1
      if (bom)
Packit df99a1
        {
Packit df99a1
          buffer += 2; 
Packit df99a1
          len -= 2;
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  if (bom == 0xfeff)
Packit df99a1
    for (int i=0; i
Packit df99a1
      *(uint16_t*)(buffer+i) = ((buffer[i]<<8) | buffer[i+1]);
Packit df99a1
  if (bom == 0xfffe)
Packit df99a1
    for (int i=0; i
Packit df99a1
      *(uint16_t*)(buffer+i) = ((buffer[i+1]<<8) | buffer[i]);
Packit df99a1
  if (bom)
Packit df99a1
    s += GUTF8String((const uint16_t*)buffer, len/2);
Packit df99a1
  else
Packit df99a1
    s += GUTF8String((const char*)buffer, len);
Packit df99a1
}
Packit df99a1
Packit df99a1
bool 
Packit df99a1
BufferByteStream::read_ps_string(GUTF8String &s)
Packit df99a1
{
Packit df99a1
  int bom = 0;
Packit df99a1
  unsigned int pos = 0;
Packit df99a1
  char buffer[512];
Packit df99a1
  if (get() != '(') 
Packit df99a1
    return false;
Packit df99a1
  s = "";
Packit df99a1
  for(;;)
Packit df99a1
    {
Packit df99a1
      int c = get();
Packit df99a1
      if (c == '\n' || c == '\r')
Packit df99a1
        return false;
Packit df99a1
      if (c == ')')
Packit df99a1
        break;
Packit df99a1
      if (c == '\\')
Packit df99a1
        {
Packit df99a1
          c = get();
Packit df99a1
          switch (c) 
Packit df99a1
            {
Packit df99a1
            case 'n': 
Packit df99a1
              c='\n' ; break;
Packit df99a1
            case 'r': 
Packit df99a1
              c='\r' ; break;
Packit df99a1
            case 't': 
Packit df99a1
              c='\t' ; break;
Packit df99a1
            case 'b': 
Packit df99a1
              c='\b' ; break;
Packit df99a1
            case 'f': 
Packit df99a1
              c='\f' ; break;
Packit df99a1
            default:
Packit df99a1
              if (c>='0' && c<='7') 
Packit df99a1
                {
Packit df99a1
                  int n = 0;
Packit df99a1
                  int x = 0;
Packit df99a1
                  while (c>='0' && c<='7' && n<3)
Packit df99a1
                    {
Packit df99a1
                      x = (x * 8) + c - '0';
Packit df99a1
                      c = get();
Packit df99a1
                      n++;
Packit df99a1
                    }
Packit df99a1
                  unget(c);
Packit df99a1
                  c = x;
Packit df99a1
                }
Packit df99a1
              break;
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
      if (c == EOF)
Packit df99a1
        return false;
Packit df99a1
      if (pos >= (int)sizeof(buffer))
Packit df99a1
        {
Packit df99a1
          add_to_string(s, buffer, pos, bom);
Packit df99a1
          pos = 0;
Packit df99a1
        }
Packit df99a1
      buffer[pos++] = c;
Packit df99a1
    }
Packit df99a1
  add_to_string(s, buffer, pos, bom);
Packit df99a1
  return true;
Packit df99a1
}
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// COLOR CONNECTED COMPONENT ANALYSIS
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
// -- A run of pixels with the same color
Packit df99a1
struct Run    
Packit df99a1
{ 
Packit df99a1
  short y;       // vertical coordinate
Packit df99a1
  short x1;      // first horizontal coordinate
Packit df99a1
  short x2;      // last horizontal coordinate
Packit df99a1
  short color;   // color id
Packit df99a1
  int ccid;      // component id
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Compares runs for y-x sorting
Packit df99a1
static inline bool
Packit df99a1
operator <= (const Run &a, const Run &b)
Packit df99a1
{
Packit df99a1
  return (a.y
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Color component descriptor
Packit df99a1
struct CC    
Packit df99a1
{
Packit df99a1
  GRect bb;      // bounding box
Packit df99a1
  int npix;      // number of black pixels
Packit df99a1
  int nrun;      // number of runs
Packit df99a1
  int frun;      // first run in cc ordered array of runs
Packit df99a1
  int color;     // color id
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- An image composed of runs
Packit df99a1
class CRLEImage 
Packit df99a1
{
Packit df99a1
public:
Packit df99a1
  int height;            // Height of the image in pixels
Packit df99a1
  int width;             // Width of the image in pixels
Packit df99a1
  GP<DjVuPalette> pal;   // Color palette
Packit df99a1
  GTArray<Run>    runs;  // Array of runs
Packit df99a1
  GTArray<CC>     ccs;   // Array of component descriptors
Packit df99a1
  int  nregularccs;      // Number of regular ccs (set by merge_and_split_ccs)
Packit df99a1
  char bg_flags;         // Comment flags about background.
Packit df99a1
  char fg_flags;         // Comment flags about foreground.
Packit df99a1
  CRLEImage(BufferByteStream &bs);
Packit df99a1
  GP<GBitmap> get_bitmap_for_cc(int ccid) const;
Packit df99a1
  void make_ccids_by_analysis();
Packit df99a1
  void make_ccs_from_ccids();
Packit df99a1
  void merge_and_split_ccs(int smallsize, int largesize);
Packit df99a1
  void sort_in_reading_order(); 
Packit df99a1
private:
Packit df99a1
  unsigned int read_integer(BufferByteStream &bs);
Packit df99a1
  void insert_runs(int y, const short *x1x2color, int nruns);
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for CRLEImage::CRLEImage(ByteStream &bs)
Packit df99a1
unsigned int 
Packit df99a1
CRLEImage::read_integer(BufferByteStream &bs)
Packit df99a1
{
Packit df99a1
  int c, x;
Packit df99a1
  while (bs.skip() && bs.expect(c, "#"))
Packit df99a1
    {
Packit df99a1
      char buffer[256];
Packit df99a1
      char *s = buffer;
Packit df99a1
      while (c != EOF && c != '\n' && c != '\r')
Packit df99a1
        {
Packit df99a1
          if (s - buffer < (int)sizeof(buffer) - 1)
Packit df99a1
            *s++ = c;
Packit df99a1
          c = bs.get();
Packit df99a1
        }
Packit df99a1
      *s = 0;
Packit df99a1
      for(s = buffer; *s; s++)
Packit df99a1
        {
Packit df99a1
          if (!strncmp(s, "bg-", 3))
Packit df99a1
            {
Packit df99a1
              if (!strncmp(s+3,"bw",2)   ||
Packit df99a1
                  !strncmp(s+3,"gray",4) ||
Packit df99a1
                  !strncmp(s+3,"color",5)  )
Packit df99a1
                bg_flags = s[3];
Packit df99a1
            }
Packit df99a1
          if (!strncmp(s, "fg-", 3))
Packit df99a1
            {
Packit df99a1
              if (!strncmp(s+3,"bw",2)   ||
Packit df99a1
                  !strncmp(s+3,"gray",4) ||
Packit df99a1
                  !strncmp(s+3,"color",5)  )
Packit df99a1
                fg_flags = s[3];
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  if (! bs.read_integer(x) )
Packit df99a1
    G_THROW("csepdjvu: corrupted input file (bad file header)");
Packit df99a1
  return x;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for CRLEImage::CRLEImage(ByteStream &bs)
Packit df99a1
void
Packit df99a1
CRLEImage::insert_runs(int y, const short *x1x2color, int count)
Packit df99a1
{
Packit df99a1
  if (count > 0)
Packit df99a1
    {
Packit df99a1
      int index = runs.lbound() - count;
Packit df99a1
      runs.resize(index, runs.hbound());
Packit df99a1
      Run *run = &runs[index];
Packit df99a1
      while (--count>=0) {
Packit df99a1
        run->y     = y;
Packit df99a1
        run->x1    = *x1x2color++;
Packit df99a1
        run->x2    = *x1x2color++;
Packit df99a1
        run->color = *x1x2color++;
Packit df99a1
        run->ccid  = 0;
Packit df99a1
        run++;
Packit df99a1
      }
Packit df99a1
      runs.shift(-index);
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Constructs CRLEImage from a run lenght encoded file,
Packit df99a1
//    making sure that runs are properly sorted.
Packit df99a1
CRLEImage::CRLEImage(BufferByteStream &bs)
Packit df99a1
  : height(0), width(0), nregularccs(0), bg_flags(0), fg_flags(0)
Packit df99a1
{
Packit df99a1
  unsigned int magic = bs.read16();
Packit df99a1
  width = read_integer(bs);
Packit df99a1
  height = read_integer(bs);
Packit df99a1
  if (width<1 || height<1)
Packit df99a1
    G_THROW("csepdjvu: corrupted input file (bad image size)");
Packit df99a1
  // An array for the runs and the buffered data
Packit df99a1
  GTArray<short> ax(3*width+3);
Packit df99a1
  // File format switch
Packit df99a1
  if (magic == 0x5234) // Black&White RLE data
Packit df99a1
    {
Packit df99a1
      // Skip one character
Packit df99a1
      bs.get(); 
Packit df99a1
      // Setup palette with one color
Packit df99a1
      pal = DjVuPalette::create();
Packit df99a1
      static char zeroes[4];
Packit df99a1
      GP<ByteStream> gzbs=ByteStream::create_static(zeroes,4);
Packit df99a1
      ByteStream &zbs=*gzbs;
Packit df99a1
      pal->decode_rgb_entries(zbs, 1);
Packit df99a1
      // RLE format
Packit df99a1
      int x, c, n;
Packit df99a1
      unsigned char p = 0;
Packit df99a1
      short *px = ax;
Packit df99a1
      n = height - 1;
Packit df99a1
      c = 0;
Packit df99a1
      while (n >= 0)
Packit df99a1
        {
Packit df99a1
          if (bs.eof())
Packit df99a1
            G_THROW( ByteStream::EndOfFile );
Packit df99a1
          x = bs.get();
Packit df99a1
          if (x >= 0xc0)
Packit df99a1
            x = (bs.get()) + ((x - 0xc0) << 8);
Packit df99a1
          if (c+x > width)
Packit df99a1
            G_THROW("csepdjvu: corrupted input file (lost RLE sync.)");
Packit df99a1
          if (p)
Packit df99a1
            {
Packit df99a1
              px[0] = c;
Packit df99a1
              px[1] = c+x-1;
Packit df99a1
              px[2] = 0;
Packit df99a1
              px += 3;
Packit df99a1
            }
Packit df99a1
          c += x;
Packit df99a1
          p = 1 - p;
Packit df99a1
          if (c >= width)
Packit df99a1
            {
Packit df99a1
              insert_runs(n, ax, (px-ax)/3);
Packit df99a1
              c = 0;
Packit df99a1
              p = 0;
Packit df99a1
              n -= 1; 
Packit df99a1
              px = ax;
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    } else if (magic == 0x5236) { // Color-RLE data 
Packit df99a1
      // Read ncolors and skip one character.
Packit df99a1
      int ncolors = read_integer(bs);
Packit df99a1
      bs.get();
Packit df99a1
      // Setup palette 
Packit df99a1
      if (ncolors<1 || ncolors>4095) 
Packit df99a1
        G_THROW("csepdjvu: corrupted input file (bad number of colors)");
Packit df99a1
Packit df99a1
      pal = DjVuPalette::create();
Packit df99a1
      pal->decode_rgb_entries(bs, ncolors);
Packit df99a1
      // RLE format
Packit df99a1
      int x, c, n, p;
Packit df99a1
      n = height - 1;
Packit df99a1
      c = 0;
Packit df99a1
      short *px = ax;
Packit df99a1
      while (n >= 0)
Packit df99a1
        {
Packit df99a1
          if (bs.eof())
Packit df99a1
            G_THROW( ByteStream::EndOfFile );
Packit df99a1
          x  = (bs.get() << 24);
Packit df99a1
          x  |= (bs.get() << 16);
Packit df99a1
          x  |= (bs.get() << 8);
Packit df99a1
          x  |= (bs.get());
Packit df99a1
          p = (x >> 20) & 0xfff;
Packit df99a1
          x = (x & 0xfffff);
Packit df99a1
          if (c+x > width)
Packit df99a1
            G_THROW("csepdjvu: corrupted input file (lost RLE sync.)");
Packit df99a1
          if (p >= 0 && p < ncolors)
Packit df99a1
            {
Packit df99a1
              px[0] = c;
Packit df99a1
              px[1] = c+x-1;
Packit df99a1
              px[2] = p;
Packit df99a1
              px += 3;
Packit df99a1
            }
Packit df99a1
          c += x;
Packit df99a1
          if (c >= width)
Packit df99a1
            {
Packit df99a1
              insert_runs(n, ax, (px-ax)/3);
Packit df99a1
              c = 0;
Packit df99a1
              p = 0;
Packit df99a1
              n -= 1; 
Packit df99a1
              px = ax;
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    } else { // Unrecognized file
Packit df99a1
      G_THROW("csepdjvu: corrupted input file (bad file header)");
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Performs color connected component analysis
Packit df99a1
//    assuming that runs are already y-x sorted.
Packit df99a1
void
Packit df99a1
CRLEImage::make_ccids_by_analysis()
Packit df99a1
{
Packit df99a1
  // runs.sort(); (we know that runs are 
Packit df99a1
  // Single Pass Connected Component Analysis (with unodes)
Packit df99a1
  int n;
Packit df99a1
  int p=0;
Packit df99a1
  GTArray<int> umap;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      int y = runs[n].y;
Packit df99a1
      int x1 = runs[n].x1 - 1;
Packit df99a1
      int x2 = runs[n].x2 + 1;
Packit df99a1
      int color = runs[n].color;
Packit df99a1
      int id = (umap.hbound() + 1);
Packit df99a1
      // iterate over previous line runs
Packit df99a1
      if (p>0) p--;
Packit df99a1
      for(;runs[p].y < y-1;p++);
Packit df99a1
      for(;(runs[p].y < y) && (runs[p].x1 <= x2);p++ )
Packit df99a1
        {
Packit df99a1
          if ( runs[p].x2 >= x1 )
Packit df99a1
            {
Packit df99a1
              if (runs[p].color == color)
Packit df99a1
                {
Packit df99a1
                  // previous run touches current run and has same color
Packit df99a1
                  int oid = runs[p].ccid;
Packit df99a1
                  while (umap[oid] < oid)
Packit df99a1
                    oid = umap[oid];
Packit df99a1
                  if ((int)id > umap.hbound()) {
Packit df99a1
                    id = oid;
Packit df99a1
                  } else if (id < oid) {
Packit df99a1
                    umap[oid] = id;
Packit df99a1
                  } else {
Packit df99a1
                    umap[id] = oid;
Packit df99a1
                    id = oid;
Packit df99a1
                  }
Packit df99a1
                  // freshen previous run id
Packit df99a1
                  runs[p].ccid = id;
Packit df99a1
                }
Packit df99a1
              // stop if previous run goes past current run
Packit df99a1
              if (runs[p].x2 >= x2)
Packit df99a1
                break;
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
      // create new entry in umap
Packit df99a1
      runs[n].ccid = id;
Packit df99a1
      if (id > umap.hbound())
Packit df99a1
        {
Packit df99a1
          umap.touch(id);
Packit df99a1
          umap[id] = id;
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  // Update umap and ccid
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      Run &run = runs[n];
Packit df99a1
      int ccid = run.ccid;
Packit df99a1
      while (umap[ccid] < ccid)
Packit df99a1
        ccid = umap[ccid];
Packit df99a1
      umap[run.ccid] = ccid;
Packit df99a1
      run.ccid = ccid;
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Constructs the ``ccs'' array from run's ccids.
Packit df99a1
void
Packit df99a1
CRLEImage::make_ccs_from_ccids()
Packit df99a1
{
Packit df99a1
  int n;
Packit df99a1
  Run *pruns = runs;
Packit df99a1
  // Find maximal ccid
Packit df99a1
  int maxccid = -1;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    if (pruns[n].ccid > maxccid)
Packit df99a1
      maxccid = runs[n].ccid;
Packit df99a1
  GTArray<int> armap(0,maxccid);
Packit df99a1
  int *rmap = armap;
Packit df99a1
  // Renumber ccs 
Packit df99a1
  for (n=0; n<=maxccid; n++)
Packit df99a1
    armap[n] = -1;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    if (pruns[n].ccid >= 0)
Packit df99a1
      rmap[ pruns[n].ccid ] = 1;
Packit df99a1
  int nid = 0;
Packit df99a1
  for (n=0; n<=maxccid; n++)
Packit df99a1
    if (rmap[n] > 0)
Packit df99a1
      rmap[n] = nid++;
Packit df99a1
  // Adjust nregularccs (since ccs are renumbered)
Packit df99a1
  while (nregularccs>0 && rmap[nregularccs-1]<0)
Packit df99a1
    nregularccs -= 1;
Packit df99a1
  if (nregularccs>0)
Packit df99a1
    nregularccs = 1 + rmap[nregularccs-1];
Packit df99a1
  // Prepare cc descriptors
Packit df99a1
  ccs.resize(0,nid-1);
Packit df99a1
  for (n=0; n
Packit df99a1
    ccs[n].nrun = 0;
Packit df99a1
  // Relabel runs
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      Run &run = pruns[n];
Packit df99a1
      if (run.ccid < 0) continue;  // runs with negative ccids are destroyed
Packit df99a1
      int oldccid = run.ccid;
Packit df99a1
      int newccid = rmap[oldccid];
Packit df99a1
      CC &cc = ccs[newccid];
Packit df99a1
      run.ccid = newccid;
Packit df99a1
      cc.nrun += 1;
Packit df99a1
    }
Packit df99a1
  // Compute positions for runs of cc
Packit df99a1
  int frun = 0;
Packit df99a1
  for (n=0; n
Packit df99a1
    {
Packit df99a1
      ccs[n].frun = rmap[n] = frun;
Packit df99a1
      frun += ccs[n].nrun;
Packit df99a1
    }
Packit df99a1
  // Copy runs
Packit df99a1
  GTArray<Run> rtmp;
Packit df99a1
  rtmp.steal(runs);
Packit df99a1
  Run *ptmp = rtmp;
Packit df99a1
  runs.resize(0,frun-1);
Packit df99a1
  pruns = runs;
Packit df99a1
  for (n=0; n<=rtmp.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      int id = ptmp[n].ccid;
Packit df99a1
      if (id < 0) continue;
Packit df99a1
      int pos = rmap[id]++;
Packit df99a1
      pruns[pos] = ptmp[n];
Packit df99a1
    }
Packit df99a1
  // Finalize ccs
Packit df99a1
  for (n=0; n
Packit df99a1
    {
Packit df99a1
      CC &cc = ccs[n];
Packit df99a1
      int npix = 0;
Packit df99a1
      runs.sort(cc.frun, cc.frun+cc.nrun-1);
Packit df99a1
      Run *run = &runs[cc.frun];
Packit df99a1
      int xmin = run->x1;
Packit df99a1
      int xmax = run->x2;
Packit df99a1
      int ymin = run->y;
Packit df99a1
      int ymax = run->y;
Packit df99a1
      cc.color = run->color;
Packit df99a1
      for (int i=0; i
Packit df99a1
        {
Packit df99a1
          if (run->x1 < xmin)  xmin = run->x1;
Packit df99a1
          if (run->x2 > xmax)  xmax = run->x2;
Packit df99a1
          if (run->y  < ymin)  ymin = run->y;
Packit df99a1
          if (run->y  > ymax)  ymax = run->y;
Packit df99a1
          npix += run->x2 - run->x1 + 1;
Packit df99a1
        }
Packit df99a1
      cc.npix = npix;
Packit df99a1
      cc.bb.xmin = xmin;
Packit df99a1
      cc.bb.ymin = ymin;
Packit df99a1
      cc.bb.xmax = xmax + 1;
Packit df99a1
      cc.bb.ymax = ymax + 1;
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
struct Grid_x_Color 
Packit df99a1
{
Packit df99a1
  short gridi;
Packit df99a1
  short gridj;
Packit df99a1
  int color;
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static inline unsigned int
Packit df99a1
hash(const Grid_x_Color &x) 
Packit df99a1
{
Packit df99a1
  return (x.gridi<<16) ^ (x.gridj<<8) ^ x.color;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static inline bool
Packit df99a1
operator==(const Grid_x_Color &x, const Grid_x_Color &y)
Packit df99a1
{
Packit df99a1
  return (x.gridi==y.gridi) && (x.gridj==y.gridj) && (x.color==y.color);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static int
Packit df99a1
makeccid(const Grid_x_Color &x, GMap<Grid_x_Color,int> &map, int &ncc)
Packit df99a1
{
Packit df99a1
  GPosition p = map.contains(x);
Packit df99a1
  if (p) return map[p];
Packit df99a1
  return map[x] = ncc++;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Merges small ccs of similar color and splits large ccs
Packit df99a1
void
Packit df99a1
CRLEImage::merge_and_split_ccs(int smallsize, int largesize)
Packit df99a1
{
Packit df99a1
  int ncc = ccs.size();
Packit df99a1
  int nruns = runs.size();
Packit df99a1
  int splitsize = largesize;
Packit df99a1
  if (ncc <= 0) return;
Packit df99a1
  // Associative map for storing merged ccids
Packit df99a1
  GMap<Grid_x_Color,int> map;
Packit df99a1
  nregularccs = ncc;
Packit df99a1
  // Set the correct ccids for the runs
Packit df99a1
  for (int ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      CC* cc = &ccs[ccid];
Packit df99a1
      if (cc->nrun <= 0) continue;
Packit df99a1
      Grid_x_Color key;
Packit df99a1
      key.color = cc->color;
Packit df99a1
      int ccheight = cc->bb.height();
Packit df99a1
      int ccwidth = cc->bb.width();
Packit df99a1
      if (ccheight<=smallsize && ccwidth<=smallsize)
Packit df99a1
        {
Packit df99a1
          key.gridi = (cc->bb.ymin+cc->bb.ymax)/splitsize/2;
Packit df99a1
          key.gridj = (cc->bb.xmin+cc->bb.xmax)/splitsize/2;
Packit df99a1
          int newccid = makeccid(key, map, ncc);
Packit df99a1
          for(int runid=cc->frun; runid<cc->frun+cc->nrun; runid++)
Packit df99a1
            runs[runid].ccid = newccid;
Packit df99a1
        }
Packit df99a1
      else if (ccheight>=largesize || ccwidth>=largesize)
Packit df99a1
        {
Packit df99a1
          for(int runid=cc->frun; runid<cc->frun+cc->nrun; runid++)
Packit df99a1
            {
Packit df99a1
              Run *r = & runs[runid];
Packit df99a1
              key.gridi = r->y/splitsize;
Packit df99a1
              key.gridj = r->x1/splitsize;
Packit df99a1
              int gridj_end = r->x2/splitsize;
Packit df99a1
              int gridj_span = gridj_end - key.gridj;
Packit df99a1
              r->ccid = makeccid(key, map, ncc);
Packit df99a1
              if (gridj_span>0)
Packit df99a1
                {
Packit df99a1
                  // truncate current run 
Packit df99a1
                  runs.touch(nruns+gridj_span-1);
Packit df99a1
                  r = &runs[runid];
Packit df99a1
                  int x = key.gridj*splitsize + splitsize;
Packit df99a1
                  int x_end = r->x2;
Packit df99a1
                  r->x2 = x-1;
Packit df99a1
                  // append additional runs to the runs array
Packit df99a1
                  while (++key.gridj < gridj_end)
Packit df99a1
                    {
Packit df99a1
                      Run& newrun = runs[nruns++];
Packit df99a1
                      newrun.y = r->y;
Packit df99a1
                      newrun.x1 = x;
Packit df99a1
                      x += splitsize;
Packit df99a1
                      newrun.x2 = x-1;
Packit df99a1
                      newrun.color = key.color;
Packit df99a1
                      newrun.ccid = makeccid(key, map, ncc);
Packit df99a1
                    }
Packit df99a1
                  // append last run to the run array
Packit df99a1
                  Run& newrun = runs[nruns++];
Packit df99a1
                  newrun.y = r->y;
Packit df99a1
                  newrun.x1 = x;
Packit df99a1
                  newrun.x2 = x_end;
Packit df99a1
                  newrun.color = key.color;
Packit df99a1
                  newrun.ccid = makeccid(key, map, ncc);
Packit df99a1
                }
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  // Recompute cc descriptors
Packit df99a1
  make_ccs_from_ccids();
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
top_edges_descending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  if (((CC*) pa)->bb.ymax != ((CC*) pb)->bb.ymax)
Packit df99a1
    return (((CC*) pb)->bb.ymax - ((CC*) pa)->bb.ymax);
Packit df99a1
  if (((CC*) pa)->bb.xmin != ((CC*) pb)->bb.xmin)
Packit df99a1
    return (((CC*) pa)->bb.xmin - ((CC*) pb)->bb.xmin);
Packit df99a1
  return (((CC*) pa)->frun - ((CC*) pb)->frun);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
left_edges_ascending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  if (((CC*) pa)->bb.xmin != ((CC*) pb)->bb.xmin)
Packit df99a1
    return (((CC*) pa)->bb.xmin - ((CC*) pb)->bb.xmin);
Packit df99a1
  if (((CC*) pb)->bb.ymax != ((CC*) pa)->bb.ymax)
Packit df99a1
    return (((CC*) pb)->bb.ymax - ((CC*) pa)->bb.ymax);
Packit df99a1
  return (((CC*) pa)->frun - ((CC*) pb)->frun);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
integer_ascending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  return ( *(int*)pb - *(int*)pa );
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Sort ccs in approximate reading order
Packit df99a1
void 
Packit df99a1
CRLEImage::sort_in_reading_order()
Packit df99a1
{
Packit df99a1
  if (nregularccs<2) return;
Packit df99a1
  CC *ccarray = new CC[nregularccs];
Packit df99a1
  // Copy existing ccarray (but segregate special ccs)
Packit df99a1
  int ccid;
Packit df99a1
  for(ccid=0; ccid
Packit df99a1
    ccarray[ccid] = ccs[ccid];
Packit df99a1
  // Sort the ccarray list into top-to-bottom order.
Packit df99a1
  qsort (ccarray, nregularccs, sizeof(CC), top_edges_descending);
Packit df99a1
  // Subdivide the ccarray list roughly into text lines
Packit df99a1
  int maxtopchange = width / 40;
Packit df99a1
  if (maxtopchange < 32) 
Packit df99a1
    maxtopchange = 32;
Packit df99a1
  // - Loop until processing all ccs
Packit df99a1
  int ccno = 0;
Packit df99a1
  int *bottoms = new int[nregularccs];
Packit df99a1
  while (ccno < nregularccs)
Packit df99a1
    {
Packit df99a1
      // - Gather first line approximation
Packit df99a1
      int nccno;
Packit df99a1
      int sublist_top = ccarray[ccno].bb.ymax-1;
Packit df99a1
      int sublist_bottom = ccarray[ccno].bb.ymin;
Packit df99a1
      for (nccno=ccno; nccno < nregularccs; nccno++)
Packit df99a1
        {
Packit df99a1
          if (ccarray[nccno].bb.ymax-1 < sublist_bottom) break;
Packit df99a1
          if (ccarray[nccno].bb.ymax-1 < sublist_top - maxtopchange) break;
Packit df99a1
          int bottom = ccarray[nccno].bb.ymin;
Packit df99a1
          bottoms[nccno-ccno] = bottom;
Packit df99a1
          if (bottom < sublist_bottom)
Packit df99a1
            sublist_bottom = bottom;
Packit df99a1
        }
Packit df99a1
      // - If more than one candidate cc for the line
Packit df99a1
      if (nccno > ccno + 1)
Packit df99a1
        {
Packit df99a1
          // - Compute median bottom
Packit df99a1
          qsort(bottoms, nccno-ccno, sizeof(int), integer_ascending);
Packit df99a1
          int bottom = bottoms[ (nccno-ccno-1)/2 ];
Packit df99a1
          // - Compose final line
Packit df99a1
          for (nccno=ccno; nccno < nregularccs; nccno++)
Packit df99a1
            if (ccarray[nccno].bb.ymax-1 < bottom)
Packit df99a1
              break;
Packit df99a1
          // - Sort final line
Packit df99a1
          qsort (ccarray+ccno, nccno-ccno, sizeof(CC), left_edges_ascending);
Packit df99a1
        }
Packit df99a1
      // - Next line
Packit df99a1
      ccno = nccno;
Packit df99a1
    }
Packit df99a1
  // Copy ccarray back and renumber the runs
Packit df99a1
  for(ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      CC& cc = ccarray[ccid];
Packit df99a1
      ccs[ccid] = cc;
Packit df99a1
      for(int r=cc.frun; r
Packit df99a1
        runs[r].ccid = ccid;
Packit df99a1
    }
Packit df99a1
  // Free memory
Packit df99a1
  delete [] bottoms;
Packit df99a1
  delete[] ccarray;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Creates a bitmap for a particular component
Packit df99a1
GP<GBitmap>   
Packit df99a1
CRLEImage::get_bitmap_for_cc(const int ccid) const
Packit df99a1
{
Packit df99a1
  const CC &cc = ccs[ccid];
Packit df99a1
  const GRect &bb = cc.bb;
Packit df99a1
  GP<GBitmap> bits = GBitmap::create(bb.height(), bb.width());
Packit df99a1
  const Run *prun = & runs[(int)cc.frun];
Packit df99a1
  for (int i=0; i
Packit df99a1
    {
Packit df99a1
      if (prun->y<bb.ymin || prun->y>=bb.ymax)
Packit df99a1
        G_THROW("Internal error (y bounds)");
Packit df99a1
      if (prun->x1<bb.xmin || prun->x2>=bb.xmax)
Packit df99a1
        G_THROW("Internal error (x bounds)");
Packit df99a1
      unsigned char *row = (*bits)[prun->y - bb.ymin];
Packit df99a1
      for (int x=prun->x1; x<=prun->x2; x++)
Packit df99a1
        row[x - bb.xmin] = 1;
Packit df99a1
    }
Packit df99a1
  return bits;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// PROCESS BACKGROUND PIXMAP
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
// -- Tries to read a background pixmap
Packit df99a1
GP<GPixmap>
Packit df99a1
read_background(BufferByteStream &bs, int w, int h, int &bgred)
Packit df99a1
{
Packit df99a1
  // Skip null bytes (why?)
Packit df99a1
  int lookahead;
Packit df99a1
  while (! (lookahead = bs.get())) { }
Packit df99a1
  bs.unget(lookahead);
Packit df99a1
  // Check pixmap
Packit df99a1
  if (lookahead != 'P') 
Packit df99a1
    return 0;
Packit df99a1
  GP<GPixmap> pix = GPixmap::create(bs);
Packit df99a1
  // Check background reduction
Packit df99a1
  for (bgred=1; bgred<=12; bgred++) 
Packit df99a1
    {
Packit df99a1
      int subw = (w + bgred - 1) / bgred;
Packit df99a1
      int subh = (h + bgred - 1) / bgred;
Packit df99a1
      if (subh == (int)pix->rows() && subw == (int)pix->columns())
Packit df99a1
        // Found reduction factor
Packit df99a1
        return pix;
Packit df99a1
    }
Packit df99a1
  // Failure
Packit df99a1
  G_THROW("Background pixmap size does not match foreground");
Packit df99a1
  return 0;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// HANDLE COMMENTS IN SEP FILES
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
class Comments
Packit df99a1
{
Packit df99a1
public:
Packit df99a1
  Comments(int w, int h, const csepdjvuopts &opts);
Packit df99a1
  void process_comments(BufferByteStream &bs, int verbose=0);
Packit df99a1
  bool parse_comment_line(BufferByteStream &bs);
Packit df99a1
  void make_chunks(IFFByteStream &iff);
Packit df99a1
  GP<DjVmNav> get_djvm_nav();
Packit df99a1
protected:
Packit df99a1
  int w;
Packit df99a1
  int h;
Packit df99a1
  GRectMapper mapper;
Packit df99a1
  DjVuTXT::ZoneType detail;
Packit df99a1
  int lastx, lasty;
Packit df99a1
  int lastdirx, lastdiry;
Packit df99a1
  int lastsize[3];
Packit df99a1
  struct TxtMark : public GPEnabled {
Packit df99a1
    int x,y,dx,dy;
Packit df99a1
    int inter;
Packit df99a1
    GRect r;
Packit df99a1
    GUTF8String s;
Packit df99a1
  };
Packit df99a1
  GPList<TxtMark> lastline;
Packit df99a1
  GP<DjVuTXT> txt;
Packit df99a1
  struct LnkMark : public GPEnabled {
Packit df99a1
    GRect r;
Packit df99a1
    GUTF8String s;
Packit df99a1
  };
Packit df99a1
  GPList<LnkMark> links;
Packit df99a1
  GP<DjVmNav> nav;
Packit df99a1
protected:
Packit df99a1
  bool allspace(const TxtMark *mark);
Packit df99a1
  void textmark(GP<TxtMark> mark);
Packit df99a1
  void textflush(void);
Packit df99a1
};
Packit df99a1
Packit df99a1
Comments::Comments(int w, int h, const csepdjvuopts &opts)
Packit df99a1
  : w(w), h(h), detail(opts.text)
Packit df99a1
{
Packit df99a1
  GRect pagerect(0,0,w,h);
Packit df99a1
  mapper.set_input(pagerect);
Packit df99a1
  mapper.set_output(pagerect);
Packit df99a1
  mapper.mirrory();
Packit df99a1
}
Packit df99a1
Packit df99a1
void 
Packit df99a1
Comments::process_comments(BufferByteStream &bs, int verbose)
Packit df99a1
{
Packit df99a1
  int c;
Packit df99a1
  // Skip null bytes
Packit df99a1
  while (! (c = bs.get())) { }
Packit df99a1
  // Process comment lines
Packit df99a1
  while (c == '#')
Packit df99a1
    {
Packit df99a1
      const char *message = 0;
Packit df99a1
      bs.skip(" \t");
Packit df99a1
      G_TRY
Packit df99a1
        {
Packit df99a1
          if (! parse_comment_line(bs) && verbose > 1)
Packit df99a1
            message = "csepdjvu: unrecognized comment '# ";
Packit df99a1
          else if (bs.skip(" \t") && bs.expect(c, "\n\r"))
Packit df99a1
            bs.unget(c);
Packit df99a1
          else if (verbose > 1)
Packit df99a1
            message = "csepdjvu: garbage in comments: '";
Packit df99a1
        }
Packit df99a1
      G_CATCH(ex)
Packit df99a1
      {
Packit df99a1
        message = 0;
Packit df99a1
        GUTF8String str = DjVuMessageLite::LookUpUTF8(ex.get_cause());
Packit df99a1
        if (verbose > 1)
Packit df99a1
          DjVuPrintErrorUTF8("%s\n",(const char *)str);
Packit df99a1
      } 
Packit df99a1
      G_ENDCATCH;
Packit df99a1
      if (message)
Packit df99a1
        DjVuPrintErrorUTF8(message);
Packit df99a1
      c = bs.get();
Packit df99a1
      while (c != EOF && c != '\r' && c != '\n')
Packit df99a1
        {
Packit df99a1
          if (message)
Packit df99a1
            DjVuPrintErrorUTF8("%c", c);
Packit df99a1
          c = bs.get();
Packit df99a1
        }
Packit df99a1
      if (message)
Packit df99a1
        DjVuPrintErrorUTF8("'\n");
Packit df99a1
      bs.skip();
Packit df99a1
      c = bs.get();
Packit df99a1
    }
Packit df99a1
  bs.unget(c);
Packit df99a1
}
Packit df99a1
Packit df99a1
bool
Packit df99a1
Comments::parse_comment_line(BufferByteStream &bs)
Packit df99a1
{
Packit df99a1
  int c = bs.get();
Packit df99a1
  // Text comments
Packit df99a1
  if (c == 'T')
Packit df99a1
    {
Packit df99a1
      GP<TxtMark> mark = new TxtMark;
Packit df99a1
      if (! (bs.skip(" \t") && bs.read_pair(mark->x,mark->y) &&
Packit df99a1
             bs.skip(" \t") && bs.read_pair(mark->dx,mark->dy) &&
Packit df99a1
             bs.skip(" \t") && bs.read_geometry(mark->r) &&
Packit df99a1
             bs.skip(" \t") && bs.read_ps_string(mark->s) ) )
Packit df99a1
        G_THROW("csepdjvu: corrupted file (syntax error in text comment)");
Packit df99a1
      if (mark->r.isempty())
Packit df99a1
        G_THROW("csepdjvu: corrupted file (empty rectangle in text comment)");
Packit df99a1
      textmark(mark);
Packit df99a1
      return true;
Packit df99a1
    }
Packit df99a1
  // Link comments
Packit df99a1
  if (c == 'L')
Packit df99a1
    {
Packit df99a1
      GP<LnkMark> mark = new LnkMark;
Packit df99a1
      if (! (bs.skip(" \t") && bs.read_geometry(mark->r) &&
Packit df99a1
             bs.skip(" \t") && bs.read_ps_string(mark->s) ) )
Packit df99a1
        G_THROW("csepdjvu: corrupted file (syntax error in link comment)");
Packit df99a1
      if (mark->r.isempty())
Packit df99a1
        G_THROW("csepdjvu: corrupted file (empty rectangle in link comment)");
Packit df99a1
      int ymax = h - mark->r.ymin - 1; // reversed in gsdjvu ?
Packit df99a1
      int ymin = h - mark->r.ymax - 1; // reversed in gsdjvu ?
Packit df99a1
      mark->r.ymax = ymax;
Packit df99a1
      mark->r.ymin = ymin;
Packit df99a1
      links.append(mark);
Packit df99a1
      return true;
Packit df99a1
    }
Packit df99a1
  // Bookmark comments
Packit df99a1
  if (c == 'B')
Packit df99a1
    {
Packit df99a1
      int count; 
Packit df99a1
      GUTF8String url;
Packit df99a1
      GUTF8String title;
Packit df99a1
      if (! (bs.skip(" \t") && bs.read_integer(count) &&
Packit df99a1
             bs.skip(" \t") && bs.read_ps_string(title) &&
Packit df99a1
             bs.skip(" \t") && bs.read_ps_string(url) ) )
Packit df99a1
        G_THROW("csepdjvu: corrupted file (syntax error in outline comment)");
Packit df99a1
      GP<DjVmNav::DjVuBookMark> b = 
Packit df99a1
        DjVmNav::DjVuBookMark::create(count, title, url);
Packit df99a1
      if (b && ! nav)
Packit df99a1
        nav = DjVmNav::create();
Packit df99a1
      if (b)
Packit df99a1
        nav->append(b);
Packit df99a1
      return true;
Packit df99a1
    }
Packit df99a1
  // Unrecognized
Packit df99a1
  bs.unget(c);
Packit df99a1
  return false;
Packit df99a1
}
Packit df99a1
Packit df99a1
static int 
Packit df99a1
median3(int *p)
Packit df99a1
{
Packit df99a1
  if (p[0] > p[1])
Packit df99a1
    return MAX(p[1],MIN(p[0],p[2]));
Packit df99a1
  else
Packit df99a1
    return MIN(p[1],MAX(p[0],p[2]));
Packit df99a1
}
Packit df99a1
Packit df99a1
static bool 
Packit df99a1
allspaces(const GUTF8String &s)
Packit df99a1
{
Packit df99a1
  bool ok = true;
Packit df99a1
  for (int i=0; ok && i<(int)s.length(); i++)
Packit df99a1
    if (s[i] != ' ')
Packit df99a1
      ok = false;
Packit df99a1
  return ok;
Packit df99a1
}
Packit df99a1
Packit df99a1
void 
Packit df99a1
Comments::textmark(GP<TxtMark> mark)
Packit df99a1
{
Packit df99a1
  // determine direction
Packit df99a1
  int dirx = 0;
Packit df99a1
  int diry = 0;
Packit df99a1
  int size = 0;
Packit df99a1
  if (abs(mark->dx) > 8*abs(mark->dy))
Packit df99a1
    {
Packit df99a1
      dirx = (mark->dx > 0) ? +1 : -1;
Packit df99a1
      size = mark->r.height();
Packit df99a1
    }
Packit df99a1
  else if (abs(mark->dy) > 8*abs(mark->dy))
Packit df99a1
    {
Packit df99a1
      diry = (mark->dy > 0) ? +1 : -1;
Packit df99a1
      size = mark->r.width();
Packit df99a1
    }
Packit df99a1
  // make mark
Packit df99a1
  mark->inter = 0;
Packit df99a1
  // flush previous line
Packit df99a1
  if (lastline.size())
Packit df99a1
    {
Packit df99a1
      if (size != lastsize[0])
Packit df99a1
        {
Packit df99a1
          lastsize[2] = lastsize[1];
Packit df99a1
          lastsize[1] = lastsize[0];
Packit df99a1
          lastsize[0] = size;
Packit df99a1
        }
Packit df99a1
      int fontsize = median3(lastsize) + 1;
Packit df99a1
      int shx = (mark->x - lastx) * 100 / fontsize;
Packit df99a1
      int shy = (mark->y - lasty) * 100 / fontsize;
Packit df99a1
      int inter = dirx * shx + diry * shy;
Packit df99a1
      if ( (dirx || diry) && (dirx == lastdirx) && (diry == lastdiry) &&
Packit df99a1
           (inter > -150) && (inter < 300) && 
Packit df99a1
           abs(diry * shx + dirx * shy) < 80 )
Packit df99a1
        mark->inter = inter;
Packit df99a1
      else
Packit df99a1
        textflush();
Packit df99a1
    }
Packit df99a1
  if (! lastline.size())
Packit df99a1
    lastsize[0] = lastsize[1] = lastsize[2] = size;
Packit df99a1
  lastline.append(mark);
Packit df99a1
  lastdirx = dirx;
Packit df99a1
  lastdiry = diry;
Packit df99a1
  lastx = mark->x + mark->dx;
Packit df99a1
  lasty = mark->y + mark->dy;
Packit df99a1
}
Packit df99a1
Packit df99a1
void 
Packit df99a1
Comments::textflush(void)
Packit df99a1
{
Packit df99a1
  int size = lastline.size();
Packit df99a1
  if (size > 0)
Packit df99a1
    {
Packit df99a1
      // compute word spacing
Packit df99a1
      int i = 0;
Packit df99a1
      GTArray<int> inter(0,size-1);
Packit df99a1
      for (GPosition p=lastline; p; ++p)
Packit df99a1
        inter[i++] = lastline[p]->inter;
Packit df99a1
      inter.sort();
Packit df99a1
      int wordsep = MAX(10, 2 * inter[(2*size)/3]);
Packit df99a1
      // compute word list
Packit df99a1
      GP<TxtMark> word;
Packit df99a1
      GPList<TxtMark> words;
Packit df99a1
	  { // extra nesting for windows
Packit df99a1
        for (GPosition p=lastline; p; ++p)
Packit df99a1
        {
Packit df99a1
          TxtMark *mark = lastline[p];
Packit df99a1
          if (word && mark->inter > wordsep) 
Packit df99a1
            {
Packit df99a1
              if (! allspaces(word->s))
Packit df99a1
                words.append(word);
Packit df99a1
              word = 0;
Packit df99a1
            }
Packit df99a1
          if (! word) 
Packit df99a1
            {
Packit df99a1
              word = mark;
Packit df99a1
            } 
Packit df99a1
          else 
Packit df99a1
            {
Packit df99a1
              word->dx += mark->dx;
Packit df99a1
              word->dy += mark->dy;
Packit df99a1
              word->s += mark->s;
Packit df99a1
              word->r.recthull(word->r, mark->r);
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
	  }
Packit df99a1
      if (word)
Packit df99a1
        {
Packit df99a1
          if (! allspaces(word->s))
Packit df99a1
            words.append(word);
Packit df99a1
          word = 0;
Packit df99a1
        }
Packit df99a1
      // create text data
Packit df99a1
      int size = words.size();
Packit df99a1
      if (size)
Packit df99a1
        {
Packit df99a1
          DjVuTXT::Zone *lzone = 0;
Packit df99a1
          for (GPosition p = words; p; ++p)
Packit df99a1
            {
Packit df99a1
              word = words[p];
Packit df99a1
              mapper.map(word->r);
Packit df99a1
              if (! lzone)
Packit df99a1
                {
Packit df99a1
                  if (! txt)
Packit df99a1
                    {
Packit df99a1
                      txt = DjVuTXT::create();
Packit df99a1
                      txt->page_zone.ztype = DjVuTXT::PAGE;
Packit df99a1
                      txt->page_zone.rect = GRect(0,0,w,h);
Packit df99a1
                      txt->page_zone.text_start = 0;
Packit df99a1
                      txt->page_zone.text_length = 0;
Packit df99a1
                    }
Packit df99a1
                  lzone = txt->page_zone.append_child();
Packit df99a1
                  lzone->ztype = DjVuTXT::LINE;
Packit df99a1
                  lzone->text_start = txt->textUTF8.length();
Packit df99a1
                  lzone->text_length = 0;
Packit df99a1
                }
Packit df99a1
              if (detail >= DjVuTXT::WORD)
Packit df99a1
                {
Packit df99a1
                  DjVuTXT::Zone *wzone = lzone->append_child();
Packit df99a1
                  wzone->ztype = DjVuTXT::WORD;
Packit df99a1
                  wzone->text_start = txt->textUTF8.length();
Packit df99a1
                  txt->textUTF8 += word->s;
Packit df99a1
                  wzone->text_length = 
Packit df99a1
                    txt->textUTF8.length() - wzone->text_start;
Packit df99a1
                  wzone->rect = word->r;
Packit df99a1
                  lzone->rect.recthull(lzone->rect, word->r);
Packit df99a1
                }
Packit df99a1
              else
Packit df99a1
                {
Packit df99a1
                  if (lzone->text_length > 0) txt->textUTF8 += " ";
Packit df99a1
                  txt->textUTF8 += word->s;
Packit df99a1
                  lzone->text_length = 
Packit df99a1
                    txt->textUTF8.length() - lzone->text_start;
Packit df99a1
                  lzone->rect.recthull(lzone->rect, word->r);
Packit df99a1
                }
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  lastline.empty();
Packit df99a1
}
Packit df99a1
Packit df99a1
static int 
Packit df99a1
bytestream_fputs(miniexp_io_t *io, const char *s)
Packit df99a1
{
Packit df99a1
  ByteStream *outbs = (ByteStream*)io->data[0];
Packit df99a1
  return (outbs) ? outbs->write((const void*)s, strlen(s)) : -1;
Packit df99a1
}
Packit df99a1
Packit df99a1
void 
Packit df99a1
Comments::make_chunks(IFFByteStream &iff)
Packit df99a1
{
Packit df99a1
  // Write text chunk
Packit df99a1
  textflush();
Packit df99a1
  if (txt)
Packit df99a1
    {
Packit df99a1
      txt->normalize_text();
Packit df99a1
      iff.put_chunk("TXTz");
Packit df99a1
      {
Packit df99a1
        GP<ByteStream> bsb = BSByteStream::create(iff.get_bytestream(), 50);
Packit df99a1
        txt->encode(bsb);
Packit df99a1
      }
Packit df99a1
      iff.close_chunk();
Packit df99a1
    }
Packit df99a1
  // Create annotation chunk
Packit df99a1
  if (links.size() > 0)
Packit df99a1
    {
Packit df99a1
      iff.put_chunk("ANTz");
Packit df99a1
      {
Packit df99a1
        GP<ByteStream> bsb = BSByteStream::create(iff.get_bytestream(), 50);
Packit df99a1
        miniexp_io_t io;
Packit df99a1
        miniexp_io_init(&io);
Packit df99a1
        io.fputs = bytestream_fputs;
Packit df99a1
        io.data[0] = (void*)(ByteStream*)bsb;
Packit df99a1
        minivar_t exor = miniexp_cons(miniexp_symbol("xor"),miniexp_nil);
Packit df99a1
        minivar_t zstr = miniexp_string("");
Packit df99a1
        for (GPosition p = links; p; ++p)
Packit df99a1
          {
Packit df99a1
            GP<LnkMark> mark = links[p];
Packit df99a1
            minivar_t url = miniexp_string((const char*)(mark->s));
Packit df99a1
            minivar_t expr = miniexp_cons(exor, miniexp_nil);
Packit df99a1
            minivar_t area;
Packit df99a1
            area = miniexp_cons(miniexp_number(mark->r.height()), area);
Packit df99a1
            area = miniexp_cons(miniexp_number(mark->r.width()), area);
Packit df99a1
            area = miniexp_cons(miniexp_number(mark->r.ymin), area);
Packit df99a1
            area = miniexp_cons(miniexp_number(mark->r.xmin), area);
Packit df99a1
            area = miniexp_cons(miniexp_symbol("rect"),area);
Packit df99a1
            expr = miniexp_cons(area, expr);
Packit df99a1
            expr = miniexp_cons(zstr, expr);
Packit df99a1
            expr = miniexp_cons(url, expr);
Packit df99a1
            expr = miniexp_cons(miniexp_symbol("maparea"), expr);
Packit df99a1
            miniexp_pprint_r(&io, expr, 72);
Packit df99a1
          }
Packit df99a1
      }
Packit df99a1
      iff.close_chunk();
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
GP<DjVmNav>
Packit df99a1
Comments::get_djvm_nav()
Packit df99a1
{
Packit df99a1
  if (nav && nav->getBookMarkCount() && nav->isValidBookmark())
Packit df99a1
    return nav;
Packit df99a1
  if (nav)
Packit df99a1
    DjVuPrintErrorUTF8("%s", "csepdjvu: corrupted outline comments.\n");
Packit df99a1
  return 0;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// MAIN COMPRESSION ROUTINE
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
// -- Compresses one page:
Packit df99a1
//    - bytestream bs contains the input separated file.
Packit df99a1
//    - bytestream obs will receive the output djvu file.
Packit df99a1
void 
Packit df99a1
csepdjvu_page(BufferByteStream &bs, 
Packit df99a1
              GP<ByteStream> obs, 
Packit df99a1
              GP<DjVmNav> &nav,
Packit df99a1
              const csepdjvuopts &opts)
Packit df99a1
{
Packit df99a1
  // Read rle data from separation file
Packit df99a1
  CRLEImage rimg(bs);
Packit df99a1
  int w = rimg.width;
Packit df99a1
  int h = rimg.height;
Packit df99a1
  if (opts.verbose > 1)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d\t%d\t%d\t%d",
Packit df99a1
                     ERR_MSG("csepdjvu.summary"), 
Packit df99a1
                     w, h, rimg.pal->size(), rimg.runs.size());
Packit df99a1
  
Packit df99a1
  // Perform Color Connected Component Analysis
Packit df99a1
  rimg.make_ccids_by_analysis();                  // Obtain ccids
Packit df99a1
  rimg.make_ccs_from_ccids();                     // Compute cc descriptors
Packit df99a1
  if (opts.verbose > 1)
Packit df99a1
    DjVuFormatErrorUTF8("%s\t%d", ERR_MSG("csepdjvu.analyzed"), 
Packit df99a1
                        rimg.ccs.size());
Packit df99a1
  
Packit df99a1
  // Post-process Color Connected Components
Packit df99a1
  int largesize = MIN(500, MAX(64, opts.dpi));
Packit df99a1
  int smallsize = MAX(2, opts.dpi/150);
Packit df99a1
  rimg.merge_and_split_ccs(smallsize,largesize);  // Eliminates gross ccs
Packit df99a1
  if (opts.verbose > 1)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d",
Packit df99a1
                     ERR_MSG("csepdjvu.merge_split"), 
Packit df99a1
                     rimg.ccs.size());
Packit df99a1
  rimg.sort_in_reading_order();                   // Sort cc descriptors
Packit df99a1
  
Packit df99a1
  // Create JB2Image and fill colordata
Packit df99a1
  GP<JB2Image> gjimg=JB2Image::create(); 
Packit df99a1
  JB2Image &jimg=*gjimg;
Packit df99a1
  jimg.set_dimension(w, h);
Packit df99a1
  int nccs = rimg.ccs.size();
Packit df99a1
  for (int ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      JB2Shape shape;
Packit df99a1
      JB2Blit  blit;
Packit df99a1
      shape.parent = -1;
Packit df99a1
      shape.userdata = 0;
Packit df99a1
      if (ccid >= rimg.nregularccs)
Packit df99a1
        shape.userdata |= JB2SHAPE_SPECIAL;
Packit df99a1
      shape.bits = rimg.get_bitmap_for_cc(ccid);
Packit df99a1
      shape.bits->compress();
Packit df99a1
      CC& cc = rimg.ccs[ccid];
Packit df99a1
      blit.shapeno = jimg.add_shape(shape);
Packit df99a1
      blit.left = cc.bb.xmin;
Packit df99a1
      blit.bottom = cc.bb.ymin;
Packit df99a1
      int blitno = jimg.add_blit(blit);
Packit df99a1
      rimg.pal->colordata.touch(blitno);
Packit df99a1
      rimg.pal->colordata[blitno] = cc.color;
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Organize JB2Image
Packit df99a1
  tune_jb2image_lossless(&jimg);
Packit df99a1
  if (opts.verbose> 1)
Packit df99a1
    {
Packit df99a1
      int nshape=0, nrefine=0;
Packit df99a1
      for (int i=0; i
Packit df99a1
        {
Packit df99a1
          if (!jimg.get_shape(i).bits) continue;
Packit df99a1
          if (jimg.get_shape(i).parent >= 0) nrefine++; 
Packit df99a1
          nshape++; 
Packit df99a1
        }
Packit df99a1
      DjVuFormatErrorUTF8( "%s\t%d\t%d",
Packit df99a1
                       ERR_MSG("csepdjvu.cross_code"), 
Packit df99a1
                       nshape, nrefine);
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Obtain background image
Packit df99a1
  int bgred;
Packit df99a1
  GP<GPixmap> bgpix = read_background(bs, w, h, bgred);
Packit df99a1
  if (opts.verbose > 1 && bgpix)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("csepdjvu.reduction"), bgred);
Packit df99a1
Packit df99a1
  // Process comments
Packit df99a1
  Comments coms(w, h, opts);
Packit df99a1
  coms.process_comments(bs, opts.verbose);
Packit df99a1
  
Packit df99a1
  // Compute flags for simplifying output format
Packit df99a1
  bool white_background = (bgpix ? false : true);
Packit df99a1
  bool gray_background = white_background;
Packit df99a1
  if (rimg.bg_flags == 'g' || rimg.bg_flags=='b')
Packit df99a1
    gray_background = true;
Packit df99a1
  bool bitonal = false;
Packit df99a1
  if (white_background && rimg.pal->size() == 1) 
Packit df99a1
    {
Packit df99a1
      GPixel fgcolor;
Packit df99a1
      rimg.pal->index_to_color(0, fgcolor);
Packit df99a1
      if (fgcolor == GPixel::BLACK)
Packit df99a1
        bitonal = true;
Packit df99a1
    }
Packit df99a1
  if (opts.verbose > 1)
Packit df99a1
    {
Packit df99a1
      if (bitonal)
Packit df99a1
        DjVuWriteError( ERR_MSG("csepdjvu.bilevel") );
Packit df99a1
      else if (white_background) 
Packit df99a1
        DjVuWriteError( ERR_MSG("csepdjvu.white_bg") );
Packit df99a1
      else if (gray_background) 
Packit df99a1
        DjVuWriteError( ERR_MSG("csepdjvu.gray_bg") );
Packit df99a1
      else 
Packit df99a1
        DjVuWriteError( ERR_MSG("csepdjvu.color") );
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Create background image
Packit df99a1
  GP<IW44Image> iw;
Packit df99a1
  if (! white_background)
Packit df99a1
    {
Packit df99a1
      /* Perform masked compression */
Packit df99a1
      GP<GBitmap> mask = jimg.get_bitmap(bgred);
Packit df99a1
      mask->binarize_grays(bgred*bgred-1);
Packit df99a1
      IW44Image::CRCBMode mode = IW44Image::CRCBnormal;
Packit df99a1
      if (gray_background) mode = IW44Image::CRCBnone;
Packit df99a1
      iw = IW44Image::create_encode(*bgpix, mask, mode);
Packit df99a1
      bgpix = 0;
Packit df99a1
    } 
Packit df99a1
  else if (! bitonal) 
Packit df99a1
    {
Packit df99a1
      /* Compute white background */
Packit df99a1
      GPixel bgcolor = GPixel::WHITE;
Packit df99a1
      GP<GPixmap> inputsub=GPixmap::create((h+11)/12, (w+11)/12, &bgcolor);
Packit df99a1
      iw = IW44Image::create_encode(*inputsub, 0, IW44Image::CRCBnone);
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Assemble DJVU file
Packit df99a1
  GP<IFFByteStream> giff=IFFByteStream::create(obs);
Packit df99a1
  IFFByteStream &iff=*giff;
Packit df99a1
  // -- main composite chunk
Packit df99a1
  iff.put_chunk("FORM:DJVU", 1);
Packit df99a1
  // -- ``INFO'' chunk
Packit df99a1
  iff.put_chunk("INFO");
Packit df99a1
  GP<DjVuInfo> ginfo=DjVuInfo::create();
Packit df99a1
  DjVuInfo &info=*ginfo;
Packit df99a1
  info.height = h;
Packit df99a1
  info.width = w;
Packit df99a1
  info.dpi = opts.dpi;
Packit df99a1
  info.encode(*iff.get_bytestream());
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- ``Sjbz'' chunk
Packit df99a1
  iff.put_chunk("Sjbz");
Packit df99a1
  jimg.encode(iff.get_bytestream());
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- Color stuff
Packit df99a1
  if (! bitonal) 
Packit df99a1
    {
Packit df99a1
      // -- ``FGbz'' chunk
Packit df99a1
      iff.put_chunk("FGbz");
Packit df99a1
      rimg.pal->encode(iff.get_bytestream());
Packit df99a1
      iff.close_chunk();
Packit df99a1
      // -- ``BG44'' chunk
Packit df99a1
      IWEncoderParms iwparms;
Packit df99a1
      if (white_background) 
Packit df99a1
        {
Packit df99a1
          iff.put_chunk("BG44");
Packit df99a1
          iwparms.slices = 97;
Packit df99a1
          iw->encode_chunk(iff.get_bytestream(), iwparms);
Packit df99a1
          iff.close_chunk();
Packit df99a1
        }
Packit df99a1
      else
Packit df99a1
        {
Packit df99a1
          const unsigned char *slice = opts.slice;
Packit df99a1
          while ((iwparms.slices = *slice++))
Packit df99a1
            {
Packit df99a1
              iff.put_chunk("BG44");
Packit df99a1
              iw->encode_chunk(iff.get_bytestream(), iwparms);
Packit df99a1
              iff.close_chunk();
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  // -- terminate main composite chunk
Packit df99a1
  coms.make_chunks(iff);
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- store outline
Packit df99a1
  if (! nav) 
Packit df99a1
    nav = coms.get_djvm_nav();
Packit df99a1
}  
Packit df99a1
Packit df99a1
// -- Checks whether there is another page in the same file
Packit df99a1
bool
Packit df99a1
check_for_another_page(BufferByteStream &bs, const csepdjvuopts &opts)
Packit df99a1
{
Packit df99a1
  // Skip null bytes (why?)
Packit df99a1
  int lookahead;
Packit df99a1
  while (! (lookahead = bs.get())) { }
Packit df99a1
  bs.unget(lookahead);
Packit df99a1
  // Check next header
Packit df99a1
  if (lookahead == 'R') 
Packit df99a1
    return true;
Packit df99a1
  if (lookahead != EOF) 
Packit df99a1
    DjVuPrintErrorUTF8("%s","csepdjvu: found corrupted data\n");
Packit df99a1
  return false;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Prints usage message
Packit df99a1
void
Packit df99a1
usage()
Packit df99a1
{
Packit df99a1
  const char *msg = 
Packit df99a1
#ifdef DJVULIBRE_VERSION
Packit df99a1
    "CSEPDJVU --- DjVuLibre-" DJVULIBRE_VERSION "\n"
Packit df99a1
#endif
Packit df99a1
    "DjVu encoder working with \"separated\" files\n\n"
Packit df99a1
    "Usage: csepdjvu <...options_or_separatedfiles...> <outputdjvufile>\n"
Packit df99a1
    "Options are:\n"
Packit df99a1
    "   -v, -vv    Select verbosity level.\n"
Packit df99a1
    "   -d <n>     Set resolution to <n> dpi (default: 300).\n"
Packit df99a1
    "   -t         Restricts text information to lines only.\n"
Packit df99a1
    "   -q <spec>  Select quality for background (default: 72+11+10+10);\n"
Packit df99a1
    "              see option -slice in program c44 for more information.\n"
Packit df99a1
    "Each separated files contain one or more pages\n"
Packit df99a1
    "Each page is composed of:\n"
Packit df99a1
    " (1) a B&W-RLE or Color-RLE image representing the foreground,\n"
Packit df99a1
    " (2) an optional PPM image representing the background layer.\n";
Packit df99a1
  DjVuPrintErrorUTF8(msg);
Packit df99a1
  exit(10);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Parsing quality spec (borrowed from c44)
Packit df99a1
void 
Packit df99a1
parse_slice(const char *q, csepdjvuopts &opts)
Packit df99a1
{
Packit df99a1
  int count = 0;
Packit df99a1
  int lastx = 0;
Packit df99a1
  while (*q)
Packit df99a1
    {
Packit df99a1
      char *ptr; 
Packit df99a1
      int x = strtol(q, &ptr, 10);
Packit df99a1
      if (ptr == q)
Packit df99a1
        G_THROW("csepdjvu: "
Packit df99a1
                "illegal quality specification (number expected)");
Packit df99a1
      if (lastx>0 && q[-1]=='+')
Packit df99a1
        x += lastx;
Packit df99a1
      if (x<1 || x>1000 || x
Packit df99a1
        G_THROW("csepdjvu: "
Packit df99a1
                "illegal quality specification (number out of range)");
Packit df99a1
      lastx = x;
Packit df99a1
      if (*ptr && *ptr!='+' && *ptr!=',')
Packit df99a1
        G_THROW("csepdjvu: "
Packit df99a1
                "illegal quality specification (comma expected)");        
Packit df99a1
      q = (*ptr ? ptr+1 : ptr);
Packit df99a1
      if (count+1 >= (int)(sizeof(opts.slice)/sizeof(opts.slice[0])))
Packit df99a1
        G_THROW("csepdjvu: "
Packit df99a1
                "illegal quality specification (too many chunks)");
Packit df99a1
      opts.slice[count++] = x;
Packit df99a1
      opts.slice[count] = 0;
Packit df99a1
    }
Packit df99a1
  if (count < 1)
Packit df99a1
    G_THROW("csepdjvu: "
Packit df99a1
            "illegal quality specification (no chunks)");
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Main routine
Packit df99a1
int 
Packit df99a1
main(int argc, const char **argv)
Packit df99a1
{
Packit df99a1
  DJVU_LOCALE;
Packit df99a1
  GArray<GUTF8String> dargv(0,argc-1);
Packit df99a1
  for(int i=0;i
Packit df99a1
    dargv[i]=GNativeString(argv[i]);
Packit df99a1
  G_TRY
Packit df99a1
    {
Packit df99a1
      GP<DjVmDoc> gdoc=DjVmDoc::create();
Packit df99a1
      GP<DjVmNav> gnav;
Packit df99a1
      DjVmDoc &doc=*gdoc;
Packit df99a1
      GURL outputurl;
Packit df99a1
      GP<ByteStream> goutputpage=ByteStream::create();
Packit df99a1
      csepdjvuopts opts;
Packit df99a1
      int pageno = 0;
Packit df99a1
      // Read outputurl name
Packit df99a1
      if (argc < 3) usage();
Packit df99a1
      outputurl = GURL::Filename::UTF8(dargv[--argc]);
Packit df99a1
      // Process arguments
Packit df99a1
      for (int i=1; i
Packit df99a1
        {
Packit df99a1
          GUTF8String arg = dargv[i];
Packit df99a1
          if (arg == "-v")
Packit df99a1
            opts.verbose = 1;
Packit df99a1
          else if (arg == "-vv")
Packit df99a1
            opts.verbose = 2;
Packit df99a1
          else if (arg == "-t")
Packit df99a1
            opts.text = DjVuTXT::LINE;
Packit df99a1
          else if (arg == "-d" && i+1
Packit df99a1
            {
Packit df99a1
              // Specify resolution
Packit df99a1
              char *end;
Packit df99a1
              opts.dpi = strtol(dargv[++i], &end, 10);
Packit df99a1
              if (*end || opts.dpi<25 || opts.dpi>6000)
Packit df99a1
                usage();
Packit df99a1
            }
Packit df99a1
          else if (arg == "-q" && i+1 < argc)
Packit df99a1
            {
Packit df99a1
              // Specify background quality
Packit df99a1
              parse_slice(dargv[++i], opts);
Packit df99a1
            }
Packit df99a1
          else if (arg == "-l" || arg == "-t" || arg == "-h")
Packit df99a1
            {
Packit df99a1
              DjVuPrintErrorUTF8("csepdjvu: option %s not yet supported\n",
Packit df99a1
                                 (const char *)arg );
Packit df99a1
            }
Packit df99a1
          else if ((arg == "-j" || arg == "-p") && i+1 < argc)
Packit df99a1
            {
Packit df99a1
              i += 1;
Packit df99a1
              DjVuPrintErrorUTF8("csepdjvu: option %s not yet supported\n",
Packit df99a1
                                 (const char *)arg );
Packit df99a1
            }
Packit df99a1
          else 
Packit df99a1
            {
Packit df99a1
              // Process separation file
Packit df99a1
              GP<ByteStream> fbs = 
Packit df99a1
                ByteStream::create(GURL::Filename::UTF8(arg),"rb");
Packit df99a1
              BufferByteStream ibs(*fbs);
Packit df99a1
              do {
Packit df99a1
                char pagename[16];
Packit df99a1
                sprintf(pagename, "p%04d.djvu", ++pageno);
Packit df99a1
                if (opts.verbose > 1)
Packit df99a1
                  DjVuPrintErrorUTF8("%s","--------------------\n");
Packit df99a1
                // Compress page 
Packit df99a1
                goutputpage=ByteStream::create();
Packit df99a1
                ByteStream &outputpage=*goutputpage;
Packit df99a1
                csepdjvu_page(ibs, goutputpage, gnav, opts);
Packit df99a1
                if (opts.verbose) {
Packit df99a1
                  DjVuPrintErrorUTF8("csepdjvu: %d bytes for page %d",
Packit df99a1
                                     outputpage.size(), pageno);
Packit df99a1
                  if (arg == "-")
Packit df99a1
                    DjVuPrintErrorUTF8("%s"," (from stdin)\n");
Packit df99a1
                  else
Packit df99a1
                    DjVuPrintErrorUTF8(" (from file '%s')\n", 
Packit df99a1
                                       (const char*)arg);
Packit df99a1
                }
Packit df99a1
                // Insert page into document
Packit df99a1
                outputpage.seek(0);
Packit df99a1
                doc.insert_file(outputpage, DjVmDir::File::PAGE, 
Packit df99a1
                                pagename, pagename);
Packit df99a1
              } while (check_for_another_page(ibs, opts));
Packit df99a1
            }
Packit df99a1
        } 
Packit df99a1
      // Save file
Packit df99a1
      if (pageno == 1 && ! gnav) 
Packit df99a1
        {
Packit df99a1
          ByteStream &outputpage=*goutputpage;
Packit df99a1
          // Save as a single page 
Packit df99a1
          outputpage.seek(0);
Packit df99a1
          ByteStream::create(outputurl,"wb")->copy(outputpage);
Packit df99a1
        }
Packit df99a1
      else if (pageno >= 1)
Packit df99a1
        {
Packit df99a1
          // Save as a bundled file
Packit df99a1
          doc.set_djvm_nav(gnav);
Packit df99a1
          doc.write(ByteStream::create(outputurl,"wb"));
Packit df99a1
        }
Packit df99a1
      else 
Packit df99a1
        usage();
Packit df99a1
    }
Packit df99a1
  G_CATCH(ex)
Packit df99a1
    {
Packit df99a1
      ex.perror();
Packit df99a1
      exit(1);
Packit df99a1
    }
Packit df99a1
  G_ENDCATCH;
Packit df99a1
  return 0;
Packit df99a1
}
Packit df99a1