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 42 static 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 */ 101 int _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 */ 271 static 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 */ 308 GL_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