//C-  -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001  AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, either Version 2 of the license,
//C- or (at your option) any later version. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//C- GNU General Public License for more details.
//C- 
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library from
//C- Lizardtech Software.  Lizardtech Software has authorized us to
//C- replace the original DjVu(r) Reference Library notice by the following
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.djvu):
//C-
//C-  ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, either Version 2 of the license,
//C- | or (at your option) any later version. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
//C- | General Public License.   This grant only confers the right to 
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
//C- | the extent such infringement is reasonably necessary to enable 
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
//C- | any greater extent that may be necessary to utilize further 
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------
// 
// $Id: DjVuToPS.cpp,v 1.27 2008/08/05 20:50:35 bpearlmutter Exp $
// $Name: release_3_5_23 $

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif

#include "DjVuToPS.h"
#include "IFFByteStream.h"
#include "BSByteStream.h"
#include "DjVuImage.h"
#include "DjVuText.h"
#include "DataPool.h"
#include "IW44Image.h"
#include "JB2Image.h"
#include "GBitmap.h"
#include "GPixmap.h"
#include "debug.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <math.h>
#ifdef UNIX
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#endif


#ifdef HAVE_NAMESPACES
namespace DJVU {
# ifdef NOT_DEFINED // Just to fool emacs c++ mode
}
#endif
#endif


static const size_t ps_string_size=15000;

// ***************************************************************************
// ****************************** Options ************************************
// ***************************************************************************

DjVuToPS::Options::
Options(void)
: format(PS), 
  level(2), 
  orientation(AUTO), 
  mode(COLOR), 
  zoom(0),
  color(true), 
  calibrate(true), 
  text(false),
  gamma((double)2.2), 
  copies(1), 
  frame(false),
  cropmarks(false),
  bookletmode(OFF),
  bookletmax(0),
  bookletalign(0),
  bookletfold(18),
  bookletxfold(200)
{}

void
DjVuToPS::Options::
set_format(Format xformat)
{
  if (xformat != EPS && xformat != PS)
    G_THROW(ERR_MSG("DjVuToPS.bad_format"));
  format=xformat;
}

void
DjVuToPS::Options::
set_level(int xlevel)
{
  if (xlevel<1 || xlevel>3)
    G_THROW(ERR_MSG("DjVuToPS.bad_level")
           + GUTF8String("\t") + GUTF8String(xlevel));
  level=xlevel;
}

void
DjVuToPS::Options::
set_orientation(Orientation xorientation)
{
  if (xorientation!=PORTRAIT && 
      xorientation!=LANDSCAPE &&
      xorientation!=AUTO )
    G_THROW(ERR_MSG("DjVuToPS.bad_orient"));
  orientation=xorientation;
}

void
DjVuToPS::Options::
set_mode(Mode xmode)
{
  if (xmode!=COLOR && xmode!=FORE && xmode!=BACK && xmode!=BW)
    G_THROW(ERR_MSG("DjVuToPS.bad_mode"));
  mode=xmode;
}

void
DjVuToPS::Options::
set_zoom(int xzoom)
{
  if (xzoom!=0 && !(xzoom>=5 && xzoom<=999))
    G_THROW(ERR_MSG("DjVuToPS.bad_zoom"));
  zoom=xzoom;
}

void
DjVuToPS::Options::
set_color(bool xcolor)
{
  color=xcolor;
}

void 
DjVuToPS::Options::
set_sRGB(bool xcalibrate)
{
  calibrate=xcalibrate;
}

void
DjVuToPS::Options::
set_gamma(double xgamma)
{
  if  (xgamma<(double)(0.3-0.0001) || xgamma>(double)(5.0+0.0001))
    G_THROW(ERR_MSG("DjVuToPS.bad_gamma"));
  gamma=xgamma;
}

void
DjVuToPS::Options::
set_copies(int xcopies)
{
  if (xcopies<=0)
    G_THROW(ERR_MSG("DjVuToPS.bad_number"));
  copies=xcopies;
}

void
DjVuToPS::Options::
set_frame(bool xframe)
{
  frame=xframe;
}

void
DjVuToPS::Options::
set_cropmarks(bool xmarks)
{
  cropmarks=xmarks;
}

void
DjVuToPS::Options::
set_text(bool xtext)
{
  text=xtext;
}

void 
DjVuToPS::Options::
set_bookletmode(BookletMode m)
{
  bookletmode = m;
}

void 
DjVuToPS::Options::
set_bookletmax(int m)
{
  bookletmax = 0;
  if (m > 0)
    bookletmax = (m+3)/4;
  bookletmax *= 4;
}

void 
DjVuToPS::Options::
set_bookletalign(int m)
{
  bookletalign = m;
}

void 
DjVuToPS::Options::
set_bookletfold(int fold, int xfold)
{
  if (fold >= 0)
    bookletfold = fold;
  if (xfold >= 0)
    bookletxfold = xfold;
}


// ***************************************************************************
// ******************************* DjVuToPS **********************************
// ***************************************************************************

static char bin2hex[256][2];

DjVuToPS::DjVuToPS(void)
{
  DEBUG_MSG("DjVuToPS::DjVuToPS(): initializing...\n");
  DEBUG_MAKE_INDENT(3);
  DEBUG_MSG("Initializing dig2hex[]\n");
  // Creating tables for bin=>text translation
  static const char * dig2hex="0123456789ABCDEF";
  int i;
  for(i=0;i<256;i++)
    {
      bin2hex[i][0]=dig2hex[i/16];
      bin2hex[i][1]=dig2hex[i%16];
    }
  refresh_cb=0;
  refresh_cl_data=0;
  prn_progress_cb=0;
  prn_progress_cl_data=0;
  dec_progress_cb=0;
  dec_progress_cl_data=0;
  info_cb=0;
  info_cl_data=0;
}

#ifdef __GNUC__
static void
write(ByteStream &str, const char *format, ...)
__attribute__((format (printf, 2, 3)));
#endif

static void
write(ByteStream &str, const char *format, ...)
{
  /* Will output the formated string to the specified \Ref{ByteStream}
     like #fprintf# would do it for a #FILE#. */
  va_list args;
  va_start(args, format);
  GUTF8String tmp;
  tmp.vformat(format, args);
  str.writall((const char *) tmp, tmp.length());
}

// ************************* DOCUMENT LEVEL *********************************

void
DjVuToPS::
store_doc_prolog(ByteStream &str, int pages, int dpi, GRect *grect)
{
  /* Will store the {\em document prolog}, which is basically a
     block of document-level comments in PS DSC 3.0 format.
     @param str Stream where PostScript data should be written
     @param pages Total number of pages
     @param dpi (EPS mode only) 
     @param grect (EPS mode only) */
  DEBUG_MSG("storing the document prolog\n");
  DEBUG_MAKE_INDENT(3);
  if (options.get_format()==Options::EPS)
    write(str,
          "%%!PS-Adobe-3.0 EPSF 3.0\n"
          "%%%%BoundingBox: 0 0 %d %d\n",
          (grect->width()*100+dpi-1)/dpi, 
          (grect->height()*100+dpi-1)/dpi );
  else
    write(str, "%%!PS-Adobe-3.0\n");
  write(str,
        "%%%%Title: DjVu PostScript document\n"
        "%%%%Copyright: Copyright (c) 1998-1999 AT&T\n"
        "%%%%Creator: DjVu (code by Andrei Erofeev)\n"
        "%%%%DocumentData: Clean7Bit\n");
  // Date
  time_t tm=time(0);
  write(str, "%%%%CreationDate: %s", ctime(&tm));
  // For
#ifdef UNIX
  passwd *pswd = getpwuid(getuid());
  if (pswd)
    {
      char *s = strchr(pswd->pw_gecos, ',');
      if (s) 
        *s = 0;
      s = 0;
      if (pswd->pw_gecos && strlen(pswd->pw_gecos))
        s = pswd->pw_gecos;
      else if (pswd->pw_name && strlen(pswd->pw_name))
        s = pswd->pw_name;
      if (s)
        write(str, "%%%%For: %s\n", s);
    }
#endif
  // Language
  write(str, "%%%%LanguageLevel: %d\n", options.get_level());
  if (options.get_level()<2 && options.get_color())
    write(str, "%%%%Extensions: CMYK\n");
  // Pages
  write(str, "%%%%Pages: %d\n",pages );
  write(str, "%%%%PageOrder: Ascend\n");
  // Orientation
  if (options.get_orientation() != Options::AUTO)
    write(str, "%%%%Orientation: %s\n", 
          options.get_orientation()==Options::PORTRAIT ?
          "Portrait" : "Landscape" );
  // Requirements
  if (options.get_format() == Options::PS)
    {
      write(str, "%%%%Requirements:");
      if (options.get_color())
        write(str, " color");
      if (options.get_copies()>1)
        write(str, " numcopies(%d)", options.get_copies());
      if (options.get_level()>=2)
        {
          if (options.get_copies()>1)
            write(str, " collate");
          if (options.get_bookletmode() == Options::RECTOVERSO)
            write(str, " duplex(tumble)");
        }
      write(str, "\n");
    }
  // End
  write(str,
        "%%%%EndComments\n"
        "%%%%EndProlog\n"
        "\n");
}

void
DjVuToPS::
store_doc_setup(ByteStream &str)
{
  /* Will store the {\em document setup}, which is a set of
     PostScript commands and functions used to inspect and prepare
     the PostScript interpreter environment before displaying images. */
  write(str, 
        "%%%%BeginSetup\n"
        "/doc-origstate save def\n");
  if (options.get_level()>=2)
    {
      if (options.get_format() == Options::PS)
        {
          if (options.get_copies()>1)
            write(str, 
                  "[{\n"
                  "%%%%BeginFeature: NumCopies %d\n"
                  "<< /NumCopies %d >> setpagedevice\n"
                  "%%%%EndFeature\n"
                  "} stopped cleartomark\n"
                  "[{\n"
                  "%%%%BeginFeature: Collate\n"
                  "<< /Collate true >> setpagedevice\n"
                  "%%%%EndFeature\n"
                  "} stopped cleartomark\n",
                  options.get_copies(),
                  options.get_copies() );
          if (options.get_bookletmode()==Options::RECTOVERSO)
            write(str, 
                  "[{\n"
                  "%%%%BeginFeature: Duplex DuplexTumble\n"
                  "<< /Duplex true /Tumble true >> setpagedevice\n"
                  "%%%%EndFeature\n"
                  "} stopped cleartomark\n");
        }
      if (options.get_color())
        write(str, 
              "%% -- procs for reading color image\n"
              "/readR () def\n"
              "/readG () def\n"
              "/readB () def\n"
              "/ReadData {\n"
              "   currentfile /ASCII85Decode filter dup\n"
              "   /RunLengthDecode filter\n"
              "   bufferR readstring pop /readR exch def\n"
              "   dup status { flushfile } { pop } ifelse\n"
              "   currentfile /ASCII85Decode filter dup\n"
              "   /RunLengthDecode filter\n"
              "   bufferG readstring pop /readG exch def\n"
              "   dup status { flushfile } { pop } ifelse\n"
              "   currentfile /ASCII85Decode filter dup\n"
              "   /RunLengthDecode filter\n"
              "   bufferB readstring pop /readB exch def\n"
              "   dup status { flushfile } { pop } ifelse\n"
              "} bind def\n"
              "/ReadR {\n"
              "   readR length 0 eq { ReadData } if\n"
              "   readR /readR () def\n"
              "} bind def\n"
              "/ReadG {\n"
              "   readG length 0 eq { ReadData } if\n"
              "   readG /readG () def\n"
              "} bind def\n"
              "/ReadB {\n"
              "   readB length 0 eq { ReadData } if\n"
              "   readB /readB () def\n"
              "} bind def\n");
      write(str,
            "%% -- procs for foreground layer\n"
            "/g {gsave 0 0 0 0 5 index 5 index setcachedevice\n"
            "    true [1 0 0 1 0 0] 5 4 roll imagemask grestore\n"
            "} bind def\n"
            "/gn {gsave 0 0 0 0 6 index 6 index setcachedevice\n"
            "  true [1 0 0 1 0 0] 3 2 roll 5 1 roll \n"
            "  { 1 sub 0 index 2 add 1 index  1 add roll\n"
            "  } imagemask grestore pop \n"
            "} bind def\n"
            "/c {setcolor rmoveto glyphshow} bind def\n"
            "/s {rmoveto glyphshow} bind def\n"
            "/S {rmoveto gsave show grestore} bind def\n" 
            "/F {(Helvetica) findfont exch scalefont setfont} bind def\n"
            "%% -- emulations\n"
            "systemdict /rectstroke known not {\n"
            "  /rectstroke  %% stack : x y width height \n"
            "  { newpath 4 2 roll moveto 1 index 0 rlineto\n"
            "    0 exch rlineto neg 0 rlineto closepath stroke\n"
            "  } bind def } if\n"
            "systemdict /rectclip known not {\n"
            "  /rectclip  %% stack : x y width height \n"
            "  { newpath 4 2 roll moveto 1 index 0 rlineto\n"
            "    0 exch rlineto neg 0 rlineto closepath clip\n"
            "  } bind def } if\n"
            "%% -- color space\n" );
      if (options.get_sRGB())
        write(str,
              "/DjVuColorSpace [ %s\n"
              "<< /DecodeLMN [ { dup 0.03928 le {\n"
              "       12.92321 div\n"
              "     } {\n"
              "       0.055 add 1.055 div 2.4 exp\n"
              "     } ifelse } bind dup dup ]\n"
              "   /MatrixLMN [\n"
              "      0.412457 0.212673 0.019334\n"
              "      0.357576 0.715152 0.119192\n"
              "      0.180437 0.072175 0.950301 ]\n"
              "   /WhitePoint [ 0.9505 1 1.0890 ] %% D65 \n"
              "   /BlackPoint[0 0 0] >> ] def\n",
              (options.get_color()) ? "/CIEBasedABC" : "/CIEBasedA" );
      else if (options.get_color())
        write(str,"/DjVuColorSpace /DeviceRGB def\n");
      else
        write(str,"/DjVuColorSpace /DeviceGray def\n");
    } 
  else 
    {
      // level<2
      if (options.get_format() == Options::PS)
        if (options.get_copies() > 1)
          write(str,"/#copies %d def\n", options.get_copies());
      if (options.get_color())
        write(str, 
              "%% -- buffers for reading image\n"
              "/buffer8 () def\n"
              "/buffer24 () def\n"
              "%% -- colorimage emulation\n"
              "systemdict /colorimage known {\n"
              "   /ColorProc {\n"
              "      currentfile buffer24 readhexstring pop\n"
              "   } bind def\n"
              "   /ColorImage {\n"
              "      colorimage\n"
              "   } bind def\n"
              "} {\n"
              "   /ColorProc {\n"
              "      currentfile buffer24 readhexstring pop\n"
              "      /data exch def /datalen data length def\n"
              "      /cnt 0 def\n"
              "      0 1 datalen 3 idiv 1 sub {\n"
              "         buffer8 exch\n"
              "                data cnt get 20 mul /cnt cnt 1 add def\n"
              "                data cnt get 32 mul /cnt cnt 1 add def\n"
              "                data cnt get 12 mul /cnt cnt 1 add def\n"
              "                add add 64 idiv put\n"
              "      } for\n"
              "      buffer8 0 datalen 3 idiv getinterval\n"
              "   } bind def\n"
              "   /ColorImage {\n"
              "      pop pop image\n"
              "   } bind def\n"
              "} ifelse\n");
    } // level<2
  write(str, "%%%%EndSetup\n\n");
}

void
DjVuToPS::
store_doc_trailer(ByteStream &str)
{
  /* Will store the {\em document trailer}, which is a clean-up code
     used to return the PostScript interpeter back to the state, in which
     it was before displaying this document. */
  write(str, 
        "%%%%Trailer\n"
        "doc-origstate restore\n"
        "%%%%EOF\n");
}

// ***********************************************************************
// ***************************** PAGE LEVEL ******************************
// ***********************************************************************

static unsigned char *
ASCII85_encode(unsigned char * dst, 
               const unsigned char * src_start,
               const unsigned char * src_end)
{
  /* Will read data between #src_start# and #src_end# pointers (excluding byte
     pointed by #src_end#), encode it using {\bf ASCII85} algorithm, and
     output the result into the destination buffer pointed by #dst#.  The
     function returns pointer to the first unused byte in the destination
     buffer. */
  int symbols=0;
  const unsigned char * ptr;
  for(ptr=src_start;ptr<src_end;ptr+=4)
    {
      unsigned int num=0;
      if (ptr+3<src_end)
        {
          num |= ptr[0] << 24; 
          num |= ptr[1] << 16; 
          num |= ptr[2] << 8; 
          num |= ptr[3];
        }
      else
        {
          num |= ptr[0] << 24; 
          if (ptr+1<src_end) 
            num |= ptr[1] << 16; 
          if (ptr+2<src_end) 
            num |= ptr[2] << 8; 
        }
      int a1, a2, a3, a4, a5;
      a5=num % 85; num/=85;
      a4=num % 85; num/=85;
      a3=num % 85; num/=85;
      a2=num % 85;
      a1=num / 85;
      *dst++ = a1+33;
      *dst++ = a2+33;
      if (ptr+1<src_end)
        *dst++ = a3+33;
      if (ptr+2<src_end)
        *dst++ = a4+33;
      if (ptr+3<src_end)
        *dst++ = a5+33;
      symbols += 5;
      if (symbols > 70 && ptr+4<src_end)
        { 
          *dst++='\n'; 
          symbols=0; 
        }
    }
  return dst;
}

static unsigned char *
RLE_encode(unsigned char * dst,
           const unsigned char * src_start,
           const unsigned char * src_end)
{
  /* Will read data between #src_start# and #src_end# pointers (excluding byte
     pointed by #src_end#), RLE encode it, and output the result into the
     destination buffer pointed by #dst#.  #counter# is used to count the
     number of output bytes.  The function returns pointer to the first unused
     byte in the destination buffer. */
  const unsigned char * ptr;
  for(ptr=src_start;ptr<src_end;ptr++)
    {
      if (ptr==src_end-1)
        {
          *dst++=0; *dst++=*ptr;
        } 
      else if (ptr[0]!=ptr[1])
        {
          // Guess how many non repeating bytes we have
          const unsigned char * ptr1;
          for(ptr1=ptr+1;ptr1<src_end-1;ptr1++)
            if (ptr1[0]==ptr1[1] || ptr1-ptr>=128) break;
          int pixels=ptr1-ptr;
          *dst++=pixels-1;
          for(int cnt=0;cnt<pixels;cnt++)
            *dst++=*ptr++;
          ptr--;
        } 
      else
        {
          // Get the number of repeating bytes
          const unsigned char * ptr1;
          for(ptr1=ptr+1;ptr1<src_end-1;ptr1++)
            if (ptr1[0]!=ptr1[1] || ptr1-ptr+1>=128) break;
          int pixels=ptr1-ptr+1;
          *dst++=257-pixels;
          *dst++=*ptr;
          ptr=ptr1;
        }
    }
  return dst;
}

#define GRAY(r,g,b) (((r)*20+(g)*32+(b)*12)/64)

void
DjVuToPS::
store_page_setup(ByteStream &str, 
                 int dpi, 
                 const GRect &grect, 
                 int align )
{
  /* Will store PostScript code necessary to prepare page for
     the coming \Ref{DjVuImage}. This is basically a scaling
     code plus initialization of some buffers. */
  if (options.get_format() == Options::EPS)
    write(str, 
          "/page-origstate save def\n"
          "%% -- coordinate system\n"
          "/image-dpi %d def\n"
          "/image-x 0 def\n"
          "/image-y 0 def\n"
          "/image-width  %d def\n"
          "/image-height %d def\n"
          "/coeff 100 image-dpi div def\n"
          "/a11 coeff def\n"
          "/a12 0 def\n"
          "/a13 0 def\n"
          "/a21 0 def\n"
          "/a22 coeff def\n"
          "/a23 0 def\n"
          "[a11 a21 a12 a22 a13 a23] concat\n"
          "gsave 0 0 image-width image-height rectclip\n"
          "%% -- begin printing\n",
          dpi, grect.width(), grect.height() );
  else
    {
      int margin = 0;
      const char *xauto = "false";
      const char *xportrait = "false";
      const char *xfit = "false";
      if (options.get_orientation()==Options::AUTO)
        xauto = "true";
      if (options.get_orientation()==Options::PORTRAIT)
        xportrait = "true";
      if (options.get_zoom()<=0)
        xfit = "true";
      if (options.get_cropmarks())
        margin = 36;
      else if (options.get_frame())
        margin = 6;
      write(str, 
            "/page-origstate save def\n"
            "%% -- coordinate system\n"
            "/auto-orient %s def\n"
            "/portrait %s def\n"
            "/fit-page %s def\n"
            "/zoom %d def\n"
            "/image-dpi %d def\n"
            "clippath pathbbox newpath\n"
            "2 index sub exch 3 index sub\n"
            "/page-width exch def\n"
            "/page-height exch def\n"
            "/page-y exch def\n"
            "/page-x exch def\n"
            "/image-x 0 def\n"
            "/image-y 0 def\n"
            "/image-width  %d def\n"
            "/image-height %d def\n"
            "/margin %d def\n"
            "/halign %d def\n"
            "/valign 0 def\n",
            xauto, xportrait, xfit, options.get_zoom(), 
            dpi, grect.width(), grect.height(),
            margin, align );
      write(str, 
            "%% -- position page\n"
            "auto-orient {\n"
            "  image-height image-width sub\n"
            "  page-height page-width sub\n"
            "  mul 0 ge /portrait exch def\n" 
            "} if\n"
            "fit-page {\n"
            "  /page-width page-width margin sub\n"
            "     halign 0 eq { margin sub } if def\n"
            "  /page-height page-height margin sub\n"
            "     valign 0 eq { margin sub } if def\n"
            "  /page-x page-x halign 0 ge { margin add } if def\n"
            "  /page-y page-y valign 0 ge { margin add } if def\n"
            "} if\n"
            "portrait {\n"
            "  fit-page {\n"
            "    image-height page-height div\n"
            "    image-width page-width div\n"
            "    gt {\n"
            "      page-height image-height div /coeff exch def\n"
            "    } {\n"
            "      page-width image-width div /coeff exch def\n"
            "    } ifelse\n"
            "  } {\n"
            "    /coeff 72 image-dpi div zoom mul 100 div def\n"
            "  } ifelse\n"
            "  /start-x page-x page-width image-width\n"
            "    coeff mul sub 2 div halign 1 add mul add def\n"
            "  /start-y page-y page-height image-height\n"
            "    coeff mul sub 2 div valign 1 add mul add def\n"
            "  /a11 coeff def\n"
            "  /a12 0 def\n"
            "  /a13 start-x def\n"
            "  /a21 0 def\n"
            "  /a22 coeff def\n"
            "  /a23 start-y def\n"
            "} { %% landscape\n"
            "  fit-page {\n"
            "    image-height page-width div\n"
            "    image-width page-height div\n"
            "    gt {\n"
            "      page-width image-height div /coeff exch def\n"
            "    } {\n"
            "      page-height image-width div /coeff exch def\n"
            "    } ifelse\n"
            "  } {\n"
            "    /coeff 72 image-dpi div zoom mul 100 div def\n"
            "  } ifelse\n"
            "  /start-x page-x page-width add page-width image-height\n"
            "    coeff mul sub 2 div valign 1 add mul sub def\n"
            "  /start-y page-y page-height image-width\n"
            "    coeff mul sub 2 div halign 1 add mul add def\n"
            "  /a11 0 def\n"
            "  /a12 coeff neg def\n"
            "  /a13 start-x image-y coeff neg mul sub def\n"
            "  /a21 coeff def\n"
            "  /a22 0 def\n"
            "  /a23 start-y image-x coeff mul add def \n"
            "} ifelse\n"
            "[a11 a21 a12 a22 a13 a23] concat\n"
            "gsave 0 0 image-width image-height rectclip\n"
            "%% -- begin print\n");
    }
}

void
DjVuToPS::
store_page_trailer(ByteStream &str)
{
  write(str, 
        "%% -- end print\n" 
        "grestore\n");
  if (options.get_frame())
    write(str, 
          "%% Drawing frame\n"
          "gsave 0.7 setgray 0.5 coeff div setlinewidth 0 0\n"
          "image-width image-height rectstroke\n"
          "grestore\n");
  if (options.get_cropmarks() &&
      options.get_format() != Options::EPS )
    write(str,
          "%% Drawing crop marks\n"
          "/cm { gsave translate rotate 1 coeff div dup scale\n"
          "      0 setgray 0.5 setlinewidth -36 0 moveto 0 0 lineto\n"
          "      0 -36 lineto stroke grestore } bind def\n"
          "0 0 0 cm 180 image-width image-height cm\n"
          "90 image-width 0 cm 270 0 image-height cm\n");
  write(str,
        "page-origstate restore\n");
}

static int
compute_red(int w, int h, int rw, int rh)
{
  for (int red=1; red<16; red++)
    if (((w+red-1)/red==rw) && ((h+red-1)/red==rh))
      return red;
  return 16;
}

static int
get_bg_red(GP<DjVuImage> dimg) 
{
  GP<GPixmap> pm = 0;
  // Access image size
  int width = dimg->get_width();
  int height = dimg->get_height();
  if (width<=0 || height<=0) return 0;
  // CASE1: Incremental BG IW44Image
  GP<IW44Image> bg44 = dimg->get_bg44();
  if (bg44)
    {
      int w = bg44->get_width();
      int h = bg44->get_height();
      // Avoid silly cases
      if (w==0 || h==0 || width==0 || height==0)
        return 0;
      return compute_red(width,height,w,h);
    }
  // CASE 2: Raw background pixmap
  GP<GPixmap>  bgpm = dimg->get_bgpm();
  if (bgpm)
    {
      int w = bgpm->columns();
      int h = bgpm->rows();
      // Avoid silly cases
      if (w==0 || h==0 || width==0 || height==0)
        return 0;
      return compute_red(width,height,w,h);
    }
  return 0;
}

static GP<GPixmap>
get_bg_pixmap(GP<DjVuImage> dimg, const GRect &rect)
{
  GP<GPixmap> pm = 0;
  // Access image size
  int width = dimg->get_width();
  int height = dimg->get_height();
  GP<DjVuInfo> info = dimg->get_info();
  if (width<=0 || height<=0 || !info) return 0;
  // CASE1: Incremental BG IW44Image
  GP<IW44Image> bg44 = dimg->get_bg44();
  if (bg44)
    {
      int w = bg44->get_width();
      int h = bg44->get_height();
      // Avoid silly cases
      if (w==0 || h==0 || width==0 || height==0)
        return 0;
      pm = bg44->get_pixmap(1,rect);
      return pm;
    }
  // CASE 2: Raw background pixmap
  GP<GPixmap>  bgpm = dimg->get_bgpm();
  if (bgpm)
    {
      int w = bgpm->columns();
      int h = bgpm->rows();
      // Avoid silly cases
      if (w==0 || h==0 || width==0 || height==0)
        return 0;
      pm->init(*bgpm, rect);
      return pm;
    }
  // FAILURE
  return 0;
}

void 
DjVuToPS::
make_gamma_ramp(GP<DjVuImage> dimg)
{
  double targetgamma = options.get_gamma();
  double whitepoint = (options.get_sRGB() ? 255 : 280);
  for (int i=0; i<256; i++)
    ramp[i] = i;
  if (! dimg->get_info()) 
    return;
  if (targetgamma < 0.1)
    return;
  double filegamma = dimg->get_info()->gamma;
  double correction = filegamma / targetgamma;
  if (correction<0.1 || correction>10)
    return;
  {
    for (int i=0; i<256; i++)
    {
      double x = (double)(i)/255.0;
      if (correction != 1.0) 
        x = pow(x, correction);        
      int j = (int) floor(whitepoint * x + 0.5);
      ramp[i] = (j>255) ? 255 : (j<0) ? 0 : j;
    }
  }
}

void
DjVuToPS::
print_fg_2layer(ByteStream &str, 
                GP<DjVuImage> dimg,
                const GRect &prn_rect, 
                unsigned char *blit_list)
{
  // Pure-jb2 or color-jb2 case.
  GPixel p;
  int currentx=0;
  int currenty=0;
  GP<DjVuPalette> pal = dimg->get_fgbc();
  GP<JB2Image> jb2 = dimg->get_fgjb();
  if (! jb2) return;
  int num_blits = jb2->get_blit_count();
  int current_blit;
  for(current_blit=0; current_blit<num_blits; current_blit++)
    {
      if (blit_list[current_blit])
        {
          JB2Blit *blit = jb2->get_blit(current_blit);
          if ((pal) && !(options.get_mode()==Options::BW))
            {
              pal->index_to_color(pal->colordata[current_blit], p);
              if (options.get_color())
                {
                  write(str,"/%d %d %d %f %f %f c\n",
                        blit->shapeno, 
                        blit->left-currentx, blit->bottom-currenty,
                        ramp[p.r]/255.0, ramp[p.g]/255.0, ramp[p.b]/255.0);
                } 
              else
                {
                  write(str,"/%d %d %d %f c\n",
                        blit->shapeno, 
                        blit->left-currentx, blit->bottom-currenty,
                        ramp[GRAY(p.r, p.g, p.b)]/255.0);
                }
            }
          else
            {
              write(str,"/%d %d %d s\n", 
                    blit->shapeno, 
                    blit->left-currentx, blit->bottom-currenty);
            }
          currentx = blit->left;
          currenty = blit->bottom;
        }
    }
}

void
DjVuToPS::
print_fg_3layer(ByteStream &str, 
                GP<DjVuImage> dimg,
                const GRect &cprn_rect, 
                unsigned char *blit_list )
{
  GRect prn_rect;
  GP<GPixmap> brush = dimg->get_fgpm();
  if (! brush) return;
  int br = brush->rows();
  int bc = brush->columns();
  int red = compute_red(dimg->get_width(),dimg->get_height(),bc,br);
  prn_rect.ymin = (cprn_rect.ymin)/red;
  prn_rect.xmin = (cprn_rect.xmin)/red;
  prn_rect.ymax = (cprn_rect.ymax+red-1)/red;
  prn_rect.xmax = (cprn_rect.xmax+red-1)/red;
  int color_nb = ((options.get_color()) ? 3 : 1);
  GP<JB2Image> jb2 = dimg->get_fgjb();
  if (! jb2) return;
  int pw = bc;
  int ph = 2;

  write(str,
        "/P {\n" 
        "  11 dict dup begin 4 1 roll\n"
        "    /PatternType 1 def\n"
        "    /PaintType 1 def\n"
        "    /TilingType 1 def\n"
        "    /H exch def\n"
        "    /W exch def\n"
        "    /Red %d def\n"
        "    /PatternString exch def\n"
        "    /XStep W Red mul def\n"
        "    /YStep H Red mul def\n"
        "    /BBox [0 0 XStep YStep] def\n"
        "    /PaintProc { begin\n"
        "       Red dup scale\n"
        "       << /ImageType 1 /Width W /Height H\n"
        "          /BitsPerComponent 8 /Interpolate false\n"
        "          /Decode [%s] /ImageMatrix [1 0 0 1 0 0]\n"
        "          /DataSource PatternString >> image\n"
        "       end } bind def\n"
        "     0 0 XStep YStep rectclip\n"
        "     end matrix makepattern\n"
        "  /Pattern setcolorspace setpattern\n"
        "  0 0 moveto\n"
        "} def\n", red, (color_nb == 1) ? "0 1" : "0 1 0 1 0 1" );

  unsigned char *s;
  GPBuffer<unsigned char> gs(s,pw*ph*color_nb);
  unsigned char *s_ascii_encoded;
  GPBuffer<unsigned char> gs_ascii_encoded(s_ascii_encoded,pw*ph*2*color_nb);
    {
      for (int y=prn_rect.ymin; y<prn_rect.ymax; y+=ph)
        for (int x=prn_rect.xmin; x<prn_rect.xmax; x+=pw)
          {
            int w = ((x+pw > prn_rect.xmax) ? prn_rect.xmax-x : pw);
            int h = ((y+ph > prn_rect.ymax) ? prn_rect.ymax-y : ph);
            int currentx = x * red;
            int currenty = y * red;
            // Find first intersecting blit
            int current_blit;
            int num_blits = jb2->get_blit_count();
            GRect rect1(currentx,currenty, w*red, h*red);
            for(current_blit=0; current_blit<num_blits; current_blit++)
              if (blit_list[current_blit])
                {
                  JB2Blit *blit = jb2->get_blit(current_blit);
                  GRect rect2(blit->left, blit->bottom,
                              jb2->get_shape(blit->shapeno).bits->columns(),
                              jb2->get_shape(blit->shapeno).bits->rows());
                  if (rect2.intersect(rect1,rect2)) 
                    break;
                }
            if (current_blit >= num_blits)
              continue;
            // Setup pattern
            write(str,"gsave %d %d translate\n", currentx, currenty);
            write(str,"<~");
            unsigned char *q = s;
            for(int current_row = y; current_row<y+h; current_row++)
              { 
                GPixel *row_pix = (*brush)[current_row];
                for(int current_col = x; current_col<x+w; current_col++)
                  { 
                    GPixel &p = row_pix[current_col];
                    if (color_nb>1)
                      {
                        *q++ = ramp[p.r];
                        *q++ = ramp[p.g];
                        *q++ = ramp[p.b];
                      }
                    else
                      {
                        *q++ = ramp[GRAY(p.r,p.g,p.b)];
                      }
                  }
              }
            unsigned char *stop_ascii = 
              ASCII85_encode(s_ascii_encoded,s,s+w*h*color_nb);
            *stop_ascii++='\0';
            write(str,"%s",s_ascii_encoded);
            write(str,"~> %d %d P\n", w, h);
            // Keep performing blits
            for(; current_blit<num_blits; current_blit++)
              if (blit_list[current_blit])
                {
                  JB2Blit *blit = jb2->get_blit(current_blit);
                  GRect rect2(blit->left, blit->bottom,
                              jb2->get_shape(blit->shapeno).bits->columns(),
                              jb2->get_shape(blit->shapeno).bits->rows()); 
                  if (rect2.intersect(rect1,rect2)) 
                    {   
                      write(str,"/%d %d %d s\n",
                            blit->shapeno, 
                            blit->left-currentx, blit->bottom-currenty);
                      currentx = blit->left;
                      currenty = blit->bottom;
                    }
                }
            write(str,"grestore\n");
          }
      // Cleanup
    }
}

void
DjVuToPS::
print_fg(ByteStream &str, 
         GP<DjVuImage> dimg,
         const GRect &prn_rect )
{
  GP<JB2Image> jb2=dimg->get_fgjb();
  if (! jb2) return;
  int num_blits = jb2->get_blit_count();
  int num_shapes = jb2->get_shape_count();
  unsigned char *dict_shapes = 0;
  unsigned char *blit_list = 0;
  GPBuffer<unsigned char> gdict_shapes(dict_shapes,num_shapes);
  GPBuffer<unsigned char> gblit_list(blit_list,num_blits);
  for(int i=0; i<num_shapes; i++)
  {
    dict_shapes[i]=0;
  }
  for(int current_blit=0; current_blit<num_blits; current_blit++)
  {
    JB2Blit *blit = jb2->get_blit(current_blit);
    JB2Shape *shape = & jb2->get_shape(blit->shapeno);
    blit_list[current_blit] = 0;
    if (! shape->bits) 
      continue;
    GRect rect2(blit->left, blit->bottom, 
      shape->bits->columns(), shape->bits->rows());
    if (rect2.intersect(rect2, prn_rect))
    {
      dict_shapes[blit->shapeno] = 1;
      blit_list[current_blit] = 1;
    }
  }
  write(str,
    "%% --- now doing the foreground\n"
    "gsave DjVuColorSpace setcolorspace\n" );
      // Define font
  write(str,
    "/$DjVuLocalFont 7 dict def\n"
    "$DjVuLocalFont begin\n"
    "/FontType 3 def \n"
    "/FontMatrix [1 0 0 1 0 0] def\n"
    "/FontBBox [0 0 1 .5] def\n"
    "/CharStrings %d dict def\n"
    "/Encoding 2 array def\n"
    "0 1 1 {Encoding exch /.notdef put} for \n"
    "CharStrings begin\n"
    "/.notdef {} def\n",
    num_shapes+1);
  for(int current_shape=0; current_shape<num_shapes; current_shape++)
  {
    if (dict_shapes[current_shape])
    {
      JB2Shape *shape = & jb2->get_shape(current_shape);
      GP<GBitmap> bitmap = shape->bits;
      int rows = bitmap->rows();
      int columns = bitmap->columns();
      int nbytes = (columns+7)/8*rows+1;
      int nrows = rows;
      int nstrings=0;
      if (nbytes>(int)ps_string_size)   //max string length
      {
        nrows=ps_string_size/((columns+7)/8);
        nbytes=(columns+7)/8*nrows+1;
      }
      unsigned char *s_start;
      GPBuffer<unsigned char> gs_start(s_start,nbytes);
      unsigned char *s_ascii;
      GPBuffer<unsigned char> gs_ascii(s_ascii,nbytes*2);
      write(str,"/%d {",current_shape);

      unsigned char *s = s_start;
      for(int current_row=0; current_row<rows; current_row++)
      {  
        unsigned char * row_bits = (*bitmap)[current_row];
        unsigned char acc = 0;
        unsigned char mask = 0;
        for(int current_col=0; current_col<columns; current_col++)
        {
          if (mask == 0)
            mask = 0x80;
          if (row_bits[current_col])
            acc |= mask;
          mask >>= 1;
          if (mask == 0)
          {
            *s=acc;
            s++;
            acc = mask = 0;
          }
        }
        if (mask != 0)
        {
          *s=acc;
          s++;
        }
        if (!((current_row+1)%nrows))
        {
          unsigned char *stop_ascii = ASCII85_encode(s_ascii,s_start,s); 
          *stop_ascii++='\0';
          write(str,"<~%s~> ",s_ascii);
          s=s_start;
          nstrings++;
        }
      }
      if (s!=s_start)
      {
        unsigned char *stop_ascii = ASCII85_encode(s_ascii,s_start,s);
        *stop_ascii++='\0';
        write(str,"<~%s~> ",s_ascii);
          nstrings++;
      }
      if (nstrings==1)
        write(str," %d %d g} def\n", columns, rows);                  
      else
        write(str," %d %d %d gn} def\n", columns, rows,nstrings);
    }
  }
  write(str, 
    "end\n"
    "/BuildGlyph {\n"
    "  exch /CharStrings get exch\n"
    "  2 copy known not\n"
    "  {pop /.notdef} if\n"
    "  get exec \n"
    "} bind def\n"
    "end\n"
    "/LocalDjVuFont $DjVuLocalFont definefont pop\n"
    "/LocalDjVuFont findfont setfont\n" );
  write(str,
    "-%d -%d translate\n"
    "0 0 moveto\n",
    prn_rect.xmin, prn_rect.ymin);
  // Print the foreground layer
  if (dimg->get_fgpm() && !(options.get_mode()==Options::BW)) 
    print_fg_3layer(str, dimg, prn_rect, blit_list);
  else
    print_fg_2layer(str, dimg, prn_rect, blit_list);        
  write(str, "/LocalDjVuFont undefinefont grestore\n");
}


void 
DjVuToPS::
print_bg(ByteStream &str, 
         GP<DjVuImage> dimg,
         const GRect &cprn_rect)
{
  GP<GPixmap> pm;
  GRect prn_rect;
  double print_done = 0;
  int red = 0;
  write(str, "%% --- now doing the background\n");
  if (! (red = get_bg_red(dimg)))
    return;
  write(str, 
        "gsave -%d -%d translate\n"
        "/bgred %d def bgred bgred scale\n",
        cprn_rect.xmin % red, 
        cprn_rect.ymin % red, 
        red);
  prn_rect.ymin = (cprn_rect.ymin)/red;
  prn_rect.ymax = (cprn_rect.ymax+red-1)/red;
  prn_rect.xmin = (cprn_rect.xmin)/red;
  prn_rect.xmax = (cprn_rect.xmax+red-1)/red;
  // Display image
  int band_bytes = 125000;
  int band_height = band_bytes/prn_rect.width();
  int buffer_size = band_height*prn_rect.width();
  int ps_chunk_height = 30960/prn_rect.width()+1;
  buffer_size = buffer_size*23/10;
  bool do_color = options.get_color();
  if ((!dimg->is_legal_photo() &&
       !dimg->is_legal_compound())
      || options.get_mode()==Options::BW)
    do_color = false;
  if (do_color) 
    buffer_size *= 3;
  if (do_color)
    write(str, 
          "/bufferR %d string def\n"
          "/bufferG %d string def\n"
          "/bufferB %d string def\n"
          "DjVuColorSpace setcolorspace\n"
          "<< /ImageType 1\n"
          "   /Width %d\n"
          "   /Height %d\n"
          "   /BitsPerComponent 8\n"
          "   /Decode [0 1 0 1 0 1]\n"
          "   /ImageMatrix [1 0 0 1 0 0]\n"
          "   /MultipleDataSources true\n"
          "   /DataSource [ { ReadR } { ReadG } { ReadB } ]\n"
          "   /Interpolate false >> image\n",
          ps_chunk_height*prn_rect.width(),
          ps_chunk_height*prn_rect.width(),
          ps_chunk_height*prn_rect.width(),
          prn_rect.width(), prn_rect.height());
  else
    write(str, 
          "DjVuColorSpace setcolorspace\n"
          "<< /ImageType 1\n"
          "   /Width %d\n"
          "   /Height %d\n"
          "   /BitsPerComponent 8\n"
          "   /Decode [0 1]\n"
          "   /ImageMatrix [1 0 0 1 0 0]\n"
          "   /DataSource currentfile /ASCII85Decode\n"
          "      filter /RunLengthDecode filter\n"
          "   /Interpolate false >> image\n",
          prn_rect.width(), prn_rect.height());
  
  unsigned char *buffer;
  GPBuffer<unsigned char> gbuffer(buffer,buffer_size);
  unsigned char *rle_in;
  GPBuffer<unsigned char> grle_in(rle_in,ps_chunk_height*prn_rect.width());
  unsigned char *rle_out;
  GPBuffer<unsigned char> grle_out(rle_out,2*ps_chunk_height*prn_rect.width());
  {
    // Start storing image in bands
    unsigned char * rle_out_end = rle_out;
    GRect grectBand = prn_rect;
    grectBand.ymax = grectBand.ymin;
    while(grectBand.ymax < prn_rect.ymax)
      {
        GP<GPixmap> pm = 0;
        // Compute next band
        grectBand.ymin=grectBand.ymax;
        grectBand.ymax=grectBand.ymin+band_bytes/grectBand.width();
        if (grectBand.ymax>prn_rect.ymax)
          grectBand.ymax=prn_rect.ymax;
        pm = get_bg_pixmap(dimg, grectBand);
        unsigned char *buf_ptr = buffer;
        if (pm)
          {
            if (do_color)
              {
                int y=0;
                while(y<grectBand.height())
                  {
                    int row, y1;
                    unsigned char *ptr, *ptr1;
                    // Doing R component of current chunk
                    for (row=0,ptr=rle_in,y1=y; 
                         row<ps_chunk_height && y1<grectBand.height(); 
                         row++,y1++)
                      {
                        GPixel *pix = (*pm)[y1];
                        for (int x=grectBand.width(); x>0; x--,pix++)
                          *ptr++ = ramp[pix->r];
                      }
                    ptr1 = RLE_encode(rle_out, rle_in, ptr); 
                    *ptr1++ = 0x80;
                    buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                    *buf_ptr++ = '~'; *buf_ptr++ = '>'; *buf_ptr++ = '\n';
                    // Doing G component of current chunk
                    for (row=0,ptr=rle_in,y1=y; 
                         row<ps_chunk_height && y1<grectBand.height(); 
                         row++,y1++)
                      {
                        GPixel *pix = (*pm)[y1];
                        for (int x=grectBand.width(); x>0; x--,pix++)
                          *ptr++ = ramp[pix->g];
                      }
                    ptr1 = RLE_encode(rle_out, rle_in, ptr); 
                    *ptr1++ = 0x80;
                    buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                    *buf_ptr++ = '~'; 
                    *buf_ptr++ = '>'; 
                    *buf_ptr++ = '\n';
                    // Doing B component of current chunk
                    for (row=0, ptr=rle_in, y1=y;
                         row<ps_chunk_height && y1<grectBand.height(); 
                         row++,y1++)
                      {
                        GPixel *pix = (*pm)[y1];
                        for (int x=grectBand.width(); x>0; x--,pix++)
                          *ptr++ = ramp[pix->b];
                      }
                    ptr1 = RLE_encode(rle_out, rle_in, ptr);
                    *ptr1++ = 0x80;
                    buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                    *buf_ptr++ = '~'; 
                    *buf_ptr++ = '>'; 
                    *buf_ptr++ = '\n';
                    y=y1;
                    if (refresh_cb) 
                      refresh_cb(refresh_cl_data);
                  } //while (y>=0)
              } 
            else
              {
                // Don't use color
                int y=0;
                while(y<grectBand.height())
                  {
                    unsigned char *ptr = rle_in;
                    for(int row=0; 
                        row<ps_chunk_height && y<grectBand.height(); 
                        row++,y++)
                      {
                        GPixel *pix = (*pm)[y];
                        for (int x=grectBand.width(); x>0; x--,pix++)
                          *ptr++ = ramp[GRAY(pix->r,pix->g,pix->b)];
                      }
                    rle_out_end = RLE_encode(rle_out_end, rle_in, ptr);
                    unsigned char *encode_to 
                      = rle_out+(rle_out_end-rle_out)/4*4;
                    int bytes_left = rle_out_end-encode_to;
                    buf_ptr = ASCII85_encode(buf_ptr, rle_out, encode_to);
                    *buf_ptr++ = '\n';
                    memcpy(rle_out, encode_to, bytes_left);
                    rle_out_end = rle_out+bytes_left;
                    if (refresh_cb) 
                      refresh_cb(refresh_cl_data);
                  }
              }
          } // if (pm)
        str.writall(buffer, buf_ptr-buffer);
        if (prn_progress_cb)
          {
            double done=(double)(grectBand.ymax 
                                 - prn_rect.ymin)/prn_rect.height();
            if ((int) (20*print_done)!=(int) (20*done))
              {
                print_done=done;
                prn_progress_cb(done, prn_progress_cl_data);
              }
          }
      } // while(grectBand.yax<grect.ymax)
    if (! do_color)
      {
        unsigned char * buf_ptr = buffer;
        *rle_out_end++ = 0x80;
        buf_ptr = ASCII85_encode(buf_ptr, rle_out, rle_out_end);
        *buf_ptr++='~'; 
        *buf_ptr++='>'; 
        *buf_ptr++='\n';
        str.writall(buffer, buf_ptr-buffer);
      }
  } 
  //restore the scaling
  write(str, "grestore\n");
}

void
DjVuToPS::
print_image_lev1(ByteStream &str, 
                 GP<DjVuImage> dimg,
                 const GRect &prn_rect)
{         
  double print_done=0;
  GRect all(0,0, dimg->get_width(),dimg->get_height());
  GP<GPixmap> pm;
  GP<GBitmap> bm;
  GRect test(0,0,1,1);
  if (options.get_mode() == Options::FORE)
    pm = dimg->get_fg_pixmap(test, all);
  else if (options.get_mode() == Options::BACK)
    pm = dimg->get_bg_pixmap(test, all);
  else if (options.get_mode() != Options::BW)
    pm = dimg->get_pixmap(test, all);
  if (! pm)
    bm = dimg->get_bitmap(test,all);
  if (! pm && ! bm)
    return;
  write(str,
        "%% --- now doing a level 1 image\n"
        "gsave\n");
  // Display image
  int band_bytes=125000;
  int band_height = band_bytes/prn_rect.width();
  int buffer_size = band_height*prn_rect.width();
  buffer_size = buffer_size*21/10;
  bool do_color = false;
  bool do_color_or_gray = false;
  if (pm && (options.get_mode() != Options::BW))
    do_color_or_gray = true;
  if (do_color_or_gray && options.get_color())
    do_color = true;
  if (do_color) 
    buffer_size *= 3;
  if (do_color)
    write(str, "/buffer24 %d string def\n", 3*prn_rect.width());
  if (do_color_or_gray)
    write(str, "/buffer8 %d string def\n", prn_rect.width());
  else
    write(str, "/buffer8 %d string def\n", (prn_rect.width()+7)/8);
  if (do_color)
    {
      write(str,
            "%d %d 8 [ 1 0 0 1 0 0 ]\n"
            "{ ColorProc } false 3 ColorImage\n",
            prn_rect.width(), prn_rect.height());
    } 
  else if (do_color_or_gray)
    {
      write(str,
            "%d %d 8 [ 1 0 0 1 0 0 ]\n"
            "{ currentfile buffer8 readhexstring pop } image\n",
            prn_rect.width(), prn_rect.height());
    } 
  else
    {
      write(str,
            "%d %d 1 [ 1 0 0 1 0 0 ]\n"
            "{ currentfile buffer8 readhexstring pop } image\n",
            prn_rect.width(), prn_rect.height());
    }
  unsigned char * buffer;
  GPBuffer<unsigned char> gbuffer(buffer,buffer_size);
    {
      // Start storing image in bands
      GRect grectBand = prn_rect;
      grectBand.ymax = grectBand.ymin;
      while(grectBand.ymax < prn_rect.ymax)
        {
          // Compute next band
          grectBand.ymin = grectBand.ymax;
          grectBand.ymax = grectBand.ymin+band_bytes/grectBand.width();
          if (grectBand.ymax > prn_rect.ymax)
            grectBand.ymax = prn_rect.ymax;
          GRect all(0,0, dimg->get_width(),dimg->get_height());
          pm = 0;
          bm = 0;
          if (do_color_or_gray)
            {
              if (options.get_mode() == Options::FORE)
                pm = dimg->get_fg_pixmap(grectBand, all);
              else if (options.get_mode() == Options::BACK)
                pm = dimg->get_bg_pixmap(grectBand, all);
              else
                pm = dimg->get_pixmap(grectBand, all);
            }
          else 
            {
              bm = dimg->get_bitmap(grectBand, all);
            }
          // Store next band
          unsigned char *buf_ptr = buffer;
          int symbols=0;
          for (int y=0; y<grectBand.height(); y++)
            {
              if (pm && do_color_or_gray)
                {
                  GPixel *pix = (*pm)[y];
                  for (int x=grectBand.width(); x>0; x--, pix++)
                    {
                      if (do_color)
                        {
                          char *data;
                          data = bin2hex[ramp[pix->r]];
                          *buf_ptr++ = data[0];
                          *buf_ptr++ = data[1];
                          data = bin2hex[ramp[pix->g]];
                          *buf_ptr++ = data[0];
                          *buf_ptr++ = data[1];
                          data = bin2hex[ramp[pix->b]];
                          *buf_ptr++ = data[0];
                          *buf_ptr++ = data[1];
                          symbols += 6;
                        }
                      else
                        {
                          char *data;
                          data = bin2hex[ramp[GRAY(pix->r,pix->g,pix->b)]];
                          *buf_ptr++ = data[0];
                          *buf_ptr++ = data[1];
                          symbols += 2;
                        }
                      if (symbols>70) 
                        { 
                          *buf_ptr++ = '\n'; 
                          symbols=0; 
                        }
                    }
                }
              else if (bm)
                {
                  unsigned char *pix = (*bm)[y];
                  unsigned char acc = 0;
                  unsigned char mask = 0;
                  char *data;
                  for (int x=grectBand.width(); x>0; x--, pix++)
                    {
                      if (mask == 0)
                        mask = 0x80;
                      if (! *pix)
                        acc |= mask;
                      mask >>= 1;
                      if (mask == 0)
                        {
                          data = bin2hex[acc];
                          acc = 0;
                          *buf_ptr++ = data[0];
                          *buf_ptr++ = data[1];
                          symbols += 2;
                          if (symbols>70) 
                            { 
                              *buf_ptr++ = '\n'; 
                              symbols = 0; 
                            }
                        }
                    }
                  if (mask != 0) 
                    {
                      data = bin2hex[acc];
                      *buf_ptr++ = data[0];
                      *buf_ptr++ = data[1];
                      symbols += 2;
                    }
                }
              if (refresh_cb) 
                refresh_cb(refresh_cl_data);
            }
          str.writall(buffer, buf_ptr-buffer);
          if (prn_progress_cb)
            {
              double done=(double) (grectBand.ymax 
                                    - prn_rect.ymin)/prn_rect.height();
              if ((int) (20*print_done)!=(int) (20*done))
                {
                  print_done=done;
                  prn_progress_cb(done, prn_progress_cl_data);
                }
            }
        }
      write(str, "\n");
    } 
  write(str, "grestore\n");
}

void
DjVuToPS::
print_image_lev2(ByteStream &str, 
                 GP<DjVuImage> dimg,
                 const GRect &prn_rect)
{         
  double print_done=0;
  GRect all(0,0, dimg->get_width(),dimg->get_height());
  GP<GPixmap> pm;
  GRect test(0,0,1,1);
  if (options.get_mode() == Options::FORE)
    pm = dimg->get_fg_pixmap(test, all);
  else if (options.get_mode() == Options::BACK)
    pm = dimg->get_bg_pixmap(test, all);
  else if (options.get_mode() != Options::BW)
    pm = dimg->get_pixmap(test, all);
  if (! pm)
    return;
  write(str,
        "%% --- now doing a level 2 image\n"
        "gsave\n");
  // Display image
  int band_bytes=125000;
  int band_height = band_bytes/prn_rect.width();
  int buffer_size = band_height*prn_rect.width();
  int ps_chunk_height = 30960/prn_rect.width()+1;
  buffer_size = buffer_size*21/10 + 32;
  bool do_color = options.get_color();
  if (do_color)
    {
      buffer_size *= 3;
      write(str, 
            "/bufferR %d string def\n"
            "/bufferG %d string def\n"
            "/bufferB %d string def\n"
            "DjVuColorSpace setcolorspace\n"
            "<< /ImageType 1\n"
            "   /Width %d\n"
            "   /Height %d\n"
            "   /BitsPerComponent 8\n"
            "   /Decode [0 1 0 1 0 1]\n"
            "   /ImageMatrix [1 0 0 1 0 0]\n"
            "   /MultipleDataSources true\n"
            "   /DataSource [ { ReadR } { ReadG } { ReadB } ]\n"
            "   /Interpolate false >> image\n",
            ps_chunk_height*prn_rect.width(),
            ps_chunk_height*prn_rect.width(),
            ps_chunk_height*prn_rect.width(),
            prn_rect.width(), prn_rect.height());
    } 
  else
    {
      write(str, 
            "DjVuColorSpace setcolorspace\n"
            "<< /ImageType 1\n"
            "   /Width %d\n"
            "   /Height %d\n"
            "   /BitsPerComponent 8\n"
            "   /Decode [0 1]\n"
            "   /ImageMatrix [1 0 0 1 0 0]\n"
            "   /DataSource currentfile /ASCII85Decode\n"
            "       filter /RunLengthDecode filter\n"
            "   /Interpolate false >> image\n",
            prn_rect.width(), prn_rect.height());
    } 
  unsigned char *buffer;
  GPBuffer<unsigned char> gbuffer(buffer,buffer_size);
  unsigned char *rle_in;
  GPBuffer<unsigned char> grle_in(rle_in,ps_chunk_height*prn_rect.width());
  unsigned char *rle_out;
  GPBuffer<unsigned char> grle_out(rle_out,2*ps_chunk_height*prn_rect.width());
    {
      // Start storing image in bands
      unsigned char * rle_out_end = rle_out;
      GRect grectBand = prn_rect;
      grectBand.ymax = grectBand.ymin;
      while(grectBand.ymax < prn_rect.ymax)
        {
          // Compute next band
          grectBand.ymin = grectBand.ymax;
          grectBand.ymax = grectBand.ymin+band_bytes/grectBand.width();
          if (grectBand.ymax > prn_rect.ymax)
            grectBand.ymax = prn_rect.ymax;
          GRect all(0,0, dimg->get_width(),dimg->get_height());
          pm = 0;
          if (options.get_mode() == Options::FORE)
            pm = dimg->get_fg_pixmap(grectBand, all);
          else if (options.get_mode() == Options::BACK)
            pm = dimg->get_bg_pixmap(grectBand, all);
          else
            pm = dimg->get_pixmap(grectBand, all);
          // Store next band
          unsigned char *buf_ptr = buffer;
          if (do_color && pm)
            {
              int y=0;
              while(y<grectBand.height())
                {
                  int row, y1;
                  unsigned char *ptr, *ptr1;
                  // Doing R component of current chunk
                  for (row=0,ptr=rle_in,y1=y; 
                       row<ps_chunk_height && y1<grectBand.height(); 
                       row++,y1++)
                    {
                      GPixel *pix = (*pm)[y1];
                      for (int x=grectBand.width(); x>0; x--,pix++)
                        *ptr++ = ramp[pix->r];
                    }
                  ptr1 = RLE_encode(rle_out, rle_in, ptr); 
                  *ptr1++ = 0x80;
                  buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                  *buf_ptr++ = '~'; *buf_ptr++ = '>'; *buf_ptr++ = '\n';
                  // Doing G component of current chunk
                  for (row=0,ptr=rle_in,y1=y; 
                       row<ps_chunk_height && y1<grectBand.height(); 
                       row++,y1++)
                    {
                      GPixel *pix = (*pm)[y1];
                      for (int x=grectBand.width(); x>0; x--,pix++)
                        *ptr++ = ramp[pix->g];
                    }
                  ptr1 = RLE_encode(rle_out, rle_in, ptr); 
                  *ptr1++ = 0x80;
                  buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                  *buf_ptr++ = '~'; 
                  *buf_ptr++ = '>'; 
                  *buf_ptr++ = '\n';
                  // Doing B component of current chunk
                  for (row=0, ptr=rle_in, y1=y;
                       row<ps_chunk_height && y1<grectBand.height(); 
                       row++,y1++)
                    {
                      GPixel *pix = (*pm)[y1];
                      for (int x=grectBand.width(); x>0; x--,pix++)
                        *ptr++ = ramp[pix->b];
                    }
                  ptr1 = RLE_encode(rle_out, rle_in, ptr);
                  *ptr1++ = 0x80;
                  buf_ptr = ASCII85_encode(buf_ptr, rle_out, ptr1);
                  *buf_ptr++ = '~'; 
                  *buf_ptr++ = '>'; 
                  *buf_ptr++ = '\n';
                  y=y1;
                  if (refresh_cb) 
                    refresh_cb(refresh_cl_data);
                } //while (y>=0)
            } 
          else if (pm)
            {
              // Don't use color
              int y=0;
              while(y<grectBand.height())
                {
                  unsigned char *ptr = rle_in;
                  for(int row=0;
                      row<ps_chunk_height && y<grectBand.height(); 
                      row++,y++)
                    {
                      GPixel *pix = (*pm)[y];
                      for (int x=grectBand.width(); x>0; x--,pix++)
                        *ptr++ = ramp[GRAY(pix->r,pix->g,pix->b)];
                    }
                  rle_out_end = RLE_encode(rle_out_end, rle_in, ptr);
                  unsigned char *encode_to = rle_out 
                    + (rle_out_end-rle_out)/4*4;
                  int bytes_left = rle_out_end-encode_to;
                  buf_ptr = ASCII85_encode(buf_ptr, rle_out, encode_to);
                  *buf_ptr++ = '\n';
                  memcpy(rle_out, encode_to, bytes_left);
                  rle_out_end = rle_out+bytes_left;
                  if (refresh_cb) 
                    refresh_cb(refresh_cl_data);
                }
              if (grectBand.ymax >= prn_rect.ymax)
                {
                  *rle_out_end++ = 0x80; // Add EOF marker
                  buf_ptr = ASCII85_encode(buf_ptr, rle_out, rle_out_end);
                  *buf_ptr++ = '~'; 
                  *buf_ptr++ = '>'; 
                  *buf_ptr++ = '\n';
                }
            }
          str.writall(buffer, buf_ptr-buffer);
          if (prn_progress_cb)
            {
              double done=(double) (grectBand.ymax
                                    - prn_rect.ymin)/prn_rect.height();
              if ((int) (20*print_done)!=(int) (20*done))
                {
                  print_done=done;
                  prn_progress_cb(done, prn_progress_cl_data);
                }
            }
        }
      write(str, "\n");
    } 
  write(str, "grestore\n");
}

static void 
get_anno_sub(IFFByteStream &iff, IFFByteStream &out)
{
  GUTF8String chkid;
  while (iff.get_chunk(chkid))
    {
      if (iff.composite())
        get_anno_sub(iff, out);
      else if (chkid == "ANTa" || chkid == "ANTz" ||
               chkid == "TXTa" || chkid == "TXTz"   )
        {
          out.put_chunk(chkid);
          out.copy(*iff.get_bytestream());
          out.close_chunk();
        }
      iff.close_chunk();
    }
}

static GP<ByteStream>
get_anno(GP<DjVuFile> f)
{
  if (! f->anno) 
    {
      GP<ByteStream> bs = f->get_init_data_pool()->get_stream();
      GP<ByteStream> anno = ByteStream::create();
      GP<IFFByteStream> in = IFFByteStream::create(bs);
      GP<IFFByteStream> out = IFFByteStream::create(anno);
      get_anno_sub(*in, *out);
      f->anno = anno;
    }
  f->anno->seek(0);
  return f->anno;
}

static GP<DjVuTXT>
get_text(GP<DjVuFile> file)
{ 
  GUTF8String chkid;
  GP<IFFByteStream> iff = IFFByteStream::create(get_anno(file));
  while (iff->get_chunk(chkid))
    {
      if (chkid == "TXTa") 
        {
          GP<DjVuTXT> txt = DjVuTXT::create();
          txt->decode(iff->get_bytestream());
          return txt;
        }
      else if (chkid == "TXTz") 
        {
          GP<DjVuTXT> txt = DjVuTXT::create();
          GP<ByteStream> bsiff = BSByteStream::create(iff->get_bytestream());
          txt->decode(bsiff);
          return txt;
        }
      iff->close_chunk();
    }
  return 0;
}

static void
print_ps_string(const char *data, int length, ByteStream &out)
{
  while (*data && length>0) 
    {
      int span = 0;
      while (span<length && data[span]>=0x20 && data[span]<0x7f 
             && data[span]!='(' && data[span]!=')' && data[span]!='\\' )
        span++;
      if (span > 0) 
        {
          out.write(data, span);
          data += span;
          length -= span;
        }
      else
        {
          char buffer[5];
          sprintf(buffer,"\\%03o", *(unsigned char*)data);
          out.write(buffer,4);
          data += 1;
          length -= 1;
        }
    }
}

static void
print_txt_sub(DjVuTXT &txt, DjVuTXT::Zone &zone, 
              ByteStream &out,int &lastx,int &lasty)
{
  // Get separator
  char separator = 0;
  switch(zone.ztype)
    {
    case DjVuTXT::COLUMN: 
      separator = DjVuTXT::end_of_column; break;
    case DjVuTXT::REGION: 
      separator = DjVuTXT::end_of_region; break;
    case DjVuTXT::PARAGRAPH: 
      separator = DjVuTXT::end_of_paragraph; break;
    case DjVuTXT::LINE: 
      separator = DjVuTXT::end_of_line; break;
    case DjVuTXT::WORD: 
      separator = ' '; break;
    default:
      separator = 0; break;
    }
  // Zone children
  if (zone.children.isempty()) 
    {
      const char *data = (const char*)txt.textUTF8 + zone.text_start;
      int length = zone.text_length;
      if (data[length-1] == separator)
        length -= 1;
      out.write("( ",2);
      print_ps_string(data,length,out);
      out.write(")",1);
      GUTF8String message;
      int tmpx= zone.rect.xmin-lastx;
      int tmpy= zone.rect.ymin-lasty;
      message.format(" %d %d S \n", tmpx, tmpy);
      lastx=zone.rect.xmin;
      lasty=zone.rect.ymin;
      out.write((const char*)message, message.length());
    }
  else
    {
      if (zone.ztype==DjVuTXT::LINE)
        {
          GUTF8String message;
          message.format("%d F\n",zone.rect.ymax-zone.rect.ymin);
          out.write((const char*)message,message.length());
        }
      for (GPosition pos=zone.children; pos; ++pos)
        print_txt_sub(txt, zone.children[pos], out,lastx,lasty);
    }
}

static void
print_txt(GP<DjVuTXT> txt, 
          ByteStream &out )
{
  if (txt)
    {
      int lastx=0;
      int lasty=0;
      GUTF8String message = 
        "%% -- now doing hidden text\n"
        "gsave -1 -1 0 0 clip 0 0 moveto\n";
      out.write((const char*)message,message.length());
      print_txt_sub(*txt, txt->page_zone, out,lastx,lasty);
      message = 
        "grestore \n";
      out.write((const char*)message,message.length());
    }
}

void
DjVuToPS::
print_image(ByteStream &str, 
            GP<DjVuImage> dimg,
            const GRect &prn_rect, 
            GP<DjVuTXT> txt)
{
  /* Just outputs the specified image. The function assumes, that
     all add-ons (like {\em document setup}, {\em page setup}) are
     already there. It will just output the image. Since
     output of this function will generate PostScript errors when
     used without output of auxiliary functions, it should be
     used carefully. */
  DEBUG_MSG("DjVuToPS::print_image(): Printing DjVuImage to a stream\n");
  DEBUG_MAKE_INDENT(3);
  if (!dimg)
    G_THROW(ERR_MSG("DjVuToPS.empty_image"));
  if (prn_rect.isempty())
    G_THROW(ERR_MSG("DjVuToPS.empty_rect"));
  if (prn_progress_cb)
    prn_progress_cb(0, prn_progress_cl_data);
  // Compute information for chosen display mode
  print_txt(txt, str);
  make_gamma_ramp(dimg);
  if (options.get_level() < 2)
    {
      print_image_lev1(str, dimg, prn_rect);
    }
  else if (options.get_level() < 3 && dimg->get_fgpm())
    {
      switch(options.get_mode())
        {
        case Options::COLOR:
        case Options::FORE:
          print_image_lev2(str, dimg, prn_rect);
          break;
        case Options::BW:
          print_fg(str, dimg, prn_rect);
          break;
        case Options::BACK:
          print_bg(str, dimg, prn_rect);
          break;
        }
    }
  else 
    {
      switch(options.get_mode())
        {
        case Options::COLOR:
          print_bg(str, dimg, prn_rect);
          print_fg(str, dimg, prn_rect);
          break;
        case Options::FORE:
        case Options::BW:
          print_fg(str, dimg, prn_rect);
          break;
        case Options::BACK:
          print_bg(str, dimg, prn_rect);
          break;
        }
    }
  if (prn_progress_cb)
    prn_progress_cb(1, prn_progress_cl_data);
}




// ***********************************************************************
// ******* PUBLIC FUNCTION FOR PRINTING A SINGLE PAGE ********************
// ***********************************************************************




void
DjVuToPS::
print(ByteStream &str, 
      GP<DjVuImage> dimg,
      const GRect &prn_rect_in, 
      const GRect &img_rect,
      int override_dpi)
{
  DEBUG_MSG("DjVuToPS::print(): Printing DjVu page to a stream\n");
  DEBUG_MAKE_INDENT(3);
  GRect prn_rect;
  prn_rect.intersect(prn_rect_in, img_rect);
  DEBUG_MSG("prn_rect=(" << prn_rect.xmin << ", " << prn_rect.ymin << ", " <<
            prn_rect.width() << ", " << prn_rect.height() << ")\n");
  DEBUG_MSG("img_rect=(" << img_rect.xmin << ", " << img_rect.ymin << ", " <<
            img_rect.width() << ", " << img_rect.height() << ")\n");
  if (!dimg)
    G_THROW(ERR_MSG("DjVuToPS.empty_image"));
  if (prn_rect.isempty())
    G_THROW(ERR_MSG("DjVuToPS.empty_rect"));
  if (img_rect.isempty())
    G_THROW(ERR_MSG("DjVuToPS.bad_scale"));
  GRectMapper mapper;
  mapper.set_input(img_rect);
  GRect full_rect(0, 0, dimg->get_width(), dimg->get_height());
  mapper.set_output(full_rect);
  mapper.map(prn_rect);
  int image_dpi =  dimg->get_dpi();
  if (override_dpi>0) 
    image_dpi = override_dpi;
  if (image_dpi <= 0) 
    image_dpi = 300;
  store_doc_prolog(str, 1, (int)(image_dpi), &prn_rect);
  store_doc_setup(str);
  write(str,"%%%%Page: 1 1\n");
  store_page_setup(str, (int)(image_dpi), prn_rect);
  print_image(str, dimg, prn_rect, 0);
  store_page_trailer(str);
  write(str,"showpage\n");
  store_doc_trailer(str);
}




// ***********************************************************************
// *************************** DOCUMENT LEVEL ****************************
// ***********************************************************************


void
DjVuToPS::
parse_range(GP<DjVuDocument> doc, 
             GUTF8String page_range, 
             GList<int> &pages_todo)
{
  int doc_pages = doc->get_pages_num();
  if (!page_range.length())
    page_range.format("1-%d", doc_pages);
  DEBUG_MSG("page_range='" << (const char *)page_range << "'\n");
  int spec = 0;
  int both = 1;
  int start_page = 1;
  int end_page = doc_pages;
  const char *q = (const char*)page_range;
  char *p = (char*)q;
  while (*p)
    {
      while (*p==' ')
        p += 1;
      if (! *p)
        break;
      if (*p>='0' && *p<='9') 
        {
          end_page = strtol(p, &p, 10);
          spec = 1;
        } 
      else if (*p=='$') 
        {
          spec = 1;
          end_page = doc_pages;
          p += 1;
        } 
      else if (both) 
        {
          end_page = 1;
        } 
      else 
        {
          end_page = doc_pages;
        }
      while (*p==' ')
        p += 1;
      if (both)
        {
          start_page = end_page;
          if (*p == '-') 
            {
              p += 1;
              both = 0;
              continue;
            }
        }
      both = 1;
      while (*p==' ')
        p += 1;
      if (*p && *p != ',')
        G_THROW(ERR_MSG("DjVuToPS.bad_range") 
                + GUTF8String("\t") + GUTF8String(p) );
      if (*p == ',')
        p += 1;
      if (! spec)
        G_THROW(ERR_MSG("DjVuToPS.bad_range") 
                + GUTF8String("\t") + page_range );
      spec = 0;
      if (end_page < 0)
        end_page = 0;
      if (start_page < 0)
        start_page = 0;
      if (end_page > doc_pages)
        end_page = doc_pages;
      if (start_page > doc_pages)
        start_page = doc_pages;
      if (start_page <= end_page)
        for(int page_num=start_page; page_num<=end_page; page_num++)
          pages_todo.append(page_num-1);
      else
        for(int page_num=start_page; page_num>=end_page; page_num--)
          pages_todo.append(page_num-1);
    }
}

class DjVuToPS::DecodePort : public DjVuPort
{
protected:
  DecodePort(void);
public:
  static GP<DecodePort> create(void);
  GEvent decode_event;
  bool decode_event_received;
  double decode_done;
  GURL decode_page_url;
  virtual void notify_file_flags_changed(const DjVuFile*,long,long);
  virtual void notify_decode_progress(const DjVuPort*,double);
};

DjVuToPS::DecodePort::
DecodePort(void)
  : decode_event_received(false),
    decode_done((double)0) 
{
}

GP<DjVuToPS::DecodePort> 
DjVuToPS::DecodePort::
create(void)
{
  return new DecodePort;
}

void 
DjVuToPS::DecodePort::
notify_file_flags_changed(const DjVuFile *source, 
                          long set_mask, long clr_mask)
{
  // WARNING! This function is called from another thread
  if (set_mask & (DjVuFile::DECODE_OK | 
                  DjVuFile::DECODE_FAILED | 
                  DjVuFile::DECODE_STOPPED ))
    {
      if (source->get_url() == decode_page_url)
        {
          decode_event_received=true;
          decode_event.set();
        }
    }
}

void 
DjVuToPS::DecodePort::
notify_decode_progress(const DjVuPort *source, double done)
{
  // WARNING! This function is called from another thread
  if (source->inherits("DjVuFile"))
    {
      DjVuFile * file=(DjVuFile *) source;
      if (file->get_url()==decode_page_url)
        if ((int) (decode_done*20)!=(int) (done*20))
          {
            decode_done=done;
            decode_event_received=true;
            decode_event.set();
          }
    }
}

void 
DjVuToPS::
set_refresh_cb(void (*_refresh_cb)(void*), void *_refresh_cl_data)
{
  refresh_cb = _refresh_cb;
  refresh_cl_data = _refresh_cl_data;
}

void 
DjVuToPS::
set_prn_progress_cb(void (*_prn_progress_cb)(double, void *),
                    void *_prn_progress_cl_data)
{
  prn_progress_cb=_prn_progress_cb;
  prn_progress_cl_data=_prn_progress_cl_data;
}

void 
DjVuToPS::
set_dec_progress_cb(void (*_dec_progress_cb)(double, void *),
                    void *_dec_progress_cl_data)
{
  dec_progress_cb=_dec_progress_cb;
  dec_progress_cl_data=_dec_progress_cl_data;
}

void 
DjVuToPS::
set_info_cb(void (*_info_cb)(int, int, int, Stage, void*),
            void *_info_cl_data)
{
  info_cb=_info_cb;
  info_cl_data=_info_cl_data;
}

GP<DjVuImage>
DjVuToPS::
decode_page(GP<DjVuDocument> doc, 
            int page_num, int cnt, int todo)
{
  DEBUG_MSG("processing page #" << page_num << "\n");
  if (! port)
    {
      port = DecodePort::create();
      DjVuPort::get_portcaster()->add_route((DjVuDocument*)doc, port);
    }
  port->decode_event_received = false;
  port->decode_done = 0;
  GP<DjVuFile> djvu_file;
  GP<DjVuImage> dimg;
  if (page_num >= 0 && page_num < doc->get_pages_num())
    djvu_file = doc->get_djvu_file(page_num);
  if (! djvu_file )
    return 0;
  if (djvu_file->is_decode_ok())
    return doc->get_page(page_num, false);
  // This is the best place to call info_cb(). Note, that
  // get_page() will start decoding if necessary, and will not
  // return until the decoding is over in a single threaded
  // environment. That's why we call get_djvu_file() first.
  if (info_cb)
    info_cb(page_num, cnt, todo, DECODING, info_cl_data);
  // Do NOT decode the page synchronously here!!!
  // The plugin will deadlock otherwise.
  dimg = doc->get_page(page_num, false);
  djvu_file = dimg->get_djvu_file();
  port->decode_page_url = djvu_file->get_url();
  if (djvu_file->is_decode_ok())
    return dimg;
  DEBUG_MSG("decoding\n");
  if (dec_progress_cb)
    dec_progress_cb(0, dec_progress_cl_data);
  while(! djvu_file->is_decode_ok())
    {
      while(!port->decode_event_received && 
            !djvu_file->is_decode_ok())
        {
          port->decode_event.wait(250);
          if (refresh_cb) 
            refresh_cb(refresh_cl_data);
        }
      port->decode_event_received = false;
      if (djvu_file->is_decode_failed() || 
          djvu_file->is_decode_stopped())
        G_THROW(ERR_MSG("DjVuToPS.no_image") 
                + GUTF8String("\t") 
                + GUTF8String(page_num));
      if (dec_progress_cb)
        dec_progress_cb(port->decode_done, dec_progress_cl_data);
    }
  if (dec_progress_cb)
    dec_progress_cb(1, dec_progress_cl_data);
  return dimg;
}

void
DjVuToPS::
process_single_page(ByteStream &str, 
                    GP<DjVuDocument> doc,
                    int page_num, int cnt, int todo,
                    int magic)
{
  GP<DjVuTXT> txt;
  GP<DjVuImage> dimg;
  dimg = decode_page(doc, page_num, cnt, todo);
  if (options.get_text())
    txt = get_text(dimg->get_djvu_file());
  if (info_cb)
    info_cb(page_num, cnt, todo, PRINTING, info_cl_data);
  if (!magic)
    write(str, "%%%%Page: %d %d\n", page_num+1, cnt+1);
  if (dimg)
    {
      int dpi = dimg->get_dpi();
      dpi = ((dpi <= 0) ? 300 : dpi);
      GRect img_rect(0, 0, dimg->get_width(), dimg->get_height());
      store_page_setup(str, dpi, img_rect, magic);
      print_image(str, dimg, img_rect,txt);
      store_page_trailer(str);
    }
  if (!magic)
    write(str,"showpage\n");
}


struct pdata {
  int page1, page2;
  int smax, spos;
  int offset;
};

void 
DjVuToPS::
process_double_page(ByteStream &str, 
                    GP<DjVuDocument> doc,
                    void *v, int cnt, int todo)
{
  const pdata *inf = (const pdata*)v;
  int off = abs(inf->offset);
  write(str,
        "%%%%Page: (%d,%d) %d\n"
        "gsave\n"
        "/fold-dict 8 dict dup 3 1 roll def begin\n"
        " clippath pathbbox newpath pop pop translate\n"
        " clippath pathbbox newpath 4 2 roll pop pop\n"
        " /ph exch def\n"
        " /pw exch def\n"
        " /w ph %d sub 2 div def\n"
        " /m1 %d def\n"
        " /m2 %d def\n"
        "end\n",
        inf->page1 + 1, inf->page2 + 1, cnt,
        2 * (off + options.get_bookletfold(inf->smax-1)),
        inf->offset + options.get_bookletfold(inf->spos),
        inf->offset - options.get_bookletfold(inf->spos));
  if (options.get_cropmarks())
    write(str,
          "%% -- folding marks\n"
          "fold-dict begin\n"
          " 0 setgray 0.5 setlinewidth\n"
          " ph m1 m2 add add 2 div dup\n"
          " 0 exch moveto 36 0 rlineto stroke\n"
          " pw exch moveto -36 0 rlineto stroke\n"
          "end\n");
  write(str,
        "%% -- first page\n"
        "gsave fold-dict begin\n"
        " 0 ph 2 div w add m1 add translate 270 rotate\n"
        " 0 0 w pw rectclip end\n");
  if (inf->page1 >= 0)
    process_single_page(str, doc, inf->page1, cnt*2, todo*2, +1);
  write(str,
        "grestore\n"
        "%% -- second page\n"
        "gsave fold-dict begin\n"
        " 0 ph 2 div m2 add translate 270 rotate\n"
        " 0 0 w pw rectclip end\n");
  if (inf->page2 >= 0)
    process_single_page(str, doc, inf->page2, cnt*2+1, todo*2, -1);
  write(str,
        "grestore\n"
        "grestore\n"
        "showpage\n");
}

static void
booklet_order(GList<int>& pages, int smax)
{
  // -- make a multiple of four
  while (pages.size() & 0x3)
    pages.append(-1);
  // -- copy to array
  int i = 0;
  int n = pages.size();
  GTArray<int> p(0,n-1);
  for (GPosition pos=pages; pos; ++pos)
    p[i++] = pages[pos];
  // -- rebuild
  pages.empty();
  for (i=0; i<n; i+=smax)
    {
      int lo = i;
      int hi = i+smax-1;
      if (hi >= n)
        hi = n-1;
      while (lo < hi)
        {
          pages.append(p[hi--]);
          pages.append(p[lo++]);
          pages.append(p[lo++]);
          pages.append(p[hi--]);
        }
    }
}


// ***********************************************************************
// ******* PUBLIC FUNCTIONS FOR PRINTING MULTIPLE PAGES ******************
// ***********************************************************************



void
DjVuToPS::
print(ByteStream &str, 
      GP<DjVuDocument> doc, 
      GUTF8String page_range)
{
  DEBUG_MSG("DjVuToPS::print(): Printing DjVu document\n");
  DEBUG_MAKE_INDENT(3);
  // Get page range
  GList<int> pages_todo;
  parse_range(doc, page_range, pages_todo);
  int todo = pages_todo.size();
  if (options.get_format()==Options::EPS)
    {
      /* Encapsulated Postscript mode */
      if (todo != 1)
        G_THROW(ERR_MSG("DjVuToPS.only_one_page"));
      GPosition pos = pages_todo;
      int page_num = pages_todo[pos];
      GP<DjVuImage> dimg = decode_page(doc,page_num,0,todo);
      if (! dimg)
        G_THROW(ERR_MSG("DjVuToPS.no_image") + GUTF8String("\t1"));
      GRect bbox(0, 0, dimg->get_width(), dimg->get_height());
      store_doc_prolog(str, 1, dimg->get_dpi(), &bbox);
      store_doc_setup(str);
      process_single_page(str, doc, page_num, 0, todo, 0);
    }
  else if (options.get_bookletmode()==Options::OFF)
    {
      /* Normal mode */
      int cnt = 0;
      store_doc_prolog(str, todo, 0, 0);
      store_doc_setup(str);
      for(GPosition pos = pages_todo; pos; ++pos)
        process_single_page(str,doc,pages_todo[pos],cnt++,todo,0);
      store_doc_trailer(str);
    }
  else
    {
      /* Booklet mode */
      int sheets_left = (todo+3)/4;
      int sides_todo = sheets_left;
      if (options.get_bookletmode() == Options::RECTOVERSO)
        sides_todo *= 2;
      int sheets_max = (options.get_bookletmax()+3)/4;
      if (! sheets_max)
        sheets_max = sheets_left;
      // -- reorder pages
      booklet_order(pages_todo, sheets_max*4);
      // -- print
      int sides = 0;
      int sheetpos = sheets_max;
      store_doc_prolog(str, sides_todo, 0, 0);
      store_doc_setup(str);
      for (GPosition p=pages_todo; p; ++p)
        {
          struct pdata inf;
          inf.page1 = pages_todo[p]; 
          inf.page2 = pages_todo[++p]; 
          inf.smax = sheets_max;
          inf.spos = --sheetpos;
          inf.offset = options.get_bookletalign();
          if (options.get_bookletmode() != Options::VERSO)
            process_double_page(str,doc,(void*)&inf,sides++,sides_todo);
          inf.page1 = pages_todo[++p]; 
          inf.page2 = pages_todo[++p]; 
          inf.offset = -inf.offset;
          if (options.get_bookletmode() != Options::RECTO)
            process_double_page(str,doc,(void*)&inf,sides++,sides_todo);
          sheets_left -= 1;
          if (sheetpos <= 0)
            sheetpos = ((sheets_max<sheets_left) ? sheets_max : sheets_left);
        }
      store_doc_trailer(str);
    }
}


void
DjVuToPS::
print(ByteStream &str, GP<DjVuDocument> doc)
{
  GUTF8String dummy;
  print(str,doc,dummy);
}



#ifdef HAVE_NAMESPACES
}
# ifndef NOT_USING_DJVU_NAMESPACE
using namespace DJVU;
# endif
#endif

