/* * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include "ioutil.h" static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n); /*....................................................................... * Display a left-justified string over multiple terminal lines, * taking account of the specified width of the terminal. Optional * indentation and an option prefix string can be specified to be * displayed at the start of each new terminal line used, and if * needed, a single paragraph can be broken across multiple calls. * Note that literal newlines in the input string can be used to force * a newline at any point, and that in order to allow individual * paragraphs to be written using multiple calls to this function, * unless an explicit newline character is specified at the end of the * string, a newline will not be started at the end of the last word * in the string. Note that when a new line is started between two * words that are separated by spaces, those spaces are not output, * whereas when a new line is started because a newline character was * found in the string, only the spaces before the newline character * are discarded. * * Input: * write_fn GlWriteFn * The callback function to use to write the * output. * data void * A pointer to arbitrary data to be passed to * write_fn() whenever it is called. * fp FILE * The stdio stream to write to. * indentation int The number of fill characters to use to * indent the start of each new terminal line. * prefix const char * An optional prefix string to write after the * indentation margin at the start of each new * terminal line. You can specify NULL if no * prefix is required. * suffix const char * An optional suffix string to draw at the end * of the terminal line. The line will be padded * where necessary to ensure that the suffix ends * in the last column of the terminal line. If * no suffix is desired, specify NULL. * fill_char int The padding character to use when indenting * and filling up to the suffix. * term_width int The width of the terminal being written to. * start int The number of characters already written to * the start of the current terminal line. This * is primarily used to allow individual * paragraphs to be written over multiple calls * to this function, but can also be used to * allow you to start the first line of a * paragraph with a different prefix or * indentation than those specified above. * string const char * The string to be written. * Output: * return int On error -1 is returned. Otherwise the * return value is the terminal column index at * which the cursor was left after writing the * final word in the string. Successful return * values can thus be passed verbatim to the * 'start' arguments of subsequent calls to * _io_display_text() to allow the printing of a * paragraph to be broken across multiple calls * to _io_display_text(). */ int _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) { int ndone; /* The number of characters written from string[] */ int nnew; /* The number of characters to be displayed next */ int was_space; /* True if the previous character was a space or tab */ int last = start; /* The column number of the last character written */ int prefix_len; /* The length of the optional line prefix string */ int suffix_len; /* The length of the optional line prefix string */ int margin_width; /* The total number of columns used by the indentation */ /* margin and the prefix string. */ int i; /* * Check the arguments? */ if(!string || !write_fn) { errno = EINVAL; return -1; }; /* * Enforce sensible values on the arguments. */ if(term_width < 0) term_width = 0; if(indentation > term_width) indentation = term_width; else if(indentation < 0) indentation = 0; if(start > term_width) start = term_width; else if(start < 0) start = 0; /* * Get the length of the prefix string. */ prefix_len = prefix ? strlen(prefix) : 0; /* * Get the length of the suffix string. */ suffix_len = suffix ? strlen(suffix) : 0; /* * How many characters are devoted to indenting and prefixing each line? */ margin_width = indentation + prefix_len; /* * Write as many terminal lines as are needed to display the whole string. */ for(ndone=0; string[ndone]; start=0) { last = start; /* * Write spaces from the current position in the terminal line to the * width of the requested indentation margin. */ if(indentation > 0 && last < indentation) { if(_io_pad_line(write_fn, data, fill_char, indentation - last)) return -1; last = indentation; }; /* * If a prefix string has been specified, display it unless we have * passed where it should end in the terminal output line. */ if(prefix_len > 0 && last < margin_width) { int pstart = last - indentation; int plen = prefix_len - pstart; if(write_fn(data, prefix+pstart, plen) != plen) return -1; last = margin_width; }; /* * Locate the end of the last complete word in the string before * (term_width - start) characters have been seen. To handle the case * where a single word is wider than the available space after the * indentation and prefix margins, always make sure that at least one * word is printed after the margin, regardless of whether it won't * fit on the line. The two exceptions to this rule are if an embedded * newline is found in the string or the end of the string is reached * before any word has been seen. */ nnew = 0; was_space = 0; for(i=ndone; string[i] && (last+i-ndone < term_width - suffix_len || (nnew==0 && last==margin_width)); i++) { if(string[i] == '\n') { if(!was_space) nnew = i-ndone; break; } else if(isspace((int) string[i])) { if(!was_space) { nnew = i-ndone+1; was_space = 1; }; } else { was_space = 0; }; }; /* * Does the end of the string delimit the last word that will fit on the * output line? */ if(nnew==0 && string[i] == '\0') nnew = i-ndone; /* * Write the new line. */ if(write_fn(data, string+ndone, nnew) != nnew) return -1; ndone += nnew; last += nnew; /* * Start a newline unless we have reached the end of the input string. * In the latter case, in order to give the caller the chance to * concatenate multiple calls to _io_display_text(), omit the newline, * leaving it up to the caller to write this. */ if(string[ndone] != '\0') { /* * If a suffix has been provided, pad out the end of the line with spaces * such that the suffix will end in the right-most terminal column. */ if(suffix_len > 0) { int npad = term_width - suffix_len - last; if(npad > 0 && _io_pad_line(write_fn, data, fill_char, npad)) return -1; last += npad; if(write_fn(data, suffix, suffix_len) != suffix_len) return -1; last += suffix_len; }; /* * Start a new line. */ if(write_fn(data, "\n", 1) != 1) return -1; /* * Skip any spaces and tabs that follow the last word that was written. */ while(string[ndone] && isspace((int)string[ndone]) && string[ndone] != '\n') ndone++; /* * If the terminating character was a literal newline character, * skip it in the input string, since we just wrote it. */ if(string[ndone] == '\n') ndone++; last = 0; }; }; /* * Return the column number of the last character printed. */ return last; } /*....................................................................... * Write a given number of spaces to the specified stdio output string. * * Input: * write_fn GlWriteFn * The callback function to use to write the * output. * data void * A pointer to arbitrary data to be passed to * write_fn() whenever it is called. * c int The padding character. * n int The number of spaces to be written. * Output: * return int 0 - OK. * 1 - Error. */ static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n) { enum {FILL_SIZE=20}; char fill[FILL_SIZE+1]; /* * Fill the buffer with the specified padding character. */ memset(fill, c, FILL_SIZE); fill[FILL_SIZE] = '\0'; /* * Write the spaces using the above literal string of spaces as * many times as needed to output the requested number of spaces. */ while(n > 0) { int nnew = n <= FILL_SIZE ? n : FILL_SIZE; if(write_fn(data, fill, nnew) != nnew) return 1; n -= nnew; }; return 0; } /*....................................................................... * The following is an output callback function which uses fwrite() * to write to the stdio stream specified via its callback data argument. * * Input: * data void * The stdio stream to write to, specified via a * (FILE *) pointer cast to (void *). * s const char * The string to be written. * n int The length of the prefix of s[] to attempt to * write. * Output: * return int The number of characters written from s[]. This * should normally be a number in the range 0 to n. * To signal that an I/O error occurred, return -1. */ GL_WRITE_FN(_io_write_stdio) { int ndone; /* The total number of characters written */ int nnew; /* The number of characters written in the latest write */ /* * The callback data is the stdio stream to write to. */ FILE *fp = (FILE *) data; /* * Because of signals we may need to do more than one write to output * the whole string. */ for(ndone=0; ndone