/*   GPDriver source gpdriver.c
 *   $Id: gpdriver.c 1.19 2004/11/26 17:03:09 root Exp root $
 *
 *   GPDriver - Gimp-Print based printer spooler for RISC OS/GemPrint
 *   Copyright (C) 2004 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
 */

/*   The GPDriver command-line tool reads in a GemPrint job file and prints it to
 *   the given print job destination stream using libgimpprint. This
 *   implementation is completely independent of the RISC OS printing system.
 *   It only uses OSLib to interface with RISC OS (and the Toolbox).
 *
 *   GPDriver prints a GemPrint job, i.e., one GemPrint job file for each page to
 *   be printed. It consults a printer configuration file that holds the advanced
 *   printer-specific settings.
 *
 *   The printer-specific settings are read from Choices:GPDriver.<printer name>
 *   (where printer name is the short, editable name in the printer
 *   configuration window, so you can have multiple instances of the same
 *   printer with different properties). If the choices file cannot be found,
 *   then the default choices for this printer are used (possibly with poor
 *   results).
 */

/*   usage: gpdriver <job prefix> <number of pages> <output path> */
/*          where <job prefix> is an arbitrary string (GemPrint passes a
 *          four-nibble hex string), <number of pages> is a decimal representation
 *          of the number of job files with this prefix (the page sequence number
 *          is encoded as a four-nibble hex number suffix in each file name) and
 *          <output path> is the filename to write the output to
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gimp-print/gimp-print.h"
#include "oslib/osfind.h"
#include "oslib/osfile.h"
#include "oslib/osgbpb.h"
#include "oslib/syslog.h"
#include "oslib/wimp.h"
#include "image.h"
#include "gpd_gui.h"
#include "img_types.h"
#include "gpshared.h"

/* the location of the GemPrint job files */
#define JOB_FILE_PATH "<Wimp$ScrapDir>.GemPrint."

/* the location of the printer settings files */
#define SETTINGS_FILE_PATH "Choices:GPDriver."
#define GPDRIVER_SETTINGS_LEAF_NAME "GPDriver"

#define SETTINGS_HEADER "# GPSetup "
/* the file version of the settings file that we require */
#define CURRENT_SETTINGS_FVER 100

#define FILETYPE_POSCRIPT (0xff5)

#define DEBUG 0

#include "debug.h"

/* Gimp-Print settings are a list of key-value pairs */
typedef struct gimp_settings_s {
  char* key;
  char* value;
  struct gimp_settings_s* next;
} gimp_settings;

os_fw job_file_h = 0, output_file_h = 0;

/* memory state */

/* a structure to hold information about an allocated DA */
struct da_struct {
  os_dynamic_area_no no;    /* dynamic area number */
  byte* next_free;          /* address of the next free byte */
  int physical_free;        /* number of free bytes (physical, i.e., real memory) */
  int logical_free;         /* number of logical free bytes (i.e., related to the maximum size of the DA) */
  int size;                 /* current physical size of DA */
  struct da_struct* next;
  struct da_struct* prev;
};

int use_da = FALSE;                /* do we want to use DAs at all? */
struct da_struct *first_da = NULL; /* pointer to first DA structure (linked list) */
int da_allocated, da_freed;        /* number of blocks currently allocated and freed in the DAs */
int mem_estimated_k;
int print_mem_allocated;
int keep_open = TRUE;              /* do we want to keep the window open after printing has completed? */

static void read_gpdriver_choices(void);
static void do_print(char* job_prefix, int pages, char* output_path);
static void close_files(void);
static int print_image(gpd_image_t* img, gimp_settings* settings, int page_no, int copy_no, int copies);
static int set_page_parameters(stp_vars_t stp_vars, stp_printer_t printer, int is_postscript, gpd_image_t* img);
static gimp_settings* read_settings(const char* printer, int report_errors);
static void* checked_alloc(size_t size);
void intercept_stp_memory(int start);
void remove_das(void);

int main(int argc, char* argv[])
{
  int pages;

  gui_log_msg("GPDriver started", 100);
  if (argc != 4) gui_fatal_error2("GPDriver: invalid command line");

  pages = atoi(argv[2]);
  if (pages <= 0) {
    gui_log_msg("Nothing to print", 100); exit(EXIT_SUCCESS);
  }
  read_gpdriver_choices();

#ifndef GPDRIVER_GUI
  printf("Job number: %s, pages: %s, output to: %s\n", argv[1], argv[2], argv[3]);
#endif

  if (!gui_init()) {
    gui_fatal_error2(gui_lookup("CNIG"));
  }
  atexit(close_files);                /* make sure that open files are closed when the program is aborted */

  do_print(argv[1], pages, argv[3]);
}

static void read_gpdriver_choices(void)
{
#if 0
/*## not implemented yet */
  char filename[256];
  os_fw config_file_h;
  os_error* err;

  sprintf(filename,"%s%s",SETTINGS_FILE_PATH,GPDRIVER_SETTINGS_LEAF_NAME);
  if ((err = xosfind_openinw(osfind_NO_PATH + osfind_ERROR_IF_DIR + osfind_ERROR_IF_ABSENT,
                             filename, NULL, &config_file_h)) == NULL) {
    /* file is present */
    /*## read general choices */
    osfind_closew(config_file_h);
  }
  else {
    /*## use defaults */
  }
#endif
}

static void do_print(char* job_prefix, int pages, char* output_path)
{
  gpd_image_t* img;
  os_error* err;
  int i;
  char filename[256];
  gimp_settings* settings = NULL;
  int print_success = 1;
  int pages_printed = 0;
  int is_postscript;
  int copies, this_copy;

  gui_set_pages(pages); gui_set_page(0);

  gui_inform(gui_lookup("INIT"), 1); gui_inform("", 2);
  stp_init();

  gui_inform(gui_lookup("OPEN"), 1);
  if ((err = xosfind_openoutw(osfind_NO_PATH + osfind_ERROR_IF_DIR,
                              output_path, NULL, &output_file_h)) != NULL) {
    gui_fatal_error(err);
  }

  for (i = 0; i < pages; i++) {
    gui_inform_dd(gui_lookup("OPENdd"), i+1, pages, 1);
    sprintf(filename,"%sJ%s%04X",JOB_FILE_PATH,job_prefix,i);
    if ((err = xosfind_openinw(osfind_NO_PATH + osfind_ERROR_IF_DIR + osfind_ERROR_IF_ABSENT,
                             filename, NULL, &job_file_h)) != NULL) {
      gui_fatal_error(err);
    }
    gui_inform_dd(gui_lookup("PRINTdd"), i+1, pages, 1);
    gui_set_page(i+1);

    /* read the job file and create an image structure from it */
    img = gpdi_create_image(job_file_h, gui_progress_row);
    if (!img) {
      gui_fatal_error2(gui_lookup("CNRPJF"));
    }

    if (i == 0) {
      /* first page, so read the settings file for the printer that was used */
      /* in theory, each page could have a different number of copies, but we
         ignore this when forecasting the number of pages - we still read copies
         from each image, so at least the real requested number will be printed */
      is_postscript = (strncmp(gpdi_get_long_name(img),"PostScript",10) == 0);
      REPORT("calling read_settings")
      settings = read_settings(gpdi_get_short_name(img), TRUE);
      if (settings == (gimp_settings*)-1) {
        gui_error2(gui_lookup("SPST"));
        settings = read_settings(gpdi_get_short_name(img), TRUE);
      }
      if (!settings || settings == (gimp_settings*)-1) {
        /* we did not get any settings so ask the user whether he really wants to
           print using the default settings */
        gui_error2(gui_lookup("DEFS"));
        settings = NULL;
      }
      else {
        REPORT("got settings")
      }
    }

    /* the gui_error2 above may have caused the job to be aborted already, but in this case, the
       loop is not executed at all */
    copies = gpdi_get_copies(img);
    print_success = TRUE;
    for (this_copy = 0; this_copy < copies && !gui_aborted() && print_success; this_copy++) {
      REPORT("calling gui_progress_start")
      gui_progress_start();

      /* do the main work */
      REPORT("calling print_image")
      print_success = print_image(img, settings, i + 1, this_copy + 1, copies);  /* may cause gui_aborted() to become true
                                                                                    but we first clean up before checking below */
      REPORT("returned from print_image")
      if (!gui_aborted() && print_success) {
        gui_set_pages_printed(++pages_printed);
        if (this_copy + 1 < copies) {
          /* reset the image for the next iteration */
          if (!gpdi_reset_image(img)) print_success = FALSE;
        }
      }
    }

    osfind_closew(job_file_h);                     /* close the job file */
    job_file_h = 0;

    /* delete the job file */
    xosfile_delete(filename, NULL, NULL, NULL, NULL, NULL);

    /* reclaim some memory */
    gpdi_dispose(img);
    if (gui_aborted()) break;
  }

  /* close the output file */
  osfind_closew(output_file_h);
  output_file_h = 0;
  if (pages && is_postscript) {
    xosfile_set_type(output_path, FILETYPE_POSCRIPT);
  }
  if (gui_aborted() || !print_success) {
    REPORT("GPDriver aborted")
    gui_log_msg("GPDriver aborted",100);
  }
  else {
    REPORT("GPDriver finished")
    gui_log_msg("GPDriver finished",100);
  }
  gui_progress_end(keep_open);      /* does not return */
}

/* return -1 in case the settings file cannot be found */
/* return NULL in case the file can be found but contains errors */
static gimp_settings* read_settings(const char* printer, int report_errors)
{
  os_error* err;
  char filename[256];
  fileswitch_object_type obj_type;
  int file_size;
  int fver;
  char* file_buffer;
  char* p;
  char *q;
  int line;
  gimp_settings* settings = NULL;
  gimp_settings* this_setting;
  gimp_settings** last_next = &settings;

  REPORT("read_settings")
  sprintf(filename, SETTINGS_FILE_PATH"%s",printer);
  /* find out the file size */
  if ((err = xosfile_read_stamped(filename, &obj_type,NULL,NULL,&file_size,NULL,NULL)) != NULL || obj_type != 1) {
    /* we did not find the settings file, so just return with no settings */
    return (gimp_settings*)-1;
  }
  /* we load the complete file in a buffer */
  /* then, we can use the contents directly by using pointers into the buffer */
  file_buffer = checked_alloc(file_size + 2);   /* if the last line does not have a terminator, we will add one */
  err = xosfile_load_stamped_no_path(filename,(byte*)file_buffer,&obj_type, NULL,NULL,NULL,NULL);
  if (err) {
    if (report_errors) gui_wimp_error(err);
    return NULL;
  }

  /* OK, we have got the file */
  /* we terminate the last line in case there is no LF (if there is already an LF we add an empty line, which is
     harmless), finally we add a NUL byte to terminate the file */
  /* doing so will save us a lot of tests below because we know that there is a minimum of one LF-terminated line
     in the buffer and that the buffer is NUL-terminated */
  file_buffer[file_size] = '\n';
  file_buffer[file_size + 1] = '\0';
  p = file_buffer;

  /* check the leading comment */
  if (strncmp(p, SETTINGS_HEADER, strlen(SETTINGS_HEADER)) != 0) {
    /* incorrect format */
    if (report_errors) gui_wimp_error2(gui_lookup("INV"));
    return NULL;
  }
  p += strlen(SETTINGS_HEADER);

  /* we expect the file version number on the same line */
  if (*p < '0' || *p > '9') return NULL;
  q = p;
  while(*++p != 10) {
    if (*p < '0' || *p > '9') {
      if (report_errors) gui_wimp_error2(gui_lookup("INV"));
      return NULL;
    }
  }
  *p++ = '\0';                                  /* replace the LF by a NUL and proceed to start of next line */
  fver = atoi(q);                               /* get the decimal file version number */
  if (fver > CURRENT_SETTINGS_FVER) {
    if (report_errors) gui_wimp_error2(gui_lookup("LATER"));
    return NULL;
  }
  if (fver < CURRENT_SETTINGS_FVER) {
    /* convert the data in some way, not needed at the moment */
    /* after the conversion, adjust fver to the current version */
  }
  if (fver != CURRENT_SETTINGS_FVER) {
    if (report_errors) gui_wimp_error2(gui_lookup("EARLY"));
    return NULL;
  }

  /* read the remaining lines */
  line = 2;
  while(*p) {
    if (*p == '#' || *p == 10) {
      /* comment line or empty line, just skip */
      while(*p++ != 10) ;
      ++line;
      continue;
    }
    if (*p == '+') {
      /* a Gimp-Print setting */
      q = ++p;
      while(*p && *p != ':') p++;
      if (!*p) {
        /* no colon on this line, so this is an error */
        /* we return the settings we have collected so far and ignore the rest */
        if (report_errors) gui_wimp_error_d(gui_lookup("INC"),line);
        return settings;
      }
      *p++ = '\0'; /* replace the colon by NUL */
      /* now, q points to the key and p points to the value */
      /* allocate a block and link it into the list */
      this_setting = checked_alloc(sizeof(gimp_settings));
      this_setting->key = q;
      this_setting->value = p;
      this_setting->next = NULL;
      *last_next = this_setting;
      last_next = &this_setting->next;
      while(*p != 10) p++;
      *p++ = '\0'; /* replace the LF by NUL */
      ++line;
    }
    else if (strncmp(p,"KeepOpen:",9)==0) {
      if (strncmp(p+9,"No",2)==0) keep_open = 0;
      while(*p != 10) p++;
      ++line;
    }
    else if (*p == '#' || *p == 10) {
      /* comment line or empty line, just skip */
      while(*p++ != 10) ;
      ++line;
    }
    else {
      /* ignore other lines as well */
      while(*p++ != 10) ;
      ++line;
    }
  }
  return settings;
}

void close_files(void)
{
  if (job_file_h) {
    osfind_closew(job_file_h);
    job_file_h = 0;
  }
  if (output_file_h) {
    osfind_closew(output_file_h);
    output_file_h = 0;
  }
  if (use_da && first_da) {
    remove_das();
    use_da = 0;
  }
}

static int print_error_reported;

void print_error(void *data, const char *buffer, size_t bytes)
{
  char msg[256];
  REPORT("print error")
  strncpy(msg,buffer,256);
  if (bytes < 256) msg[bytes] = '\0'; else msg[255] = '\0';
  gui_error2(msg);
  print_error_reported = 1;
  if (gui_aborted()) gui_progress_end(keep_open);   /* does not return */
}

static int data_dumped;

void print_output(void *data, const char *buffer, size_t bytes)
{
  const byte* buff = (const byte*)buffer;
  int unwritten = bytes;
  /* printf("Dumping %d bytes of data.\n", bytes); */
  /* REPORT_D("Dumping %d bytes", bytes) */
  while(unwritten) {
    os_error *err;
    if ((err = xosgbpb_writew(output_file_h,buff,unwritten,&unwritten)) != NULL) {
      gui_fatal_error(err); return;
    }
    buff += unwritten;
  }
  data_dumped += bytes;
}

#if 0
/* old 4.2.5 interface */
extern void
stp_register_memory_funcs(int (*init_f)(size_t), void* (*zalloc_f)(size_t), void (*free_f)(void*));
#endif

extern void
stp_register_memory_funcs(void*(*cmalloc)(size_t),void(*cfree)(void*));

extern void
stp_register_memory_inform_func(void (*mfunc)(size_t));

#if 0
#define BASE_SIZE (1024 * 1024)     /* rough estimate */
/* only in old 4.2.5 interface */
int init_mem(size_t size)
{
  /* maybe check the amount of available memory? */
  /* better estimate for overall requirement? */
  gui_inform_d(gui_lookup("MREQd"), (size + BASE_SIZE) >> 10, 2);
  return 1;
}
#endif

void memory_inform(size_t mem)
{
  int current_slot, free_size;
  /* we read the current slot size and add to it the additional amount we require */
  xwimp_slot_size(-1,-1,&current_slot, NULL, &free_size);
  REPORT_D("current slot = %dk", current_slot >> 10);
  mem_estimated_k = (current_slot + mem) >> 10;
  mem_estimated_k = 100 * (mem_estimated_k / 100) + 100;
  REPORT_D("mem_estimated_k = %d",mem_estimated_k);
}

void report_memory(size_t size)
{
  /* let us find out the additional amount of memory we need */
  int more_mem_k = mem_estimated_k - (print_mem_allocated >> 10);
  if (more_mem_k <= 0) more_mem_k = size >> 10;
  if (more_mem_k == 0) more_mem_k = 1;
  gui_error2_d(gui_lookup("NEEDMEMd"), more_mem_k);
  if (gui_aborted()) gui_progress_end(keep_open);   /* does not return */
}

/* use DAs with a max size of 32MB */
#define DA_SIZE (32*1024*1024)
/* increase size in steps of 256k */
#define DA_DELTA (256*1024)

/* our strategy is to divert all memory claims to the dynamic area(s)
   and then throw the area(s) away after the page has been printed */
void* alloc_mem_da(size_t size)
{
  void* memptr = NULL;
  struct da_struct* this_da = first_da;
  struct da_struct* prev = NULL;
  struct da_struct** last_next = &first_da;
  os_error* err;
  size = (size + 3) &~3;    /* whole number of words! */
  /* REPORT_D("Allocate %d",size); */
  while (this_da) {
    if (this_da->logical_free >= size) {
      /* we can fit the block in here */
      /* assuming we can extend the DA accordingly */
      /* REPORT_D("Use existing DA %d", this_da->no); */
      break;
    }
    last_next = &(this_da->next);
    prev = this_da;
    this_da = this_da->next;
  }
  if (!this_da) {
    /* we could not fit the block in one of our existing DAs, so allocate a new one */
    /* and add it to the end of the list */
    os_dynamic_area_no no;
    byte* base;
    int init_size = DA_DELTA;
    int max_size;
    /* REPORT("Allocate new DA"); */
    if (size > init_size) init_size = size + DA_DELTA;
    /* REPORT_D("Allocate physical size %dk",init_size >> 10); */
    if ((err=xosdynamicarea_create(osdynamicarea_ALLOCATE_AREA, init_size, osdynamicarea_ALLOCATE_BASE,
                                   os_AREA_ACCESS_READ_WRITE | os_AREA_NO_USER_DRAG,
                                   DA_SIZE, NULL, NULL, "GPDriver workspace", &no, &base, &max_size)) != NULL) {
      /* allocation failed, so try exact size */
      init_size = size;
      while ((err=xosdynamicarea_create(osdynamicarea_ALLOCATE_AREA, init_size, osdynamicarea_ALLOCATE_BASE,
                                        os_AREA_ACCESS_READ_WRITE | os_AREA_NO_USER_DRAG,
                                        DA_SIZE, NULL, NULL, "GPDriver workspace", &no, &base, &max_size)) != NULL) {
        /* allocation still failed, so notify the user */
        report_memory(size);
      }
    }
    /* REPORT_D("Allocated DA %d",no); */
    /* REPORT_D("DA allocated, max size = %dk",max_size >> 10); */

    /* we need to read back the actual initial size allocated */
    if ((err=xosdynamicarea_read(no, &init_size, NULL,NULL, &max_size, NULL,NULL,NULL)) != NULL) {
      /* allocation failed! */
      gui_fatal_error(err);
      return NULL;
    }

    this_da = malloc(sizeof(struct da_struct));
    *last_next = this_da;
    this_da->no = no;
    this_da->prev = prev;
    this_da->next = NULL;
    this_da->size = init_size;
    this_da->physical_free = init_size;
    this_da->logical_free = max_size;
    this_da->next_free = base;
  }
  /* do we need to extend the DA? */
  if (this_da->physical_free < size) {
    /* extend it by DA_DELTA, but at least by size - physical_free */
    int delta = DA_DELTA;
    /* REPORT("Extending DA"); */
    if (delta < size - this_da->physical_free) {
      delta = size - this_da->physical_free;
    }
    while ((err = xos_change_dynamic_area(this_da->no, delta, &delta)) != NULL) {
      report_memory(size);
    }
    /* REPORT_D("Extended by %d",delta); */
    this_da->physical_free += delta;
    this_da->size += delta;
  }

  /* finally, there is enough space in our DA, so we can allocate the block */
  this_da->physical_free -= size;
  this_da->logical_free -= size;
  memptr = (void*)(this_da->next_free);
  this_da->next_free += size;
  da_allocated++;
  print_mem_allocated += size;
  /* REPORT_D("Block allocated, bytes remaining %d",this_da->physical_free); */
  return memptr;
}

void* alloc_mem_heap(size_t size)
{
  void* memptr;
  while((memptr = malloc(size)) == NULL) {
    report_memory(size);
  }
  print_mem_allocated += size;
  return memptr;
}

void free_mem_da(void* ptr)
{
  /* we do nothing */
  da_freed++;
}

void free_mem_heap(void* ptr)
{
  free(ptr);
}

int interpret_image_type(const char* name)
{
  if (strcmp(name, IMAGE_LINE_ART_REP) == 0) return IMAGE_LINE_ART;
  if (strcmp(name, IMAGE_SOLID_TONE_REP) == 0) return IMAGE_SOLID_TONE;
  if (strcmp(name, IMAGE_CONTINUOUS_REP) == 0) return IMAGE_CONTINUOUS;
  else return IMAGE_CONTINUOUS;
}

int interpret_output_type(const char* name)
{
  if (strcmp(name, OUTPUT_GRAY_REP) == 0) return OUTPUT_GRAY;
  if (strcmp(name, OUTPUT_COLOR_REP) == 0) return OUTPUT_COLOR;
  if (strcmp(name, OUTPUT_MONOCHROME_REP) == 0) return OUTPUT_MONOCHROME;
  else return OUTPUT_COLOR;
}

/* return 1 for success, 0 for failure */
/* page number starts from 1 */
int print_image(gpd_image_t* img, gimp_settings* settings, int page_no, int copy_no, int copies)
{
  stp_printer_t printer;
  stp_vars_t stp_vars;
  const stp_printfuncs_t *printfuncs;
  int is_postscript = 0;
  int gamma_set = 0;

  printer = stp_get_printer_by_long_name(gpdi_get_long_name(img));
  if (printer == NULL) {
    REPORT("printer not known")
    gui_error_c(gui_lookup("PNK"),gpdi_get_long_name(img)); return 0;
  }

  /* allocate a structure and set the defaults for this printer */
  REPORT("allocate vars")
  stp_vars = stp_allocate_vars();

  stp_set_errdata(stp_vars, NULL);
  stp_set_errfunc(stp_vars, &print_error);
  REPORT("set defaults")
  stp_set_printer_defaults(stp_vars,printer,NULL);
  stp_set_page_number(stp_vars, page_no);

  is_postscript = (strncmp(gpdi_get_long_name(img),"PostScript",10) == 0);

  /* set the resolution unless this is PostScript */
  if (!is_postscript) {
    REPORT("set resolution")
    stp_set_resolution(stp_vars,gpdi_get_resolution_name(img));
  }
  /* PageSize and Resolution are set in !Printers */

  /* preset image type and dither type, just in case those are not specified later */
  REPORT("preset image type")
  stp_set_image_type(stp_vars,IMAGE_CONTINUOUS);
  REPORT("preset dither algorithm")
  stp_set_dither_algorithm(stp_vars,"Adaptive");
  /* we cannot preset the media type since it is printer-specific */
  /* we do not set the output type, OUTPUT_COLOR is the default anyway */

  /* set up the printer from the settings structure */
  /* options should include InkType, MediaType, InputSlot, DitherType and ImageType */
  while(settings) {
    REPORT_C("setting '%s'",settings->key)
    if (strcmp(settings->key, "InkType") == 0) {
      stp_set_ink_type(stp_vars, settings->value);
    }
    else if (strcmp(settings->key, "MediaType") == 0) {
      stp_set_media_type(stp_vars, settings->value);
    }
    else if (strcmp(settings->key, "InputSlot") == 0) {
      stp_set_media_source(stp_vars, settings->value);
    }
    /* these three have printer-independent values */
    else if (strcmp(settings->key, "DitherType") == 0) {
      stp_set_dither_algorithm(stp_vars, settings->value);
    }
    else if (strcmp(settings->key, "ImageType") == 0) {
      stp_set_image_type(stp_vars, interpret_image_type(settings->value));
    }
    else if (strcmp(settings->key, "OutputType") == 0) {
      stp_set_output_type(stp_vars, interpret_output_type(settings->value));
    }
    else if (strcmp(settings->key, "Cyan") == 0) {
      float val = atof(settings->value);
      stp_set_cyan(stp_vars, val);
    }
    else if (strcmp(settings->key, "Magenta") == 0) {
      float val = atof(settings->value);
      stp_set_magenta(stp_vars, val);
    }
    else if (strcmp(settings->key, "Yellow") == 0) {
      float val = atof(settings->value);
      stp_set_yellow(stp_vars, val);
    }
    else if (strcmp(settings->key, "Saturation") == 0) {
      float val = atof(settings->value);
      stp_set_saturation(stp_vars, val);
    }
    else if (strcmp(settings->key, "Gamma") == 0) {
      float val = atof(settings->value);
      stp_set_gamma(stp_vars, val);
      gamma_set = 1;
    }
    else if (strcmp(settings->key, "Density") == 0) {
      float val = atof(settings->value);
      stp_set_density(stp_vars, val);
    }
    else if (strcmp(settings->key, "Brightness") == 0) {
      float val = atof(settings->value);
      stp_set_brightness(stp_vars, val);
    }
    else if (strcmp(settings->key, "Contrast") == 0) {
      float val = atof(settings->value);
      stp_set_contrast(stp_vars, val);
    }
    else {
      /* unknown settings, we had better report an error */
      gui_error_c(gui_lookup("UNKS"), settings->key);
    }
    settings = settings->next;
  }
  if (!gamma_set) stp_set_gamma(stp_vars, GAMMA_DEFAULT_F);

  /* standard settings */
  stp_set_input_color_model(stp_vars,COLOR_MODEL_RGB);
  /* we do not set the output colour model - we take the default
     (should be CMY anyway - we could not do better than setting
     CMY output model) */
  stp_set_orientation(stp_vars,ORIENT_PORTRAIT);
  /* make sure that our bitmap is printed exactly onto device pixels */
  stp_set_scaling(stp_vars, (float)-gpdi_get_virtual_res(img));

  /* canon end job command is only sent in JOB MODE, so we choose that */
  stp_set_job_mode(stp_vars, STP_JOB_MODE_JOB);

  /* let Gimp-Print compute the parameters it needs */
  if (!set_page_parameters(stp_vars, printer, is_postscript, img)) {
    stp_free_vars(stp_vars); return 0;
  }

  /* Now print using libgimpprint */
  printfuncs = stp_printer_get_printfuncs(printer);

  /* register the function to output the printer codes */
  stp_set_outdata(stp_vars, NULL);
  stp_set_outfunc(stp_vars, &print_output);

  gui_inform(gui_lookup("VER"),2);
  if (!printfuncs->verify(printer, stp_vars)) {
    gui_fatal_error2(gui_lookup("CNV")); stp_free_vars(stp_vars); return 0;
  }

  if (copies > 1) gui_inform_dd(gui_lookup("PRINTCdd"), copy_no, copies, 2);
  else gui_inform("", 2);

  /* start intercepting memory allocation */
  mem_estimated_k = 0;
  intercept_stp_memory(1);
  data_dumped = 0; print_error_reported = 0;

  REPORT("calling start_job")
  printfuncs->start_job(printer, gpdi_get_stp_image(img), stp_vars);

  /* CALL THE MAIN PRINT FUNCTION */
  REPORT("calling print")
  printfuncs->print(printer, gpdi_get_stp_image(img), stp_vars);

  REPORT("calling end_job")
  printfuncs->end_job(printer, gpdi_get_stp_image(img), stp_vars);

  /* stop intercepting memory allocation */
  intercept_stp_memory(0);
  stp_free_vars(stp_vars);
  if (print_error_reported) return 0;

  /* if we do arrive here, we have finished successfully */
  gui_inform(gui_lookup("FIN"),2);
  gui_inform_d(gui_lookup("DUMP"),data_dumped,2);
  return 1;
}

/* start (start=1) or stop (start=0) intercepting Gimp-Print's memory allocation */
void intercept_stp_memory(int start)
{
  if (start) {
    /* we need to check whether we need to use dynamic areas */
    int current_slot, max_size, free_size;
    REPORT("Start intercept memory");
    use_da = 0;
    /* read the maximum app slot size */
    xos_read_dynamic_area(os_DYNAMIC_AREA_APPLICATION_SPACE, NULL, NULL, &max_size);
    REPORT_D("app slot max size = %dk", max_size >> 10);
    xwimp_slot_size(-1,-1,&current_slot, NULL, &free_size);
    REPORT_D("current slot = %dk", current_slot >> 10);
    REPORT_D("free memory = %dk", free_size >> 10);
    /* if we can fit all the free memory in the app slot, then we will not need a DA */
    if (current_slot + free_size > max_size) {
      /* we cannot make use of all the free memory within the wimp slot, so
         intercept Gimp-Print's memory management */
      REPORT("Use DA");
      use_da = TRUE;
      stp_register_memory_funcs(&alloc_mem_da, &free_mem_da);
      da_allocated = 0; da_freed = 0;
    }
    else {
      /* use the heap */
      stp_register_memory_funcs(&alloc_mem_heap, &free_mem_heap);
    }
    print_mem_allocated = 0;
    stp_register_memory_inform_func(&memory_inform);
  }
  else {
    REPORT("End intercept memory");
    stp_register_memory_funcs(NULL, NULL);
    if (use_da) {
      /* stop intercepting Gimp-Print's memory management */
      remove_das();
      use_da = 0;
    }
    stp_register_memory_inform_func(NULL);
  }
}

void remove_das(void)
{
  struct da_struct* this_da = first_da;
  /* remove all DAs in reverse order of allocation */
  /* first, find the last da in list */
  while(this_da && this_da->next) {
    this_da = this_da->next;
  }
  /* then delete the items travelling backwards in the list */
  REPORT("Remove DAs");
  while(this_da) {
    struct da_struct* prev = this_da->prev;
    xosdynamicarea_delete(this_da->no);
    free(this_da); this_da = prev;
  }
  first_da = NULL;
}

/* work out media size from paper size info in job file */
/* return 1 for success */
int set_page_parameters(stp_vars_t stp_vars, stp_printer_t printer, int is_postscript, gpd_image_t* img)
{
  stp_papersize_t paper_size;
  int pwidth_mp, pheight_mp, pwidth, pheight, left, top, n_paper_sizes, this_paper;
  int i_left, i_right, i_top, i_bottom;   /* imageable area as reported from Gimp-Print */
  int i_top_from_top;                     /* top border of imageable area */
  int m_width, m_height;                  /* media size as reported from Gimp-Print */
  int moved_right = 0, moved_down = 0;
  stp_param_t* parameters;
  const char* our_paper;
  const stp_printfuncs_t *printfuncs = stp_printer_get_printfuncs(printer);

  pwidth_mp = gpdi_get_pwidth_mp(img);
  pheight_mp = gpdi_get_pheight_mp(img);
  pwidth = pwidth_mp / 1000;
  pheight = pheight_mp / 1000;

#ifndef GPDRIVER_GUI
  printf("Finding paper size (%d,%d)\n",pheight, pwidth);
#endif
  REPORT("set_page_parameters")

  /* try to find an appropriate media size in gimp-print */
  paper_size = stp_get_papersize_by_size(pheight, pwidth);

  /* we set the media size to a named paper size in gimp-print */
  /* this may or may not be overridden by set_page_width/height below */
  if (!paper_size) {
    /* we did not find the media size */
#ifndef GPDRIVER_GUI
    printf("Could not find paper size, using Custom");
#endif
    REPORT("Could not find paper size, trying Custom")
    stp_set_media_size(stp_vars,"Custom");
  }
  else {
#ifndef GPDRIVER_GUI
    printf("Found paper size %s\n",stp_papersize_get_name(paper_size));
#endif
    REPORT_C("Found paper size %s", stp_papersize_get_name(paper_size))
    stp_set_media_size(stp_vars,stp_papersize_get_name(paper_size));
  }

  if (!is_postscript) {
    /* now we check whether that page size is supported by the given printer! */
    our_paper = stp_get_media_size(stp_vars);
    parameters = printfuncs->parameters(printer, NULL, "PageSize", &n_paper_sizes);
    for (this_paper = 0; this_paper < n_paper_sizes; ++this_paper) {
      if (strcmp(our_paper, parameters[this_paper].name) == 0) break;
    }
    if (this_paper == n_paper_sizes) {
      /* we did not find our paper in the list! */
      /* we choose the smallest page that is bigger than the one we need */
      char msg2[256];
      int this_size = -1;
      int best_paper = -1;    /* no suitable paper found yet */
      REPORT("Paper size not supported by printer")
      for (this_paper = 0; this_paper < n_paper_sizes; ++this_paper) {
        const char* this_page_name = parameters[this_paper].name;
        stp_papersize_t this_paper_size = stp_get_papersize_by_name(this_page_name);
        int width = stp_papersize_get_width(this_paper_size);
        int height = stp_papersize_get_height(this_paper_size);
        if (width >= pwidth && height >= pheight) {
          REPORT_C("found suitable paper size %s", this_page_name)
          if (best_paper == -1 || (width * height < this_size)) {
            /* no suitable paper found yet or this one is smaller than the best match so far */
            REPORT("best candidate")
            best_paper = this_paper;
            this_size = width * height;
          }
        }
      }
      if (best_paper == -1) {
        REPORT("Did not find any suitable paper size")
        gui_fatal_error2(gui_lookup("PAGEnf2"));
        return 0;
      }
      sprintf(msg2,gui_lookup("PAGEnvc"),parameters[best_paper].text);
      gui_error2(msg2);
      if (gui_aborted()) return 0;
      stp_set_media_size(stp_vars,parameters[best_paper].name);
    }
  }

  /* we set the physical page width to the user's chosen page size */
  stp_set_page_width(stp_vars, pwidth);
  stp_set_page_height(stp_vars, pheight);

  /* read the imagable area */
  printfuncs->imageable_area(printer, stp_vars, &i_left, &i_right, &i_bottom, &i_top);
  REPORT_D("Imageable left %d",i_left);
  REPORT_D("Imageable right %d",i_right);
  REPORT_D("Imageable top %d",i_top);
  REPORT_D("Imageable bottom %d",i_bottom);

  /* we want the top border from the top */
  printfuncs->media_size(printer, stp_vars, &m_width, &m_height);
  i_top_from_top = m_height - i_top;
  REPORT_D("Imageable top from top %d pt", i_top_from_top);
  REPORT_D("Imageable top from top %d mm/10", 2540 * i_top_from_top / 720);

  /* Find out about the top and left margins
   * These are at least the paper margins as defined in !Printers and stored in the job
   * file, but there are additional factors (in the job file as well):
   * - FIRSTR/FIRSTP
   * - TOPM/LEFTM
   * FIRSTR/FIRSTP are offsets from PDumperGP when finding out that some top rows or left
   * columns are completely empty, in which case the resulting bitmap does not contain
   * them. TOPM/LEFTM are offsets from PDriverDP ("additional blank rows to print", "left
   * margin") when the printed area as passed by the application is smaller than the
   * printable area of the paper.
   * We ignore the paper borders completely, they have been dealt with by PDriverDP and
   * are now subsumed in TOPM/LEFTM.
   */
  /* left = left paper margin + FIRSTP + LEFTM, all converted to pt */
  REPORT_D("LEFTM %dpt",gpdi_get_leftm(img) * 72 / gpdi_get_xres(img))
  REPORT_D("LEFTM %dmm/10",2540 * gpdi_get_leftm(img) / gpdi_get_xres(img))
  REPORT_D("FIRSTP %dpt",gpdi_get_firstp(img) * 72 / gpdi_get_xres(img))
  REPORT_D("FIRSTP %dmm/10",2540 * gpdi_get_firstp(img) / gpdi_get_xres(img))
  REPORT_D("Page X0 %dpt",gpdi_get_page_x0_mp(img) / 1000);
  REPORT_D("Page X0 %dmm/10",254 * gpdi_get_page_x0_mp(img) / 7200);

  left = 72 * (gpdi_get_firstp(img) + gpdi_get_leftm(img)) / gpdi_get_xres(img);
#ifndef GPDRIVER_GUI
  printf("Left = %dpt (%d mm)\n",left,254 * left / 720);
  printf("Page Y1 = %dpt (%dmm)\n",gpdi_get_page_y1_mp(img) / 1000,(gpdi_get_page_y1_mp(img) / 1000) * 254 / 720);
  printf("FirstR = %d, YRes = %d\n",gpdi_get_firstr(img), gpdi_get_yres(img));
#endif
  REPORT_D("Left = %d pt", left);
  REPORT_D("Left = %d mm/10",2540 * left / 720);
  /* top = top paper margin + FIRSTR + TOPM, all converted to pt */
  top = 72 * (gpdi_get_firstr(img) + gpdi_get_topm(img))/ gpdi_get_yres(img);
#ifndef GPDRIVER_GUI
  printf("Top = %dpt (%d mm)\n",top,254 * top / 720);
#endif
  REPORT_D("Top = %d pt", top);
  REPORT_D("Top = %d mm/10",2540 * top / 720);

  /* now, these are relative to the page border, but Gimp-Print appears wants them relative
     to the imageable area */
  /* obviously, if they are smaller than the physical print border, we have a problem */
  if (i_top_from_top > top) { moved_down = 1; }
  if (i_left > left) { moved_right = 1; }

  if (moved_right || moved_down) {
    char msg[256];
    if (moved_right && moved_down) {
      sprintf(msg, gui_lookup("MVRDff"), 254.0 * (i_left - left) / 720, 254.0 * (i_top_from_top - top) / 720);
    }
    else if (moved_right) {
      sprintf(msg, gui_lookup("MVRf"), 254.0 * (i_left - left) / 720);
    }
    else {
      /* moved down only */
      sprintf(msg, gui_lookup("MVDf"), 254.0 * (i_top_from_top - top) / 720);
    }
    gui_error2(msg);
    if (gui_aborted()) return 0;
  }

  if (i_top_from_top > top) { top = i_top_from_top; }
  if (i_left > left) { left = i_left; }

  REPORT_D("setting left %d", left - i_left);
  stp_set_left(stp_vars, left - i_left);
  REPORT_D("setting top %d", top - i_top_from_top);
  stp_set_top(stp_vars, top - i_top_from_top);
  return 1;
}

/* does not return if malloc failed */
static void* checked_alloc(size_t size)
{
  void* memptr = malloc(size);
  if (!memptr) {
    gui_fatal_error2(gui_lookup("NOMEM"));
  }
  return memptr;
}
