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#pragma ident	"%Z%%M%	%I%	%E% SMI"
33
34#include <stdlib.h>
35#include <stdio.h>
36#include <string.h>
37#include <ctype.h>
38#include <errno.h>
39
40#include "ioutil.h"
41
42static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n);
43
44/*.......................................................................
45 * Display a left-justified string over multiple terminal lines,
46 * taking account of the specified width of the terminal. Optional
47 * indentation and an option prefix string can be specified to be
48 * displayed at the start of each new terminal line used, and if
49 * needed, a single paragraph can be broken across multiple calls.
50 * Note that literal newlines in the input string can be used to force
51 * a newline at any point, and that in order to allow individual
52 * paragraphs to be written using multiple calls to this function,
53 * unless an explicit newline character is specified at the end of the
54 * string, a newline will not be started at the end of the last word
55 * in the string. Note that when a new line is started between two
56 * words that are separated by spaces, those spaces are not output,
57 * whereas when a new line is started because a newline character was
58 * found in the string, only the spaces before the newline character
59 * are discarded.
60 *
61 * Input:
62 *  write_fn  GlWriteFn *  The callback function to use to write the
63 *                         output.
64 *  data           void *  A pointer to arbitrary data to be passed to
65 *                         write_fn() whenever it is called.
66 *  fp             FILE *  The stdio stream to write to.
67 *  indentation     int    The number of fill characters to use to
68 *                        indent the start of each new terminal line.
69 *  prefix   const char *  An optional prefix string to write after the
70 *                         indentation margin at the start of each new
71 *                         terminal line. You can specify NULL if no
72 *                         prefix is required.
73 *  suffix   const char *  An optional suffix string to draw at the end
74 *                         of the terminal line. The line will be padded
75 *                         where necessary to ensure that the suffix ends
76 *                         in the last column of the terminal line. If
77 *                         no suffix is desired, specify NULL.
78 *  fill_char       int    The padding character to use when indenting
79 *                         and filling up to the suffix.
80 *  term_width      int    The width of the terminal being written to.
81 *  start           int    The number of characters already written to
82 *                         the start of the current terminal line. This
83 *                         is primarily used to allow individual
84 *                         paragraphs to be written over multiple calls
85 *                         to this function, but can also be used to
86 *                         allow you to start the first line of a
87 *                         paragraph with a different prefix or
88 *                         indentation than those specified above.
89 *  string   const char *  The string to be written.
90 * Output:
91 *  return          int    On error -1 is returned. Otherwise the
92 *                         return value is the terminal column index at
93 *                         which the cursor was left after writing the
94 *                         final word in the string. Successful return
95 *                         values can thus be passed verbatim to the
96 *                         'start' arguments of subsequent calls to
97 *                         _io_display_text() to allow the printing of a
98 *                         paragraph to be broken across multiple calls
99 *                         to _io_display_text().
100 */
101int _io_display_text(GlWriteFn *write_fn, void *data, int indentation,
102		     const char *prefix, const char *suffix, int fill_char,
103		     int term_width, int start, const char *string)
104{
105  int ndone;        /* The number of characters written from string[] */
106  int nnew;         /* The number of characters to be displayed next */
107  int was_space;    /* True if the previous character was a space or tab */
108  int last = start; /* The column number of the last character written */
109  int prefix_len;   /* The length of the optional line prefix string */
110  int suffix_len;   /* The length of the optional line prefix string */
111  int margin_width; /* The total number of columns used by the indentation */
112                    /*  margin and the prefix string. */
113  int i;
114/*
115 * Check the arguments?
116 */
117  if(!string || !write_fn) {
118    errno = EINVAL;
119    return -1;
120  };
121/*
122 * Enforce sensible values on the arguments.
123 */
124  if(term_width < 0)
125    term_width = 0;
126  if(indentation > term_width)
127    indentation = term_width;
128  else if(indentation < 0)
129    indentation = 0;
130  if(start > term_width)
131    start = term_width;
132  else if(start < 0)
133    start = 0;
134/*
135 * Get the length of the prefix string.
136 */
137  prefix_len = prefix ? strlen(prefix) : 0;
138/*
139 * Get the length of the suffix string.
140 */
141  suffix_len = suffix ? strlen(suffix) : 0;
142/*
143 * How many characters are devoted to indenting and prefixing each line?
144 */
145  margin_width = indentation + prefix_len;
146/*
147 * Write as many terminal lines as are needed to display the whole string.
148 */
149  for(ndone=0; string[ndone]; start=0) {
150    last = start;
151/*
152 * Write spaces from the current position in the terminal line to the
153 * width of the requested indentation margin.
154 */
155    if(indentation > 0 && last < indentation) {
156      if(_io_pad_line(write_fn, data, fill_char, indentation - last))
157	return -1;
158      last = indentation;
159    };
160/*
161 * If a prefix string has been specified, display it unless we have
162 * passed where it should end in the terminal output line.
163 */
164    if(prefix_len > 0 && last < margin_width) {
165      int pstart = last - indentation;
166      int plen = prefix_len - pstart;
167      if(write_fn(data, prefix+pstart, plen) != plen)
168	return -1;
169      last = margin_width;
170    };
171/*
172 * Locate the end of the last complete word in the string before
173 * (term_width - start) characters have been seen. To handle the case
174 * where a single word is wider than the available space after the
175 * indentation and prefix margins, always make sure that at least one
176 * word is printed after the margin, regardless of whether it won't
177 * fit on the line. The two exceptions to this rule are if an embedded
178 * newline is found in the string or the end of the string is reached
179 * before any word has been seen.
180 */
181    nnew = 0;
182    was_space = 0;
183    for(i=ndone; string[i] && (last+i-ndone < term_width - suffix_len ||
184			   (nnew==0 && last==margin_width)); i++) {
185      if(string[i] == '\n') {
186	if(!was_space)
187	  nnew = i-ndone;
188	break;
189      } else if(isspace((int) string[i])) {
190	if(!was_space) {
191	  nnew = i-ndone+1;
192	  was_space = 1;
193	};
194      } else {
195	was_space = 0;
196      };
197    };
198/*
199 * Does the end of the string delimit the last word that will fit on the
200 * output line?
201 */
202    if(nnew==0 && string[i] == '\0')
203      nnew = i-ndone;
204/*
205 * Write the new line.
206 */
207    if(write_fn(data, string+ndone, nnew) != nnew)
208      return -1;
209    ndone += nnew;
210    last += nnew;
211/*
212 * Start a newline unless we have reached the end of the input string.
213 * In the latter case, in order to give the caller the chance to
214 * concatenate multiple calls to _io_display_text(), omit the newline,
215 * leaving it up to the caller to write this.
216 */
217    if(string[ndone] != '\0') {
218/*
219 * If a suffix has been provided, pad out the end of the line with spaces
220 * such that the suffix will end in the right-most terminal column.
221 */
222      if(suffix_len > 0) {
223	int npad = term_width - suffix_len - last;
224	if(npad > 0 && _io_pad_line(write_fn, data, fill_char, npad))
225	  return -1;
226	last += npad;
227	if(write_fn(data, suffix, suffix_len) != suffix_len)
228	  return -1;
229	last += suffix_len;
230      };
231/*
232 * Start a new line.
233 */
234      if(write_fn(data, "\n",  1) != 1)
235	return -1;
236/*
237 * Skip any spaces and tabs that follow the last word that was written.
238 */
239      while(string[ndone] && isspace((int)string[ndone]) &&
240	    string[ndone] != '\n')
241	ndone++;
242/*
243 * If the terminating character was a literal newline character,
244 * skip it in the input string, since we just wrote it.
245 */
246      if(string[ndone] == '\n')
247	ndone++;
248      last = 0;
249    };
250  };
251/*
252 * Return the column number of the last character printed.
253 */
254  return last;
255}
256
257/*.......................................................................
258 * Write a given number of spaces to the specified stdio output string.
259 *
260 * Input:
261 *  write_fn  GlWriteFn *  The callback function to use to write the
262 *                         output.
263 *  data           void *  A pointer to arbitrary data to be passed to
264 *                         write_fn() whenever it is called.
265 *  c               int    The padding character.
266 *  n               int    The number of spaces to be written.
267 * Output:
268 *  return          int    0 - OK.
269 *                         1 - Error.
270 */
271static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n)
272{
273  enum {FILL_SIZE=20};
274  char fill[FILL_SIZE+1];
275/*
276 * Fill the buffer with the specified padding character.
277 */
278  memset(fill, c, FILL_SIZE);
279  fill[FILL_SIZE] = '\0';
280/*
281 * Write the spaces using the above literal string of spaces as
282 * many times as needed to output the requested number of spaces.
283 */
284  while(n > 0) {
285    int nnew = n <= FILL_SIZE ? n : FILL_SIZE;
286    if(write_fn(data, fill, nnew) != nnew)
287      return 1;
288    n -= nnew;
289  };
290  return 0;
291}
292
293/*.......................................................................
294 * The following is an output callback function which uses fwrite()
295 * to write to the stdio stream specified via its callback data argument.
296 *
297 * Input:
298 *  data     void *  The stdio stream to write to, specified via a
299 *                   (FILE *) pointer cast to (void *).
300 *  s  const char *  The string to be written.
301 *  n         int    The length of the prefix of s[] to attempt to
302 *                   write.
303 * Output:
304 *  return    int    The number of characters written from s[]. This
305 *                   should normally be a number in the range 0 to n.
306 *                   To signal that an I/O error occurred, return -1.
307 */
308GL_WRITE_FN(_io_write_stdio)
309{
310  int ndone;   /* The total number of characters written */
311  int nnew;    /* The number of characters written in the latest write */
312/*
313 * The callback data is the stdio stream to write to.
314 */
315  FILE *fp = (FILE *) data;
316/*
317 * Because of signals we may need to do more than one write to output
318 * the whole string.
319 */
320  for(ndone=0; ndone<n; ndone += nnew) {
321    int nmore = n - ndone;
322    nnew = fwrite(s, sizeof(char), nmore, fp);
323    if(nnew < nmore) {
324      if(errno == EINTR)
325	clearerr(fp);
326      else
327	return ferror(fp) ? -1 : ndone + nnew;
328    };
329  };
330  return ndone;
331}
332
333