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 */ 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 */ 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 */ 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