/* display.c: show output online using the X window system.

Copyright (C) 1992 Free Software Foundation, Inc.

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, 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.  */

#include "config.h"

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xmu/SysUtil.h>

#include "report.h"
#include "spline.h"
#include "vector.h"

#include "display.h"
#include "main.h"
#include "xserver.h"


/* Says whether to show stuff online or not.  (-do-display)  */
boolean wants_display = false;

/* How many expanded pixels we put between lines on the grid.  Even
   though all this size and the others must be nonnegative, we must
   declare them as integers, since we do subtractions on them that might
   result in values less then zero.  (-display-grid-size) */
int display_grid_size = 10;

/* Size of square we print each pixel in.  (-display-pixel-size)  */ 
int display_pixel_size = 9;

/* Size of what we display inside the square for each pixel (must be
   less than `display_pixel_size').  (-display-rectangle-size)  */
int display_rectangle_size = 6;

/* Says whether to stop and wait for a <return> after each character. 
   (-display-continue)  */
boolean display_continue = false;

/* These are used for identifying the server. */
Atom foserver_identity_atom;

/* The messages our server recognizes.  */
Atom foserver_exit_atom, foserver_update_pixmap_atom;

/* The X state.  */

/* Where we should send our requests.  (This is information about
   our own ``limn server'', not the X server.)   We need the
   window only for when we send messages to our server.  */
static Display *display;
static Window server_window;

/* What we actually draw into.  */
static Pixmap pixmap = None;

/* The GC we use for drawing.  */
static GC gc;

/* Expands into the four arguments that define a rectangle for Xlib.  */
#define X_RECTANGLE(c) \
  (c).x, (c).y, display_rectangle_size, display_rectangle_size

/* Ditto, for lines.  */
#define X_LINE(c1, c2) (c1).x, (c1).y, (c2).x, (c2).y

/* Offsets for converting Cartesian coordinates to X ones.  */
static bounding_box_type offset_bb;

static coordinate_type cartesian_to_x (coordinate_type);
static void digitize_spline (spline_type);
static string get_identity (void);
static void get_server_info (Window *, Display **, string);
static coordinate_type real_cartesian_to_x (real_coordinate_type);
static coordinate_type real_cartesian_to_offset_x (real_coordinate_type);
static XSegment segment_to_x (XSegment);
static void send_event (Window, Atom, long);
static Bool window_change_p (Display *, XEvent *, char *);

/* Start things off.  */

void
init_display (bitmap_font_type f)
{
  string server_identity;
  
  /* Do nothing unless the user wants us to.  */
  if (!wants_display) return;
  
  server_identity = get_identity ();

#ifndef STANDALONE_SERVER
  { /* Fork our server.  This is the production case.  */
    unsigned design_size;

    int pid = fork ();
    switch (pid)
      {
      case -1:
        FATAL_PERROR ("fork");

      case 0:
        { /* We are the child, i.e., the server.  Convert the design size,
             which we have in points, to pixels.  `start_server' never
             returns, because it waits forever for events.  */
          design_size = (BITMAP_FONT_DESIGN_SIZE (f) * atof (dpi)
                         / POINTS_PER_INCH);

          start_server (design_size, server_identity);
          FATAL ("init_display: start_server returned");
        }

      default:
        /* We are the parent, i.e., the main process.
           Continue outside the switch.  */
        ;
      }
  }
#endif /* not STANDALONE_SERVER */

  /* Assign to the globals so that our other routines can send messages.  */
  get_server_info (&server_window, &display, server_identity);

  /* Register our message atoms with the X server.  */
  foserver_exit_atom = XInternAtom (display, FOSERVER_EXIT_ATOM, False);
  foserver_update_pixmap_atom
    = XInternAtom (display, FOSERVER_UPDATE_PIXMAP_ATOM, False);

}


/* Return the first child of W that has the property
   FOSERVER_IDENTITY_ATOM with the attribute string S.  This is not
   very efficient, but it is the only sure way.  We could have a window
   manager that put a lot of windows around the display server.
  
   If the search does not succed we return None.  */

static Window
search_children (Display *display, Window w, string s)
{
  Window root, parent, win;
  Window *children;
  unsigned nchildren;
  XTextProperty textP;

  /* Test this window. */
  if (XGetTextProperty (display, w, &textP, foserver_identity_atom)
      && textP.encoding == XA_STRING && textP.encoding == XA_STRING
      && textP.format == 8 && textP.nitems != 0
      && STREQ ((string) textP.value, s))
    {
      XFree ((char *) textP.value);
      return w; /* This is the correct window.  */
    }

  /* OK, we found nothing. Recursively try the children. */
  if (XQueryTree (display, w, &root, &parent, &children, &nchildren) == 0)
    FATAL ("limn: XQueryTree failed on parent");

  for (; nchildren > 0; nchildren--)
    if (win = search_children (display, children[nchildren-1], s))
      {
        XFree ((char *) children);
        return win;
      }
  XFree ((char *) children);

  return None; /* We didn't find anything. */
}


/* Return `hostname:pid' as a string.  */

static string
get_identity ()
{
  char s[1024];
  int i;
  
  i = XmuGetHostname (s, sizeof (s));
  if (i == 0)
    FATAL ("limn: Could not get hostname");

  sprintf (&s[i], ":%d", getpid ());
  return xstrdup (s);
}


/* Return the X window id for the window that the forked process should
   have created.  */

static void
get_server_info (Window *server_window, Display **display, string identity)
{
  int scr;
  
  /* Open our own connection to the X server, since sending messages
     through the server's connection hangs.  */
  *display = XOpenDisplay (NULL);
  if (*display == NULL)
    {
      string d = getenv ("DISPLAY");
      if (d == NULL)
        FATAL ("limn: Could not open display, since your DISPLAY \
environment variable is not set");
      else
        FATAL1 ("limn: Could not open display `%s'", getenv ("DISPLAY"));
    }

  /* Get the identity atom. Create it if it doesn't exist.  */
  foserver_identity_atom
    = XInternAtom (*display, FOSERVER_IDENTITY_ATOM, False);

  /* Ask to be told about most window events.  */
  XSelectInput (*display, DefaultRootWindow (*display),
                SubstructureNotifyMask);

  /* Remove potential race condition below -- if our server has already
     had its window realized, we wouldn't find it otherwise. Search all
     windows first.  We might just as well search all screens too. */
  *server_window = None;
  for (scr = ScreenCount (*display); scr > 0 && *server_window == None; scr--)
    {
      Window root = RootWindow (*display, scr - 1);
      *server_window = search_children (*display, root, identity);
    }

  while (*server_window == None)
    {
      XEvent event;
      Window w;
      
      /* Wait for an event that might mean our server is ready.  */
      XIfEvent (*display, &event, window_change_p, NULL);

      /* We look for some property on the window, and rely on our server
         setting that property.  */
      switch (event.type)
        {
        case MapNotify:
          w = event.xmap.window;
	  *server_window = search_children (*display, w, identity);
          break;
          
        default:
          FATAL1 ("limn: Unexpected event (type %d)", event.type);
        }
    }

  /* We don't want to see any events any more.  */
  XSelectInput (*display, DefaultRootWindow (*display),
                NoEventMask);
}


/* The only event we're interested in is when a window gets mapped,
   because that's the last thing our server does before waiting for
   events.  */

static Bool
window_change_p (Display *d, XEvent *e, char *client_data)
{
  return e->type == MapNotify;
}


/* Tell our server to shut down, and close our own connection.  */

void
close_display ()
{
  if (!wants_display) return;

  send_event (server_window, foserver_exit_atom, 0);

  XCloseDisplay (display);
}

/* To start off a new character, we print a grid so we can locate points.  */

void 
x_start_char (char_info_type c)
{
  XGCValues gc_values;
  int x, y;
  unsigned n_vlines, n_hlines;
  unsigned char_width = (CHAR_BITMAP_WIDTH (c) + 2) * display_pixel_size;
  unsigned max_vlines = 1 + char_width / display_grid_size;
  unsigned char_height = (CHAR_BITMAP_HEIGHT (c) + 2) * display_pixel_size;
  unsigned max_hlines = 1 + char_height / display_grid_size;
  XSegment lines[max_vlines + max_hlines];
  
  if (!wants_display) return;

  /* Remember this bounding box, since we have to change coordinates
     from its coordinates into X coordinates.  */
  offset_bb = CHAR_BB (c);
  
  /* Allow for rounding errors.  */
  MAX_ROW (offset_bb) += 1;
  MIN_ROW (offset_bb) -= 1;
  MAX_COL (offset_bb) += 1;
  MIN_COL (offset_bb) -= 1;

  /* If this isn't the very first time we are called, we must free the
     storage for the previous character.  */
  if (pixmap != None)
    {
      XFreePixmap (display, pixmap);
      /* The GC and the pixmap are created together, so we can free them
         together.  */ 
      XFreeGC (display, gc);
    }

  /* We always want a Pixmap of depth one.  */
  pixmap = XCreatePixmap (display, DefaultRootWindow (display),
                          char_width, char_height, 1); 
  
  /* When drawing onto `pixmap', we want to draw in 1's, and have the
     background be 0's.  The Label widget does an XCopyPlane on
     monochrome Pixmaps, so this will result in the right colors on the
     screen.  But we want to start with the foreground 0 (which is the
     default), since the first thing we want to do is initialize it to
     all white.  */
  gc_values.background = 0;
  gc = XCreateGC (display, pixmap, GCBackground, &gc_values);

  /* Initialize `pixmap' to white.  */
  XFillRectangle (display, pixmap, gc, 0, 0, char_width, char_height);
  XSetForeground (display, gc, 1);

  /* Draw the grid.  */
  for (x = (MIN_COL (offset_bb) / display_grid_size) * display_grid_size,
         n_vlines = 0;
       x <= MAX_COL (offset_bb) && n_vlines < max_vlines;
       x += display_grid_size, n_vlines++)
    {
      lines[n_vlines].x1 = lines[n_vlines].x2 = x;
      lines[n_vlines].y1 = MAX_ROW (offset_bb);
      lines[n_vlines].y2 = MIN_ROW (offset_bb);
      lines[n_vlines] = segment_to_x (lines[n_vlines]);
    }

  /* X considers the origin to be at the upper left.  But it is easier
     to understand the display if the pixels in the row y=0 are directly 
     above a grid line, rather than hanging below it.  So we offset the
     y value by one.  */
  for (y = ((MIN_ROW (offset_bb)) / display_grid_size) * display_grid_size,
         n_hlines = 0;
       y <= MAX_ROW (offset_bb) && n_hlines < max_hlines;
       y += display_grid_size, n_hlines++)
    {
      lines[n_vlines + n_hlines].x1 = MIN_COL (offset_bb);
      lines[n_vlines + n_hlines].x2 = MAX_COL (offset_bb);
      lines[n_vlines + n_hlines].y1 = lines[n_vlines + n_hlines].y2 = y - 1;
      lines[n_vlines + n_hlines] = segment_to_x (lines[n_vlines + n_hlines]);
    }

  /* The grid is less obtrusive if we use dashed lines.  */
  XSetLineAttributes (display, gc, 0, LineOnOffDash, CapButt, JoinMiter);
  XDrawSegments (display, pixmap, gc, lines, n_vlines + n_hlines);
  XSetLineAttributes (display, gc, 0, LineSolid, CapButt, JoinMiter);
}


/* This routine is called for every pixel in the outline of the filtered
   bitmap.  */ 

void
display_pixel (real_coordinate_type real_c)
{
  coordinate_type c;

  if (!wants_display) return;
  
  c = real_cartesian_to_x (real_c);
  XDrawRectangle (display, pixmap, gc, X_RECTANGLE (c));
}


/* This marks the corners we find before we start the fitting.  Since
   these corners are on the original pixel outline, they are integers.  */

void
display_corner (coordinate_type c)
{
  if (!wants_display) return;

  c = cartesian_to_x (c);
  XFillRectangle (display, pixmap, gc, X_RECTANGLE (c));
}


/* This marks the points at which we subdivide during the fitting. 
   Since the filtering has happened now, the coordinates are not
   necessarily integers.  */

void
display_subdivision (real_coordinate_type real_c)
{
  coordinate_type c;
  if (!wants_display) return;
  
  c = real_cartesian_to_x (real_c);
  
  /* So the pixel we already drew at this location doesn't still appear
     behind the circle we are about to draw, thus turning the circle
     into a square.  */
  XSetForeground (display, gc, 0);
  XFillRectangle (display, pixmap, gc, X_RECTANGLE (c));
  XSetForeground (display, gc, 1);
  
  /* Oddly enough, the ``circle'' that's drawn is a rectangle, for small
     enough sizes.  */
  XFillArc (display, pixmap, gc, c.x, c.y,
            display_pixel_size, display_pixel_size, 0, 360 * 64);
}


/* Here we display the character C after fitting.  The list SPLINE_LIST
   is the fitted segments.  */

void
x_output_char (char_info_type c, spline_list_array_type splines)
{
  unsigned this_list;
  
  if (!wants_display) return;
  
  /* Draw the fitted lines a little thicker, so they'll be easier to see.  */
  XSetLineAttributes (display, gc, 1, LineSolid, CapButt, JoinMiter);

  for (this_list = 0; this_list < SPLINE_LIST_ARRAY_LENGTH (splines);
       this_list++) 
    {
      unsigned this_spline;
      spline_list_type list = SPLINE_LIST_ARRAY_ELT (splines, this_list);

      for (this_spline = 0; this_spline < SPLINE_LIST_LENGTH (list);
           this_spline++)
        {
          spline_type s = SPLINE_LIST_ELT (list, this_spline);

          if (SPLINE_DEGREE (s) == LINEAR)
            {
              coordinate_type start
                = real_cartesian_to_offset_x (START_POINT (s));
              coordinate_type end
                = real_cartesian_to_offset_x (END_POINT (s));

              XDrawLine (display, pixmap, gc, X_LINE (start, end));
            }

          else if (SPLINE_DEGREE (s) == CUBIC)
            digitize_spline (s);

          else
            FATAL1 ("x_output_char: Spline with degree %d", SPLINE_DEGREE (s));
        }
    }

  /* If desired, make sure everything is visible, then wait for the user
     to hit return, so s/he can look at the results.  */
  if (!display_continue)
    {
      send_event (server_window, foserver_update_pixmap_atom, pixmap);
      XSync (display, False);
      
      if (verbose) putchar ('\n');
      printf ("RET to continue: " );
      (void) getchar ();
    }
}

/* Rasterize the spline S using forward differences, in floating point,
   non-adaptively.  This is slow compared to the integer, adaptive,
   version, but it's easier to program, and the speed is good enough for
   our purposes (i.e., non-interactive).  A brief but readable
   description of the algorithm is in ``Fast generation of outline
   characters'', by J. Gonczarowski, in the proceedings of the 1989
   conference on raster imaging and digital typography (page 100).  More
   of the math is spelled out in @cite{Fundamentals of interactive
   computer graphics}, by J.D. Foley and A. Van Dam (pp.531-534).  */
   
static void
digitize_spline (spline_type s)
{
  coordinate_type start = real_cartesian_to_offset_x (START_POINT (s)),
                  control1 = real_cartesian_to_offset_x (CONTROL1 (s)),
                  control2 = real_cartesian_to_offset_x (CONTROL2 (s)),
                  end = real_cartesian_to_offset_x (END_POINT (s));

  /* These a_i are the coefficients of the polynomial
     p(t) = a_3 t^3 + a_2 t^2 + a_1 t + a_0,
     computed by expanding the Bernshte\u in polynomial 
     z(t) = (1 - t)^3z_1 + 3(1 - t)^2tz_2 + 3(1 - t)t^2z_3 + t^3z_4,
     where z_1 is the starting point of the spline, z_2 and z_3 the
     control points, and z_4 the ending point.  We have two such
     polynomials p(t), one for the x-coordinate, one for the
     y-coordinate.  So everything here is done with points and vectors,
     instead of just numbers.
     
     a_0 = x_0
     a_1 = 3(x_1 - x_0)
     a_2 = 3(x_0 + x_2 - 2x_1)
     a_3 = x_3 - x_0 + 3(x_1 - x_2)  */
     
  coordinate_type /* a0 = start, */
    a1 = IPmult_scalar (IPsubtractP (control1, start), 3),
    a2 = IPmult_scalar (IPsubtractP (IPadd (start, control2),
                                     IPmult_scalar (control1, 2)),
                        3),
    a3 = IPadd (IPsubtractP (end, start),
                IPmult_scalar (IPsubtractP (control1, control2), 3));

  /* The step size.  We want to use the length of the bounding rectangle
     to compute this, instead of the distance between the starting point
     and the ending point, since the latter will be zero if the spline
     is cyclic.  */
  real factor = int_distance (control1, start)
                + int_distance (control2, control1)
                + int_distance (end, control2)
                + int_distance (start, end),
       /* Avoid division by zero, in pathological cases.  (We will just
          produce one point.  */
       delta = 1.0 / MAX (factor, 1),
       delta_squared = delta * delta,
       delta_cubed = delta_squared * delta;

  /* The current position.  */
  coordinate_type p = start,
                  previous_p = { start.x - 1, 0 };
  /* The real current position.  */
  real_coordinate_type real_p = int_to_real_coord (p);

  /* The first three forward differences evaluated at t = 0.  */
  vector_type d = make_vector (Padd (IPmult_real (a3, delta_cubed), 
                                     Padd (IPmult_real (a2, delta_squared),
                                           IPmult_real (a1, delta)))),
              d2 = make_vector (Padd (IPmult_real (a3, 6 * delta_cubed),
                                      IPmult_real (a2, 2 * delta_squared))),
              d3 = make_vector (IPmult_real (a3, 6 * delta_cubed));

  /* Where we will collect the points to output.  */
  unsigned point_count = 0;
  XPoint point_list[(unsigned) (1.0 / delta) + 1];

  real t;

  for (t = 0.0; t <= 1.0; t += delta)
    {
      if (!IPequal (p, previous_p))
        {
          point_list[point_count].x = p.x;
          point_list[point_count].y = p.y;
          point_count++;
          previous_p = p;
        }

      /* We must keep track of the current position in unrounded 
         coordinates, so the calculations do not lose precision.  */
      real_p = Padd_vector (real_p, d);
      p.x = ROUND (real_p.x);
      p.y = ROUND (real_p.y);
      d = Vadd (d, d2);
      d2 = Vadd (d2, d3);
      /* d3 is constant with respect to t.  */
    }

#if 0
  /* GDB under system V doesn't grok XPoint, so here's a way to print
     out the points we find.  */
  {
    unsigned i;
    for (i = 0; i < point_count; i++)
      printf ("(%d,%d)", point_list[i].x, point_list[i].y);
    puts ("");
  }
#endif

  XDrawPoints (display, pixmap, gc, point_list, point_count,
               CoordModeOrigin);
}

/* Send a ClientMessage event to the X window W, on the display
   `display'.  The message type is ATOM and the data DATA.  We could
   simply use the semi-global `server_window' (as we do `display'), but
   it proves useful to be able to send messages to other windows for
   testing.  */

static void
send_event (Window w, Atom atom, long data)
{
  XEvent e;
  
  e.xclient.type = ClientMessage;
  e.xclient.display = display;
  e.xclient.window = w;
  e.xclient.message_type = atom;
  e.xclient.format = 32;
  e.xclient.data.l[0] = data;
  
  if (XSendEvent (display, w, False, NoEventMask, &e) == 0)
    FATAL1 ("limn: XSendEvent (of %s) failed", XGetAtomName (display, atom));
}

/* Convert a single point from Cartesian coordinates, where the origin
   is at the reference point of the character, to X coordinates, where
   the origin is at the upper left corner.  */

static coordinate_type 
cartesian_to_x (coordinate_type in_c)
{
  coordinate_type c = in_c;

  c.x *= display_pixel_size;
  c.y *= display_pixel_size;
  c.x -= MIN_COL (offset_bb) * display_pixel_size;
  c.y = MAX_ROW (offset_bb) * display_pixel_size - c.y;

  return c;
}


/* Convert a single point, with real-valued coordinates, to X
   coordinates.  By rounding after the multiplications, we can display
   the real coordinates more accurately.  */

static coordinate_type
real_cartesian_to_x (real_coordinate_type real_c)
{
  coordinate_type c;
  
  real_c.x *= display_pixel_size;
  real_c.y *= display_pixel_size;
  real_c.x -= MIN_COL (offset_bb) * display_pixel_size;
  real_c.y = MAX_ROW (offset_bb) * display_pixel_size - real_c.y;

  c = real_to_int_coord (real_c);
  
  return c;
}


/* Like `real_cartesian_to_x', but translate the result to the center of
   the display_pixel_size by display_pixel_size rectangle that we turn each
   point into.  This is used for displaying the fitted character, since
   visually it makes more sense to have it go through the centers of the
   rectangles that represent the pixels, instead of the upper left
   corner.  */

static coordinate_type
real_cartesian_to_offset_x (real_coordinate_type real_c)
{
  coordinate_type c = real_cartesian_to_x (real_c);
  
  c.x += display_pixel_size / 2;
  c.y += display_pixel_size / 2;
  
  return c;
}


/* Convert two points, i.e., a line segment, from Cartesian to X
   coordinates.  Because an XSegment is not two XPoints, I didn't see
   any gain in defining another routine to deal with XPoints.  */

static XSegment
segment_to_x (XSegment in_s)
{
  XSegment out_s;
  coordinate_type c;
  
  c.x = in_s.x1;
  c.y = in_s.y1;
  c = cartesian_to_x (c);
  out_s.x1 = c.x;
  out_s.y1 = c.y;
  
  c.x = in_s.x2;
  c.y = in_s.y2;
  c = cartesian_to_x (c);
  out_s.x2 = c.x;
  out_s.y2 = c.y;
  
  return out_s;
}
