// C++ code Copyright (C) David R. Evans G4AMJ/NQ0I

//  surface class for laserwriter

#include <surf_lw.h>

#include <ctype.h>
#include <fstream.h>
#include <math.h>

// constructor
laserwriter::laserwriter(const int x0, const int y0, const int x1,
                         const int y1, const int ofsx, const int ofsy) :
               last_dot(0, 0, 0)
{ x_ofs = ofsx;
  y_ofs = ofsy;
  use_left = x0;
  use_right = x1;
  use_top = y0;
  use_bottom = y1;
  _dotsize = 1;
  printing_in_progress = false;
  n_writes = 0;
  heap_check(gflwp = new grey_font_laserwriter(0, 0));
  gfp = gflwp;
  
  _init();
}

// copy constructor
laserwriter::laserwriter(laserwriter& l) : last_dot(l.last_dot)
{ x_ofs = l.x_ofs;
  y_ofs = l.y_ofs;
  use_left = l.use_left;
  use_right = l.use_right;
  use_top = l.use_top;
  use_bottom = l.use_bottom;
  font = l.font;
  im_top = l.im_top;
  im_bottom = l.im_bottom;
  im_left = l.im_left;
  im_right = l.im_right;
  printing_in_progress = false;

  tmpnam(postscript_filename);
  l._flush();
  const char* command_string = (const char*)(DREstring("cp ") +
                               (DREstring)l.postscript_filename +
                               DREstring(" ") + (DREstring)postscript_filename); 
  system(command_string);
  postscript_file = fopen(postscript_filename, "a+");
  if (!postscript_file) then
    fatal_error("laserwriter copy constructor");
}

// destructor
laserwriter::~laserwriter(void)
{ fclose(postscript_file);              // may fail if already closed
  if (!printing_in_progress) then
  { const char* command_string = DREstring("rm ") +
                                   (DREstring)postscript_filename;
    system(command_string);
  }
}

// private function
void laserwriter::prologue(void)
{ *this << "%!PS-Adobe-1.0" <<
// transform to correct system
        "72 300 div dup scale" <<
	"-90 rotate" <<
	"-3300 0 translate" <<
// define dot operator -- goes in with x, y, size on stack
         "/D" <<
	 "{ newpath" <<
	 "  /DotSize exch 1 sub def" <<
	 "  2 copy 4 copy moveto" <<
	 "  exch DotSize sub exch lineto" <<
	 "  DotSize sub exch DotSize sub exch lineto" <<
	 "  DotSize sub lineto" <<
	 "  fill" <<
	 "} def";

   _flush();
}

// private function
void laserwriter::_init(void)
{ tmpnam(postscript_filename);
  postscript_file = fopen(postscript_filename, "w+");
  prologue();
}

// laserwriter << LINE
surface& laserwriter::operator<<(LINE& rhs)
{ return (*this < DREstring(rhs.x1() + x_ofs, 10) < " " <
                  DREstring(height() -  rhs.y1() + y_ofs, 10) < " moveto\n" <
                  DREstring(rhs.x2() + x_ofs, 10) < " " <
                  DREstring(height() - rhs.y2() + y_ofs, 10) <
                  " lineto stroke\n");
}

surface& laserwriter::operator<<(dot& rhs)
{ if (last_dot != rhs) then
  { last_dot = rhs;
     return (*this < DREstring(rhs.x() + x_ofs, 10) < " " <
                    DREstring(height() - rhs.y() + y_ofs, 10) < " " <
                    DREstring((rhs.size() == -1) ? _dotsize : rhs.size(), 10) <
                    " D\n");
  }
  else
    return *this;
}

surface& laserwriter::operator<<(text& rhs)
{ if (rhs.length()) then
  { DREstring ps_font_name;
    ps_font_name = font;
    if (font == (DREstring)"ROMAN") then
      ps_font_name = "Times-Roman";
    *this < "/" < ps_font_name < " findfont " <
             DREstring10(rhs.height()) < " scalefont setfont\n";
    if (rhs.x() < 0) then
    { *this < DREstring(im_left, 10) < " " <
        DREstring((int)((abs(rhs.x())/100.0) * image_width()) + x_ofs, 10) < 
                      " add\n";
      *this < "(" < rhs.words() < ")" < " stringwidth\n";
      *this < "pop 2 div sub " < 
                 DREstring10(height() - rhs.y() + y_ofs) < " moveto\n";
    }
    else
      *this < DREstring10(rhs.x() + x_ofs) < " " < 
              DREstring10(height() - rhs.y() + y_ofs) < " moveto\n";
    *this < "(" < rhs.words() < ") show\n";
  }

  return *this;
}

int laserwriter::width(void)
{ return 3276; }

int laserwriter::height(void)
{ return 2400; }

void laserwriter::set_font(DREstring& fontname, const int size)
{ font = fontname;
  if (size) then
    font_height = size;
  DREstring t_fontname = fontname;
  if (t_fontname == (DREstring)"ROMAN") then
    t_fontname = "Times-Roman";
  *this < "/" < t_fontname < " findfont " <
             DREstring10(font_height) < " scalefont setfont\n";
}

void laserwriter::set_font_height(const int size)
{ DREstring fontname = DREstring("FONT") + DREstring10(gfp->number());
  font = fontname;
  if (size) then
    font_height = size;
  *this < "/" < fontname < " findfont " <
             DREstring10(font_height) < " scalefont setfont\n";
  _save();
}

void laserwriter::_print(const char* printer_name)
{ *this << "showpage";

#if 0
  *this << "serverdict begin" <<
           "statusdict begin" <<
           "0 checkpassword " <<
           " { 0 exitserver }" <<
           " {(Incorrect Server Password!!)= flush }" <<
           "ifelse" <<
           "statusdict begin" <<
           "800 800 setmargins" <<
           "end";
#endif
  
  _flush().close();

// Special for PORTRAIT and LANDSCAPE printers
  if (DREstring(printer_name) == (DREstring)"PORTRAIT") then
  { const char* command_string = DREstring("cat ") +
(DREstring)postscript_filename +
                                 (DREstring) " | " +
                             (DREstring)"/usr/local/bin/postscript -c A4 -";
    system(command_string);
    return;
  }

  if (DREstring(printer_name) == (DREstring)"LANDSCAPE") then
  { const char* command_string = DREstring("cat ") +
(DREstring)postscript_filename +
                             (DREstring)" | " +
                             (DREstring)"/usr/local/bin/postscript -c M4 -";
    system(command_string);
    return;
  }

  const char* command_string = DREstring("lpr -P") + (DREstring)printer_name +
                     (DREstring)" -r -s " + (DREstring)postscript_filename;
  printing_in_progress = true;
  system(command_string);
}

void laserwriter::clear(void)
{ // delete the file
  fclose(postscript_file);
  const char* command_string = DREstring("rm ") + (DREstring)postscript_filename;
  system(command_string);
  postscript_file = fopen(postscript_filename, "a+");
  prologue();
}

void laserwriter::print(const char* printer_name)
{ laserwriter temp(*this);
  temp._print(printer_name);
  
// clear as well; in general, this may not be a good thing to do; if
// that turns out to be the case, then we should declare a seperate
// print_with_clear function and use pointers to members to select
// the correct version at run-time
  clear();
}

// detach and reattach to a new file
void laserwriter::detach(void)
{ *this << "showpage";
  _flush().close();
  _init();
}

// detach and reattach to a new file
void laserwriter::detach(const char* filename)
{ *this << "showpage";
  _flush().close();
  const char* command_string = DREstring("cp ") + (DREstring)postscript_filename
+
                              (DREstring)" " + (DREstring)filename;
  system(command_string);
  _init();
}

// detach and reattach to a new file
void laserwriter::detach(ostream& ostr)
{ *this << "showpage";
  _flush().close();

// copy the file to the output stream
  ifstream PSfile(postscript_filename, ios::in);
  DREstring postscript_line;
  
  while (PSfile.good())
  { PSfile >> postscript_line;
    ostr << postscript_line << endl;
  }
  _init();
}

// resize a laserwriter font
int laserwriter::resize_font(const int width, const int height)
{ gfp->resize(width, height);
  *this << "9 dict dup begin" <<
           "/FontType 3 def" <<
           "/FontMatrix [1 0 0 1 0 0] def" <<
           "/FontBBox [0 0 0 0] def" <<
           "/Encoding 256 array def" <<
           "0 1 255 { Encoding exch /.notdef put } for" <<
           "Encoding";
  
  const int max_char_nr = gfp->width() * gfp->height(),
            bias = (max_char_nr < 50 ? 65 : 0);
  for (int char_nr = 0; char_nr <= max_char_nr; char_nr++)
  { const int n = char_nr + bias,
              n1 = n % 26,
              n2 = (n / 26) % 26;

    DREstring id;
    id += (char)(65 + n2);
    id += (char)(65 + n1);

    DREstring temp_string = (DREstring)((char_nr == max_char_nr) ? " " : "dup ")
+ 
		  (DREstring10(char_nr + bias) + (DREstring)" /C" + id +
		  (DREstring)" put");
    *this << temp_string;
  }

  *this << "/BuildChar" <<
           "{ 0 begin" <<
           "  /char exch def" <<
           "  /fontdict exch def" <<
           "  /charname fontdict /Encoding get char get def" <<
           "  /charinfo fontdict /CharData get charname get def" <<
           "  /wx charinfo 0 get def" <<
           "  /charbbox charinfo 1 4 getinterval def" <<
// BEWARE  --  I use "width" to mean "y-width", i. e. "height" !!

           "  0 wx neg charbbox aload pop setcachedevice" <<
           "  charinfo 5 get charinfo 6 get true" <<
           "  fontdict /imagemaskmatrix get" <<
           "  dup 4 charinfo 7 get put" <<
           "  dup 5 charinfo 8 get put" <<
           "  charinfo 9 1 getinterval cvx" <<
           "  imagemask" <<
           "  end" <<
           "} def" <<
           "/BuildChar load 0 6 dict put";

  DREstring height_string(gfp->height(), 10);
 
  *this < "/imagemaskmatrix [" < height_string < " 0 0 " < height_string <
           " 0 0] def\n" <
           "/CharData 260 dict def\n" <
           "CharData begin\n";

  for (char_nr = 0; char_nr <= max_char_nr; char_nr++)
    *this << gflwp->_ps_character(char_nr);

  *this << "end" <<
           "/UniqueID 2 def" <<
           "end" <<
           ((DREstring)"/FONT" + DREstring10(gfp->number()) +
            (DREstring)" exch definefont pop") <<
           ((DREstring)"/FONT" + DREstring10(gfp->number()) +
            (DREstring)" findfont " +
            height_string + (DREstring)" scalefont setfont");

  _flush();
  return (width * height);
}
 
// write the internal buffer
void laserwriter::flush(void)
{ char out_chars[GREY_FONT_BUFFER_SIZE];
  int out_index = 0;

  if (!(gfp->null())) then
  { for (int n = 0; n < gfp->length(); n++)
    { if ((gfp->element(n) <= 127) && (isalnum(gfp->element(n)))) then
        out_chars[out_index++] = char(gfp->element(n));
      else
      { out_chars[out_index++] = 92;	// back slash
        DREstring t_string = left_fill(DREstring(gfp->element(n), 8), 3, "0");
        for (int k = 1; k <= 3; k++)
          out_chars[out_index++] = t_string.chr(k);
      }
    }
    out_chars[out_index] = '\0';

    *this < '(';
    *this < out_chars;
    *this << ") show";
    
    if (++n_writes == VM_trigger) then
    { _restore();
      _save();
      n_writes = 0;
    }
    
  }
  gfp->length(0);
// when I have it working, try a _flush() here.
}

// move current point
void laserwriter::moveto(const int x, const int y)
{ *this < DREstring(x, 10);
  *this < ' ';
  *this < DREstring(y, 10);
  *this << " moveto";
}

// ----------------------  portrait_laserwriter  -------------------

// default constructor
portrait_laserwriter::portrait_laserwriter(int x0, int y0, int x1,
                                           int y1, int ofsx, int ofsy) :
  laserwriter(x0, y0, x1, y1, ofsx, ofsy)
{ // transform back to portrait mode
  *this << "3300 0 translate" << "90 rotate";
  _flush();
}

int portrait_laserwriter::width(void)
{ return 2400; }

int portrait_laserwriter::height(void)
{ return 3276; }
