/* "Face" primitives
   Copyright (C) 1994 Free Software Foundation, Inc.
   Copyright (C) 1995 Board of Trustees, University of Illinois
   Copyright (C) 1995 Ben Wing

This file is part of XEmacs.

XEmacs 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.

XEmacs 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 XEmacs; see the file COPYING.  If not, write to the Free
Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Synched up with: Not in FSF. */

/* Written by Chuck Thompson and Ben Wing,
   based loosely on old face code by Jamie Zawinski. */

#include <config.h>
#include "lisp.h"

#include "buffer.h"
#include "device.h"
#include "elhash.h"
#include "extents.h"
#include "faces.h"
#include "frame.h"
#include "glyphs.h"
#include "hash.h"
#include "objects.h"
#include "specifier.h"
#include "window.h"

#ifdef HAVE_X_WINDOWS
#include "device-x.h"
#include "frame-x.h"
#include "objects-x.h"
#include "EmacsFrame.h"
#endif /* HAVE_X_WINDOWS */

#ifdef HAVE_NEXTSTEP
#include "device-ns.h"
#include "frame-ns.h"
#include "objects-ns.h"
#endif /* HAVE_NEXTSTEP */

/* Qfont, Qdoc_string defined in general.c */
Lisp_Object Qface, Qfacep;
Lisp_Object Qforeground, Qbackground, Qdisplay_table;
/* Qhighlight, Qreverse defined in general.c */
Lisp_Object Qbackground_pixmap, Qunderline, Qdim;
Lisp_Object Qblinking;

Lisp_Object Qinit_face_from_resources;
Lisp_Object Qinit_frame_faces;
Lisp_Object Qinit_device_faces;
Lisp_Object Qinit_global_faces;
Lisp_Object Qinit_faces;

/* These faces are used directly internally.  We use these variables
   to be able to reference them directly and save the overhead of
   calling Ffind_face. */
Lisp_Object Vdefault_face, Vmodeline_face, Vhighlight_face;
Lisp_Object Vleft_margin_face, Vright_margin_face;

/* Qdefault, Qhighlight defined in general.c */
Lisp_Object Qmodeline, Qleft_margin, Qright_margin;

/* In the old implementation Vface_list was a list of the face names,
   not the faces themselves.  We now distinguish between permanent and
   temporary faces.  Permanent faces are kept in a regular hash table,
   temporary faces in a weak hash table. */
Lisp_Object Vpermanent_faces_cache;
Lisp_Object Vtemporary_faces_cache;


static Lisp_Object mark_face (Lisp_Object, void (*) (Lisp_Object));
static void print_face (Lisp_Object, Lisp_Object, int);
static int face_equal (Lisp_Object, Lisp_Object, int depth);
static unsigned long face_hash (Lisp_Object obj, int depth);
static int face_getprop (Lisp_Object obj, Lisp_Object prop,
			 Lisp_Object *value_out);
static int face_putprop (Lisp_Object obj, Lisp_Object prop, Lisp_Object value);
static int face_remprop (Lisp_Object obj, Lisp_Object prop);
static Lisp_Object face_props (Lisp_Object obj);
DEFINE_LRECORD_IMPLEMENTATION_WITH_PROPS ("face", face,
					  mark_face, print_face, 0, face_equal,
					  face_hash, face_getprop,
					  face_putprop, face_remprop,
					  face_props, struct Lisp_Face);

static Lisp_Object
mark_face (Lisp_Object obj, void (*markobj) (Lisp_Object))
{
  struct Lisp_Face *face =  XFACE (obj);

  ((markobj) (face->name));
  ((markobj) (face->doc_string));

  ((markobj) (face->foreground));
  ((markobj) (face->background));
  ((markobj) (face->font));
  ((markobj) (face->display_table));
  ((markobj) (face->background_pixmap));
  ((markobj) (face->underline));
  ((markobj) (face->highlight));
  ((markobj) (face->dim));
  ((markobj) (face->blinking));
  ((markobj) (face->reverse));

  return (face->plist);
}

static void
print_face (Lisp_Object obj, Lisp_Object printcharfun, int escapeflag)
{
  struct Lisp_Face *face = XFACE (obj);

  if (print_readably)
    error ("printing unreadable object #<face %s>",
	   string_data (XSYMBOL (face->name)->name));

  write_c_string ("#<face ", printcharfun);
  print_internal (face->name, printcharfun, 1);
  if (!NILP (face->doc_string))
    {
      write_c_string (" ", printcharfun);
      print_internal (face->doc_string, printcharfun, 1);
    }
  write_c_string (">", printcharfun);
}

/* Faces are equal if all of their display attributes are equal.  We
   don't compare names or doc-strings, because that would make equal
   be eq.

   This isn't concerned with "unspecified" attributes, that's what
   #'face-differs-from-default-p is for. */
static int
face_equal (Lisp_Object o1, Lisp_Object o2, int depth)
{
  struct Lisp_Face *f1 = XFACE (o1);
  struct Lisp_Face *f2 = XFACE (o2);

  depth++;

  if (!internal_equal (f1->foreground, f2->foreground, depth) ||
      !internal_equal (f1->background, f2->background, depth) ||
      !internal_equal (f1->font, f2->font, depth) ||
      !internal_equal (f1->display_table, f2->display_table, depth) ||
      !internal_equal (f1->background_pixmap, f2->background_pixmap, depth) ||
      !internal_equal (f1->underline, f2->underline, depth) ||
      !internal_equal (f1->highlight, f2->highlight, depth) ||
      !internal_equal (f1->dim, f2->dim, depth) ||
      !internal_equal (f1->blinking, f2->blinking, depth) ||
      !internal_equal (f1->reverse, f2->reverse, depth) ||
      plists_differ (f1->plist, f2->plist, depth + 1))
    return 0;

  return 1;
}

static unsigned long
face_hash (Lisp_Object obj, int depth)
{
  struct Lisp_Face *f = XFACE (obj);

  depth++;

  /* No need to hash all of the elements; that would take too long.
     Just hash the most common ones. */
  return HASH3 (internal_hash (f->foreground, depth),
		internal_hash (f->background, depth),
		internal_hash (f->font, depth));
}

static int
face_getprop (Lisp_Object obj, Lisp_Object prop, Lisp_Object *value_out)
{
  struct Lisp_Face *f = XFACE (obj);

#define FROB(propprop) 							\
do {									\
  if (EQ (prop, Q##propprop))						\
    {									\
      *value_out = f->propprop;						\
      return 1;								\
    }									\
} while (0)

  FROB (foreground);
  FROB (background);
  FROB (font);
  FROB (display_table);
  FROB (background_pixmap);
  FROB (underline);
  FROB (highlight);
  FROB (dim);
  FROB (blinking);
  FROB (reverse);

#undef FROB

  return internal_getf (f->plist, prop, value_out);
}

static int
face_putprop (Lisp_Object obj, Lisp_Object prop, Lisp_Object value)
{
  struct Lisp_Face *f = XFACE (obj);

#define FROB(propprop) 							\
do {									\
  if (EQ (prop, Q##propprop))						\
    return 0;								\
} while (0)

  FROB (foreground);
  FROB (background);
  FROB (font);
  FROB (display_table);
  FROB (background_pixmap);
  FROB (underline);
  FROB (highlight);
  FROB (dim);
  FROB (blinking);
  FROB (reverse);

#undef FROB

  if (EQ (prop, Qdoc_string))
    {
      if (!NILP (value))
	CHECK_STRING (value, 0);
      f->doc_string = value;
      return 1;
    }

  internal_putf (&f->plist, prop, value);
  return 1;
}

static int
face_remprop (Lisp_Object obj, Lisp_Object prop)
{
  struct Lisp_Face *f = XFACE (obj);

#define FROB(propprop) 							\
do {									\
  if (EQ (prop, Q##propprop))						\
    return -1;								\
} while (0)

  FROB (foreground);
  FROB (background);
  FROB (font);
  FROB (display_table);
  FROB (background_pixmap);
  FROB (underline);
  FROB (highlight);
  FROB (dim);
  FROB (blinking);
  FROB (reverse);

#undef FROB

  if (EQ (prop, Qdoc_string))
    {
      f->doc_string = Qnil;
      return 1;
    }

  return internal_remprop (&f->plist, prop);
}

static Lisp_Object
face_props (Lisp_Object obj)
{
  struct Lisp_Face *f = XFACE (obj);
  Lisp_Object result = Qnil;

#define FROB(propprop) 							\
do {									\
  result = Fcons (f->propprop, Fcons (Q##propprop, result));		\
} while (0)

  /* backwards order; we reverse it below */
  FROB (reverse);
  FROB (blinking);
  FROB (dim);
  FROB (highlight);
  FROB (underline);
  FROB (background_pixmap);
  FROB (display_table);
  FROB (font);
  FROB (background);
  FROB (foreground);

#undef FROB
  return nconc2 (Fnreverse (result), Fcopy_sequence (f->plist));
}


/*****************************************************************************
 utility functions
 ****************************************************************************/

static void
reset_face (struct Lisp_Face *f)
{
  f->name = Qnil;
  f->doc_string = Qnil;
  f->dirty = 0;
  f->foreground = Qnil;
  f->background = Qnil;
  f->font = Qnil;
  f->display_table = Qnil;
  f->background_pixmap = Qnil;
  f->underline = Qnil;
  f->highlight = Qnil;
  f->dim = Qnil;
  f->blinking = Qnil;
  f->reverse = Qnil;
  f->plist = Qnil;
}

static struct Lisp_Face *
allocate_face (void)
{
  struct Lisp_Face *result =
    alloc_lcrecord (sizeof (struct Lisp_Face), lrecord_face);

  reset_face (result);
  return result;
}


/* We store the faces in hash tables with the names as the key and the
   actual face object as the value.  Occasionally we need to use them
   in a list format.  These routines provide us with that. */
struct face_list_closure
{
  Lisp_Object *face_list;
};

static void
add_face_to_list_mapper (CONST void *hash_key, void *hash_contents,
			 void *face_list_closure)
{
  /* This function can GC */
  Lisp_Object key, contents;
  Lisp_Object *face_list;
  struct face_list_closure *fcl = face_list_closure;
  CVOID_TO_LISP (key, hash_key);
  VOID_TO_LISP (contents, hash_contents);
  face_list = fcl->face_list;

  *face_list = Fcons (XFACE (contents)->name, *face_list);
}

static Lisp_Object
faces_list_internal (Lisp_Object list)
{
  Lisp_Object face_list = Qnil;
  struct gcpro gcpro1;
  struct face_list_closure face_list_closure;

  GCPRO1 (face_list);
  face_list_closure.face_list = &face_list;
  elisp_maphash (add_face_to_list_mapper, list, &face_list_closure);
  UNGCPRO;

  return face_list;
}

static Lisp_Object
permanent_faces_list (void)
{
  return faces_list_internal (Vpermanent_faces_cache);
}

static Lisp_Object
temporary_faces_list (void)
{
  return faces_list_internal (Vtemporary_faces_cache);
}


static void
mark_face_as_clean_mapper (CONST void *hash_key, void *hash_contents,
			   void *flag_closure)
{
  /* This function can GC */
  Lisp_Object key, contents;
  int *flag = flag_closure;
  CVOID_TO_LISP (key, hash_key);
  VOID_TO_LISP (contents, hash_contents);
  XFACE (contents)->dirty = *flag;
}

static void
mark_all_faces_internal (int flag)
{
  elisp_maphash (mark_face_as_clean_mapper, Vpermanent_faces_cache, &flag);
  elisp_maphash (mark_face_as_clean_mapper, Vtemporary_faces_cache, &flag);
}

void
mark_all_faces_as_clean (void)
{
  mark_all_faces_internal (0);
}

/* #### OBSOLETE ME, PLEASE.  Maybe.  Maybe this is just as good as
   any other solution. */
struct face_inheritance_closure
{
  Lisp_Object face;
  Lisp_Object property;
};

static void
update_inheritance_mapper_internal (Lisp_Object cur_face,
				    Lisp_Object inh_face,
				    Lisp_Object property)
{
  /* #### fix this function */
  Lisp_Object elt = Qnil;
  struct gcpro gcpro1;

  GCPRO1 (elt);

  for (elt = FACE_PROPERTY_SPEC_LIST (cur_face, property, Qall);
       !NILP (elt);
       elt = XCDR (elt))
    {
      Lisp_Object locale, values;

      locale = XCAR (XCAR (elt));
      values = XCDR (XCAR (elt));

      for (; !NILP (values); values = XCDR (values))
	{
	  Lisp_Object tag = XCAR (XCAR (values));
	  Lisp_Object value = XCDR (XCAR (values));
	  if (VECTORP (value) && XVECTOR (value)->size)
	    {
	      if (EQ (Ffind_face (vector_data (XVECTOR (value))[0]), inh_face))
		{
		  /* #### This should probably be changed.  We just want
		     to mark the face as dirty and reset its specifier
		     instance list. */
		  SET_FACE_PROPERTY (cur_face, property, value, locale, tag,
				     Qnil);
		}
	    }
	}
    }

  UNGCPRO;
}

static void
update_face_inheritance_mapper (CONST void *hash_key, void *hash_contents,
				void *face_inheritance_closure)
{
  Lisp_Object key, contents;
  struct face_inheritance_closure *fcl = face_inheritance_closure;

  CVOID_TO_LISP (key, hash_key);
  VOID_TO_LISP (contents, hash_contents);

  if (EQ (fcl->property, Qfont))
    {
      update_inheritance_mapper_internal (contents, fcl->face, Qfont);
    }
  else if (EQ (fcl->property, Qforeground)
	   || EQ (fcl->property, Qbackground))
    {
      update_inheritance_mapper_internal (contents, fcl->face, Qforeground);
      update_inheritance_mapper_internal (contents, fcl->face, Qbackground);
    }
  else if (EQ (fcl->property, Qunderline)
	   || EQ (fcl->property, Qhighlight)
	   || EQ (fcl->property, Qdim)
	   || EQ (fcl->property, Qblinking)
	   || EQ (fcl->property, Qreverse))
    {
      update_inheritance_mapper_internal (contents, fcl->face, Qunderline);
      update_inheritance_mapper_internal (contents, fcl->face, Qhighlight);
      update_inheritance_mapper_internal (contents, fcl->face, Qdim);
      update_inheritance_mapper_internal (contents, fcl->face, Qblinking);
      update_inheritance_mapper_internal (contents, fcl->face, Qreverse);
    }
}

static void
update_faces_inheritance (Lisp_Object face, Lisp_Object property)
{
  struct face_inheritance_closure face_inheritance_closure;
  struct gcpro gcpro1, gcpro2;

  GCPRO2 (face, property);
  face_inheritance_closure.face = face;
  face_inheritance_closure.property = property;

  elisp_maphash (update_face_inheritance_mapper, Vpermanent_faces_cache,
		 &face_inheritance_closure);
  elisp_maphash (update_face_inheritance_mapper, Vtemporary_faces_cache,
		 &face_inheritance_closure);

  UNGCPRO;
}


DEFUN ("facep", Ffacep, Sfacep, 1, 1, 0,
       "Return non-nil if OBJECT is a face.")
     (object)
     Lisp_Object object;
{
  return (FACEP (object) ? Qt : Qnil);
}

DEFUN ("find-face", Ffind_face, Sfind_face, 1, 1, 0,
       "Retrieve the face of the given name.\n\
If FACE-OR-NAME is a face object, it is simply returned.\n\
Otherwise, FACE-OR-NAME should be a symbol.  If there is no such face,\n\
nil is returned.  Otherwise the associated face object is returned.")
     (face_or_name)
     Lisp_Object face_or_name;
{
  Lisp_Object retval;

  if (FACEP (face_or_name))
    return face_or_name;
  CHECK_SYMBOL (face_or_name, 0);

  /* Check if the name represents a permanent face. */
  retval = Fgethash (face_or_name, Vpermanent_faces_cache, Qnil);
  if (!NILP (retval))
    return retval;

  /* Check if the name represents a temporary face. */
  return Fgethash (face_or_name, Vtemporary_faces_cache, Qnil);
}

DEFUN ("get-face", Fget_face, Sget_face, 1, 1, 0,
       "Retrieve the face of the given name.\n\
Same as `find-face' except an error is signalled if there is no such\n\
face instead of returning nil.")
     (name)
     Lisp_Object name;
{
  Lisp_Object face = Ffind_face (name);

  if (NILP (face))
    signal_simple_error ("No such face", name);
  return face;
}

DEFUN ("face-name", Fface_name, Sface_name, 1, 1, 0,
       "Return the name of the given face.")
     (face)
     Lisp_Object face;
{
  return (XFACE (Fget_face (face))->name);
}

void
face_font_metric_info (Lisp_Object face, Lisp_Object domain,
		       struct font_metric_info *fm)
{
  struct device *d = XDEVICE (DFW_DEVICE (domain));

  DEVMETH (d, font_metric_info, (d, FACE_FONT (face, domain),
				 fm));
}

/* These values are retrieved so often that we make a special
   function.

   #### Should this perhaps look in the face-cache-element cache
   for the domain, esp. if it is a window (as is usually the case)? */

void
default_face_height_and_width (Lisp_Object domain,
			       int *height, int *width)
{
  struct device *d = XDEVICE (DFW_DEVICE (domain));
  struct font_metric_info fm;

  DEVMETH (d, font_metric_info, (d, FACE_FONT (Vdefault_face, domain),
				 &fm));
  *height = fm.height;
  *width = fm.width;
}

DEFUN ("face-ascent", Fface_ascent, Sface_ascent, 1, 2, 0,
       "Return the ascent portion of the FACE's height in the given DOMAIN.\n\
If DOMAIN is nil, it will be determined from the selected window.")
     (face, domain)
     Lisp_Object face, domain;
{
  struct font_metric_info fm;

  face = Fget_face (face);
  domain = decode_domain (domain);

  face_font_metric_info (face, domain, &fm);
  return (make_number (fm.ascent));
}

DEFUN ("face-descent", Fface_descent, Sface_descent, 1, 2, 0,
       "Return the descent portion of the FACE's height in the given DOMAIN.\n\
If DOMAIN is nil, it will be determined from the selected window.")
     (face, domain)
     Lisp_Object face, domain;
{
  struct font_metric_info fm;

  face = Fget_face (face);
  domain = decode_domain (domain);

  face_font_metric_info (face, domain, &fm);
  return (make_number (fm.descent));
}

DEFUN ("face-width", Fface_width, Sface_width, 1, 2, 0,
       "Return the width of FACE in the given DOMAIN.\n\
If DOMAIN is nil, it will be determined from the selected window.")
     (face, domain)
     Lisp_Object face, domain;
{
  struct font_metric_info fm;

  face = Fget_face (face);
  domain = decode_domain (domain);

  face_font_metric_info (face, domain, &fm);
  return (make_number (fm.width));
}

DEFUN ("face-list", Fface_list, Sface_list, 0, 1, 0,
       "Return a list of the names of all defined faces.\n\
If TEMPORARY is nil, only the permanent faces are included.\n\
If it is t, only the temporary faces are included.  If it is any\n\
other non-nil value both permanent and temporary are included.")
     (temporary)
     Lisp_Object temporary;
{
  Lisp_Object face_list = Qnil;

  /* Added the permanent faces, if requested. */
  if (NILP (temporary) || !EQ (Qt, temporary))
    face_list = permanent_faces_list ();

  if (!NILP (temporary))
    {
      struct gcpro gcpro1;
      GCPRO1 (face_list);
      face_list = nconc2 (face_list, temporary_faces_list ());
      UNGCPRO;
    }

  return face_list;
}

DEFUN ("extent-face", Fextent_face, Sextent_face, 1, 1, 0,
       "Return the name of the face in which EXTENT is displayed, or nil\n\
if the extent's face is unspecified.")
     (extent)
     Lisp_Object extent;
{
  CHECK_EXTENT (extent, 0);

  if (NILP (extent_face (XEXTENT (extent))))
    return Qnil;
  else
    return (XFACE (extent_face (XEXTENT (extent)))->name);
}

DEFUN ("set-extent-face", Fset_extent_face, Sset_extent_face, 2, 2, 0,
       "Make the given EXTENT have the graphic attributes specified by FACE.")
     (extent, face)
     Lisp_Object extent, face;
{
  EXTENT e;

  CHECK_EXTENT (extent, 0);
  e = XEXTENT (extent);

  if (!NILP (face))
    face = Fget_face (face);

  set_extent_face (e, face);

  return face;
}

DEFUN ("make-face", Fmake_face, Smake_face, 1, 3, 0,
       "Defines and returns a new FACE described by DOC-STRING.\n\
You can modify the font, color, etc of a face with the set-face- functions.\n\
If the face already exists, it is unmodified.\n\
If TEMPORARY is non-nil, this face will cease to exist if not in use.")
     (name, doc_string, temporary)
     Lisp_Object name, doc_string, temporary;
{
  /* This function can GC if initialized is non-zero */
  struct Lisp_Face *f;
  Lisp_Object face;

  CHECK_SYMBOL (name, 0);
  if (!NILP (doc_string))
    CHECK_STRING (doc_string, 0);

  face = Ffind_face (name);
  if (!NILP (face))
    return face;

  f = allocate_face ();
  XSETFACE (face, f);

  f->name = name;
  f->doc_string = doc_string;
  f->foreground = Fmake_specifier (Qcolor);
  set_color_attached_to (f->foreground, face, Qforeground);
  f->background = Fmake_specifier (Qcolor);
  set_color_attached_to (f->background, face, Qbackground);
  f->font = Fmake_specifier (Qfont);
  set_font_attached_to (f->font, face, Qfont);
  f->background_pixmap = Fmake_specifier (Qimage);
  set_image_attached_to (f->background_pixmap, face, Qbackground_pixmap);
  /* #### need a special display-table specifier */
  f->display_table = Fmake_specifier (Qgeneric);
  f->underline = Fmake_specifier (Qface_boolean);
  set_face_boolean_attached_to (f->underline, face, Qunderline);
  f->highlight = Fmake_specifier (Qface_boolean);
  set_face_boolean_attached_to (f->highlight, face, Qhighlight);
  f->dim = Fmake_specifier (Qface_boolean);
  set_face_boolean_attached_to (f->dim, face, Qdim);
  f->blinking = Fmake_specifier (Qface_boolean);
  set_face_boolean_attached_to (f->blinking, face, Qblinking);
  f->reverse = Fmake_specifier (Qface_boolean);
  set_face_boolean_attached_to (f->reverse, face, Qreverse);
  if (!NILP (Vdefault_face))
    {
      /* If the default face has already been created, set it as
	 the default fallback specifier for all the specifiers we
	 just created.  This implements the standard "all faces
	 inherit from default" behavior. */
      set_specifier_fallback (f->foreground,
			     Fget (Vdefault_face, Qforeground, Qunbound));
      set_specifier_fallback (f->background,
			     Fget (Vdefault_face, Qbackground, Qunbound));
      set_specifier_fallback (f->font,
			     Fget (Vdefault_face, Qfont, Qunbound));
      set_specifier_fallback (f->background_pixmap,
			     Fget (Vdefault_face, Qbackground_pixmap,
				   Qunbound));
      set_specifier_fallback (f->display_table,
			     Fget (Vdefault_face, Qdisplay_table, Qunbound));
      set_specifier_fallback (f->underline,
			     Fget (Vdefault_face, Qunderline, Qunbound));
      set_specifier_fallback (f->highlight,
			     Fget (Vdefault_face, Qhighlight, Qunbound));
      set_specifier_fallback (f->dim,
			     Fget (Vdefault_face, Qdim, Qunbound));
      set_specifier_fallback (f->blinking,
			     Fget (Vdefault_face, Qblinking, Qunbound));
      set_specifier_fallback (f->reverse,
			     Fget (Vdefault_face, Qreverse, Qunbound));
    }

  /* Add the face to the appropriate list. */
  if (NILP (temporary))
    Fputhash (name, face, Vpermanent_faces_cache);
  else
    Fputhash (name, face, Vtemporary_faces_cache);

  if (initialized)
    {
      struct gcpro gcpro1, gcpro2;

      GCPRO2 (name, face);
      call1 (Qinit_face_from_resources, name);
      UNGCPRO;
    }

  return face;
}


/*****************************************************************************
 initialization code
 ****************************************************************************/

void
init_global_faces (struct device *d)
{
  /* When making the initial terminal device, there is no Lisp code
     loaded, so we can't do this. */
  if (initialized)
    {
      call_critical_lisp_code (d, Qinit_global_faces, Qnil);
    }
}

void
init_device_faces (struct device *d)
{
  /* When making the initial terminal device, there is no Lisp code
     loaded, so we can't do this. */
  if (initialized)
    {
      Lisp_Object tdevice;
      XSETDEVICE (tdevice, d);
      call_critical_lisp_code (d, Qinit_device_faces, tdevice);
    }
}

void
init_frame_faces (struct frame *frm)
{
  /* When making the initial terminal device, there is no Lisp code
     loaded, so we can't do this. */
  if (initialized)
    {
      Lisp_Object tframe;
      XSETFRAME (tframe, frm);

      /* DO NOT change the selected frame here.  If the debugger goes off
         it will try and display on the frame being created, but it is not
         ready for that yet and a horrible death will occur.  Any random
         code depending on the selected-frame as an implicit arg should be
         tracked down and shot.  For the benefit of the one known,
         xpm-color-symbols, make-frame sets the variable
         Vframe_being_created to the frame it is making and sets it to nil
         when done.  Internal functions that this could trigger which are
         currently depending on selected-frame should use this instead.  It
         is not currently visible at the lisp level. */
      call_critical_lisp_code (XDEVICE (FRAME_DEVICE (frm)),
			       Qinit_frame_faces, tframe);
    }
}


/*****************************************************************************
 face cache element functions
 ****************************************************************************/
void
mark_face_cache_elements (face_cache_element_dynarr *elements,
			  void (*markobj) (Lisp_Object))
{
  int elt;

  if (!elements)
    return;

  for (elt = 0; elt < Dynarr_length (elements); elt++)
    {
      struct face_cache_element *inst = Dynarr_atp (elements, elt);

      ((markobj) (inst->face));
      ((markobj) (inst->foreground));
      ((markobj) (inst->background));
      ((markobj) (inst->font));
      ((markobj) (inst->display_table));
      ((markobj) (inst->background_pixmap));
    }
}

static void
update_face_cache_element_data (struct window *w, Lisp_Object face,
				struct face_cache_element *cachel)
{
  if (XFACE (face)->dirty || UNBOUNDP (cachel->face))
    {
      int default_face = EQ (face, Vdefault_face);
      Lisp_Object window = Qnil;

      XSETWINDOW (window, w);
      cachel->face = face;

      /* We normally only set the _specified flags if the value was
         actually bound.  The exception is for the default face where
         we always set it since it is the ultimate fallback. */

#define FROB(field)							\
  do {									\
    Lisp_Object new_val = FACE_PROPERTY_INSTANCE (face, Q##field, window, 1); \
    int bound = 1;							\
    if (UNBOUNDP (new_val))						\
      {									\
	bound = 0;							\
	new_val = FACE_PROPERTY_INSTANCE (face, Q##field, window, 0);	\
      }									\
    if (!EQ (new_val, cachel->field))					\
      {									\
	cachel->field = new_val;					\
	cachel->dirty = 1;						\
      }									\
    cachel->field##_specified = (bound || default_face);		\
  } while (0)

      FROB (foreground);
      FROB (background);
      FROB (font);
      FROB (display_table);
      FROB (background_pixmap);
#undef FROB

#define FROB(field)							\
  do {									\
    Lisp_Object new_val = FACE_PROPERTY_INSTANCE (face, Q##field, window, 1); \
    int bound = 1;							\
    int new_val_int;							\
    if (UNBOUNDP (new_val))						\
      {									\
	bound = 0;							\
	new_val = FACE_PROPERTY_INSTANCE (face, Q##field, window, 0);	\
      }									\
    new_val_int = EQ (new_val, Qt);					\
    if (cachel->field != new_val_int)					\
      {									\
	cachel->field = new_val_int;					\
	cachel->dirty = 1;						\
      }									\
    cachel->field##_specified = bound;					\
  } while (0)

      FROB (underline);
      FROB (highlight);
      FROB (dim);
      FROB (reverse);
      FROB (blinking);
#undef FROB
    }

  cachel->updated = 1;
}

static void
merge_face_cache_element_data (struct window *w, face_index findex,
			       struct face_cache_element *cachel)
{
#define FINDEX_FIELD(field)						\
  Dynarr_atp (w->face_cache_elements, findex)->field

#define FROB(field)							\
  do {									\
    if (!cachel->field##_specified && FINDEX_FIELD (field##_specified))	\
      {									\
	cachel->field = FINDEX_FIELD (field);				\
	cachel->field##_specified = 1;					\
	cachel->dirty = 1;						\
      }									\
  } while (0)

  FROB (foreground);
  FROB (background);
  FROB (font);
  FROB (display_table);
  FROB (background_pixmap);
  FROB (underline);
  FROB (highlight);
  FROB (dim);
  FROB (reverse);
  FROB (blinking);
#undef FROB
#undef FINDEX_FIELD

  cachel->updated = 1;
}

static void
reset_face_cache_element (struct face_cache_element *inst)
{
  memset (inst, 0, sizeof (struct face_cache_element));
  inst->face = Qunbound;
  inst->foreground = Qunbound;
  inst->background = Qunbound;
  inst->font = Qunbound;
  inst->display_table = Qunbound;
  inst->background_pixmap = Qunbound;
}

static void
add_face_cache_element (struct window *w, Lisp_Object face)
{
  struct face_cache_element new_cachel;

  reset_face_cache_element (&new_cachel);
  update_face_cache_element_data (w, face, &new_cachel);
  Dynarr_add (w->face_cache_elements, new_cachel);
}

face_index
get_builtin_face_cache_index (struct window *w, Lisp_Object face)
{
  int elt;

  if (noninteractive)
    return 0;

  for (elt = 0; elt < Dynarr_length (w->face_cache_elements); elt++)
    {
      struct face_cache_element *cachel =
	Dynarr_atp (w->face_cache_elements, elt);

      if (EQ (cachel->face, face))
	{
	  if (!cachel->updated)
	    update_face_cache_element_data (w, face, cachel);
	  return elt;
	}
    }

  /* If we didn't find the face, add it and then return its index. */
  add_face_cache_element (w, face);
  return elt;
}

void
reset_face_cache_elements (struct window *w)
{
  /* #### Not initialized in batch mode for the stream device. */
  if (w->face_cache_elements)
    {
      Dynarr_reset (w->face_cache_elements);
      get_builtin_face_cache_index (w, Vdefault_face);
      get_builtin_face_cache_index (w, Vmodeline_face);
      XFRAME (w->frame)->window_face_cache_reset = 1;
    }
}

void
mark_face_cache_elements_as_clean (struct window *w)
{
  int elt;

  for (elt = 0; elt < Dynarr_length (w->face_cache_elements); elt++)
    Dynarr_atp (w->face_cache_elements, elt)->dirty = 0;
}

void
mark_face_cache_elements_as_not_updated (struct window *w)
{
  int elt;

  for (elt = 0; elt < Dynarr_length (w->face_cache_elements); elt++)
    Dynarr_atp (w->face_cache_elements, elt)->updated = 0;
}

static int
compare_face_cache_elements (struct face_cache_element *cachel1,
			     struct face_cache_element *cachel2)
{
  if (EQ (cachel1->foreground, cachel2->foreground)
      && EQ (cachel1->background, cachel2->background)
      && EQ (cachel1->font, cachel2->font)
      && EQ (cachel1->display_table, cachel2->display_table)
      && EQ (cachel1->background_pixmap, cachel2->background_pixmap)
      && cachel1->underline == cachel2->underline
      && cachel1->highlight == cachel2->highlight
      && cachel1->dim == cachel2->dim
      && cachel1->blinking == cachel2->blinking
      && cachel1->reverse == cachel2->reverse)
    return 1;
  else
    return 0;
}


/*****************************************************************************
 merged face functions
 ****************************************************************************/
static face_index
get_merged_face_element_index (struct window *w,
			       struct face_cache_element *merged_inst)
{
  int elt;
  int cache_size = Dynarr_length (w->face_cache_elements);

  for (elt = 0; elt < cache_size; elt++)
    {
      struct face_cache_element *inst =
	Dynarr_atp (w->face_cache_elements, elt);

      /* Do not compare the face names.  Merged elements will have it
         as nil and this would cause false positives.  Also, by doing
         it this way if a merged instance happens to match a builtin
         face instance, we'll still return it.  Why add two identical
         instances? */
      if (compare_face_cache_elements (inst, merged_inst))
	return elt;
    }

  /* We didn't find it so add this instance to the cache. */
  merged_inst->updated = 1;
  merged_inst->dirty = 1;
  Dynarr_add (w->face_cache_elements, *merged_inst);
  return cache_size;
}

face_index
get_extent_fragment_face_cache_index (struct window *w,
				      struct extent_fragment *ef,
				      EXTENT dummy_lhe_extent)
{
  struct face_cache_element cachel;
  int len = Dynarr_length (ef->extents);
  face_index findex;

  /* Optimize the default case. */
  if (len == 0)
    return DEFAULT_INDEX;
  else
    {
      int i;
      Lisp_Object window = Qnil;
      XSETWINDOW (window, w);

      /* Merge the faces of the extents together in order.

	 Remember that one of the extents in the list might be our dummy
	 extent representing the highlighting that is attached to some other
	 extent that is currently mouse-highlighted.  When an extent is
	 mouse-highlighted, it is as if there are two extents there, of
	 potentially different priorities: the extent being highlighted, with
	 whatever face and priority it has; and an ephemeral extent in the
	 `highlight' face with `mouse-highlight-priority'.
       */

      reset_face_cache_element (&cachel);

      for (i = len - 1; i >= 0; i--)
	{
	  EXTENT current = Dynarr_at (ef->extents, i);

	  if (current == dummy_lhe_extent)
	    {
	      /* this isn't a real extent; use the highlight face. */
	      findex = get_builtin_face_cache_index (w, Vhighlight_face);
	      merge_face_cache_element_data (w, findex, &cachel);
	    }
	  else if (!NILP (extent_face (current)))
	    {
	      findex = get_builtin_face_cache_index (w, extent_face (current));
	      merge_face_cache_element_data (w, findex, &cachel);
	    }
	}

      /* Now finally merge in the default face. */
      findex = get_builtin_face_cache_index (w, Vdefault_face);
      merge_face_cache_element_data (w, findex, &cachel);

      return get_merged_face_element_index (w, &cachel);
    }
}


/*****************************************************************************
 interface functions
 ****************************************************************************/

/* #### This function should be converted into appropriate device methods. */
static void
update_EmacsFrame (Lisp_Object frame, Lisp_Object name)
{
  struct frame *frm = XFRAME (frame);

#ifdef HAVE_X_WINDOWS
  if (FRAME_IS_X (frm))
     {
       Arg av[10];
       int ac = 0;
     
       if (EQ (name, Qforeground))
	 {
	   Lisp_Object color = FACE_FOREGROUND (Vdefault_face, frame);
	   XColor fgc;

	   fgc = COLOR_INSTANCE_X_COLOR (XCOLOR_INSTANCE (color));
	   XtSetArg (av[ac], XtNforeground, (void *) fgc.pixel); ac++;
	 }
       else if (EQ (name, Qbackground))
	 {
	   Lisp_Object color = FACE_BACKGROUND (Vdefault_face, frame);
	   XColor bgc;

	   bgc = COLOR_INSTANCE_X_COLOR (XCOLOR_INSTANCE (color));
	   XtSetArg (av[ac], XtNbackground, (void *) bgc.pixel); ac++;

	   /* Really crappy way to force the modeline shadows to be
              redrawn.  But effective. */
	   MARK_FRAME_WINDOWS_STRUCTURE_CHANGED (frm);
	   MARK_FRAME_CHANGED (frm);
	 }
       else if (EQ (name, Qfont))
	 {
	   Lisp_Object font = FACE_FONT (Vdefault_face, frame);

	   XtSetArg (av[ac], XtNfont,
		     (void *) FONT_INSTANCE_X_FONT (XFONT_INSTANCE (font)));
	   ac++;
	 }
       else
	 abort ();

       XtSetValues (FRAME_X_TEXT_WIDGET (frm), av, ac);

       /* Setting the background clears the entire frame area
          including the toolbar so we force an immediate redraw of
          it. */
       if (EQ (name, Qbackground))
	 MAYBE_DEVMETH (XDEVICE (frm->device), redraw_frame_toolbars, (frm));

       /* The intent of this code is to cause the frame size in
	  characters to remain the same when the font changes, at the
	  expense of changing the frame size in pixels.  It's not
	  totally clear that this is the right thing to do, but it's
	  not clearly wrong either.  */
       if (EQ (name, Qfont))
	 {
	   EmacsFrameRecomputeCellSize (FRAME_X_TEXT_WIDGET (frm));
	   Fset_frame_size (frame,
			    make_number (frm->width),
			    make_number (frm->height),
			    Qnil);
	 }
     }
#endif /* HAVE_X_WINDOWS */

#ifdef HAVE_NEXTSTEP
  if (FRAME_IS_NS (frm))
    {
      /* This code still needs to be written */
    }
#endif /* HAVE_NEXSTEP */
}

static void
update_EmacsFrames (Lisp_Object locale, Lisp_Object name)
{
  if (FRAMEP (locale))
    {
      update_EmacsFrame (locale, name);
    }
  else if (DEVICEP (locale))
    {
      Lisp_Object frm;

      FRAME_LOOP (frm, XDEVICE (locale))
	update_EmacsFrame (XCAR (frm), name);
    }
  else if (EQ (locale, Qglobal) || EQ (locale, Qfallback))
    {
      Lisp_Object dev, frm;
      
      DEVICE_AND_FRAME_LOOP (dev, frm)
	update_EmacsFrame (XCAR (frm), name);
    }
}

void
update_frame_face_values (struct frame *f)
{
  Lisp_Object frm = Qnil;

  XSETFRAME (frm, f);
  update_EmacsFrame (frm, Qforeground);
  update_EmacsFrame (frm, Qbackground);
  update_EmacsFrame (frm, Qfont);
}

void
face_property_was_changed (Lisp_Object face, Lisp_Object property,
			   Lisp_Object locale)
{
  int default_face = EQ (face, Vdefault_face);

  /* If the locale could affect the frame value, then call
     update_EmacsFrames just in case. */
  if (default_face &&
      (EQ (property, Qforeground) ||
       EQ (property, Qbackground) ||
       EQ (property, Qfont)))
    update_EmacsFrames (locale, property);

  if (WINDOWP (locale))
    {
      MARK_FRAME_FACES_CHANGED (XFRAME (XWINDOW (locale)->frame));
    }
  else if (FRAMEP (locale))
    {
      MARK_FRAME_FACES_CHANGED (XFRAME (locale));
    }
  else if (DEVICEP (locale))
    {
      MARK_DEVICE_FRAMES_FACES_CHANGED (XDEVICE (locale));
    }
  else
    {
      Lisp_Object dev;
      
      DEVICE_LOOP (dev)
	MARK_DEVICE_FRAMES_FACES_CHANGED (XDEVICE (XCAR (dev)));
    }
  
  update_faces_inheritance (face, property);
  XFACE (face)->dirty = 1;
}

DEFUN ("copy-face", Fcopy_face, Scopy_face, 2, 6, 0,
  "Defines and returns a new face which is a copy of an existing one,\n\
or makes an already-existing face be exactly like another. LOCALE,\n\
TAG-SET, EXACT-P, and HOW-TO-ADD are as in `copy-specifier'.")
     (old_face, new_name, locale, tag_set, exact_p, how_to_add)
     Lisp_Object old_face, new_name, locale, tag_set, exact_p, how_to_add;
{
  struct Lisp_Face *fold, *fnew;
  Lisp_Object new_face = Qnil;
  struct gcpro gcpro1, gcpro2, gcpro3, gcpro4;

  old_face = Fget_face (old_face);

  /* We GCPRO old_face because it might be temporary, and GCing could
     occur in various places below. */
  GCPRO4 (tag_set, locale, old_face, new_face);
  /* check validity of how_to_add now. */
  (void) decode_how_to_add_specification (how_to_add);
  /* and of tag_set. */
  tag_set = decode_specifier_tag_set (tag_set);
  /* and of locale. */
  locale = decode_locale_list (locale);

  new_face = Ffind_face (new_name);
  if (NILP (new_face))
    {
      Lisp_Object temp;

      CHECK_SYMBOL (new_name, 0);

      /* Create the new face with the same status as the old face. */
      temp = (NILP (Fgethash (old_face, Vtemporary_faces_cache, Qnil))
	      ? Qnil
	      : Qt);

      new_face = Fmake_face (new_name, Qnil, temp);
    }

  fold = XFACE (old_face);
  fnew = XFACE (new_face);

  Fcopy_specifier (fold->foreground, fnew->foreground, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->background, fnew->background, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->font, fnew->font, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->display_table, fnew->display_table, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->background_pixmap, fnew->background_pixmap,
		   locale, tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->underline, fnew->underline, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->highlight, fnew->highlight, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->dim, fnew->dim, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->blinking, fnew->blinking, locale,
		   tag_set, exact_p, how_to_add);
  Fcopy_specifier (fold->reverse, fnew->reverse, locale,
		   tag_set, exact_p, how_to_add);
  /* #### should it copy the individual specifiers, if they exist? */
  fnew->plist = Fcopy_sequence (fold->plist);

  UNGCPRO;

  return new_name;
}


void
init_faces (void)
{
  if (!initialized)
    return;

  Vpermanent_faces_cache = make_lisp_hashtable (10, 0, 0, HASHTABLE_NONWEAK);
  Vtemporary_faces_cache = make_lisp_hashtable (0, 0, 0, HASHTABLE_WEAK);

  /* Create the default face now so we know what it is immediately. */
  Vdefault_face = Qnil; /* so that Fmake_face() doesn't set up a bogus
			   default value */
  Vdefault_face = Fmake_face (Qdefault, build_string ("default face"),
			      Qnil);

  /* Now create and initialize the rest of the built-in faces. */
  call0_with_handler (Qreally_early_error_handler, Qinit_faces);

  /* If Fget_face signals an error bad, bad things are going to
     happen.  That should only be possible if someone decided to stop
     creating these faces in init-faces.  Bad, bad someone. */
  Vmodeline_face = Fget_face (Qmodeline);
  Vhighlight_face = Fget_face (Qhighlight);
  Vleft_margin_face = Fget_face (Qleft_margin);
  Vright_margin_face = Fget_face (Qright_margin);

  /* Provide some last-resort fallbacks to avoid utter fuckage if
     someone provides invalid values for the global specifications. */
  set_specifier_fallback (Fget (Vdefault_face, Qforeground, Qnil),
			  list1 (Fcons (Qnil,
					build_string ("black"))));
  set_specifier_fallback (Fget (Vdefault_face, Qbackground, Qnil),
			  list1 (Fcons (Qnil,
					build_string ("white"))));

  /* #### We may want to have different fallback values if NeXTstep
     support is compiled in. */
#ifdef HAVE_X_WINDOWS
  {
    char *fonts[30];
    int n = 0;
    Lisp_Object inst_list = Qnil;

    /* The same gory list from x-faces.el.
       (#### Perhaps we should remove the stuff from x-faces.el
       and only depend on this stuff here?  That should work.)
     */
    fonts[n++] = "-*-courier-medium-r-*-*-*-120-*-*-*-*-iso8859-*";
    fonts[n++] = "-*-courier-medium-r-*-*-*-120-*-*-*-*-iso8859-*";
    fonts[n++] = "-*-courier-*-r-*-*-*-120-*-*-*-*-iso8859-*";
    fonts[n++] = "-*-*-medium-r-*-*-*-120-*-*-m-*-iso8859-*";
    fonts[n++] = "-*-*-medium-r-*-*-*-120-*-*-c-*-iso8859-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-m-*-iso8859-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-c-*-iso8859-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-*-*-iso8859-*";
    fonts[n++] = "-*-*-medium-r-*-*-*-120-*-*-m-*-*-*";
    fonts[n++] = "-*-*-medium-r-*-*-*-120-*-*-c-*-*-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-m-*-*-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-c-*-*-*";
    fonts[n++] = "-*-*-*-r-*-*-*-120-*-*-*-*-*-*";
    fonts[n++] = "-*-*-*-*-*-*-*-120-*-*-*-*-*-*";
    fonts[n++] = "*";

    for (--n; n >= 0; --n)
      inst_list = Fcons (Fcons (list1 (Qx), build_string (fonts[n])),
			 inst_list);
    set_specifier_fallback (Fget (Vdefault_face, Qfont, Qnil), inst_list);
  }
#else
  /* #### This assumes that a font of nil is an OK instantiator, which
     may not be the case. */
  set_specifier_fallback (Fget (Vdefault_face, Qfont, Qnil), Qnil);
#endif
  
  set_specifier_fallback (Fget (Vdefault_face, Qunderline, Qnil),
			 list1 (Fcons (Qnil, Qnil)));
  set_specifier_fallback (Fget (Vdefault_face, Qhighlight, Qnil),
			 list1 (Fcons (Qnil, Qnil)));
  set_specifier_fallback (Fget (Vdefault_face, Qdim, Qnil),
			 list1 (Fcons (Qnil, Qnil)));
  set_specifier_fallback (Fget (Vdefault_face, Qblinking, Qnil),
			 list1 (Fcons (Qnil, Qnil)));
  set_specifier_fallback (Fget (Vdefault_face, Qreverse, Qnil),
			 list1 (Fcons (Qnil, Qnil)));
}

void
syms_of_faces (void)
{
  /* Qdefault defined in general.c */
  defsymbol (&Qmodeline, "modeline");
  defsymbol (&Qleft_margin, "left-margin");
  defsymbol (&Qright_margin, "right-margin");

  defsubr (&Sfacep);
  defsubr (&Sfind_face);
  defsubr (&Sget_face);
  defsubr (&Sface_name);
  defsubr (&Sface_ascent);
  defsubr (&Sface_descent);
  defsubr (&Sface_width);
  defsubr (&Sface_list);
  defsubr (&Sextent_face);
  defsubr (&Sset_extent_face);
  defsubr (&Smake_face);
  defsubr (&Scopy_face);

  defsymbol (&Qface, "face");
  defsymbol (&Qfacep, "facep");
  defsymbol (&Qforeground, "foreground");
  defsymbol (&Qbackground, "background");
  /* Qfont defined in general.c */
  defsymbol (&Qdisplay_table, "display-table");
  defsymbol (&Qbackground_pixmap, "background-pixmap");
  defsymbol (&Qunderline, "underline");
  /* Qhighlight, Qreverse defined in general.c */
  defsymbol (&Qdim, "dim");
  defsymbol (&Qblinking, "blinking");

  defsymbol (&Qinit_face_from_resources, "init-face-from-resources");
  defsymbol (&Qinit_global_faces, "init-global-faces");
  defsymbol (&Qinit_device_faces, "init-device-faces");
  defsymbol (&Qinit_frame_faces, "init-frame-faces");
  defsymbol (&Qinit_faces, "init-faces");
}

void
vars_of_faces (void)
{
  staticpro (&Vpermanent_faces_cache);
  Vpermanent_faces_cache = Qnil;
  staticpro (&Vtemporary_faces_cache);
  Vtemporary_faces_cache = Qnil;

  staticpro (&Vdefault_face);
  Vdefault_face = Qnil;
  staticpro (&Vmodeline_face);
  Vmodeline_face = Qnil;
  staticpro (&Vhighlight_face);
  Vhighlight_face = Qnil;

  staticpro (&Vleft_margin_face);
  Vleft_margin_face = Qnil;
  staticpro (&Vright_margin_face);
  Vright_margin_face = Qnil;
}
