+\f
+/***********************************************************************
+ Face capability testing
+ ***********************************************************************/
+
+
+/* If the distance (as returned by color_distance) between two colors is
+ less than this, then they are considered the same, for determining
+ whether a color is supported or not. The range of values is 0-65535. */
+
+#define TTY_SAME_COLOR_THRESHOLD 10000
+
+#ifdef HAVE_WINDOW_SYSTEM
+
+/* Return non-zero if all the face attributes in ATTRS are supported
+ on the window-system frame F.
+
+ The definition of `supported' is somewhat heuristic, but basically means
+ that a face containing all the attributes in ATTRS, when merged with the
+ default face for display, can be represented in a way that's
+
+ \(1) different in appearance than the default face, and
+ \(2) `close in spirit' to what the attributes specify, if not exact. */
+
+static int
+x_supports_face_attributes_p (f, attrs, def_face)
+ struct frame *f;
+ Lisp_Object *attrs;
+ struct face *def_face;
+{
+ Lisp_Object *def_attrs = def_face->lface;
+
+ /* Check that other specified attributes are different that the default
+ face. */
+ if ((!UNSPECIFIEDP (attrs[LFACE_UNDERLINE_INDEX])
+ && face_attr_equal_p (attrs[LFACE_UNDERLINE_INDEX],
+ def_attrs[LFACE_UNDERLINE_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_INVERSE_INDEX])
+ && face_attr_equal_p (attrs[LFACE_INVERSE_INDEX],
+ def_attrs[LFACE_INVERSE_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_FOREGROUND_INDEX])
+ && face_attr_equal_p (attrs[LFACE_FOREGROUND_INDEX],
+ def_attrs[LFACE_FOREGROUND_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_BACKGROUND_INDEX])
+ && face_attr_equal_p (attrs[LFACE_BACKGROUND_INDEX],
+ def_attrs[LFACE_BACKGROUND_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_STIPPLE_INDEX])
+ && face_attr_equal_p (attrs[LFACE_STIPPLE_INDEX],
+ def_attrs[LFACE_STIPPLE_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_OVERLINE_INDEX])
+ && face_attr_equal_p (attrs[LFACE_OVERLINE_INDEX],
+ def_attrs[LFACE_OVERLINE_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_STRIKE_THROUGH_INDEX])
+ && face_attr_equal_p (attrs[LFACE_STRIKE_THROUGH_INDEX],
+ def_attrs[LFACE_STRIKE_THROUGH_INDEX]))
+ || (!UNSPECIFIEDP (attrs[LFACE_BOX_INDEX])
+ && face_attr_equal_p (attrs[LFACE_BOX_INDEX],
+ def_attrs[LFACE_BOX_INDEX])))
+ return 0;
+
+ /* Check font-related attributes, as those are the most commonly
+ "unsupported" on a window-system (because of missing fonts). */
+ if (!UNSPECIFIEDP (attrs[LFACE_FAMILY_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_HEIGHT_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_WEIGHT_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_SLANT_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_SWIDTH_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_AVGWIDTH_INDEX]))
+ {
+ struct face *face;
+ Lisp_Object merged_attrs[LFACE_VECTOR_SIZE];
+
+ bcopy (def_attrs, merged_attrs, sizeof merged_attrs);
+
+ merge_face_vectors (f, attrs, merged_attrs, 0);
+
+ face = FACE_FROM_ID (f, lookup_face (f, merged_attrs, 0, 0));
+
+ if (! face)
+ error ("Cannot make face");
+
+ /* If the font is the same, then not supported. */
+ if (face->font == def_face->font)
+ return 0;
+ }
+
+ /* Everything checks out, this face is supported. */
+ return 1;
+}
+
+#endif /* HAVE_WINDOW_SYSTEM */
+
+/* Return non-zero if all the face attributes in ATTRS are supported
+ on the tty frame F.
+
+ The definition of `supported' is somewhat heuristic, but basically means
+ that a face containing all the attributes in ATTRS, when merged
+ with the default face for display, can be represented in a way that's
+
+ \(1) different in appearance than the default face, and
+ \(2) `close in spirit' to what the attributes specify, if not exact.
+
+ Point (2) implies that a `:weight black' attribute will be satisfied
+ by any terminal that can display bold, and a `:foreground "yellow"' as
+ long as the terminal can display a yellowish color, but `:slant italic'
+ will _not_ be satisfied by the tty display code's automatic
+ substitution of a `dim' face for italic. */
+
+static int
+tty_supports_face_attributes_p (f, attrs, def_face)
+ struct frame *f;
+ Lisp_Object *attrs;
+ struct face *def_face;
+{
+ int weight;
+ Lisp_Object val, fg, bg;
+ XColor fg_tty_color, fg_std_color;
+ XColor bg_tty_color, bg_std_color;
+ unsigned test_caps = 0;
+ Lisp_Object *def_attrs = def_face->lface;
+
+
+ /* First check some easy-to-check stuff; ttys support none of the
+ following attributes, so we can just return false if any are requested
+ (even if `nominal' values are specified, we should still return false,
+ as that will be the same value that the default face uses). We
+ consider :slant unsupportable on ttys, even though the face code
+ actually `fakes' them using a dim attribute if possible. This is
+ because the faked result is too different from what the face
+ specifies. */
+ if (!UNSPECIFIEDP (attrs[LFACE_FAMILY_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_STIPPLE_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_HEIGHT_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_SWIDTH_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_OVERLINE_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_STRIKE_THROUGH_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_BOX_INDEX])
+ || !UNSPECIFIEDP (attrs[LFACE_SLANT_INDEX]))
+ return 0;
+
+
+ /* Test for terminal `capabilities' (non-color character attributes). */
+
+ /* font weight (bold/dim) */
+ weight = face_numeric_weight (attrs[LFACE_WEIGHT_INDEX]);
+ if (weight >= 0)
+ {
+ int def_weight = face_numeric_weight (def_attrs[LFACE_WEIGHT_INDEX]);
+
+ if (weight > XLFD_WEIGHT_MEDIUM)
+ {
+ if (def_weight > XLFD_WEIGHT_MEDIUM)
+ return 0; /* same as default */
+ test_caps = TTY_CAP_BOLD;
+ }
+ else if (weight < XLFD_WEIGHT_MEDIUM)
+ {
+ if (def_weight < XLFD_WEIGHT_MEDIUM)
+ return 0; /* same as default */
+ test_caps = TTY_CAP_DIM;
+ }
+ else if (def_weight == XLFD_WEIGHT_MEDIUM)
+ return 0; /* same as default */
+ }
+
+ /* underlining */
+ val = attrs[LFACE_UNDERLINE_INDEX];
+ if (!UNSPECIFIEDP (val))
+ {
+ if (STRINGP (val))
+ return 0; /* ttys can't use colored underlines */
+ else if (face_attr_equal_p (val, def_attrs[LFACE_UNDERLINE_INDEX]))
+ return 0; /* same as default */
+ else
+ test_caps |= TTY_CAP_UNDERLINE;
+ }
+
+ /* inverse video */
+ val = attrs[LFACE_INVERSE_INDEX];
+ if (!UNSPECIFIEDP (val))
+ {
+ if (face_attr_equal_p (val, def_attrs[LFACE_UNDERLINE_INDEX]))
+ return 0; /* same as default */
+ else
+ test_caps |= TTY_CAP_INVERSE;
+ }
+
+
+ /* Color testing. */
+
+ /* Default the color indices in FG_TTY_COLOR and BG_TTY_COLOR, since
+ we use them when calling `tty_capable_p' below, even if the face
+ specifies no colors. */
+ fg_tty_color.pixel = FACE_TTY_DEFAULT_FG_COLOR;
+ bg_tty_color.pixel = FACE_TTY_DEFAULT_BG_COLOR;
+
+ /* Check if foreground color is close enough. */
+ fg = attrs[LFACE_FOREGROUND_INDEX];
+ if (STRINGP (fg))
+ {
+ Lisp_Object def_fg = def_attrs[LFACE_FOREGROUND_INDEX];
+
+ if (face_attr_equal_p (fg, def_fg))
+ return 0; /* same as default */
+ else if (! tty_lookup_color (f, fg, &fg_tty_color, &fg_std_color))
+ return 0; /* not a valid color */
+ else if (color_distance (&fg_tty_color, &fg_std_color)
+ > TTY_SAME_COLOR_THRESHOLD)
+ return 0; /* displayed color is too different */
+ else
+ /* Make sure the color is really different than the default. */
+ {
+ XColor def_fg_color;
+ if (tty_lookup_color (f, def_fg, &def_fg_color, 0)
+ && (color_distance (&fg_tty_color, &def_fg_color)
+ <= TTY_SAME_COLOR_THRESHOLD))
+ return 0;
+ }
+ }
+
+ /* Check if background color is close enough. */
+ bg = attrs[LFACE_BACKGROUND_INDEX];
+ if (STRINGP (bg))
+ {
+ Lisp_Object def_bg = def_attrs[LFACE_FOREGROUND_INDEX];
+
+ if (face_attr_equal_p (bg, def_bg))
+ return 0; /* same as default */
+ else if (! tty_lookup_color (f, bg, &bg_tty_color, &bg_std_color))
+ return 0; /* not a valid color */
+ else if (color_distance (&bg_tty_color, &bg_std_color)
+ > TTY_SAME_COLOR_THRESHOLD)
+ return 0; /* displayed color is too different */
+ else
+ /* Make sure the color is really different than the default. */
+ {
+ XColor def_bg_color;
+ if (tty_lookup_color (f, def_bg, &def_bg_color, 0)
+ && (color_distance (&bg_tty_color, &def_bg_color)
+ <= TTY_SAME_COLOR_THRESHOLD))
+ return 0;
+ }
+ }
+
+ /* If both foreground and background are requested, see if the
+ distance between them is OK. We just check to see if the distance
+ between the tty's foreground and background is close enough to the
+ distance between the standard foreground and background. */
+ if (STRINGP (fg) && STRINGP (bg))
+ {
+ int delta_delta
+ = (color_distance (&fg_std_color, &bg_std_color)
+ - color_distance (&fg_tty_color, &bg_tty_color));
+ if (delta_delta > TTY_SAME_COLOR_THRESHOLD
+ || delta_delta < -TTY_SAME_COLOR_THRESHOLD)
+ return 0;
+ }
+
+
+ /* See if the capabilities we selected above are supported, with the
+ given colors. */
+ if (test_caps != 0 &&
+ ! tty_capable_p (f, test_caps, fg_tty_color.pixel, bg_tty_color.pixel))
+ return 0;
+
+
+ /* Hmmm, everything checks out, this terminal must support this face. */
+ return 1;
+}
+
+
+DEFUN ("display-supports-face-attributes-p",
+ Fdisplay_supports_face_attributes_p, Sdisplay_supports_face_attributes_p,
+ 1, 2, 0,
+ doc: /* Return non-nil if all the face attributes in ATTRIBUTES are supported.
+The optional argument DISPLAY can be a display name, a frame, or
+nil (meaning the selected frame's display).
+
+The definition of `supported' is somewhat heuristic, but basically means
+that a face containing all the attributes in ATTRIBUTES, when merged
+with the default face for display, can be represented in a way that's
+
+ \(1) different in appearance than the default face, and
+ \(2) `close in spirit' to what the attributes specify, if not exact.
+
+Point (2) implies that a `:weight black' attribute will be satisfied by
+any display that can display bold, and a `:foreground \"yellow\"' as long
+as it can display a yellowish color, but `:slant italic' will _not_ be
+satisfied by the tty display code's automatic substitution of a `dim'
+face for italic. */)
+ (attributes, display)
+ Lisp_Object attributes, display;
+{
+ int supports, i;
+ Lisp_Object frame;
+ struct frame *f;
+ struct face *def_face;
+ Lisp_Object attrs[LFACE_VECTOR_SIZE];
+
+ if (noninteractive || !initialized)
+ /* We may not be able to access low-level face information in batch
+ mode, or before being dumped, and this function is not going to
+ be very useful in those cases anyway, so just give up. */
+ return Qnil;
+
+ if (NILP (display))
+ frame = selected_frame;
+ else if (FRAMEP (display))
+ frame = display;
+ else
+ {
+ /* Find any frame on DISPLAY. */
+ Lisp_Object fl_tail;
+
+ frame = Qnil;
+ for (fl_tail = Vframe_list; CONSP (fl_tail); fl_tail = XCDR (fl_tail))
+ {
+ frame = XCAR (fl_tail);
+ if (!NILP (Fequal (Fcdr (Fassq (Qdisplay,
+ XFRAME (frame)->param_alist)),
+ display)))
+ break;
+ }
+ }
+
+ CHECK_LIVE_FRAME (frame);
+ f = XFRAME (frame);
+
+ for (i = 0; i < LFACE_VECTOR_SIZE; i++)
+ attrs[i] = Qunspecified;
+ merge_face_ref (f, attributes, attrs, 1, 0);
+
+ def_face = FACE_FROM_ID (f, DEFAULT_FACE_ID);
+ if (def_face == NULL)
+ {
+ if (! realize_basic_faces (f))
+ error ("Cannot realize default face");
+ def_face = FACE_FROM_ID (f, DEFAULT_FACE_ID);
+ if (def_face == NULL)
+ abort (); /* realize_basic_faces must have set it up */
+ }
+
+ /* Dispatch to the appropriate handler. */
+ if (FRAME_TERMCAP_P (f) || FRAME_MSDOS_P (f))
+ supports = tty_supports_face_attributes_p (f, attrs, def_face);
+#ifdef HAVE_WINDOW_SYSTEM
+ else
+ supports = x_supports_face_attributes_p (f, attrs, def_face);
+#endif
+
+ return supports ? Qt : Qnil;
+}
+