/* 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;
}
}
}