From http://www.jwz.org/xscreensaver/xscreensaver-5.27.tar.gz
[xscreensaver] / driver / mlstring.c
1 /*
2  * (c) 2007, Quest Software, Inc. All rights reserved.
3  *
4  * This file is part of XScreenSaver,
5  * Copyright (c) 1993-2009 Jamie Zawinski <jwz@jwz.org>
6  *
7  * Permission to use, copy, modify, distribute, and sell this software and its
8  * documentation for any purpose is hereby granted without fee, provided that
9  * the above copyright notice appear in all copies and that both that
10  * copyright notice and this permission notice appear in supporting
11  * documentation.  No representations are made about the suitability of this
12  * software for any purpose.  It is provided "as is" without express or 
13  * implied warranty.
14  */
15
16 #include <stdlib.h>
17 #include <ctype.h>
18
19 #include <X11/Xlib.h>
20
21 #include "mlstring.h"
22
23 #define LINE_SPACING 1.2
24
25 static mlstring *
26 mlstring_allocate(const char *msg);
27
28 static void
29 mlstring_calculate(mlstring *str, XFontStruct *font);
30
31 mlstring*
32 mlstring_new(const char *msg, XFontStruct *font, Dimension wrap_width)
33 {
34   mlstring *newstr;
35
36   if (!(newstr = mlstring_allocate(msg)))
37     return NULL;
38
39   newstr->font_id = font->fid;
40
41   mlstring_wrap(newstr, font, wrap_width);
42
43   return newstr;
44 }
45
46 mlstring *
47 mlstring_allocate(const char *msg)
48 {
49   const char *s;
50   mlstring *ml;
51   struct mlstr_line *cur, *prev = NULL;
52   size_t linelength;
53   int the_end = 0;
54
55   if (!msg)
56     return NULL;
57
58   ml = calloc(1, sizeof(mlstring));
59
60   if (!ml)
61     return NULL;
62
63   for (s = msg; !the_end; msg = ++s)
64     {
65       /* New string struct */
66       cur = calloc(1, sizeof(struct mlstr_line));
67       if (!cur)
68         goto fail;
69
70       if (!ml->lines)
71         ml->lines = cur;
72
73       /* Find the \n or end of string */
74       while (*s != '\n')
75         {
76           if (*s == '\0')
77             {
78               the_end = 1;
79               break;
80             }
81
82           ++s;
83         }
84
85       linelength = s - msg;
86
87       /* Duplicate the string */
88       cur->line = malloc(linelength + 1);
89       if (!cur->line)
90         goto fail;
91
92       strncpy(cur->line, msg, linelength);
93       cur->line[linelength] = '\0';
94
95       if (prev)
96         prev->next_line = cur;
97       prev = cur;
98     }
99
100   return ml;
101
102 fail:
103
104   if (ml)
105     mlstring_free(ml);
106
107   return NULL;
108 }
109
110
111 /*
112  * Frees an mlstring.
113  * This function does not have any unit tests.
114  */
115 void
116 mlstring_free(mlstring *str) {
117   struct mlstr_line *cur, *next;
118
119   for (cur = str->lines; cur; cur = next) {
120     next = cur->next_line;
121     free(cur->line);
122     free(cur);
123   }
124
125   free(str);
126 }
127
128
129 void
130 mlstring_wrap(mlstring *mstring, XFontStruct *font, Dimension width)
131 {
132   short char_width = font->max_bounds.width;
133   int line_length, wrap_at;
134   struct mlstr_line *mstr, *newml;
135
136   /* An alternative implementation of this function would be to keep trying
137    * XTextWidth() on space-delimited substrings until the longest one less
138    * than 'width' is found, however there shouldn't be much difference
139    * between that, and this implementation.
140    */
141
142   for (mstr = mstring->lines; mstr; mstr = mstr->next_line)
143     {
144       if (XTextWidth(font, mstr->line, strlen(mstr->line)) > width)
145         {
146           /* Wrap it */
147           line_length = width / char_width;
148           if (line_length == 0)
149             line_length = 1;
150           
151           /* First try to soft wrap by finding a space */
152           for (wrap_at = line_length; wrap_at >= 0 && !isspace(mstr->line[wrap_at]); --wrap_at);
153           
154           if (wrap_at == -1) /* No space found, hard wrap */
155             wrap_at = line_length;
156           else
157             wrap_at++; /* Leave the space at the end of the line. */
158
159           newml = calloc(1, sizeof(*newml));
160           if (!newml) /* OOM, don't bother trying to wrap */
161             break;
162
163           if (NULL == (newml->line = strdup(mstr->line + wrap_at)))
164             {
165               /* OOM, jump ship */
166               free(newml);
167               break;
168             }
169         
170           /* Terminate the existing string at its end */
171           mstr->line[wrap_at] = '\0';
172
173           newml->next_line = mstr->next_line;
174           mstr->next_line = newml;
175         }
176     }
177
178   mlstring_calculate(mstring, font);
179 }
180
181 #undef MAX
182 #define MAX(x, y) ((x) > (y) ? (x) : (y))
183
184 /*
185  * Calculates the overall extents (width + height of the multi-line string).
186  * This function is called as part of mlstring_new().
187  * It does not have any unit testing.
188  */
189 void
190 mlstring_calculate(mlstring *str, XFontStruct *font) {
191   struct mlstr_line *line;
192
193   str->font_height = font->ascent + font->descent;
194   str->overall_height = 0;
195   str->overall_width = 0;
196
197   /* XXX: Should there be some baseline calculations to help XDrawString later on? */
198   str->font_ascent = font->ascent;
199
200   for (line = str->lines; line; line = line->next_line)
201     {
202       line->line_width = XTextWidth(font, line->line, strlen(line->line));
203       str->overall_width = MAX(str->overall_width, line->line_width);
204       /* Don't add line spacing for the first line */
205       str->overall_height += (font->ascent + font->descent) *
206                              (line == str->lines ? 1 : LINE_SPACING);
207     }
208 }
209
210 void
211 mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y) {
212   struct mlstr_line *line;
213
214   if (!string)
215     return;
216   
217   y += string->font_ascent;
218
219   XSetFont(dpy, gc, string->font_id);
220
221   for (line = string->lines; line; line = line->next_line)
222     {
223       XDrawString(dpy, dialog, gc, x, y, line->line, strlen(line->line));
224       y += string->font_height * LINE_SPACING;
225     }
226 }
227
228 /* vim:ts=8:sw=2:noet
229  */