/*   GPDriver2 source image.c
 *   $Id: image.c,v 1.4 2007/07/09 13:51:34 wuerthne Exp $
 *
 *   GPDriver2 - Gutenprint based printer spooler for RISC OS
 *   Copyright (C) 2007 Martin Wuerthner
 *
 *   This program is free software; you can redistribute it and/or modify it
 *   under the terms of the GNU General Public License as published by the Free
 *   Software Foundation; either version 2 of the License, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful, but
 *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *   for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *   Martin Wuerthner <ro-printing@mw-software.com>
 *   Mannheimer Str. 18, 67655 Kaiserslautern, Germany
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gutenprint/gutenprint.h"
#include "oslib/osgbpb.h"
#include "oslib/osargs.h"
#include "image.h"
#include "gpd_gui.h"

/* set to 1 to enable debug messages via Reporter */
#define DEBUG 0
#include "debug.h"

/* a gpd_image is a structure that describes a page to be printed - it is an in-memory
   interface to the ROUGPS print job file */
/* it can be seen as an adaptor between the ROUGPS job and the stp_image interface used
   by Gutenprint */
struct gpd_image_s {
  /* The first fields are loaded directly from the job file header */
  /* so their layout is the same as in the job file */
  int nrows;           /* number of rows on page (height) */
  int npix;            /* number of pixels per row (width) */
  int firstr;          /* first non-empty row (first encoded row), equals nrows if none */
  int lastr;           /* last non-empty row (last encoded row), inclusive */
  int firstp;          /* leftmost pixel found */
  int lastp;           /* rightmost pixel found */
  int topm;            /* number of pixel rows to skip at top */
  int leftm;           /* number of pixels to skip at left margin */
  int jflags;          /* job flags */
  int copies;          /* number of copies requested */
  int pwidth;          /* paper width (millipoints) */
  int pheight;         /* paper height (mp) */
  int lowx;            /* printable area x0 */
  int lowy;            /*                y0 */
  int highx;           /*                x1 */
  int highy;           /*                y1 */
  int xres;
  int yres;
  int snamoff;         /* offset to short printer name (as returned by PDriver_Info) */
  int pnamoff;         /* offset to long printer name from PDF as used by Gutenprint */
  int rnamoff;         /* offset to resolution name from PDF as used by Gutenprint */
  int rowoff;          /* offset from start of file to first encoded row */
  /* end of file header fields */

  /* job file descriptor */
  /* the first non-header field, this property is used below to work out the size of the header! */
  os_fw job_file_h;    /* the file handle of the ROUGPS job file */
  int size;            /* size of the file */

  char* sname;         /* short printer name as read from job file */
  char* pname;         /* long printer name as read from job file */
  char* rname;         /* resolution name as read from job file */

  /* image descriptor */
  int width;           /* size of our bitmap data in XRES/YRES resolution*/
  int height;
  int virtual_width;   /* size for Gutenprint, might be smaller due to cropping */
  int virtual_height;
  int bpp;             /* for Gutenprint */

  /* allow the top/left to be cropped if it is outside the imageable area */
  int cropped_rows;
  int cropped_columns;
  /* allow the bottom/right to be cropped if it is outside the imageable area */
  int cropped_bottom_rows;
  int cropped_right_columns;
  /* the opposite: added extra empty rows/columns to make sure that the image's dimensions
     are exact multiples of 1pt */
  int invented_rows;
  int invented_columns;

  stp_image_t* stp_image;     /* the stp_image pointer to be passed to Gutenprint */

  /* gui variables */
  void (*progress_func)(int,int);

  /* caching variables */
  char* buffer;               /* input buffer */
  int current_off;            /* position of file pointer in job file */
  int current_row;            /* row number that is currently cached at offset boff in buffer
                                 (crop-adjusted Gutenprint numbering, i.e., row 0 is firstr) */
  int boff;                   /* offset of current row in buffer */
  int cached;                 /* number of buffered bytes left */
};

/* job flags */
#define JFLAGS_COLOUR (1<<0)

/* current file version we understand */
#define CURRENT_FVER (100)

typedef struct gpd_row_s {
  int len;
  int firstp;
  int lastp;
  char cid;
  char pad1, pad2, pad3;
} gpd_row_t;

#define CID_UNCOMPRESSED 0
#define CID_SIMPLE_RLE 1

#define BUFFSIZE (256 * 1024)

os_error could_not_allocate = { 0, "Could not allocate string buffer" };

os_error* read_word(os_fw file_h, int* wordp);
os_error* read_block(os_fw file_h, char* buff, int num);
os_error* read_string(os_fw file_h, int offset, char** buffp);
int read_next_row(gpd_image_t* img);
int decompress_row(gpd_image_t* img, unsigned char *data);

/* callback functions for Gutenprint */
void init_image(struct stp_image *image);
void reset_image(struct stp_image *image);
void progress_init_image(struct stp_image *image);
void note_progress_image(struct stp_image *image, double current, double total);
void progress_conclude_image(struct stp_image *image);
const char *get_image_appname(struct stp_image *image);
int get_image_bpp(struct stp_image *image);
int get_image_width(struct stp_image *image);
int get_image_height(struct stp_image *image);
stp_image_status_t get_image_row(struct stp_image *image, unsigned char *data, size_t byte_limit, int row);

gpd_image_t* gpdi_create_image(os_fw job_file_h, void (*progress_func)(int,int))
{
  struct gpd_image_s* img;
  stp_image_t* stp_image;
  int id, fver;

  /* check the job file identification word */
  if (gui_complain(read_word(job_file_h, &id))) return NULL;
  if (id != 'P' + ('D' << 8) + ('G' << 16) + ('P' << 24)) {
    gui_error2("Unknown job file identification"); return NULL;
  }
  if (gui_complain(read_word(job_file_h, &fver))) return NULL;
  if (fver > CURRENT_FVER) {
    gui_error2("Job file was produced for a more recent version of GPDriver");
    return NULL;
  }
  /* OK, we can read the job file */
  img = malloc(sizeof(struct gpd_image_s));
  if (!img) {
    gui_error2("Not enough memory to allocate image structure"); return NULL;
  }
  img->job_file_h = job_file_h;
  if (gui_complain(read_block(job_file_h, (char*)(&img->nrows), ((char*)&(img->job_file_h)) - ((char*)img)))) return NULL;

  REPORT_D("NROWS = %d",img->nrows)
  REPORT_D("NPIX = %d",img->npix)
  REPORT_D("FIRSTR = %d",img->firstr)
  REPORT_D("LASTR = %d",img->lastr)
  REPORT_D("FIRSTP = %d",img->firstp)
  REPORT_D("LASTP = %d",img->lastp)

#if 0
  printf("NROWS = %d\nNPIX = %d\nFIRSTR=%d\nLASTR=%d\n"
         "FIRSTP = %d\nLASTP = %d\n"
         "TOPM = %d\nLEFTM = %d\n"
         "PWIDTH = %d\nPHEIGHT = %d\n"
         "XRES = %d\nYRES = %d\nROWOFF = %d\n",
         img->nrows, img->npix, img->firstr, img->lastr,
         img->firstp, img->lastp, img->topm, img->leftm, img->pwidth, img->pheight,
         img->xres, img->yres,
         img->rowoff);
#endif

  if (gui_complain(read_string(job_file_h, img->snamoff, &img->sname))) return NULL;
  /* printf("short printer name: %s\n", img->sname); */
  if (gui_complain(read_string(job_file_h, img->pnamoff, &img->pname))) return NULL;
  /* printf("long printer name: %s\n", img->pname); */
  if (gui_complain(read_string(job_file_h, img->rnamoff, &img->rname))) return NULL;
  /* printf("resolution name: %s\n", img->rname); */

  img->progress_func = progress_func;

  if (img->firstr < img->nrows && img->firstp < img->npix) {
    img->width = img->lastp - img->firstp + 1;
    img->height = img->lastr - img->firstr + 1;
  }
  else {
    /* empty page - lastr, lastp undefined - we fake a 1x1 bitmap to allow us to
       use the driver to actually print an empty page */
    /* of course, get_image_row below must be prepared to still return the row
       when requested, so it tests for img->firstr > img->nrows || img->firstp > img->npix
       and returns empty rows only in this case */
    img->width = 1; img->height = 1;
  }
  REPORT_D("width = %d",img->width)
  REPORT_D("height = %d",img->height)
  /* printf("width = %d\nheight = %d\n", img->width, img->height); */
  img->bpp = 3;   /* true-colour RGB */

  img->cropped_rows = 0;
  img->cropped_columns = 0;
  img->cropped_bottom_rows = 0;
  img->cropped_right_columns = 0;
  img->invented_rows = 0;
  img->invented_columns = 0;

  img->virtual_width = img->width;
  img->virtual_height = img->height;

  /* so far we have not created an stp_image structure */
  stp_image = malloc(sizeof(stp_image_t));
  stp_image->init = &init_image;
  stp_image->reset = &reset_image;
  stp_image->width = &get_image_width;
  stp_image->height = &get_image_height;
  stp_image->get_row = &get_image_row;
  stp_image->get_appname = &get_image_appname;
  stp_image->conclude = &progress_conclude_image;
  stp_image->rep = img;

  img->stp_image = stp_image;

  /* position the file pointer at the first row */
  img->buffer = malloc(BUFFSIZE);
  if (!img->buffer) {
    gui_error2("Not enough memory for buffer"); return NULL;
  }
  if (gui_complain(xosargs_read_extw(job_file_h, &img->size))) return NULL;
  if (!gpdi_reset_image(img)) return NULL;
  return img;
}

void gpdi_dispose(gpd_image_t* img)
{
  free(img->buffer);
  free(img->stp_image);
  free(img);
}

/* get the short printer name used to create the image */
const char* gpdi_get_short_name(gpd_image_t* img)
{
  return img->sname;
}

/* get the long printer name used to create the image */
const char* gpdi_get_long_name(gpd_image_t* img)
{
  return img->pname;
}

/* get the resolution name used to create the image */
const char* gpdi_get_resolution_name(gpd_image_t* img)
{
  return img->rname;
}

/* get the x coordinate of the left edge of the printable area in millipoints */
int gpdi_get_page_x0_mp(gpd_image_t* img) { return img->lowx; }
/* get the y coordinate of the bottom edge of the printable area in millipoints */
int gpdi_get_page_y0_mp(gpd_image_t* img) { return img->lowy; }
/* get the x coordinate of the right edge of the printable area in millipoints */
int gpdi_get_page_x1_mp(gpd_image_t* img) { return img->highx; }
/* get the y coordinate of the top edge of the printable area in millipoints */
int gpdi_get_page_y1_mp(gpd_image_t* img) { return img->highy; }

/* get the media width in millipoints */
int gpdi_get_pwidth_mp(gpd_image_t* img) { return img->pwidth; }
/* get the media heigth in millipoints */
int gpdi_get_pheight_mp(gpd_image_t* img) { return img->pheight; }

/* get the width of the bitmap in pixels */
int gpdi_get_width(gpd_image_t* img) { return img->width; }
/* get the height of the bitmap in pixels */
int gpdi_get_height(gpd_image_t* img) { return img->height; }
/* get the virtual width of the bitmap in pixels as passed to Gutenprint */
int gpdi_get_virtual_width(gpd_image_t* img) { return img->virtual_width; }
/* get the virtual height of the bitmap in pixels as passed to Gutenprint */
int gpdi_get_virtual_height(gpd_image_t* img) { return img->virtual_height; }
/* get the x resolution of the bitmap */
int gpdi_get_xres(gpd_image_t* img) { return img->xres; }
/* get the y resolution of the bitmap */
int gpdi_get_yres(gpd_image_t* img) { return img->yres; }

/* get the first non-empty row on the page */
int gpdi_get_firstp(gpd_image_t* img) { return img->firstp; }
/* get the first non-empty column on the page */
int gpdi_get_firstr(gpd_image_t* img) { return img->firstr; }

/* get the number of pixel rows to skip at top of page */
int gpdi_get_topm(gpd_image_t* img) { return img->topm; }
/* get the number of pixels to skip at the left margin */
int gpdi_get_leftm(gpd_image_t* img) { return img->leftm; }

/* get the number of copies requested */
int gpdi_get_copies(gpd_image_t* img) { return img->copies; }

static void compute_virtual_size(gpd_image_t* img)
{
  img->virtual_height = img->height - img->cropped_rows - img->cropped_bottom_rows + img->invented_rows;
  img->virtual_width = img->width - img->cropped_columns - img->cropped_right_columns + img->invented_columns;
  if (img->virtual_width <= 0 || img->virtual_height <= 0) {
    /* empty page, so present a one-pixel bitmap */
    img->virtual_width = 1;
    img->virtual_height = 1;
  }
  REPORT_DD("virtual height = %d, virtual width = %d", img->virtual_height, img->virtual_width);
}

/* set the number of cropped rows and columns */
void gpdi_set_cropping(gpd_image_t* img, int cropped_rows, int cropped_columns,
                                         int cropped_bottom_rows, int cropped_right_columns)
{
  img->cropped_rows = cropped_rows;
  img->cropped_bottom_rows = cropped_bottom_rows;
  img->cropped_columns = cropped_columns;
  img->cropped_right_columns = cropped_right_columns;
  compute_virtual_size(img);
}

/* add some empty rows/columns */
void gpdi_extend_image(gpd_image_t* img, int invented_rows, int invented_columns)
{
  img->invented_rows = invented_rows;
  img->invented_columns = invented_columns;
  compute_virtual_size(img);
}

/* get the underlying stp_image pointer */
stp_image_t* gpdi_get_stp_image(gpd_image_t* img)
{
  return img->stp_image;
}

void init_image(struct stp_image *image)
{
  /* do nothing */
  /* printf("init_image\n"); */
}

void reset_image(struct stp_image *image)
{
  /* do nothing */
  /* printf("reset_image\n"); */
}

int get_image_bpp(struct stp_image *image)
{
  return ((struct gpd_image_s*)(image->rep))->bpp;
}

int get_image_width(struct stp_image *image)
{
  return ((struct gpd_image_s*)(image->rep))->virtual_width;
}

int get_image_height(struct stp_image *image)
{
  return ((struct gpd_image_s*)(image->rep))->virtual_height;
}

/*---------------*/
/* get image row */
/*---------------*/
/* MAIN CALLBACK FUNCTION for Gutenprint - called to get an image row */
stp_image_status_t
get_image_row(struct stp_image *image, unsigned char *data, size_t byte_limit, int row)
{
  gpd_image_t* img = (gpd_image_t*)image->rep;
  REPORT_DD("get_row (virtual) %d, byte_limit = %d", row, byte_limit)
  /* We might have told Gutenprint that we have fewer rows because we want to crop some
     rows at the top and bottom - rows at the bottom are not a problem because Gutenprint
     simply will not ask for those we have not advertised, but we need to address cropped
     rows at the top. So, if Gutenprint asks for row x, we need to return row x + cropped_rows */
  row += img->cropped_rows;
  /* printf("get_row (virtual) %d\n",row); */

  /* call the user interface progress function */
  img->progress_func(row, img->height);

  if (gui_aborted()) return STP_IMAGE_STATUS_ABORT;

  if (img->firstr >= img->nrows || img->firstp >= img->npix
      || img->height - img->cropped_rows - img->cropped_bottom_rows <= 0
      || img->width - img->cropped_columns - img->cropped_right_columns <= 0
      /* in the following test please note that img->cropped_rows has already been added to row */
      || row >= img->height - img->cropped_bottom_rows /* can happen with invented_rows > 0 */) {
    /* we actually have an empty page, so return an empty row */
    memset(data, 255, 3 * img->virtual_width);
    return STP_IMAGE_STATUS_OK;
  }

  /* read until the requested row is in the cache */
  while(img->current_row < row) {
    if (!read_next_row(img)) return STP_IMAGE_STATUS_ABORT;
  }
  REPORT("row read")
  if (!decompress_row(img, data)) return STP_IMAGE_STATUS_ABORT;
  return STP_IMAGE_STATUS_OK;
}

/* decompress the current row into the buffer pointed to by data */
/* NB - our compressed row data is of size img->width whereas
        Gutenprint expects img->virtual_width, i.e., some
        of the left and right possibly cropped - we need to be able
        to do that cropping when uncompressing the data
   a row has three parts: leading empty pixels to the left, some
   real pixel data (possibly compressed) and trailing empty pixels
   to the right - the cropping can in theory occur in all parts */
int decompress_row(gpd_image_t* img, unsigned char *data)
{
  unsigned char* data_out = data;
  gpd_row_t* row = (gpd_row_t*)(img->buffer + img->boff);


  /* Gutenprint expects a row of size img->virtual_width */
  REPORT_D("decompress, row->len = %d", row->len)
  if (row->len == 4) {
    /* empty row, very easy */
    memset(data_out, 255, 3 * img->virtual_width);
    REPORT("empty row decompressed")
    return 1;
  }

  /* a real row to decompress */
  int pixels_to_crop = img->cropped_columns;     /* a counter for how many left hand pixels we still have to crop */
  /* to do the right-hand side cropping we keep track of the number of pixels written */
  int pixels_to_write = img->virtual_width;
  unsigned char* buffer = ((unsigned char*)row) + sizeof(gpd_row_t);
  if (img->firstp < row->firstp) {
    /* there are empty pixels at the left of the row */
    int leading_empty_pixels = row->firstp - img->firstp;

    /* find out how many we can ignore immediately - the smaller of leading_empty_pixels and pixels_to_crop */
    int leading_to_crop = pixels_to_crop < leading_empty_pixels ? pixels_to_crop : leading_empty_pixels;
    REPORT_D("%d leading empty pixels\n", leading_empty_pixels)
    /* since we use the smaller of both, these subtractions cannot go negative */
    leading_empty_pixels -= leading_to_crop;
    pixels_to_crop -= leading_to_crop;

    /* now we have leading_empty_pixels many pixels left but they might be subject to right hand cropping, too */
    if (leading_empty_pixels > pixels_to_write)
      leading_empty_pixels = pixels_to_write;
    memset(data_out, 255, 3 * leading_empty_pixels);
    pixels_to_write -= leading_empty_pixels;
    data_out += 3 * leading_empty_pixels;
  }
  if (row->firstp <= row->lastp && pixels_to_write > 0) {
    /* there are some real pixels (should always be the case) */
    int row_pixels = row->lastp - row->firstp + 1;

    if (row->cid == CID_UNCOMPRESSED) {
      /* copy the data across */
      int main_to_crop = pixels_to_crop < row_pixels ? pixels_to_crop : row_pixels;
      row_pixels -= main_to_crop;
      pixels_to_crop -= main_to_crop;
      buffer += 3 * main_to_crop;        /* skip the cropped source pixels in the buffer */
      REPORT_D("%d uncompressed data pixels\n", row_pixels);
      if (row_pixels > pixels_to_write)
        row_pixels = pixels_to_write;
      memcpy(data_out, buffer, 3 * row_pixels);
      pixels_to_write -= row_pixels;
      data_out += 3 * row_pixels;
      buffer += 3 * row_pixels;
    }
    else if (row->cid == CID_SIMPLE_RLE) {
      /* expand the RLE data */
      unsigned char xbyte = row->pad1;
      int pixels = 0;
      const int to_do = row_pixels;                      /* NB - to_do is the real number of pixels left in the compressed row */
      REPORT_D("RLE to_do = %d", to_do)                  /*      as opposed to pixels_to_write which is the number of pixels */
      REPORT_D("pixels_to_write = %d", pixels_to_write)  /*      we are still allowed to write to the output buffer */
      while(pixels < to_do && pixels_to_write > 0) {
        unsigned char b = *buffer++;
        if (b == xbyte) {
          int num = *buffer++;
          unsigned char p1 = *buffer++;
          unsigned char p2 = *buffer++;
          unsigned char p3 = *buffer++;
          ++num;          /* the counter as stored represented the number of additional bytes */
          while(num-- && pixels_to_write > 0) {
            if (pixels_to_crop)
              pixels_to_crop--;
            else {
              *data_out++ = p1; *data_out++ = p2; *data_out++ = p3;
              pixels_to_write--;
            }
            pixels++;     /* number of input pixels processed */
          }
        }
        else {
          if (pixels_to_crop)
            pixels_to_crop--;
          else {
            *data_out++ = b; *data_out++ = *buffer++; *data_out++ = *buffer++;
            pixels_to_write--;
          }
          pixels++;
        }
      }
    }
    else {
      gui_error2("Unknown compression type");
      return 0;
    }
  }
  if ((img->lastp > row->lastp || img->invented_columns) && pixels_to_write > 0) {
    /* there are empty pixels at the right of the row */

    /* if pixels_to_crop > 0 we still have not cropped enough to the left so subtract it */
    int trailing_pixels = (img->lastp > row->lastp) ? img->lastp - row->lastp : 0;
    REPORT_D("%d trailing pixels\n",trailing_pixels);
    /* sort out left hand cropping */
    int trailing_to_crop = pixels_to_crop < trailing_pixels ? pixels_to_crop : trailing_pixels;
    trailing_pixels -= trailing_to_crop;
    pixels_to_crop -= trailing_to_crop;
    REPORT_D("%d trailing pixels (cropped)\n",trailing_pixels);

    trailing_pixels += img->invented_columns;
    REPORT_D("%d trailing pixels (cropped, plus extension)\n",trailing_pixels);

    /* we might have to do some right hand cropping, too */
    if (trailing_pixels > pixels_to_write)
      trailing_pixels = pixels_to_write;
    REPORT_D("%d trailing pixels to be written\n",trailing_pixels);
    memset(data_out, 255, 3 * trailing_pixels);
    data_out += 3 * trailing_pixels;
    pixels_to_write -= trailing_pixels;
  }

  if (data_out != data + 3 * img->virtual_width || pixels_to_write != 0)
  {
    gui_error2("Inconsistency in row decompression");
  }
  REPORT("row decompressed")
  return 1;
}

const char *get_image_appname(struct stp_image *image)
{
  return "GPDriver";
}

void progress_conclude_image(struct stp_image *image)
{
  /* do nothing */
  /* printf("progress_conclude\n");*/
}

os_error* read_word(os_fw file_h, int* wordp)
{
  return read_block(file_h, (char*) wordp, 4);
}

os_error* read_block(os_fw file_h, char* buff, int num)
{
  os_error* err = NULL;
  byte* p = (byte*)buff;
  int oldnum;
  while(num) {
    oldnum = num;
    if ((err=xosgbpb_readw(file_h,p,num,&num)) != NULL) return err;
    p += oldnum - num;
  }
  return err;
}

/* read a string from a given file position and put a pointer to it
   at a given address */
os_error* read_string(os_fw file_h, int offset, char** buffp)
{
  os_error* err;
  int len = 0;
  char* buff;
  char b;
  if ((err = xosargs_set_ptrw(file_h, offset)) != NULL) return err;
  do {
    bits psr;
    if ((err = xos_bget(file_h, &b, &psr)) != NULL) return err;
    len++;
  } while(b);
  buff = malloc(len);        /* len includes the terminating '\0' */
  if (!buff) return &could_not_allocate;
  *buffp = buff;
  if ((err = xosargs_set_ptrw(file_h, offset)) != NULL) return err;
  return read_block(file_h, buff, len);
}

/* make sure that the next row is fully present in the buffer */
int read_next_row(gpd_image_t* img)
{
  int space_in_buffer = BUFFSIZE - img->cached;
  int left_in_file, ll;

  REPORT_D("read_next_row img->cached = %d", img->cached)

    if (img->cached) {
      /* a full row is in the cache */
      int row_len = *((int*)(img->buffer + img->boff));
      REPORT_D("prev row_len = %d", row_len)
      img->boff += row_len;
      img->cached -= row_len;

      if (img->cached) {
        /* we need this check because we could have just consumed the complete rest of the buffer,
           which would mean that the following length field is not in the cache! */
        row_len = *((int*)(img->buffer + img->boff));
        REPORT_D("current row_len = %d", row_len)
        if (row_len <= img->cached) {
          /* the full row is in memory */
          REPORT("PRESENT")
          img->current_row++;
          return 1;
        }
        else {
          REPORT("MOVE")
          /* we need to cache some more data, so move remainder to beginning */
          memmove(img->buffer, img->buffer + img->boff, img->cached);
        }
      }
    }

    /* fill buffer */
    left_in_file = img->size - img->current_off;
    ll = left_in_file < space_in_buffer ? left_in_file : space_in_buffer;
    REPORT_D("Reading %d bytes", ll)
    if (gui_complain(read_block(img->job_file_h, img->buffer + img->cached, ll))) return 0;
    REPORT("Read")
    img->current_off += ll;
    img->cached += ll;
    img->boff = 0;
    if (*((int*)(img->buffer)) > BUFFSIZE) {
      gui_error2("Error: Row is larger than buffer size!"); return 0;
    }
    img->current_row++;
    REPORT("read_next_row ret")
    return 1;
}

/* reset the image, so it can be printed again */
int gpdi_reset_image(gpd_image_t* img)
{
  if (gui_complain(xosargs_set_ptrw(img->job_file_h, img->rowoff))) return FALSE;
  img->current_off = img->rowoff;
  img->current_row = -1;
  img->cached = 0;
  img->boff = 0;
  return TRUE;
}
