summaryrefslogblamecommitdiffstats
path: root/utils/xftwrap.c
blob: e88040758a130b54478e162fdfe2ca4b74db975b (plain) (tree)













































































































































































































                                                                              
/* xftwrap.c --- XftDrawStringUtf8 with multi-line strings.
 * xscreensaver, Copyright © 2021 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "utils.h"
#include "xft.h"
#include "xftwrap.h"

#undef MAX
#undef MIN
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))


#ifdef DEBUG
static void
LOG(const char *ss, const char *s, int n)
{
  int i;
  fprintf(stderr,"####%s [", ss);
  for (i = 0; i < n; i++) fprintf(stderr, "%c", s[i]);
  fprintf(stderr,"]\n");
}
#else
# define LOG(ss,s,n) /**/
#endif


/* Returns a new string word-wrapped to fit in the width in pixels.
 */
char *
xft_word_wrap (Display *dpy, XftFont *font, const char *str, int pixels)
{
  const char *in = str;
  char *ret = (char *) malloc (strlen(in) + 2);
  char *out = ret;
  const char *line_out = out;
  char *word_out = 0;
  while (1)
    {
      if (*in == ' ' || *in == '\t' ||
          *in == '\r' || *in == '\n' ||
          *in == 0)
        {
          Bool done = (*in == 0);	/* To wrap the *last* word. */
          XGlyphInfo overall;
          Bool nl = (*in == '\r' || *in == '\n');

          if (nl) in++;
          while (*in == ' ' || *in == '\t') in++;
          in--;

          XftTextExtentsUtf8 (dpy, font,
                              (FcChar8 *) line_out,
                              out - line_out,
                              &overall);
          if (overall.width - overall.x >= pixels &&
              word_out)
            {
              word_out[0] = '\n';
              line_out = word_out + 1;
              word_out = 0;
              if (done) break;
              *out++ = *in;
            }
          else
            {
              if (done) break;
              word_out = out;
              *out++ = *in;
              if (nl)
                {
                  line_out = out + 1;
                  word_out = 0;
                }
            }

          if (done) break;
        }
      else
        {
          *out++ = *in;
        }
      in++;
    }

  *out = 0;

  return ret;
}


/* Like XftTextExtentsUtf8, but handles multi-line strings.
   XGlyphInfo will contain the bounding box that encloses all of the text.
   Return value is the number of lines in the text, >= 1.
 */
int
XftTextExtentsUtf8_multi (Display *dpy, XftFont *font,
                          const FcChar8 *str, int len, XGlyphInfo *overall)
{
  int i, start = 0;
  int lines = 0;
  int line_y = 0;
  for (i = 0; i <= len; i++)
    {
      if (i == len || str[i] == '\r' || str[i] == '\n')
        {
          XGlyphInfo gi;
          XftTextExtentsUtf8 (dpy, font,
                              str + start,
                              i - start,
                              &gi);
          if (lines == 0)
            *overall = gi;
          else
            {
              /* Find the union of the two bounding boxes, placed at their
                 respective origins. */
              int ox1, oy1, ox2, oy2;		/* bbox of 'overall' */
              int nx1, ny1, nx2, ny2;		/* bbox of 'gi' */
              int ux1, uy1, ux2, uy2;		/* union */

              ox1 = overall->x;
              oy1 = overall->y;
              ox2 = ox1 + overall->width;
              oy2 = oy1 + overall->height;

              line_y += font->ascent + font->descent;  /* advance origin */

              nx1 = gi.x;
              ny1 = gi.y + line_y;
              nx2 = nx1 + gi.width;
              ny2 = ny1 + gi.height + line_y;

              ux1 = MIN (ox1, nx1);		/* upper left */
              uy1 = MIN (oy1, ny1);
              ux2 = MAX (ox2, nx2);		/* bottom right */
              uy2 = MAX (oy2, ny2);

              overall->x = ux1;
              overall->y = uy1;
              overall->width  = ux2 - ux1;
              overall->height = uy2 - uy1;
            }
          lines++;
          start = i+1;
        }
    }

  return lines;
}


/* Like XftDrawStringUtf8, but handles multi-line strings.
   Alignment is 1, 0 or -1 for left, center, right.
 */
void
XftDrawStringUtf8_multi (XftDraw *xftdraw, const XftColor *color,
                         XftFont *font, int x, int y, const FcChar8 *str,
                         int len,
                         int alignment)
{
  Display *dpy = XftDrawDisplay (xftdraw);
  int i, start = 0;
  int lines = 0;
  XGlyphInfo overall;
  if (len == 0) return;

  XftTextExtentsUtf8_multi (dpy, font, str, len, &overall);

  for (i = 0; i <= len; i++)
    {
      if (i == len || str[i] == '\r' || str[i] == '\n')
        {
          XGlyphInfo gi;
          int x2 = x;
          XftTextExtentsUtf8 (dpy, font, str + start, i - start, &gi);
          switch (alignment) {
          case  1: break;
          case  0: x2 += (overall.width - gi.width) / 2; break;
          case -1: x2 += (overall.width - gi.width);     break;
          default: abort(); break;
          }

          XftDrawStringUtf8 (xftdraw, color, font, x2, y,
                             str + start,
                             i - start);
          y += font->ascent + font->descent;
          lines++;
          start = i+1;
        }
    }
}