/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
     Written by James Clark (jjc@jclark.com)

This file is part of groff.

groff 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 3 of the License, or
(at your option) any later version.

groff 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, see <http://www.gnu.org/licenses/>. */

#include "pic.h"
#include "common.h"


const double RELATIVE_THICKNESS = -1.0;
const double BAD_THICKNESS = -2.0;

class simple_output : public common_output {
  virtual void simple_line(const position &, const position &) = 0;
  virtual void simple_spline(const position &, const position *, int n) = 0;
  virtual void simple_arc(const position &, const position &,
			  const position &) = 0;
  virtual void simple_circle(int, const position &, double rad) = 0;
  virtual void simple_ellipse(int, const position &, const distance &) = 0;
  virtual void simple_polygon(int, const position *, int) = 0;
  virtual void line_thickness(double) = 0;
  virtual void set_fill(double) = 0;
  virtual void set_color(char *, char *) = 0;
  virtual void reset_color() = 0;
  virtual char *get_last_filled() = 0;
  void dot(const position &, const line_type &) = 0;
public:
  void start_picture(double sc, const position &ll, const position &ur) = 0;
  void finish_picture() = 0;
  void text(const position &, text_piece *, int, double) = 0;
  void line(const position &, const position *, int n,
	    const line_type &);
  void polygon(const position *, int n,
	       const line_type &, double);
  void spline(const position &, const position *, int n,
	      const line_type &);
  void arc(const position &, const position &, const position &,
	   const line_type &);
  void circle(const position &, double rad, const line_type &, double);
  void ellipse(const position &, const distance &, const line_type &, double);
  int supports_filled_polygons();
};

int simple_output::supports_filled_polygons()
{
  return driver_extension_flag != 0;
}

void simple_output::arc(const position &start, const position &cent,
			const position &end, const line_type &lt)
{
  switch (lt.type) {
  case line_type::solid:
    line_thickness(lt.thickness);
    simple_arc(start, cent, end);
    break;
  case line_type::invisible:
    break;
  case line_type::dashed:
    dashed_arc(start, cent, end, lt);
    break;
  case line_type::dotted:
    dotted_arc(start, cent, end, lt);
    break;
  }
}

void simple_output::line(const position &start, const position *v, int n,
			 const line_type &lt)
{
  position pos = start;
  line_thickness(lt.thickness);
  for (int i = 0; i < n; i++) {
    switch (lt.type) {
    case line_type::solid:
      simple_line(pos, v[i]);
      break;
    case line_type::dotted:
      {
	distance vec(v[i] - pos);
	double dist = hypot(vec);
	int ndots = int(dist/lt.dash_width + .5);
	if (ndots == 0)
	  dot(pos, lt);
	else {
	  vec /= double(ndots);
	  for (int j = 0; j <= ndots; j++)
	    dot(pos + vec*j, lt);
	}
      }
      break;
    case line_type::dashed:
      {
	distance vec(v[i] - pos);
	double dist = hypot(vec);
	if (dist <= lt.dash_width*2.0)
	  simple_line(pos, v[i]);
	else {
	  int ndashes = int((dist - lt.dash_width)/(lt.dash_width*2.0) + .5);
	  distance dash_vec = vec*(lt.dash_width/dist);
	  double dash_gap = (dist - lt.dash_width)/ndashes;
	  distance dash_gap_vec = vec*(dash_gap/dist);
	  for (int j = 0; j <= ndashes; j++) {
	    position s(pos + dash_gap_vec*j);
	    simple_line(s, s + dash_vec);
	  }
	}
      }
      break;
    case line_type::invisible:
      break;
    default:
      assert(0);
    }
    pos = v[i];
  }
}

void simple_output::spline(const position &start, const position *v, int n,
			   const line_type &lt)
{
  line_thickness(lt.thickness);
  simple_spline(start, v, n);
}

void simple_output::polygon(const position *v, int n,
			    const line_type &lt, double fill)
{
  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
    if (get_last_filled() == 0)
      set_fill(fill);
    simple_polygon(1, v, n);
  }
  if (lt.type == line_type::solid && driver_extension_flag) {
    line_thickness(lt.thickness);
    simple_polygon(0, v, n);
  }
  else if (lt.type != line_type::invisible) {
    line_thickness(lt.thickness);
    line(v[n - 1], v, n, lt);
  }
}

void simple_output::circle(const position &cent, double rad,
			   const line_type &lt, double fill)
{
  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
    if (get_last_filled() == 0)
      set_fill(fill);
    simple_circle(1, cent, rad);
  }
  line_thickness(lt.thickness);
  switch (lt.type) {
  case line_type::invisible:
    break;
  case line_type::dashed:
    dashed_circle(cent, rad, lt);
    break;
  case line_type::dotted:
    dotted_circle(cent, rad, lt);
    break;
  case line_type::solid:
    simple_circle(0, cent, rad);
    break;
  default:
    assert(0);
  }
}

void simple_output::ellipse(const position &cent, const distance &dim,
			    const line_type &lt, double fill)
{
  if (driver_extension_flag && ((fill >= 0.0) || (get_last_filled() != 0))) {
    if (get_last_filled() == 0)
      set_fill(fill);
    simple_ellipse(1, cent, dim);
  }
  if (lt.type != line_type::invisible)
    line_thickness(lt.thickness);
  switch (lt.type) {
  case line_type::invisible:
    break;
  case line_type::dotted:
    dotted_ellipse(cent, dim, lt);
    break;
  case line_type::dashed:
    dashed_ellipse(cent, dim, lt);
    break;
  case line_type::solid:
    simple_ellipse(0, cent, dim);
    break;
  default:
    assert(0);
  }
}

class troff_output : public simple_output {
  const char *last_filename;
  position upper_left;
  double height;
  double scale;
  double last_line_thickness;
  double last_fill;
  char *last_filled;		// color
  char *last_outlined;		// color
public:
  troff_output();
  ~troff_output();
  void start_picture(double, const position &ll, const position &ur);
  void finish_picture();
  void text(const position &, text_piece *, int, double);
  void dot(const position &, const line_type &);
  void command(const char *, const char *, int);
  void set_location(const char *, int);
  void simple_line(const position &, const position &);
  void simple_spline(const position &, const position *, int n);
  void simple_arc(const position &, const position &, const position &);
  void simple_circle(int, const position &, double rad);
  void simple_ellipse(int, const position &, const distance &);
  void simple_polygon(int, const position *, int);
  void line_thickness(double p);
  void set_fill(double);
  void set_color(char *, char *);
  void reset_color();
  char *get_last_filled();
  char *get_outline_color();
  position transform(const position &);
};

output *make_troff_output()
{
  return new troff_output;
}

troff_output::troff_output()
: last_filename(0), last_line_thickness(BAD_THICKNESS),
  last_fill(-1.0), last_filled(0), last_outlined(0)
{
}

troff_output::~troff_output()
{
  free((char *)last_filename);
}

inline position troff_output::transform(const position &pos)
{
  return position((pos.x - upper_left.x)/scale,
		  (upper_left.y - pos.y)/scale);
}

#define FILL_REG "00"

// If this register > 0, then pic will generate \X'ps: ...' commands
// if the aligned attribute is used.
#define GROPS_REG "0p"

// If this register is defined, geqn won't produce '\x's.
#define EQN_NO_EXTRA_SPACE_REG "0x"

void troff_output::start_picture(double sc,
				 const position &ll, const position &ur)
{
  upper_left.x = ll.x;
  upper_left.y = ur.y;
  scale = compute_scale(sc, ll, ur);
  height = (ur.y - ll.y)/scale;
  double width = (ur.x - ll.x)/scale;
  printf(".PS %.3fi %.3fi", height, width);
  if (args)
    printf(" %s\n", args);
  else
    putchar('\n');
  printf(".\\\" %g %g %g %g\n", ll.x, ll.y, ur.x, ur.y);
  printf(".\\\" %.3fi %.3fi %.3fi %.3fi\n", 0.0, height, width, 0.0);
  printf(".nr " FILL_REG " \\n(.u\n.nf\n");
  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 1\n");
  // This guarantees that if the picture is used in a diversion it will
  // have the right width.
  printf("\\h'%.3fi'\n.sp -1\n", width);
}

void troff_output::finish_picture()
{
  line_thickness(BAD_THICKNESS);
  last_fill = -1.0;		// force it to be reset for each picture
  reset_color();
  if (!(want_flyback || want_alternate_flyback))
    printf(".sp %.3fi+1\n", height);
  printf(".if \\n(" FILL_REG " .fi\n");
  printf(".br\n");
  printf(".nr " EQN_NO_EXTRA_SPACE_REG " 0\n");
  // this is a little gross
  set_location(current_filename, current_lineno);
  if (want_flyback)
    fputs(".PF\n", stdout);
  else if (want_alternate_flyback)
    fputs(".PY\n", stdout);
  else
    fputs(".PE\n", stdout);
}

void troff_output::command(const char *s,
			   const char *filename, int lineno)
{
  if (filename != 0)
    set_location(filename, lineno);
  fputs(s, stdout);
  putchar('\n');
}

void troff_output::simple_circle(int filled, const position &cent, double rad)
{
  position c = transform(cent);
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'"
	 "\\D'%c %.3fi'"
	 "\n.sp -1\n",
	 c.x - rad/scale,
	 c.y,
	 (filled ? 'C' : 'c'),
	 rad*2.0/scale);
}

void troff_output::simple_ellipse(int filled, const position &cent,
				  const distance &dim)
{
  position c = transform(cent);
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'"
	 "\\D'%c %.3fi %.3fi'"
	 "\n.sp -1\n",
	 c.x - dim.x/(2.0*scale),
	 c.y,
	 (filled ? 'E' : 'e'),
	 dim.x/scale, dim.y/scale);
}

void troff_output::simple_arc(const position &start, const distance &cent,
			      const distance &end)
{
  position s = transform(start);
  position c = transform(cent);
  distance cv = c - s;
  distance ev = transform(end) - c;
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'"
	 "\\D'a %.3fi %.3fi %.3fi %.3fi'"
	 "\n.sp -1\n",
	 s.x, s.y, cv.x, cv.y, ev.x, ev.y);
}

void troff_output::simple_line(const position &start, const position &end)
{
  position s = transform(start);
  distance ev = transform(end) - s;
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'"
	 "\\D'l %.3fi %.3fi'"
	 "\n.sp -1\n",
	 s.x, s.y, ev.x, ev.y);
}

void troff_output::simple_spline(const position &start,
				 const position *v, int n)
{
  position pos = transform(start);
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'",
	 pos.x, pos.y);
  fputs("\\D'~ ", stdout);
  for (int i = 0; i < n; i++) {
    position temp = transform(v[i]);
    distance d = temp - pos;
    pos = temp;
    if (i != 0)
      putchar(' ');
    printf("%.3fi %.3fi", d.x, d.y);
  }
  printf("'\n.sp -1\n");
}

// a solid polygon

void troff_output::simple_polygon(int filled, const position *v, int n)
{
  position pos = transform(v[0]);
  printf("\\h'%.3fi'"
	 "\\v'%.3fi'",
	 pos.x, pos.y);
  printf("\\D'%c ", (filled ? 'P' : 'p'));
  for (int i = 1; i < n; i++) {
    position temp = transform(v[i]);
    distance d = temp - pos;
    pos = temp;
    if (i != 1)
      putchar(' ');
    printf("%.3fi %.3fi", d.x, d.y);
  }
  printf("'\n.sp -1\n");
}

const double TEXT_AXIS = 0.22;	// in ems

static const char *choose_delimiter(const char *text)
{
  if (strchr(text, '\'') == 0)
    return "'";
  else
    return "\\(ts";
}

void troff_output::text(const position &center, text_piece *v, int n,
			double ang)
{
  line_thickness(BAD_THICKNESS); // text might use lines (e.g., in equations)
  int rotate_flag = 0;
  if (driver_extension_flag && ang != 0.0) {
    rotate_flag = 1;
    position c = transform(center);
    printf(".if \\n(" GROPS_REG " \\{\\\n"
	   "\\h'%.3fi'"
	   "\\v'%.3fi'"
	   "\\X'ps: exec gsave currentpoint 2 copy translate %.4f rotate neg exch neg exch translate'"
	   "\n.sp -1\n"
	   ".\\}\n",
	   c.x, c.y, -ang*180.0/M_PI);
  }
  for (int i = 0; i < n; i++)
    if (v[i].text != 0 && *v[i].text != '\0') {
      position c = transform(center);
      if (v[i].filename != 0)
	set_location(v[i].filename, v[i].lineno);
      printf("\\h'%.3fi", c.x);
      const char *delim = choose_delimiter(v[i].text);
      if (v[i].adj.h == RIGHT_ADJUST)
	printf("-\\w%s%s%su", delim, v[i].text, delim);
      else if (v[i].adj.h != LEFT_ADJUST)
	printf("-(\\w%s%s%su/2u)", delim, v[i].text, delim);
      putchar('\'');
      printf("\\v'%.3fi-(%dv/2u)+%dv+%.2fm",
	     c.y,
	     n - 1,
	     i,
	     TEXT_AXIS);
      if (v[i].adj.v == ABOVE_ADJUST)
	printf("-.5v");
      else if (v[i].adj.v == BELOW_ADJUST)
	printf("+.5v");
      putchar('\'');
      fputs(v[i].text, stdout);
      fputs("\n.sp -1\n", stdout);
    }
  if (rotate_flag)
    printf(".if \\n(" GROPS_REG " \\{\\\n"
	   "\\X'ps: exec grestore'\n.sp -1\n"
	   ".\\}\n");
}

void troff_output::line_thickness(double p)
{
  if (p < 0.0)
    p = RELATIVE_THICKNESS;
  if (driver_extension_flag && p != last_line_thickness) {
    printf("\\D't %.3fp'\\h'%.3fp'\n.sp -1\n", p, -p);
    last_line_thickness = p;
  }
}

void troff_output::set_fill(double f)
{
  if (driver_extension_flag && f != last_fill) {
    // \D'Fg ...' emits a node only in compatibility mode,
    // thus we add a dummy node
    printf("\\&\\D'Fg %.3f'\n.sp -1\n", 1.0 - f);
    last_fill = f;
  }
  if (last_filled) {
    free(last_filled);
    last_filled = 0;
    printf(".fcolor\n");
  }
}

void troff_output::set_color(char *color_fill, char *color_outlined)
{
  if (driver_extension_flag) {
    if (last_filled || last_outlined) {
      reset_color();
    }
    // .gcolor and .fcolor emit a node in compatibility mode only,
    // but that won't work anyway
    if (color_fill) {
      printf(".fcolor %s\n", color_fill);
      last_filled = strsave(color_fill);
    }
    if (color_outlined) {
      printf(".gcolor %s\n", color_outlined);
      last_outlined = strsave(color_outlined);
    }
  }
}

void troff_output::reset_color()
{
  if (driver_extension_flag) {
    if (last_filled) {
      printf(".fcolor\n");
      free(last_filled);
      last_filled = 0;
    }
    if (last_outlined) {
      printf(".gcolor\n");
      free(last_outlined);
      last_outlined = 0;
    }
  }
}

char *troff_output::get_last_filled()
{
  return last_filled;
}

char *troff_output::get_outline_color()
{
  return last_outlined;
}

const double DOT_AXIS = .044;

void troff_output::dot(const position &cent, const line_type &lt)
{
  if (driver_extension_flag) {
    line_thickness(lt.thickness);
    simple_line(cent, cent);
  }
  else {
    position c = transform(cent);
    printf("\\h'%.3fi-(\\w'.'u/2u)'"
	   "\\v'%.3fi+%.2fm'"
	   ".\n.sp -1\n",
	   c.x,
	   c.y, 
	   DOT_AXIS);
  }
}

void troff_output::set_location(const char *s, int n)
{
  if (last_filename != 0 && strcmp(s, last_filename) == 0)
    printf(".lf %d\n", n);
  else {
    printf(".lf %d %s\n", n, s);
    char *lfn = strdup(s);
    if (0 == lfn)
      fatal("memory allocation failure while copying file name");
    last_filename = lfn;
  }
}

// Local Variables:
// fill-column: 72
// mode: C++
// End:
// vim: set cindent noexpandtab shiftwidth=2 textwidth=72:
