diff options
Diffstat (limited to 'libtecla-1.6.1/ioutil.c')
-rw-r--r-- | libtecla-1.6.1/ioutil.c | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/libtecla-1.6.1/ioutil.c b/libtecla-1.6.1/ioutil.c new file mode 100644 index 0000000..daf02c9 --- /dev/null +++ b/libtecla-1.6.1/ioutil.c @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2000, 2001, 2002, 2003, 2004 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 <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#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<n; ndone += nnew) { + int nmore = n - ndone; + nnew = fwrite(s, sizeof(char), nmore, fp); + if(nnew < nmore) { + if(errno == EINTR) + clearerr(fp); + else + return ferror(fp) ? -1 : ndone + nnew; + }; + }; + return ndone; +} + |