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