// -*- C++ -*-
/* $Id: cc.draw2latex 1.8 1998/10/18 15:10:15 atterer Exp $
  __   _
  |_) /|  Copyright Richard Atterer
  | \/|  <atterer@augsburg.baynet.de>
   ` 
  Conversion of Draw files into LaTeX (picture env)

  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., 675 Mass Ave, Cambridge, MA 02139, USA.
  _________________________________________________________________________

  $Log: cc.draw2latex $
  Revision 1.8  1998/10/18 15:10:15  atterer
  command line switches; released as V1

  Revision 1.7  1998/10/17 11:15:27  atterer
  extract comments from text areas, substitute '#1' to '#8'

  Revision 1.6  1998/09/08 21:10:42  atterer
  arrowheads positioned correctly for diagonal lines
  very basic circle support

  Revision 1.5  1998/09/05 00:40:10  atterer
  arrowheads (as seperate \vector commands)

  Revision 1.4  1998/09/02 23:42:38  atterer
  Text labels (with extra commands for Trinity/Homerton/Corpus as well as
  Oblique/Bold), Grouped objects of a box and a text label turned into one
  box with text centered

  Revision 1.3  1998/08/31 22:50:05  atterer
  recognizes boxes

  Revision 1.2  1998/08/30 18:55:31  atterer
  handling of short straight lines, output of numbers with supressed
  zeroes after decimal point.

  Revision 1.1  1998/08/29 16:43:21  atterer
  Initial revision

*/

#include <kernel.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <std/typeinfo.h>

static const double pi = 3.141592654;

inline double dabs(double n) {
  return (n > 0 ? n : -n);
}
inline int iabs(int n) {
  return (n > 0 ? n : -n);
}

extern const char* const rcsid =
"$Id: cc.draw2latex 1.8 1998/10/18 15:10:15 atterer Exp $";

#ifdef DEBUG
  #define D(cmd) cmd
  #define ND(cmd)
  #define log(args) printf args
#else
  #define D(cmd)
  #define ND(cmd) cmd
  #define log(args)
#endif
//______________________________________________________________________

// switches
// true => turn short straight lines into beziers, false => make longer
static bool shortbezline = false;
static const double shortlinelen = 10;
// true => (0,0) in {picture} is lower left of global bbox of drawfile,
// false => (0,0) in {picture} is (0,0) in drawfile.
static bool usedrawbbox = true;
//____________________

typedef signed coord;
typedef unsigned word;

struct XY {
  coord x, y;
  bool near(const XY& p) const {
    return dabs(x - p.x) < 8 * 256
           && dabs(y - p.y) < 8 * 256;
  }
  void avg(const XY& a, const XY& b) {
    x = (a.x + b.x) / 2;
    y = (a.y + b.y) / 2;
  }
  XY(void) { } // don't initialise
  XY(coord cx, coord cy) : x(cx), y(cy) { }
};

enum thinthick { neither, thin, thick };
static int print_thinthick(FILE* file, thinthick newt);
//________________________________________

struct BBox {
  int load(FILE* file) {
    return fread(&min.x, sizeof(coord), 4, file); // ahem, not very nice...
  }
  void set_from_mem(const void* ptr) {
    *this = *(BBox*)ptr; // AHEM...
  }
  // alter bbox so that other box is included
  void merge(const BBox* other) {
    if (min.x > other->min.x) min.x = other->min.x;
    if (min.y > other->min.y) min.y = other->min.y;
    if (max.x < other->max.x) max.x = other->max.x;
    if (max.y < other->max.y) max.y = other->max.y;
  }
  // min inclusive, max exclusive
  XY min, max;
};
//________________________________________

// base class for draw objects
class Drawfile;

class DrawObj {
public:
  DrawObj(Drawfile* p) : next(0), parent(p) { }
  virtual ~DrawObj() { }
  /* read object definition from file - file pointer points to word after
     the object type field. returns zero for success */
  virtual int load(FILE* file) = 0;
  // output LaTeX
  virtual int latex(FILE* file, const BBox& globbox) const = 0;
  virtual thinthick thinstate(void) = 0;
  void setnext(DrawObj* obj) { next = obj; return; }
  DrawObj* getnext() const { return next; }
  void setparent(Drawfile* f) { parent = f; return; }
  Drawfile* getparent() const { return parent; }
  const BBox* getbbox() const { return &bbox; }
protected:
  BBox bbox;
  DrawObj* next;
  Drawfile* parent;
};
//____________________

class Path : public DrawObj {
public:
  Path(Drawfile* p) : point(0), element(0), DrawObj(p) { }
  int load(FILE* file);
  int latex(FILE* file, const BBox& globbox) const;
  thinthick thinstate(void) { return thinl; }
  static void setlabel(const char* l1, const char* l2) {
    label1 = const_cast<char*>(l1);
    label2 = const_cast<char*>(l2);
  }
  static bool usedlabel() { return label1 == 0; }
  ~Path() {
    if (point != 0) free(point);
    if (element != 0) free(element);
  }
private:
  thinthick thinl;
  bool sarrow, earrow; // arrowhead at start/end?
  enum elemtype { move, close, draw, bezier };
  struct tag {
    elemtype type;
    int firstpoint;
  };

  static char* label1;
  static char* label2;
  int points; // nr of entries in point[]
  XY* point;
  int elements; // nr of entries in element[] and firstpoint[]
  tag* element;
};
//____________________

class Label : public DrawObj {
public:
  Label(Drawfile* p) : text(0), DrawObj(p) { }
  int load(FILE* file);
  int latex(FILE* file, const BBox& globbox) const;
  thinthick thinstate(void) { return neither; }
  const char* fontcmd(void) const;
  const char* getlabel() { return text; }
  ~Label() { if (text) delete text; }
private:
  char* text;
  int fontnr;
};
//____________________

class Group : public DrawObj {
public:
  Group(Drawfile* p) : first(0), DrawObj(p) { }
  int load(FILE* file);
  int latex(FILE* file, const BBox& globbox) const;
  thinthick thinstate(void) { return neither; }
  ~Group();
private:
  DrawObj* first;
};
//____________________

class Textarea : public DrawObj {
public:
  Textarea(Drawfile* p) : text(0), DrawObj(p) { }
  int load(FILE* file);
  int latex(FILE* file, const BBox& globbox) const;
  thinthick thinstate(void) { return thin; }
  ~Textarea();
private:
  char* text;
};
//________________________________________

enum fonttype { nofont = -1,
  roman = 0, roman_i = 1, roman_b = 2, roman_ib = 3,
  sanss = 4, sanss_i = 5, sanss_b = 6, sanss_ib = 7,
  typew = 8, typew_i = 9, typew_b = 10, typew_ib = 11 };

class Drawfile {
public:
  // object type numbers as stored in Draw files
  enum objtype {
    type_fonttable = 0,
    type_label = 1, // Text object
    type_path = 2,
    type_group = 6,
    type_textarea = 9,
    type_transformlabel = 12 };
  // set up Drawfile data structure from file
  int load(FILE* file);
  // output in LaTeX format, using {picture}
  int latex(FILE* file) const;
  // create bbox data from that of all objects in the Drawfile
  void make_bbox(void);
  static int latex_line(FILE* file, coord x0, coord y0, coord x1, coord y1);
  static coord latex_lineerrx(void) { return lineerr.x; }
  static coord latex_lineerry(void) { return lineerr.y; }
  static int latex_arrowhead(FILE* file, coord x, coord y, double angle);
  ~Drawfile();
  static int loadobj(FILE* file, DrawObj*& dest, Drawfile* destfile);
  int loadfonttable(FILE* file);
  fonttype fontentry(int idx) { return fonttable[idx]; }
private:
  static inline void Drawfile::cmp_angle(double& min, double angle,
                     double& best, signed& bestoutx, signed& bestouty,
                     signed outx, signed outy, double outangle);
  static XY lineerr;
  fonttype fonttable[256];
  BBox bbox;
  DrawObj* first;
};
//______________________________________________________________________
//______________________________________________________________________

Drawfile::~Drawfile(void)
{
  DrawObj* o = first;
  while (o) {
    DrawObj* n = o->getnext();
    log(("~Drawfile: deleting %x, next at %x\n", (int)o, (int)n));
    delete o;
    o = n;
  }
  return;
}
//________________________________________

int Drawfile::loadfonttable(FILE* file)
{
  word size;
  long pos = ftell(file);
  if (pos == -1L) return 1;
  if (fread(&size, sizeof(word), 1, file) == 0) return 1;
  pos += size - sizeof(word);

  // load font names until size reached
  char name[512];
  long currpos;
  while ((currpos = ftell(file)) != -1L && currpos < pos - 3
         && feof(file) == 0) {
    int idx = getc(file);
    if (idx == EOF) return 1;
    int c;
    char* nameptr = name;
    while ((c = getc(file)) >= ' ') *nameptr++ = c;
    *nameptr = 0;
    log(("Drawfile::loadfonttable: pos=%ld, currpos=%ld, #%d=\"%s\"\n", pos, currpos, idx, name));

    if (strncmp(name, "Trinity", 7) == 0)
      fonttable[idx] = roman;
    else if (strncmp(name, "Homerton", 8) == 0)
      fonttable[idx] = sanss;
    else if (strncmp(name, "Corpus", 6) == 0)
      fonttable[idx] = typew;
    if (fonttable[idx] != nofont) {
      if (strstr(name, ".Italic") || strstr(name, ".Oblique"))
        fonttable[idx] = (fonttype)(int(fonttable[idx]) | 1);
      if (strstr(name, ".Bold"))
        fonttable[idx] = (fonttype)(int(fonttable[idx]) | 2);
    }
  }
  log(("Drawfile::loadfonttable: end, pos=%ld, currpos=%ld\n", pos, currpos));
  if (fseek(file, pos, SEEK_SET)) return 1; else return 0;
}
//________________________________________

int Drawfile::loadobj(FILE* file, DrawObj*& dest, Drawfile* destfile)
{
  log(("Drawfile::loadobj\n"));
  // get one word of object type
  bool read = false; // may have to skip unknown objects
  while (!read) {
    word objtypeword;
    if (fread(&objtypeword, sizeof(word), 1, file) == 0) return 2;
    //if (feof(file)) return 2;

    // read object
    objtype curobjtype = (objtype)objtypeword;
    log(("Drawfile::loadobj: objtype %d\n", curobjtype));
    switch (curobjtype) {
    case type_fonttable:
      if (destfile == 0 || destfile->loadfonttable(file)) return 1;
      break;
    case type_label: {
        dest = new Label(destfile);
        if (dest->load(file)) { delete dest; dest = 0; return 1; }
        read = true;
        break;
      }
    case type_path: {
        dest = new Path(destfile);
        if (dest->load(file)) { delete dest; dest = 0; return 1; }
        read = true;
        break;
      }
    case type_group: {
        dest = new Group(destfile);
        if (dest->load(file)) { delete dest; dest = 0; return 1; }
        read = true;
        break;
      }
    case type_textarea: {
        dest = new Textarea(destfile);
        if (dest->load(file)) { delete dest; dest = 0; return 1; }
        read = true;
        break;
      }
    default:
      // unknown object type; skip object
      word len;
      if (fread(&len, sizeof(word), 1, file) == 0) {
        dest = 0;
        return 1;
      }
      log(("Drawfile::load: unknown obj %d, len %d\n", curobjtype, len));
      if (fseek(file, len - 2 * sizeof(word), SEEK_CUR)) {
        dest = 0;
        return 1;
      }
      break;
    }
  }
  return 0;
}
//________________________________________

int Drawfile::load(FILE* file)
{
  log(("Drawfile::load\n"));

  int i = 255;
  do {
    fonttable[i] = nofont;
  } while (--i >= 0);

  // read file header
  word hdrword;
  log(("Drawfile::load to %x\n", (int)&hdrword));
  if (fread(&hdrword, sizeof(word), 1, file) == 0) return 1;
  if (hdrword != 0x77617244) return 1; // 'Draw'
  if (fseek(file, 40, SEEK_SET)) return 1;
  //____________________

  // read objects
  if (loadobj(file, first, this)) return 1;
  DrawObj* prev = first;
  while (feof(file) == 0) {
    DrawObj* currobj;
    int err;
    if ((err = loadobj(file, currobj, this)) == 2) return 0;
    if (err) return 1;
    prev->setnext(currobj);
    prev = currobj;
  }
  return 0;
}
//______________________________________________________________________

void Drawfile::make_bbox(void)
{
  DrawObj* o = first;
  log(("Drawfile::make_bbox: first at %x\n", (int)o));
  if (o) {
    bbox = *o->getbbox();
    o = o->getnext();
  }
  while (o) {
    log(("Drawfile::make_bbox: merging with %x\n", (int)o));
    bbox.merge(o->getbbox());
    o = o->getnext();
  }

  if (!usedrawbbox) {
    bbox.max.x -= bbox.min.x;
    bbox.max.y -= bbox.min.y;
    bbox.min.x = 0; bbox.min.y = 0;
    return;
  }
  return;
}
//______________________________________________________________________

char* Path::label1 = 0;
char* Path::label2 = 0;

// load individual objects' data
int Path::load(FILE* file)
{
  struct {
    word size;
    coord xmin, ymin, xmax, ymax;
    word fillcol, outlinecol, outlinewidth;
    word pathstyle;
  } head;

  // read obj header
  if (fread(&head, sizeof(head), 1, file) == 0) return 1;
  bbox.set_from_mem(&head.xmin);

  if (head.outlinewidth) thinl = thick; else thinl = thin;
  if ((head.pathstyle & 0x30) == 0x30) sarrow = true; else sarrow = false;
  if ((head.pathstyle & 0xc) == 0xc) earrow = true; else earrow = false;

  if (head.pathstyle & 1<<7) { // skip dash pattern if present
    struct { word offset, elements; } dashstart;
    if (fread(&dashstart, sizeof(dashstart), 1, file) == 0) return 1;
    log(("Path::load: skipping %d dash patterns\n", dashstart.elements));
    if (fseek(file, dashstart.elements * sizeof(word), SEEK_CUR)) return 1;
  }

  point = static_cast<XY*>(malloc(0)); // coordinates
  if (point == 0) return 1;
  points = 0;
  element = static_cast<tag*>(malloc(0)); // tag info
  if (element == 0) return 1;
  elements = 0;

  // read path elements
  word tagtype;
  if (fread(&tagtype, sizeof(tagtype), 1, file) == 0) return 1;
  while ((tagtype &= 0xff) != 0) { // until "end of path"
    int coopairs; // nr of x/y pairs following
    elemtype thistype;
    switch (tagtype) {
    case 2: coopairs = 1; thistype = move; break;
    case 5: coopairs = 0; thistype = close; break;
    case 8: coopairs = 1; thistype = draw; break;
    case 6: coopairs = 3; thistype = bezier; break;
    default: // just to avoid compiler warnings
      coopairs = 0; thistype = (elemtype)0;
      return 1;
    }
    log(("Path::load: tag type %d; %d coords follow\n", tagtype, coopairs));

    // enlarge element[]
    elements++;
    tag* e = (tag*)realloc(element, elements * sizeof(tag));
    if (e == 0) return 1;
    element = e;
    element[elements - 1].type = thistype;
    element[elements - 1].firstpoint = points;

    // enlarge point[]
    if (coopairs) {
      points += coopairs;
      XY* p = (XY*)realloc(point, points * sizeof(XY));
      if (p == 0) return 1;
      point = p;
      if (fread(&point[points - coopairs], coopairs * sizeof(XY),
                1, file) == 0) return 1;
    }
    if (fread(&tagtype, sizeof(tagtype), 1, file) == 0) return 1;
  }
  log(("Path::load: returning; %d elements, %d points\n", elements, points));
  return 0;
}
//________________________________________

int Label::load(FILE* file)
{
  struct {
    word size;
    coord xmin, ymin, xmax, ymax;
    word textcol, backcol, style, xsize, ysize, xbaseline, ybaseline;
  } head;
  log(("Label::load\n"));

  if (fread(&head, sizeof(head), 1, file) == 0) return 1;
  head.xmin = head.xbaseline; head.ymin = head.ybaseline;
  bbox.set_from_mem(&head.xmin);
  int strlen = head.size - sizeof(head) - sizeof(word);
  text = new char[strlen];
  fontnr = head.style & 0xff;
  if (text == 0) return 1;
  if (fread(text, sizeof(char), strlen, file) == 0) return 1;
  return 0;
}
//________________________________________

Group::~Group(void)
{
  DrawObj* o = first;
  while (o) {
    DrawObj* n = o->getnext();
    log(("~Group: deleting %x, next at %x\n", (int)o, (int)n));
    delete o;
    o = n;
  }
  return;
}
//________________________________________

int Group::load(FILE* file)
{
  struct {
    word size;
    coord xmin, ymin, xmax, ymax;
    char description[12];
  } head;
  log(("Group::load\n"));

  long pos = ftell(file);
  if (pos == -1L) return 1;

  // read obj header
  if (fread(&head, sizeof(head), 1, file) == 0) return 1;
  bbox.set_from_mem(&head.xmin);
  pos += head.size - sizeof(word);

  // load objects until size reached
  if (Drawfile::loadobj(file, first, parent)) return 1;
  DrawObj* prev = first;
  long currpos;
  while ((currpos = ftell(file)) != -1L && currpos < pos
         && feof(file) == 0) {
    log(("Group::load: pos=%ld, currpos=%ld\n", pos, currpos));
    DrawObj* currobj;
    int err;
    if ((err = Drawfile::loadobj(file, currobj, parent)) == 2) return 0;
    log(("Group::load: obj loaded to %x, prev at %x\n", (int)currobj, (int)prev));
    if (err) return 1;
    prev->setnext(currobj);
    prev = currobj;
  }
  if (currpos >= pos) return 0; else return 1;
}
//________________________________________

Textarea::~Textarea(void)
{
  if (text) free(text);
  return;
}
//________________________________________

int Textarea::load(FILE* file)
{
  struct {
    word size;
    coord xmin, ymin, xmax, ymax;
    word dummy;
  } head;
  struct {
    word size;
    coord xmin, ymin, xmax, ymax;
    word objtype; // objtype of following text column obj, or 0
  } areaobj;
  log(("Textarea::load\n"));

  // read obj header
  if (fread(&head, sizeof(head), 1, file) == 0) return 1;
  bbox.set_from_mem(&head.xmin);
  // 4 of the 5 words are skipped below, 1 is for obj type
  head.size -= sizeof(head) + 5 * sizeof(word);

  // skip text column objects
  do {
    if (fread(&areaobj, sizeof(areaobj), 1, file) == 0) return 1;
    log(("Textarea::load: skip column\n"));
    head.size -= sizeof(areaobj);
  } while (areaobj.objtype != 0);

  // skip other words in header
  if (fread(&areaobj, 4 * sizeof(word), 1, file) == 0) return 1;

  // now load ASCII textarea source
  text = static_cast<char*>(malloc(head.size));
  if (text == 0 || fread(text, head.size, 1, file) == 0) return 1;
  log(("Textarea::load: size %d\n", head.size));
  return 0;
}
//______________________________________________________________________

// convert to LaTeX coordinates
int latex_coord(int len) {
  return len * 72 / 180 / 256;
  /* NB slightly incorrect; one TeX pt != 1/72"
     72.27 TeX pt make one inch */
}

double latex_coord(double len) {
  return len * 72 / 180 / 256;
}

double latex_coord1(double len) {
  double n = double(int(len * 72 / 180 / 256 * 10 + .5)) / 10;
  if (n > .01) return n; else return 0;
}

// convert to Draw coordinates
int coord_latex(double len) {
  return int(len / 72 * 180 * 256);
}

double round1(double n) {
  double m = double(int(n * 10 + .5)) / 10;
  if (m > .01) return m; else return 0;
}
double round2(double n) {
  double m = double(int(n * 100 +.5)) / 100;
  if (m > .001) return m; else return 0;
}
//________________________________________

// print "\thinlines" or "\thicklines"
int print_thinthick(FILE* file, thinthick newt)
{
  static thinthick thinlines = neither;

  if (file == 0) {
    thinlines = newt;
    return 0;
  }

  if (thinlines != newt) {
    switch (newt) {
    case neither:
      break;
    case thin:
      if (fprintf(file, "\n\\thinlines") == 0) return 1;
      thinlines = newt;
      break;
    case thick:
      if (fprintf(file, "\n\\thicklines") == 0) return 1;
      thinlines = newt;
      break;
    }
  }
  return 0;
}
//________________________________________

// output LaTeX
int Drawfile::latex(FILE* file) const
{
  log(("Drawfile::latex\n"));
  print_thinthick(0, neither);
  if (fprintf(file, "\\documentclass[12pt]{article}\n\\begin{document}\n")
      == 0) return 1;
  if (fprintf(file, "\n%% converted by draw2latex"
              "\n\\begin{picture}(%d,%d)",
              latex_coord(bbox.max.x - bbox.min.x),
              latex_coord(bbox.max.y - bbox.min.y))
      == 0) return 1;
  #if 0
  if (fprintf(file, "\n\\put(0,0){\\framebox(%d,%d){}}",
              latex_coord(bbox.max.x - bbox.min.x),
              latex_coord(bbox.max.y - bbox.min.y))
      == 0) return 1;
  #endif
  DrawObj* o = first;

  // output objects
  while (o) {
    thinthick newthinlines = o->thinstate();
    if (print_thinthick(file, newthinlines)) return 1;
    if (o->latex(file, bbox)) return 1;
    o = o->getnext();
  }

  if (print_thinthick(file, thin)) return 1;
  if (fprintf(file, "\n\\end{picture}\n\n\\end{document}\n")
      == 0) return 1;

  return 0;
}
//________________________________________

inline void Drawfile::cmp_angle(double& min, double angle, double& best,
                     signed& bestoutx, signed& bestouty,
                     signed outx, signed outy, double outangle) {
  double tmp;
  if (min > (tmp = dabs(angle - outangle))) {
    bestoutx = outx; bestouty = outy;
    min = tmp;
    best = outangle;
  }
}
//____________________

/* output a \vector{} command, but with a vector length of 0.
   angle is where the vector points to, i.e. 0 is right, pi/2 is up etc.
   angle must be from -pi to +pi */
int Drawfile::latex_arrowhead(FILE* file, coord x, coord y, double angle)
{
  bool downward, left;

  double lx = latex_coord(double(x));
  double ly = latex_coord(double(y));
  lx += 3.5 * cos(angle); // don't let line overstrike tip of arrowhead
  ly += 3.5 * sin(angle);
  log(("Drawfile::latex_arrowhead: angle %f (%g,%g)\n", angle, lx, ly));

  // restrict angle to 0..90 degrees
  if (angle < 0.0) {
    downward = true;
    angle = -angle;
  } else {
    downward = false;
  }
  if (angle > pi / 2) {
    left = true;
    angle = pi - angle;
  } else {
    left = false;
  }

  signed bestoutx = 1, bestouty = 0;
  double min = angle; // difference between angle and best angle so far
  double best = 0; // best angle so far

  /* update best, bestoutx, outy if outangle nearer to angle than for
     previous calls. outx,y is vector passed to \line{} */
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 4, 1.32581766);
  cmp_angle(min, angle, best, bestoutx, bestouty, 4, 1, 0.244978663);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 4, 0.927295218);
  cmp_angle(min, angle, best, bestoutx, bestouty, 4, 3, 0.643501109);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 3, 1.24904577);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 1, 0.321750554);
  cmp_angle(min, angle, best, bestoutx, bestouty, 2, 3, 0.982793723);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 2, 0.588002604);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 2, 1.10714872);
  cmp_angle(min, angle, best, bestoutx, bestouty, 2, 1, 0.463647609);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 1, 0.785398163);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 1, 0.785398163);
  cmp_angle(min, angle, best, bestoutx, bestouty, 0, 1, pi / 2);

  if (left) { bestoutx = -bestoutx; best = pi - best; }
  if (downward) { bestouty = -bestouty; best = -best; }

  if (fprintf(file, "\n\\put(%.4g,%.4g){\\vector(%d,%d){0}}",
              round1(lx), round1(ly), bestoutx, bestouty) == 0) return 1;
  return 0;
}
//____________________

XY Drawfile::lineerr;

int Drawfile::latex_line(FILE* file, coord x0, coord y0, coord x1, coord y1)
{
  double dx = latex_coord(double(x0 - x1)); // distance
  double dy = latex_coord(double(y0 - y1));

  // find best angle
  double angle = dabs(atan2(dy, dx));
  if (angle > pi / 2) angle = pi - angle; // angle 0..90 degrees
  signed bestoutx = 1, bestouty = 0;
  double min = angle; // difference between angle and best angle so far
  double best = 0; // best angle so far

  /* update best, bestoutx, outy if outangle nearer to angle than for
     previous calls. outx,y is vector passed to \line{} */
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 6, 1.40564765);
  cmp_angle(min, angle, best, bestoutx, bestouty, 6, 1, 0.165148677);
  cmp_angle(min, angle, best, bestoutx, bestouty, 5, 6, 0.87605805);
  cmp_angle(min, angle, best, bestoutx, bestouty, 6, 5, 0.694738276);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 5, 1.37340077);
  cmp_angle(min, angle, best, bestoutx, bestouty, 5, 1, 0.19739556);
  cmp_angle(min, angle, best, bestoutx, bestouty, 2, 5, 1.19028995);
  cmp_angle(min, angle, best, bestoutx, bestouty, 5, 2, 0.380506377);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 5, 1.03037683);
  cmp_angle(min, angle, best, bestoutx, bestouty, 5, 3, 0.5404195);
  cmp_angle(min, angle, best, bestoutx, bestouty, 4, 5, 0.896055384);
  cmp_angle(min, angle, best, bestoutx, bestouty, 5, 4, 0.674740942);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 4, 1.32581766);
  cmp_angle(min, angle, best, bestoutx, bestouty, 4, 1, 0.244978663);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 4, 0.927295218);
  cmp_angle(min, angle, best, bestoutx, bestouty, 4, 3, 0.643501109);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 3, 1.24904577);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 1, 0.321750554);
  cmp_angle(min, angle, best, bestoutx, bestouty, 2, 3, 0.982793723);
  cmp_angle(min, angle, best, bestoutx, bestouty, 3, 2, 0.588002604);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 2, 1.10714872);
  cmp_angle(min, angle, best, bestoutx, bestouty, 2, 1, 0.463647609);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 1, 0.785398163);
  cmp_angle(min, angle, best, bestoutx, bestouty, 1, 1, 0.785398163);
  cmp_angle(min, angle, best, bestoutx, bestouty, 0, 1, pi / 2);

  // is line too short for TeX?
  double len = sqrt(dx * dx + dy * dy);
  if (len < 2) {
    return 0; // ignore very short lines
  }
  if (bestoutx != 0 && bestouty != 0) {
    double minlen = -1;
    if (bestoutx < bestouty)
      minlen = double(bestoutx) / double(bestouty);
    else
      minlen = double(bestouty) / double(bestoutx);
    minlen = shortlinelen * sqrt(1.0 + minlen * minlen);
    if (len < minlen) {
      // too short
      if (shortbezline) {
        // either turn into bezier
        lineerr.x = 0; lineerr.y = 0;
        if (fprintf(file, "\n\\qbezier(%.4g,%.4g)(%.4g,%.4g)(%.4g,%.4g)",
                    latex_coord1(double(x0)), latex_coord1(double(y0)),
                    latex_coord1(double((x0 + x1) / 2)),
                    latex_coord1(double((y0 + y1) / 2)),
                    latex_coord1(double(x1)), latex_coord1(double(y1)))
            == 0) return 1;
        return 0;
      } else {
        // or make line longer
        len = minlen;
      }
    }
  }

  if (dx < 0) { bestoutx = -bestoutx; best = pi - best; }
  if (dy < 0) { bestouty = -bestouty; best = -best; }

  // reposition line so error is minimal
  double mx = latex_coord(double((x0 + x1) / 2)); // middle
  double my = latex_coord(double((y0 + y1) / 2));
  double wid = cos(best) * len;
  mx -= wid / 2;
  wid = dabs(wid);
  my -= sin(best) * len / 2;

  lineerr.x = coord_latex(mx) - x1;
  lineerr.y = coord_latex(my) - y1;

  if (bestoutx == 0) {
    if (fprintf(file, "\n\\put(%.4g,%.4g){\\line(0,%d){%.4g}}",
                round1(mx), round1(my), bestouty, round1(len))
        == 0) return 1;
  } else {
    if (fprintf(file, "\n\\put(%.4g,%.4g){\\line(%d,%d){%.5g}}",
                round1(mx), round1(my), bestoutx, bestouty, round2(wid))
        == 0) return 1;
  }
  /*if (fprintf(file, "\n\\put(%.4g,%.4g){\\circle*{3}}",
              (latex_coord(double(current.x))),
              (latex_coord(double(current.y)))) == 0) return 1;*/
  return 0;
}
//________________________________________

int Path::latex(FILE* file, const BBox& globbox) const
{
  XY current;

  // is path a box?
  if (elements == 6 && points == 5 && element[0].type == move
      && element[1].type == draw && element[2].type == draw
      && element[3].type == draw && element[4].type == draw
      && element[5].type == close && point[0].near(point[4])) {
    log(("Path::latex: is box?\n"));
    bool hor; // true => next segment must be horizontal, otherwise vertical
    if (iabs(point[1].x - point[0].x) > iabs(point[1].y - point[0].y))
      hor = true;
    else
      hor = false;

    bool valid = true;
    int width = 0, height = 0, midx = 0, midy = 0;
    int i = 0;
    do { // horizontal and vertical lines must alternate
      int dx = iabs(point[i+1].x - point[i].x);
      int dy = iabs(point[i+1].y - point[i].y);
      midx += point[i].x / 4; midy += point[i].y / 4;
      if (hor) {
        width += dx / 2;
      } else {
        height += dy / 2;
        int tmp = dx; dx = dy; dy = tmp;
      }
      if (dy > dx / 12) valid = false;
      hor = !hor;
    } while (++i < 4);

    if (valid) {
      if (fprintf(file, "\n\\put(%.4g,%.4g){\\framebox(%.4g,%.4g){%s%s}}",
                  latex_coord1(double(midx - width / 2 - globbox.min.x)),
                  latex_coord1(double(midy - height / 2 - globbox.min.y)),
                  latex_coord1(double(width)),
                  latex_coord1(double(height)),
                  (label1 == 0 ? "" : label1),
                  (label1 == 0 ? "" : label2)) == 0) return 1;
      label1 = 0; // "label was used"
      return 0;
    }
  }

  // is path a circle?
  // 2 to 8 points, 1st and last point same, all beziers
  if (elements >= 4 && elements <= 10
      && element[0].type == move
      && element[1].type == bezier && element[2].type == bezier
      && element[elements - 1].type == close
      && element[elements - 2].type == bezier
      && point[0].near(point[element[elements - 2].firstpoint + 2])) {
    // check remaining points (if any)
    int i;
    XY m(0, 0);
    for (i = 1; i < elements - 1; i++) {
      if (element[i].type != bezier) break;
      m.x += point[element[i].firstpoint + 2].x;
      m.y += point[element[i].firstpoint + 2].y;
    }
    if (i >= elements - 1) {
      m.x /= elements - 2;
      m.y /= elements - 2;
      double minr = 1e10, maxr = 0; // actually radius^2
      double meanr = 0;
      // measure mean radius (and get max and min)
      for (i = 1; i < elements - 1; i++) {
        double dx = double(point[element[i].firstpoint + 2].x - m.x);
        double dy = double(point[element[i].firstpoint + 2].y - m.y);
        double radius = dx * dx + dy * dy;
        log(("Path::latex: circle? Point radius %f\n", radius));
        if (minr > radius)
          minr = radius;
        else if (maxr < radius)
          maxr = radius;
        meanr += radius;
      }
      meanr /= double(elements - 2);
      double r = latex_coord1(sqrt(meanr));
      log(("Path::latex: circle? Mean radius %f pts\n", r));
      if (meanr - minr < meanr / 8 && maxr - meanr < meanr / 8 && r < 40) {
        if (fprintf(file, "\n\\put(%.4g,%.4g){\\circle{%.4g}}",
                    latex_coord1(double(m.x - globbox.min.x)),
                    latex_coord1(double(m.y - globbox.min.y)),
                    r * 2) == 0) return 1; else return 0;
      } else {
        log(("Path::latex: not circlish enough\n"));
      }
    }
  }

  // is neither box nor circle
  bool linestart = true;
  for (int i = 0; i < elements; i++) {
    switch (element[i].type) {
    case move:
      linestart = true;
      current = point[element[i].firstpoint];
      current.x -= globbox.min.x; current.y -= globbox.min.y;
      break;
    case bezier: {
      linestart = false;
      XY* points = &point[element[i].firstpoint];
      XY next = points[2];
      next.x -= globbox.min.x; next.y -= globbox.min.y;
      if (points[0].near(points[1])) {
        XY control;
        control.avg(points[0], points[1]);
        control.x -= globbox.min.x; control.y -= globbox.min.y;
        // control points are identical; use LaTeX's bezier
        if (fprintf(file, "\n\\qbezier(%.4g,%.4g)(%.4g,%.4g)(%.4g,%.4g)",
                    latex_coord1(double(current.x)),
                    latex_coord1(double(current.y)),
                    latex_coord1(double(control.x)),
                    latex_coord1(double(control.y)),
                    latex_coord1(double(next.x)),
                    latex_coord1(double(next.y))) == 0) return 1;
      } else {
        // just draw straight line
        if (Drawfile::latex_line(file, current.x, current.y, next.x, next.y))
          return 1;
      }
      current = next;
      break;
      }
    case draw: {
      XY next = point[element[i].firstpoint];
      next.x -= globbox.min.x; next.y -= globbox.min.y;
      if (Drawfile::latex_line(file, current.x, current.y, next.x, next.y))
        return 1;
      if (linestart && sarrow && Drawfile::latex_arrowhead(file,
          current.x - Drawfile::latex_lineerrx(),
          current.y - Drawfile::latex_lineerry(),
          atan2(current.y - next.y, current.x - next.x))) return 1;
      linestart = false;
      // for correct drawfiles, always at least one other tag follows 'draw'
      if (earrow && i == elements - 1) {
        if (Drawfile::latex_arrowhead(file,
            next.x + Drawfile::latex_lineerrx(),
            next.y + Drawfile::latex_lineerry(),
            atan2(next.y - current.y, next.x - current.x))) return 1;
      }
      current = next;
      break;
      }
    default:
      linestart = false;
      break; // do nothing
    }
  }
  return 0;
}
//________________________________________

const char* Label::fontcmd(void) const
{
  switch (parent->fontentry(fontnr)) {
    case roman:
      return "\\rmfamily ";
      break;
    case roman_i:
      return "\\rmfamily\\itshape ";
      break;
    case roman_b:
      return "\\rmfamily\\bfseries ";
      break;
    case roman_ib:
      return "\\rmfamily\\itshape\\bfseries ";
      break;
    case sanss:
      return "\\sffamily ";
      break;
    case sanss_i:
      return "\\sffamily\\slshape ";
      break;
    case sanss_b:
      return "\\sffamily\\bfseries ";
      break;
    case sanss_ib:
      return "\\sffamily\\slshape\\bfseries ";
      break;
    case typew:
      return "\\ttfamily ";
      break;
    case typew_i:
      return "\\ttfamily\\slshape ";
      break;
    case typew_b:
      return "\\ttfamily\\bfseries ";
      break;
    case typew_ib:
      return "\\ttfamily\\slshape\\bfseries ";
      break;
    default:
      return "";
      break;
  }
}
//____________________

int Label::latex(FILE* file, const BBox& globbox) const
{
  if (fprintf(file, "\n\\put(%.4g,%.4g){%s%s}",
              latex_coord1(bbox.min.x - globbox.min.x),
              latex_coord1(bbox.min.y - globbox.min.y),
              this->fontcmd(), text) == 0) return 1;
  return 0;
}
//________________________________________

int Group::latex(FILE* file, const BBox& globbox) const
{
  #if 0
  if (fprintf(file, "\n\\put(%d,%d){\\dashbox{2}(%d,%d){}}",
              latex_coord(bbox.min.x - globbox.min.x),
              latex_coord(bbox.min.y - globbox.min.y),
              latex_coord(bbox.max.x - bbox.min.x),
              latex_coord(bbox.max.y - bbox.min.y))
      == 0) return 1;
  #endif

  DrawObj* o = first;
  if (first && (o = first->getnext()) && o->getnext() == 0) {
    DrawObj* patho = 0;
    DrawObj* labelo = 0;
    if (typeid(*first) == typeid(Path)) patho = first;
    else if (typeid(*o) == typeid(Path)) patho = o;
    if (typeid(*first) == typeid(Label)) labelo = first;
    else if (typeid(*o) == typeid(Label)) labelo = o;
    if (patho && labelo) {
      thinthick newthinlines = ((Path*)patho)->thinstate();
      if (print_thinthick(file, newthinlines)) return 1;
      Path::setlabel(((Label*)labelo)->fontcmd(),
                       ((Label*)labelo)->getlabel());
      if (((Path*)patho)->latex(file, globbox)) return 1;
      if (((Path*)patho)->usedlabel()) return 0;
      newthinlines = ((Label*)labelo)->thinstate();
      if (print_thinthick(file, newthinlines)) return 1;
      if (((Label*)labelo)->latex(file, globbox)) return 1;
      return 0;
    }
  }

  // output objects
  o = first;
  while (o) {
    thinthick newthinlines = o->thinstate();
    if (print_thinthick(file, newthinlines)) return 1;
    if (o->latex(file, globbox)) return 1;
    o = o->getnext();
  }
  return 0;
}
//________________________________________

/* when outputting text area, copy comments (after "\;") verbatim, ignore
   rest of ASCII source */
int Textarea::latex(FILE* file, const BBox& globbox) const
{
  char* curpos = text;
  // curpos at the beginning of a new line (text always ends with \n!)
  while (curpos[1] != 0) {
    if (*++curpos == '\\' && curpos[1] == ';') {
      // copy line to output, substituting for "#1" to "#8"
      if (fputc('\n', file) == EOF) return 1;
      curpos += 2;
      do {
        switch (*curpos) {
        case '\\':
          if (*++curpos == '\n') {
            if (fputc('\\', file) == EOF) return 1;
          } else {
            if (fwrite(curpos - 1, 1, 2, file) == 0) return 1;
            curpos++;
          }
          break;
        case '#':
          if (*++curpos >= '1' && *curpos <= '8') {
            // substitute
            coord val;
            switch (*curpos++) {
            case '1': val = bbox.min.x - globbox.min.x; break;
            case '2': val = bbox.min.y - globbox.min.y; break;
            case '3': val = bbox.max.x - globbox.min.x; break;
            case '4': val = bbox.max.y - globbox.min.y; break;
            case '5': val = bbox.max.x - bbox.min.x; break;
            case '6': val = bbox.max.y - bbox.min.y; break;
            case '7':
              val = (bbox.min.x + bbox.max.x) / 2 - globbox.min.x; break;
            case '8':
              val = (bbox.min.y + bbox.max.y) / 2 - globbox.min.y; break;
            default: val = 0;
            }
            if (fprintf(file, "%.4g", latex_coord1(val)) == 0) return 1;
          } else {
            if (fputc('#', file) == EOF) return 1;
          }
          break;
        case '%': {
          // TeX comment - don't substitute for rest of line
          char* nlpos = curpos;
          while (*++nlpos != '\n') ;
          if (fwrite(curpos, 1, nlpos - curpos, file) == 0) return 1;
          curpos = nlpos;
          break;
        }
        default:
          if (*curpos != '\n')
            if (fputc(*curpos++, file) == EOF) return 1;
        }
      } while (*curpos != '\n');
    } else {
      // skip characters until next \n
      while (*curpos++ != '\n') ;
      curpos--;
    }
  }
  return 0;
}
//______________________________________________________________________

static char outname[256]; // name of output file

int main (int argc, char* argv[])
{
  argc--; argv++; // skip first word on command line

  if (argc == 0) {
    fprintf(stderr, "Syntax: draw2latex [-shortline] [-origin] <DrawFile> "
                    "[<DrawFile> ...]\n"
                    "Output is written to <DrawFile>/tex\n");
    return 0;
  }

  int tmpargc = argc;
  char** tmpargv = argv;
  while (tmpargc) {
    if (strcmp(*tmpargv, "-shortline") == 0 || strcmp(*tmpargv, "-s") == 0) {
      *tmpargv[0] = '\0';
      shortbezline = true;
    } else if (strcmp(*tmpargv, "-origin") == 0
            || strcmp(*tmpargv, "-o") == 0) {
      *tmpargv[0] = '\0';
      usedrawbbox = false;
    }
    tmpargc--; tmpargv++;
  }

  while (argc) {
    if (*argv[0] != '\0') {
      FILE* file = fopen(*argv, "r");
      if (file == 0) {
        fprintf(stderr, "Couldn't open \"%s\"\n", *argv);
      } else {
        Drawfile* d = new Drawfile;
        // load each file on command line
        if (d->load(file))
          fprintf(stderr, "Couldn't read \"%s\"\n", *argv);
        fclose(file);
        sprintf(outname, "%s/tex", *argv);
        file = fopen(outname, "w");
        if (file == 0) {
          fprintf(stderr, "Couldn't open \"%s\"\n", outname);
        } else {
          // create global bounding box
          d->make_bbox();
          // write converted LaTeX
          if (d->latex(file))
            fprintf(stderr, "Couldn't write to \"%s\"\n", outname);
          fclose(file);
          _kernel_osfile_block kb;
          kb.load = 0xaca;
          _kernel_osfile(18, outname, &kb);
        }
        delete d;
      }
    }
    argc--; argv++;
  }

  return 0;
}
