1 /*
2 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
3 *
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, and/or sell copies of the Software, and to permit persons
11 * to whom the Software is furnished to do so, provided that the above
12 * copyright notice(s) and this permission notice appear in all copies of
13 * the Software and that both the above copyright notice(s) and this
14 * permission notice appear in supporting documentation.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 *
26 * Except as contained in this notice, the name of a copyright holder
27 * shall not be used in advertising or otherwise to promote the sale, use
28 * or other dealings in this Software without prior written authorization
29 * of the copyright holder.
30 */
31
32 #include <stdlib.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <errno.h>
37
38 #include "ioutil.h"
39
40 static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n);
41
42 /*.......................................................................
43 * Display a left-justified string over multiple terminal lines,
44 * taking account of the specified width of the terminal. Optional
45 * indentation and an option prefix string can be specified to be
46 * displayed at the start of each new terminal line used, and if
47 * needed, a single paragraph can be broken across multiple calls.
48 * Note that literal newlines in the input string can be used to force
49 * a newline at any point, and that in order to allow individual
50 * paragraphs to be written using multiple calls to this function,
51 * unless an explicit newline character is specified at the end of the
52 * string, a newline will not be started at the end of the last word
53 * in the string. Note that when a new line is started between two
54 * words that are separated by spaces, those spaces are not output,
55 * whereas when a new line is started because a newline character was
56 * found in the string, only the spaces before the newline character
57 * are discarded.
58 *
59 * Input:
60 * write_fn GlWriteFn * The callback function to use to write the
61 * output.
62 * data void * A pointer to arbitrary data to be passed to
63 * write_fn() whenever it is called.
64 * fp FILE * The stdio stream to write to.
65 * indentation int The number of fill characters to use to
66 * indent the start of each new terminal line.
67 * prefix const char * An optional prefix string to write after the
68 * indentation margin at the start of each new
69 * terminal line. You can specify NULL if no
70 * prefix is required.
71 * suffix const char * An optional suffix string to draw at the end
72 * of the terminal line. The line will be padded
73 * where necessary to ensure that the suffix ends
74 * in the last column of the terminal line. If
75 * no suffix is desired, specify NULL.
76 * fill_char int The padding character to use when indenting
77 * and filling up to the suffix.
78 * term_width int The width of the terminal being written to.
79 * start int The number of characters already written to
80 * the start of the current terminal line. This
81 * is primarily used to allow individual
82 * paragraphs to be written over multiple calls
83 * to this function, but can also be used to
84 * allow you to start the first line of a
85 * paragraph with a different prefix or
86 * indentation than those specified above.
87 * string const char * The string to be written.
88 * Output:
89 * return int On error -1 is returned. Otherwise the
90 * return value is the terminal column index at
91 * which the cursor was left after writing the
92 * final word in the string. Successful return
93 * values can thus be passed verbatim to the
94 * 'start' arguments of subsequent calls to
95 * _io_display_text() to allow the printing of a
96 * paragraph to be broken across multiple calls
97 * to _io_display_text().
98 */
_io_display_text(GlWriteFn * write_fn,void * data,int indentation,const char * prefix,const char * suffix,int fill_char,int term_width,int start,const char * string)99 int _io_display_text(GlWriteFn *write_fn, void *data, int indentation,
100 const char *prefix, const char *suffix, int fill_char,
101 int term_width, int start, const char *string)
102 {
103 int ndone; /* The number of characters written from string[] */
104 int nnew; /* The number of characters to be displayed next */
105 int was_space; /* True if the previous character was a space or tab */
106 int last = start; /* The column number of the last character written */
107 int prefix_len; /* The length of the optional line prefix string */
108 int suffix_len; /* The length of the optional line prefix string */
109 int margin_width; /* The total number of columns used by the indentation */
110 /* margin and the prefix string. */
111 int i;
112 /*
113 * Check the arguments?
114 */
115 if(!string || !write_fn) {
116 errno = EINVAL;
117 return -1;
118 };
119 /*
120 * Enforce sensible values on the arguments.
121 */
122 if(term_width < 0)
123 term_width = 0;
124 if(indentation > term_width)
125 indentation = term_width;
126 else if(indentation < 0)
127 indentation = 0;
128 if(start > term_width)
129 start = term_width;
130 else if(start < 0)
131 start = 0;
132 /*
133 * Get the length of the prefix string.
134 */
135 prefix_len = prefix ? strlen(prefix) : 0;
136 /*
137 * Get the length of the suffix string.
138 */
139 suffix_len = suffix ? strlen(suffix) : 0;
140 /*
141 * How many characters are devoted to indenting and prefixing each line?
142 */
143 margin_width = indentation + prefix_len;
144 /*
145 * Write as many terminal lines as are needed to display the whole string.
146 */
147 for(ndone=0; string[ndone]; start=0) {
148 last = start;
149 /*
150 * Write spaces from the current position in the terminal line to the
151 * width of the requested indentation margin.
152 */
153 if(indentation > 0 && last < indentation) {
154 if(_io_pad_line(write_fn, data, fill_char, indentation - last))
155 return -1;
156 last = indentation;
157 };
158 /*
159 * If a prefix string has been specified, display it unless we have
160 * passed where it should end in the terminal output line.
161 */
162 if(prefix_len > 0 && last < margin_width) {
163 int pstart = last - indentation;
164 int plen = prefix_len - pstart;
165 if(write_fn(data, prefix+pstart, plen) != plen)
166 return -1;
167 last = margin_width;
168 };
169 /*
170 * Locate the end of the last complete word in the string before
171 * (term_width - start) characters have been seen. To handle the case
172 * where a single word is wider than the available space after the
173 * indentation and prefix margins, always make sure that at least one
174 * word is printed after the margin, regardless of whether it won't
175 * fit on the line. The two exceptions to this rule are if an embedded
176 * newline is found in the string or the end of the string is reached
177 * before any word has been seen.
178 */
179 nnew = 0;
180 was_space = 0;
181 for(i=ndone; string[i] && (last+i-ndone < term_width - suffix_len ||
182 (nnew==0 && last==margin_width)); i++) {
183 if(string[i] == '\n') {
184 if(!was_space)
185 nnew = i-ndone;
186 break;
187 } else if(isspace((int) string[i])) {
188 if(!was_space) {
189 nnew = i-ndone+1;
190 was_space = 1;
191 };
192 } else {
193 was_space = 0;
194 };
195 };
196 /*
197 * Does the end of the string delimit the last word that will fit on the
198 * output line?
199 */
200 if(nnew==0 && string[i] == '\0')
201 nnew = i-ndone;
202 /*
203 * Write the new line.
204 */
205 if(write_fn(data, string+ndone, nnew) != nnew)
206 return -1;
207 ndone += nnew;
208 last += nnew;
209 /*
210 * Start a newline unless we have reached the end of the input string.
211 * In the latter case, in order to give the caller the chance to
212 * concatenate multiple calls to _io_display_text(), omit the newline,
213 * leaving it up to the caller to write this.
214 */
215 if(string[ndone] != '\0') {
216 /*
217 * If a suffix has been provided, pad out the end of the line with spaces
218 * such that the suffix will end in the right-most terminal column.
219 */
220 if(suffix_len > 0) {
221 int npad = term_width - suffix_len - last;
222 if(npad > 0 && _io_pad_line(write_fn, data, fill_char, npad))
223 return -1;
224 last += npad;
225 if(write_fn(data, suffix, suffix_len) != suffix_len)
226 return -1;
227 last += suffix_len;
228 };
229 /*
230 * Start a new line.
231 */
232 if(write_fn(data, "\n", 1) != 1)
233 return -1;
234 /*
235 * Skip any spaces and tabs that follow the last word that was written.
236 */
237 while(string[ndone] && isspace((int)string[ndone]) &&
238 string[ndone] != '\n')
239 ndone++;
240 /*
241 * If the terminating character was a literal newline character,
242 * skip it in the input string, since we just wrote it.
243 */
244 if(string[ndone] == '\n')
245 ndone++;
246 last = 0;
247 };
248 };
249 /*
250 * Return the column number of the last character printed.
251 */
252 return last;
253 }
254
255 /*.......................................................................
256 * Write a given number of spaces to the specified stdio output string.
257 *
258 * Input:
259 * write_fn GlWriteFn * The callback function to use to write the
260 * output.
261 * data void * A pointer to arbitrary data to be passed to
262 * write_fn() whenever it is called.
263 * c int The padding character.
264 * n int The number of spaces to be written.
265 * Output:
266 * return int 0 - OK.
267 * 1 - Error.
268 */
_io_pad_line(GlWriteFn * write_fn,void * data,int c,int n)269 static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n)
270 {
271 enum {FILL_SIZE=20};
272 char fill[FILL_SIZE+1];
273 /*
274 * Fill the buffer with the specified padding character.
275 */
276 memset(fill, c, FILL_SIZE);
277 fill[FILL_SIZE] = '\0';
278 /*
279 * Write the spaces using the above literal string of spaces as
280 * many times as needed to output the requested number of spaces.
281 */
282 while(n > 0) {
283 int nnew = n <= FILL_SIZE ? n : FILL_SIZE;
284 if(write_fn(data, fill, nnew) != nnew)
285 return 1;
286 n -= nnew;
287 };
288 return 0;
289 }
290
291 /*.......................................................................
292 * The following is an output callback function which uses fwrite()
293 * to write to the stdio stream specified via its callback data argument.
294 *
295 * Input:
296 * data void * The stdio stream to write to, specified via a
297 * (FILE *) pointer cast to (void *).
298 * s const char * The string to be written.
299 * n int The length of the prefix of s[] to attempt to
300 * write.
301 * Output:
302 * return int The number of characters written from s[]. This
303 * should normally be a number in the range 0 to n.
304 * To signal that an I/O error occurred, return -1.
305 */
GL_WRITE_FN(_io_write_stdio)306 GL_WRITE_FN(_io_write_stdio)
307 {
308 int ndone; /* The total number of characters written */
309 int nnew; /* The number of characters written in the latest write */
310 /*
311 * The callback data is the stdio stream to write to.
312 */
313 FILE *fp = (FILE *) data;
314 /*
315 * Because of signals we may need to do more than one write to output
316 * the whole string.
317 */
318 for(ndone=0; ndone<n; ndone += nnew) {
319 int nmore = n - ndone;
320 nnew = fwrite(s, sizeof(char), nmore, fp);
321 if(nnew < nmore) {
322 if(errno == EINTR)
323 clearerr(fp);
324 else
325 return ferror(fp) ? -1 : ndone + nnew;
326 };
327 };
328 return ndone;
329 }
330
331