http://ftp.nluug.nl/pub/os/Linux/distr/pardusrepo/sources/xscreensaver-5.02.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-2004 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
157           newml = calloc(1, sizeof(*newml));
158           if (!newml) /* OOM, don't bother trying to wrap */
159             break;
160
161           if (NULL == (newml->line = strdup(mstr->line + wrap_at)))
162             {
163               /* OOM, jump ship */
164               free(newml);
165               break;
166             }
167         
168           /* Terminate the existing string at its end */
169           mstr->line[wrap_at] = '\0';
170
171           newml->next_line = mstr->next_line;
172           mstr->next_line = newml;
173         }
174     }
175
176   mlstring_calculate(mstring, font);
177 }
178
179 #undef MAX
180 #define MAX(x, y) ((x) > (y) ? (x) : (y))
181
182 /*
183  * Calculates the overall extents (width + height of the multi-line string).
184  * This function is called as part of mlstring_new().
185  * It does not have any unit testing.
186  */
187 void
188 mlstring_calculate(mlstring *str, XFontStruct *font) {
189   struct mlstr_line *line;
190
191   str->font_height = font->ascent + font->descent;
192   str->overall_height = 0;
193   str->overall_width = 0;
194
195   /* XXX: Should there be some baseline calculations to help XDrawString later on? */
196   str->font_ascent = font->ascent;
197
198   for (line = str->lines; line; line = line->next_line)
199     {
200       line->line_width = XTextWidth(font, line->line, strlen(line->line));
201       str->overall_width = MAX(str->overall_width, line->line_width);
202       /* Don't add line spacing for the first line */
203       str->overall_height += (font->ascent + font->descent) *
204                              (line == str->lines ? 1 : LINE_SPACING);
205     }
206 }
207
208 void
209 mlstring_draw(Display *dpy, Drawable dialog, GC gc, mlstring *string, int x, int y) {
210   struct mlstr_line *line;
211
212   if (!string)
213     return;
214   
215   y += string->font_ascent;
216
217   XSetFont(dpy, gc, string->font_id);
218
219   for (line = string->lines; line; line = line->next_line)
220     {
221       XDrawString(dpy, dialog, gc, x, y, line->line, strlen(line->line));
222       y += string->font_height * LINE_SPACING;
223     }
224 }
225
226 /* vim:ts=8:sw=2:noet
227  */