/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Printing.c                             */
/* Purpose: Printing functions for the browser     */
/* Author : A.D.Hodgkinson                         */
/* History: 27-Jan-97: Created                     */
/***************************************************/

#include "signal.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "swis.h"

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "printdbox.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "TBEvents.h"
#include "Utils.h"

#include "Browser.h"
#include "FontManage.h"
#include "Images.h"
#include "Memory.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Save.h"
#include "Toolaction.h"
#include "Toolbars.h"
#include "Windows.h"

#include "Printing.h"

/* Local structures */

typedef struct
{
  browser_data * p;           /* Records for whom the print dialogue was opened.                                   */
  int            copies;      /* Number of copies to print.                                                        */
  int            start;       /* 1 = whole page, 0 = top of visible area.                                          */
  int            end;         /* 0 = whole page, -1 = to bottom of visible area, > 0 = number of pages to fill.    */
  int            pages;       /* Remembers the number of pages set, if 'end' is 0 or -1, to enable Cancel to work. */
  int            reformat;    /* 1 = reformat to fit page (if start is not 0 and end is not -1), else don't.       */
  int            orientation; /* 1 = portrait, 0 = landscape.                                                      */

} print_info;

/* Local variables */

static int        globaljob     = 0;
static int        globalold_job = 0;
static int        mustfree      = 0;

static print_info print_current;
static print_info print_old;

/* Static function prototypes */

static int  print_pre_initiate (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int  print_pre_restore  (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

static void print_free_memory  (browser_data * localbrowser);

/* Local macro - swap two signed / unsigned ints / chars. */

#define SWAP(a,b) { (a) ^= (b); (b) ^= (a); (a) ^= (b); }

/*************************************************/
/* print_to_be_shown()                           */
/*                                               */
/* Called before a print dialogue opens. Deals   */
/* with setting this up with default values and  */
/* filling in print_old as appropriate, so that  */
/* if the dialogue is cancelled its contents may */
/* be correctly restored.                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

int print_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  ObjectId id;
  int      upper, lower;

  /* Get the browser_data structure pointer */

  ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &print_current.p));

  /* Get the print dialogue's actual window object ID into id. */

  ChkError(printdbox_get_window_id(0, idb->self_id, &id));

  /* Register handlers for alternate Print/Cancel buttons */

  ChkError(event_register_toolbox_handler(id,
                                          EStartPrint,
                                          print_pre_initiate,
                                          (void *) idb->ancestor_id));

  ChkError(event_register_toolbox_handler(id,
                                          ECancelPrint,
                                          print_pre_restore,
                                          (void *) idb->ancestor_id));

  /* Various alterations of icons / buttons for different UI styles */

  if (!strcmp(lookup_token("AlterNumranges:no",0,0),"yes"))
  {
    _kernel_oserror       * e;
    WimpGetIconStateBlock   icon;
    int                     iconlist[10];
    char                    buffer[MaxMsgLen];

    /* Get the object's window handle and the icon handle for the given component */

    e = window_get_wimp_handle(0, id, &icon.window_handle);

    if (!e)
    {
      ComponentId writable;
      int         loop;

      for (loop = 0; loop < 2; loop ++)
      {
        /* Get the number range's writabe component ID */

        e = numberrange_get_components(NumberRange_GetComponents_ReturnNumericalField,
                                      id,
                                      loop == 1 ? CopiesNum : EndManyNum,
                                      &writable,
                                      NULL,
                                      NULL,
                                      NULL);

        /* Turn this into an icon handle */

        if (!e) e = gadget_get_icon_list(0, id, writable, iconlist, sizeof(iconlist), NULL);

        if (!e)
        {
          icon.icon_handle = iconlist[0];

          /* Get the icon state and set the icon flags with the */
          /* programming text defined in the Messages file      */

          e = wimp_get_icon_state(&icon);

          if (!e)
          {
            strncpy(buffer, lookup_token("AlterWith",1,0), sizeof(buffer) - 1);
            buffer[sizeof(buffer) - 1] = 0;

            windows_process_icon_text(&icon, buffer, 0);
          }
        }
      }
    }
  }

  /* If print_current.copies is zero, this is the first time */
  /* the dialogue has been opened, so set up defaults.       */

  if (!print_current.copies)
  {
    /* Number of copies */

    print_current.copies = atoi(lookup_choice("PrintCopies:1",0,0));

    /* Check it's within bounds */

    ChkError(numberrange_get_bounds(NumberRange_LowerBound | NumberRange_UpperBound,
                                    id,
                                    CopiesNum,

                                    &lower,
                                    &upper,
                                    NULL,
                                    NULL));

    if (print_current.copies < lower) print_current.copies = lower;
    if (print_current.copies > upper) print_current.copies = upper;

    /* Start position - 'start' or 'visible', though in fact any */
    /* non-'visible' string defaults as 'start'.                 */

    if (!strcmp(lookup_choice("PrintStart:start",0,0),"visible")) print_current.start = 0;
    else print_current.start = 1;

    /* End position - 'end', 'visible', or a number of pages to fill. */
    /* if 'end' or 'visible' aren't recognised the number of pages is */
    /* defaulted to, if this gives a result <= 0, 1 is assumed.       */

    if (!strcmp(lookup_choice("PrintEnd:end",0,0),"end"))      print_current.end = 0;
    else if (!strcmp(lookup_choice("PrintEnd",1,0),"visible")) print_current.end = -1;
    else
    {
      print_current.end = atoi(lookup_choice("PrintEnd",1,0));
      if (print_current.end <= 0) print_current.end = 1;
    }

    ChkError(numberrange_get_bounds(NumberRange_LowerBound | NumberRange_UpperBound,
                                    id,
                                    CopiesNum,

                                    &lower,
                                    &upper,
                                    NULL,
                                    NULL));
    if (print_current.end > 1)
    {
      /* Check it's within bounds */

      if (print_current.end < lower) print_current.end = lower;
      if (print_current.end > upper) print_current.end = upper;

      print_current.pages = print_current.end;
    }
    else print_current.pages = lower;

    /* Reformat - 'yes' or 'no', default to 'yes' */

    if (!strcmp(lookup_choice("PrintReform:yes",0,0),"no")) print_current.reformat = 0;
    else print_current.reformat = 1;

    /* Orientation - 'upright' or 'sideways', though in fact any */
    /* non-'sideways' string defaults as 'upright'.              */

    if (!strcmp(lookup_choice("PrintOrient:upright",0,0),"sideways")) print_current.orientation = 0;
    else print_current.orientation = 1;
  }

  /* Copy print_current to print_old in case restoration is needed later, */
  /* and fill in the dialogue as appropriate.                             */

  print_old = print_current;

  /* Start position */

  if (!print_current.start)       ChkError(radiobutton_set_state(0, id, StartVisible, 1));
  else                            ChkError(radiobutton_set_state(0, id, StartWhole,   1));

  /* End position, including the 'number of pages to fill' number range */

  if (!print_current.end)         ChkError(radiobutton_set_state(0, id, EndWhole,     1));
  else if (print_current.end < 0) ChkError(radiobutton_set_state(0, id, EndVisible,   1));
  else                            ChkError(radiobutton_set_state(0, id, EndMany,      1));

  if (print_current.end > 0)      ChkError(numberrange_set_value(0, id, EndManyNum, print_current.end));
  else                            ChkError(numberrange_set_value(0, id, EndManyNum, print_current.pages));

  /* The reformat optionn, including greying / ungreying it */

  ChkError(optionbutton_set_state(0, id, ReformToFit, print_current.reformat));

  /* As well as greying / ungreying the reformat option, this handles */
  /* the label text on the 'pages to fill' number range.              */

  {
    IdBlock idlocal;

    idlocal.self_id = id;
    print_check_contents(NULL, NULL, &idlocal, NULL);
  }

  /* Orientation */

  if (!print_current.orientation) ChkError(radiobutton_set_state(0, id, OriSideways, 1));
  else                            ChkError(radiobutton_set_state(0, id, OriUpright,  1));

  /* Number of copies */

  ChkError(numberrange_set_value(0, id, CopiesNum, print_current.copies));

  /* Register a handler to cope with the pages number range changing */

  ChkError(event_register_toolbox_handler(id,
                                          NumberRange_ValueChanged,
                                          print_check_contents,
                                          NULL));

  /* Animation handler if there's an appropriate gadget */

  {
    int temp_type;

    if (
         fixed.dboxanims                                  &&
         !gadget_get_type(0, id, DisplayAnim, &temp_type)
       )
       register_null_claimant(Wimp_ENull,
                              toolbars_animate_slow,
                              (void *) id);
  }

  /* Done! */

  return 1;
}

/*************************************************/
/* print_pre_restore()                           */
/*                                               */
/* Called for a non-standard Cancel button in a  */
/* PrintDBox object or an alternate window it is */
/* using. Closes the dialogue, resulting in an   */
/* appropriate DialogueCompleted event being     */
/* raised; print_restore will consequently be    */
/* called for the dialogue.                      */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

static int print_pre_restore(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  ChkError(toolbox_hide_object(0, idb->self_id));

  return 1;
}

/*************************************************/
/* print_restore()                               */
/*                                               */
/* Called when a print dialogue is cancelled.    */
/* Simply copies the local static print_info     */
/* structure 'print_old' over 'print_current' so */
/* that next time the dialogue is opened, it     */
/* holds the old settings.                       */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

int print_restore(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
//  print_current = print_old;

  ChkError(event_deregister_toolbox_handlers_for_object(idb->self_id));

  /* If there was a null handler for the dialogue, remove it */

  {
    int temp_type;

    if (
         fixed.dboxanims                                            &&
         !gadget_get_type(0, idb->self_id, DisplayAnim, &temp_type)
       )
       deregister_null_claimant(Wimp_ENull,
                                toolbars_animate_slow,
                                (browser_data *) idb->self_id);
  }

  /* Restore focus to the browser window */

  {
    browser_data * ancestor;

    if (idb->ancestor_id) ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &ancestor));
    else ancestor = last_browser;

    if (ancestor) browser_give_general_focus(ancestor);
  }

  return 1;
}

/*************************************************/
/* print_pre_restore()                           */
/*                                               */
/* Called for a non-standard Print button in a   */
/* PrintDBox object or an alternate window it is */
/* using. Raises appropriate events to complete  */
/* the dialogue and initate printing through     */
/* print_initiate.                               */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

static int print_pre_initiate(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  idb->ancestor_id = (ObjectId) handle;

  print_initiate(eventcode, event, idb, (void *) 1 /* (Flag - see print_initiate) */);

  ChkError(toolbox_hide_object(0, idb->self_id));

  return 1;
}

/*************************************************/
/* print_initiate()                              */
/*                                               */
/* Examines the Print dialogue box's contents    */
/* and fills in the local static print_info      */
/* structure 'print_current' with them; then     */
/* starts a print routine by broadcasting a      */
/* PrintSave message.                            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

int print_initiate(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;
  ObjectId       id;
  int            selected;

  /* Get the browser_data pointer for the ancestor window - i.e. */
  /* the browser from which this print dialogue was opened.      */

  ChkError(toolbox_get_client_handle(0,idb->ancestor_id,(void *) &b));

  /* Get the Object ID of the window that has opened on behalf of */
  /* the Print dialogue.                                          */
  /*                                                              */
  /* If called from print_pre_initiate, the self_id will be that  */
  /* of the alternate window anyway. (int) handle = 1 flags this. */
  /*                                                              */
  /* Amazingly, printdbox_get_window_id doesn't seem to return an */
  /* error - just an invalid ID - if called on something that is  */
  /* not a PrintDBox object...                                    */

  if (!((int) handle)) ChkError(printdbox_get_window_id(0, idb->self_id, &id));
  else id = idb->self_id;

  /* Fill in the local static print_info structure 'print_current'. */
  /*                                                                */
  /* 'start' = 1 for whole page, 0 for just the visible area        */

  ChkError(radiobutton_get_state(0, id, StartWhole, &print_current.start, NULL));

  /* 'end' = 0 for whole page, -1 for just the visible area, or */
  /* any other number = the number of pages to fill.            */

  ChkError(radiobutton_get_state(0, id, EndWhole, NULL, &selected));

  switch (selected)
  {
    case EndVisible: print_current.end = -1;
    break;
    case EndMany:    ChkError(numberrange_get_value(0, id, EndManyNum, &print_current.end));
    break;
    case EndWhole:
    default:         print_current.end = 0;
  }

  /* 'reformat' = 1 to reformat to fit the paper, else 0 */

  ChkError(optionbutton_get_state(0, id, ReformToFit, &print_current.reformat));

  /* 'orientation' = 1 for portrait, 0 for landscape */

  ChkError(radiobutton_get_state(0, id, OriUpright, &print_current.orientation, NULL));

  /* Get the number of copies into 'copies' */

  ChkError(numberrange_get_value(0, id, CopiesNum, &print_current.copies));

  /* First stage of printing protocol: Broadcast a PrintSave message */

  {
    WimpMessage m;

    m.hdr.your_ref    = 0;
    m.hdr.action_code = Browser_Message_PrintSave;

    m.data.data_save.destination_window = 0;
    m.data.data_save.destination_icon   = 0;
    m.data.data_save.destination_x      = 0;
    m.data.data_save.destination_y      = 0;
    m.data.data_save.estimated_size     = -1;
    m.data.data_save.file_type          = FileType_POUT;

    StrNCpy0(m.data.data_save.leaf_name,
             lookup_token("PrintName:WebPage",0,0));

    m.hdr.size = (strlen(m.data.data_save.leaf_name) + 44);
    if (m.hdr.size & 3) m.hdr.size = (m.hdr.size & ~3) + 4;

    ChkError(wimp_send_message(Wimp_EUserMessageRecorded, &m, 0, 0, NULL));

    printer_message_ref = m.hdr.my_ref;
  }

  return 1;
}

/*************************************************/
/* print_print()                                 */
/*                                               */
/* Calls the printing engine with parameters     */
/* specified in the local static print_info      */
/* structure 'print_current'.                    */
/*                                               */
/* Parameters: Pointer to pathname to print to.  */
/*************************************************/

void print_print(const char * path)
{
  _kernel_oserror * e;

  printing = 1;

  e = print_page(print_current.p,
                 print_current.copies,
                 print_current.start,
                 print_current.end,
                 print_current.reformat,
                 print_current.orientation,
                 path);

  printing = 0;

  if (e) show_error_ret(e);
}

/*************************************************/
/* print_page()                                  */
/*                                               */
/* Prints out a page, assuming that all the      */
/* relevant protocol stuff to ensure it's OK to  */
/* proceed has been done already.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page to print;    */
/*             The number of copies to print;    */
/*             1 to start at the top of the web  */
/*             page, else start from the top of  */
/*             the visible area in the window;   */
/*             0 to end at the bottom of the web */
/*             page, -1 to end at the bottom of  */
/*             the visible area, or any >0 value */
/*             which is taken as the number of   */
/*             pages to try and fill;            */
/*             1 to reformat to fit the page     */
/*             width (orientation is taken into  */
/*             account), else 0 to keep the      */
/*             width of the window (if it falls  */
/*             off the page, tough...!);         */
/*             1 = portrait, 0 = landscape;      */
/*             Pointer to pathname to print to.  */
/*************************************************/

_kernel_oserror * print_page(browser_data * b, int copies, int from, int to,
                             int reformat, int orientation, const char * path)
{
  _kernel_oserror       * e = NULL;

  WimpRedrawWindowBlock   redraw;
  int                     job, old_job;
  BBox                    box, last_rect;
  int                     more, page, maxpages, top, bottom, temp, next_line;
  int                     estimated_pages, area_completed, page_area;
  int                     lmarg, bmarg, rmarg, tmarg;
  unsigned int            features;

  int                     portrait  [2] [2] = { {0x10000,  0}, {0, 0x10000} };
  int                     landscape [2] [2] = { {0, -0x10000}, {0x10000, 0} };

  browser_data            localbrowser;
  reformat_cell           localcell;

  void (*old_sigint_handler) (int);

  /* Check to see if there is a printer driver ready */

  e = _swix(PDriver_Info,
            _OUT(3),

            &features);

  if (e) return e;

  /* Find the current page margins (and therefore, page size, */
  /* as all margins are expressed as offsets from the bottom  */
  /* left hand corner of the paper).                          */

  e = _swix(PDriver_PageSize,
            _OUTR(3,6),

            &lmarg,
            &bmarg,
            &rmarg,
            &tmarg);

  if (e) return e;

  /* If in landscape mode, want to treat the margins */
  /* in a reversed sense.                            */

  if (!orientation)
  {
    SWAP(tmarg,rmarg);
    SWAP(bmarg,lmarg);
  }

  localbrowser = *b;
  memset(&localcell, 0, sizeof(localcell));

  /* If the user specified printing to or from something that */
  /* depends upon the visible area, can't then reformat.      */

  if (reformat && (to >= 0) && from)
  {
//    int lastchunk, percentage;

    /* Reformat the page ready for printing.                       */
    /*                                                             */
    /* First copy over the existing browser_data structure, and    */
    /* modify the contents as appropriate.                         */

    localbrowser.previous = localbrowser.next = NULL;

    localbrowser.display_width = rmarg - lmarg;

    convert_to_os(localbrowser.display_width, &localbrowser.display_width);

    localbrowser.display_extent = localbrowser.display_width;

    localbrowser.fetch_status = BS_IDLE;
    localbrowser.fetch_handle = localbrowser.display_handle;

    localbrowser.source   = NULL;
    localbrowser.urlfdata = NULL;
    localbrowser.urlddata = NULL;
    localbrowser.cell     = &localcell;

    mustfree = 1;

    /* Now call the reformatter, and loop round until finished. */

    _swix(Hourglass_On, 0);

    e = reformat_format_from(&localbrowser, -1, 1, -1);
    if (e) return e;

    while (reformat_formatting(&localbrowser))
    {
      reformat_reformatter(&localbrowser);

// Broken now that there's no such thing as a token number...
// Moreover, needs adjusting to the cell based model.
//
//      /* Percentage progress indicator based on how far down the */
//      /* token list the reformat has reached.                    */
//
//      if (localbrowser.ldata && localbrowser.nlines)
//      {
//        lastchunk = localbrowser.ldata[localbrowser.nlines - 1].chunks + localbrowser.ldata[localbrowser.nlines - 1].n - 1;
//
//        percentage = (100 * localbrowser.cdata[lastchunk].t) / localbrowser.ntokens;
//        if (percentage > 99) percentage = 99;
//
//        _swix(Hourglass_Percentage, _IN(0), percentage);
//      }
    }

    _swix(Hourglass_Off, 0);
  }
  else mustfree = 0;

  localbrowser.sourcecolours = 0;

  /* Hourglass for the print job, as this may take some time... */
  /* Using Hourglass_Start as otherwise the first percentage    */
  /* setting may be missed, since the hourglass isn't actually  */
  /* on yet (there's a default delay before appearance with     */
  /* calling Hourglass_On).                                     */

  _swix(Hourglass_Start, _IN(0), 1);

  /* Open up the output stream */

  e = _swix(OS_Find,
            _INR(0,1) | _OUT(0),

            0x8F,
            path ? path : "printer:",

            &job);

  if (e) goto out3;

  /* Estimate the number of pages to print */

  {
    int page_height;

    /* Get the printable page height */

    convert_to_os(tmarg - bmarg, &page_height);

    estimated_pages = (reformat_return_extent(&localbrowser, NULL) / page_height) + 1;
  }

  globaljob = job;

  /* Stop the C library intercepting Escape, since */
  /* this should be left to the print SWIs.        */

  old_sigint_handler = signal(SIGINT, SIG_IGN);

  /* Start up the printing system */

  e = _swix(PDriver_SelectJob,
            _INR(0,1) | _OUT(0),

            job,
            lookup_token("PJobName:Web page",0,0),

            &old_job);

  if (e) goto out1;

  globalold_job = old_job;

  /* Declare fonts that have been used */

  if (features & Browser_Printer_DeclareFont)
  {
    fm_face h;

    /* If using system font, only the system faces will be */
    /* used; otherwise, need to declare the sans, serif    */
    /* and fixed faces.                                    */

    if (choices.systemfont)
    {
      h = fm_find_font(NULL, "system", 192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "system", 192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "system", 192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "system", 192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
    }
    else
    {
      h = fm_find_font(NULL, "sans",   192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);

      h = fm_find_font(NULL, "serif",  192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);

      h = fm_find_font(NULL, "fixed",  192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
    }

    /* Finish declaring fonts */

    e = _swix(PDriver_DeclareFont,
              _INR(0,2),

              0,
              0,
              2);

    if (e) goto out2;
  }

  /* Set the bottom left hand corner of the rectangle to redraw */

  box.xmin = orientation ? lmarg : bmarg;
  box.ymin = orientation ? bmarg : rmarg;

  /* Set 'top' to the offset from the top of the document to get  */
  /* to the top of the currently visible portion, and 'bottom'    */
  /* to the offset to get to the bottom of the currently visible  */
  /* portion. The positive direction is downwards (so they should */
  /* both be positive numbers).                                   */

  {
    WimpGetWindowStateBlock state;

    state.window_handle = b->window_handle;
    e = wimp_get_window_state(&state);
    if (e) goto out2;

    /* Get the basic offsets */

    top    = -state.yscroll;
    bottom = state.visible_area.ymax - state.visible_area.ymin;

    /* Correct for toolbar presence */

    // Haven't done the wimpt_dy() stuff yet (work out the heights
    // separately, and if non-zero add wimpt_dy for the window border)

    if (!fixed.swapbars)
    {
      top    += (toolbars_url_height(b) + toolbars_button_height(b));
      bottom -= (toolbars_url_height(b) + toolbars_button_height(b) + toolbars_status_height(b) - top);
    }
    else
    {
      top    += toolbars_status_height(b);
      bottom -= (toolbars_url_height(b) + toolbars_button_height(b) + toolbars_status_height(b) - top);
    }
  }

  /* If 'from' is non-zero, want to print from the top of the whole   */
  /* page; else from the top of the visible area as worked out above. */

  // Haven't done the wimpt_dy() stuff yet (work out the heights
  // separately, and if non-zero add wimpt_dy for the window border)

  if (!fixed.swapbars)
  {
    if (from) top = toolbars_url_height(b) + toolbars_button_height(b);
  }
  else
  {
    if (from) top = toolbars_status_height(b);
  }

  redraw.yscroll = -top;
  redraw.xscroll = 0;

  /* maxpages will be set either to the user-specified number of */
  /* sheets of paper to fill ('to' holds that number if greater  */
  /* than zero), else it holds a very large number - i.e. keep   */
  /* going until another exit condition occurs within the print  */
  /* loop (e.g. reaching the end of the web page).               */

  page = 0;

  if (to > 0) maxpages = to;
  else        maxpages = 0x1000000;

  /* Loop round for all pages. The y scroll position is set */
  /* initially to be correct for the place we want to print */
  /* from, and xscroll is set to 0 to mark that no calls    */
  /* to redraw_draw have happened yet.                      */

  while (page <= maxpages)
  {
    page ++;

    /* Set up the Redraw block ready for the calls to redraw_draw */

    redraw.visible_area.xmax = rmarg - lmarg;
    redraw.visible_area.xmin = 0;

    convert_to_os(redraw.visible_area.xmax, &redraw.visible_area.xmax);

    /* Vertical margins are complicated by the user settings. For printing    */
    /* down to the bottom of the web page, want to use a full page rectangle; */
    /* for printing down to the bottom of the visible area, want to use the   */
    /* 'bottom' variable worked out above. Note the checking to work out      */
    /* pagination - it *could* be possible that the visible area is taller    */
    /* than a single sheet of paper for the current printer.                  */

    convert_to_os(tmarg - bmarg, &temp); /* temp now holds the printable page height in OS units */

    if (to >= 0)
    {
      redraw.visible_area.ymax = temp,
      redraw.visible_area.ymin = 0;
    }
    else
    {
      /* Need to subtract an amount from 'bottom' to mark that a page has been  */
      /* done, but must do that after the page rendering so the redraw routines */
      /* have filled in redraw.xscroll with the last coordinate used - if we    */
      /* don't do this, we can't tell how much of the web page was actually     */
      /* used. Remember that xscroll and yscroll are negative; xscroll refers   */
      /* to the vertical offset to start the next page at, and yscroll was the  */
      /* offset that this one started at.                                       */

      if (redraw.xscroll && bottom) bottom -= (redraw.yscroll - redraw.xscroll);

      if (bottom - top > temp) /* And *not* '>= temp'! See 'else' code below */
      {
        redraw.visible_area.ymax = temp;
      }
      else
      {
        redraw.visible_area.ymax = bottom - top;
        bottom = 0;

        /* Since this rectangle is at most a full page in height but probably */
        /* less, must shift the bottom left hand coordinate of it up an       */
        /* appropriate amount to print the page fragment at the top of the    */
        /* paper rather than the bottom.                                      */

        temp -= redraw.visible_area.ymax; /* (Page height minus rectangle height in OS units) */

        convert_to_points(temp, &temp);

        if (orientation) box.ymin += temp;
        else             box.xmin += temp;

        /* Signal to redraw_draw that the last line on this page may be split */
        /* over the bottom of the page, as that's what the user is seeing in  */
        /* the real window right now.                                         */

        printing = 2;
      }

      redraw.visible_area.ymin = 0;
    }

    /* redraw_draw will give the y coordinate of the next line to */
    /* print when it exits by placing an appropriate coordinate   */
    /* in the xscroll field of the redraw block that is passed to */
    /* it during the main printer redraw loop.                    */

    if (redraw.xscroll) redraw.yscroll = redraw.xscroll, redraw.xscroll = 0;

    /* Start drawing things */

    e = _swix(PDriver_GiveRectangle,
              _INR(0,4),

              0, /* Rectangle ID word - only 1 per page, so 0 will do */
              &redraw.visible_area,
              orientation ? &portrait : &landscape,
              &box,
              Redraw_Colour_White);

    if (e) goto out2;

    e = _swix(PDriver_DrawPage,
              _INR(0,3) | _OUT(0),

              (copies) | ((features & Browser_Printer_PreScansRectangles) ? (1<<24) : (0)),
              &redraw.redraw_area,
              0,
              0,

              &more);

    if (e) goto out2;

    /* Give an indication of progress */

    {
      int percent;

      percent = (100 * (page - 1)) / estimated_pages;

      if (percent > 99) percent = 99;

      _swix(Hourglass_Percentage, _IN(0), percent);
    }

    /* The redraw loop itself. The area stuff is for the */
    /* in-page hourglass percentage; see later comments. */

    page_area = (redraw.visible_area.xmax - redraw.visible_area.xmin) *
                (redraw.visible_area.ymax - redraw.visible_area.ymin);

    last_rect.xmin = last_rect.xmax = last_rect.ymin = last_rect.ymax = 0;

    next_line = area_completed = 0;

    while (more)
    {
      /* Ensure images are correct for the current mode */

      image_mode_change();

      /* Do the redraw */

      e = redraw_draw(&localbrowser,
                      &redraw,
                      1,
                      0);

      if (e) goto out2;

      /* Don't want to start pagination issues if this is just a prescan */

      if ((features & Browser_Printer_PreScansRectangles) && (more & 1<<24))
      {
        redraw.xscroll = 0;
      }
      else
      {
        int    this_area;
        BBox * i;

        if (redraw.xscroll != 0) next_line = redraw.xscroll, redraw.xscroll = 0;

        /* Give a percentage completed indicator. This is first based */
        /* on the current page being printed, so there's some scaling */
        /* of the 100% range to cope with the fact that if you're on  */
        /* page 3 of 4, the variation must be between 50% and 75%,    */
        /* for example. Since rectangle order cannot be relied upon,  */
        /* need to use the area printed so far for the calculation.   */
        /* This may fail under unusual circumstances, and certainly   */
        /* is not fully accurate as the rectangles always overlap by  */
        /* a small amount (the printer driver gives room for rounding */
        /* errors by overlapping the rectangles) but in any case      */
        /* there will at least be some kind of percentage indication! */
        /* With bit image printing, which can be painfully slow, this */
        /* is extremely important.                                    */
        /*                                                            */
        /* There is an attempt to correct for overlapping rectangles, */
        /* if this one and the last were overlapping.                 */

        i = intersection(&redraw.redraw_area, &last_rect);

        if (i)
        {
          this_area = (redraw.redraw_area.xmax - redraw.redraw_area.xmin) *
                      (redraw.redraw_area.ymax - redraw.redraw_area.ymin)
                    - (i->xmax                 - i->xmin) *
                      (i->ymax                 - i->ymin);
        }
        else
        {
          this_area = (redraw.redraw_area.xmax - redraw.redraw_area.xmin) *
                      (redraw.redraw_area.ymax - redraw.redraw_area.ymin);
        }

        last_rect = redraw.redraw_area;

        area_completed += this_area;
        if (area_completed > page_area) area_completed = page_area;

        {
          int percent;

          percent = (100 * (page - 1)) / estimated_pages +
                    ((100 / estimated_pages) * area_completed) / page_area;

          if (percent < 0)  percent = 0;
          if (percent > 99) percent = 99;

          _swix(Hourglass_Percentage, _IN(0), percent);
        }
      }

      /* Get the next rectangle */

      e = _swix(PDriver_GetRectangle,
                _IN(1) | _OUT(0),

                &redraw.redraw_area,
                &more);

      if (e) goto out2;
    }

    if (next_line) redraw.xscroll = next_line;

    /* If we should print down to the bottom of the visible */
    /* area of the page, this is flagged with 'to' < 0; if  */
    /* 'bottom' = 0 as well, there's nothing more to print. */

    if (to < 0 && !bottom) break;

    /* If xscroll is 0, redraw_draw must not have found any */
    /* lines that fell off the bottom of the page - so      */
    /* there cannot be any more pages.                      */

    if (!redraw.xscroll) break;

    /* Otherwise, close the outer while() loop - which */
    /* may mean we loop for another page.              */
  }

  /* Finished, so end the job, close the output stream, */
  /* turn off the hourglass and restore the previous    */
  /* job.                                               */

  e = _swix(PDriver_EndJob,
            _IN(0),

            job);

  if (e) goto out2;

  /* Ensure images are restored to the correct mode */

  image_mode_change();

  /* Turn off the hourglass */

  _swix(Hourglass_Off, 0);

  /* Restore the old Escape handler */

  signal(SIGINT, old_sigint_handler);

  /* Close the output stream */

  globaljob = 0;

  e = _swix(OS_Find,
            _INR(0,1),

            0x00,
            job);

  if (e) goto out3;

  print_free_memory(&localbrowser);

  /* Restore the previous print job */

  globalold_job = 0;

  e = _swix(PDriver_SelectJob,
            _INR(0,1),

            old_job,
            0);

  return e;

out1: /* 'Emergency exit' if PDriver_SelectJob fails */

  signal(SIGINT, old_sigint_handler);
  globaljob = 0;

  /* Close the output stream */

  _swix(OS_Find,
        _INR(0,1),

        0x00,
        job);

  /* Force the hourglass off */

  _swix(Hourglass_Smash, 0);

  /* Free temporarily allocated memory */

  print_free_memory(&localbrowser);

  /* Ensure images are still OK */

  image_mode_change();

  return e;

out2: /* 'Emergency exit' for errors whilst printing */

  signal(SIGINT, old_sigint_handler);
  print_abort_print();
  print_free_memory(&localbrowser);
  image_mode_change();

  return e;

out3: /* 'Emergency exit' for errors outside printing, where memory may be temporarily allocted */

  _swix(Hourglass_Smash, 0);
  print_free_memory(&localbrowser);
  image_mode_change();

  return e;
}

/*************************************************/
/* print_abort_print()                           */
/*                                               */
/* Forcibly aborts a print job.                  */
/*************************************************/

void print_abort_print(void)
{
  /* Abort the current print job */

  _swix(PDriver_AbortJob,
        _IN(0),

        globaljob);

  /* Close the output stream */

  _swix(OS_Find,
        _INR(0,1),

        0x00,
        globaljob);

  globaljob = 0;

  /* Restore the previous print job */

  _swix(PDriver_SelectJob,
        _INR(0,1),

        globalold_job,
        0);

  globalold_job = 0;

  /* Force the hourglass off */

  _swix(Hourglass_Smash, 0);
}

/*************************************************/
/* print_free_memory()                           */
/*                                               */
/* Frees up browser_data allocated memory used   */
/* locally for reformatting a page prior to      */
/* printing.                                     */
/*************************************************/

static void print_free_memory(browser_data * localbrowser)
{
  /* mustfree must be set to non-zero for any memory to be freed */

  if (mustfree)
  {
    memory_set_chunk_size(localbrowser, NULL, CK_LINE, 0);
    memory_set_chunk_size(localbrowser, NULL, CK_LDAT, 0);
  }
}

/*************************************************/
/* print_check_contents()                        */
/*                                               */
/* If the state of the various radio buttons     */
/* changes, this may be called to see if the     */
/* Reformat option in the Print dialogue should  */
/* be enabled (ungreyed) or disabled (greyed).   */
/* Similarly, if the contents of the number of   */
/* sheets to fill number range changes, this     */
/* should be called to ensure the label has the  */
/* correct pluralisation applied.                */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int print_check_contents(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int          state1, state2, pages;
  unsigned int flags;

  /* Get the radio button states */

  ChkError(radiobutton_get_state(0, idb->self_id, StartWhole, NULL, &state1));
  ChkError(radiobutton_get_state(0, idb->self_id, EndWhole,   NULL, &state2));

  /* If the StartVisible and EndVisible radios are not selected, */
  /* can enable the Reformat option; else disable it. But only   */
  /* change it's state (don't grey it if already greyed, say).   */

  ChkError(gadget_get_flags(0, idb->self_id, ReformToFit, &flags));

  if (state1 != StartVisible && state2 != EndVisible)
  {
    if (flags & Gadget_Faded)
    {
      ChkError(gadget_set_flags(0, idb->self_id, ReformToFit, flags & ~Gadget_Faded));
    }
  }
  else
  {
    if (!(flags & Gadget_Faded))
    {
      ChkError(gadget_set_flags(0, idb->self_id, ReformToFit, flags | Gadget_Faded));
    }
  }

  /* Check the pages number range, and update the label if necessary. */

  {
    char text[256];

    ChkError(numberrange_get_value(0, idb->self_id, EndManyNum, &pages));

    ChkError(button_get_value(0, idb->self_id, EndManyLabel, text, 256, NULL));

    if (pages == 1)
    {
      /* If the existing text isn't what we intend to change it to, then change it; */
      /* i.e. don't set the same thing twice, as this will flicker badly.           */

      if (strcmp(text, lookup_token("PagesSingle:sheet is filled",0,0)))
      {
        ChkError(button_set_value(0, idb->self_id, EndManyLabel, lookup_token("PagesSingle:sheet is filled",0,0)));
      }
    }
    else
    {
      /* Again, only change the text - don't set the same thing twice. */

      if (strcmp(text, lookup_token("PagesMany:sheets are filled",0,0)))
      {
        ChkError(button_set_value(0, idb->self_id, EndManyLabel, lookup_token("PagesMany:sheets are filled",0,0)));
      }
    }
  }

  return 1;
}

/*************************************************/