/* * Grid Layout Manager * * Purpose: To format text into columns and rows on the console. * * Author: Ryan Jennings <ryan78j@gmail.com> * * Date: Oct 28, 2006 * * Note: Modified from C++ to C for the mudding community * (email me if you want a C++ version). Please notify me * of any bugs and/or improvements! * * Credit: Got the idea from the Java UI. * * License: Code is public domain, provided you keep this header intact * and it is not used for profit. */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdarg.h> typedef struct grid_layout { char ***grid; // dynamic multi-dimensional array. size_t columnCount; // number of columns size_t columnSize; // character length of a single column size_t lineCount; // number of lines char addBorder; // draw border after block of text? } GridLayout; const size_t bufSize = bufSize; void addTextToColumn(GridLayout *, size_t, const char *, ...) __attribute__((format(printf, 3, 4))); /* Initializes the layout */ void init_output_format(GridLayout *fmt, size_t cols, size_t length) { fmt->columnCount = cols; fmt->columnSize = (length / cols); fmt->lineCount = 0; fmt->addBorder = 0; fmt->grid = NULL; } /* replaces concurrent new lines with a space */ char *remove_new_lines(char *desc) { static char buf[bufSize]; char temp[bufSize]; size_t i, t; if (!desc || !*desc) return ""; t = 0; // first replace all new line characters with control character '\x20'. for (i = 0; i < strlen(desc); ++i) { if (desc[i] == '\n' || desc[i] == '\r') temp[t++] = '\x20'; else temp[t++] = desc[i]; } temp[t] = '\0'; // then replace all '\x20' characters in a row with a single space. for(t = 0, i = 0; i < strlen(temp); ) { if(temp[i] == '\x20') { buf[t++] = ' '; do { i++; } while (temp[i] == '\x20'); } else buf[t++] = temp[i++]; } buf[t] = '\0'; return buf; } /* Find the specified length of a number of printed characters in a string. Returns the actual length, stores the printed length in printLength. */ size_t get_line_len(char *str, size_t max_len, size_t *printLength) { size_t sizeCount, rev; *printLength = 0; if (!str || !*str) return 0; for (sizeCount = 0; sizeCount < strlen(str); sizeCount++) { // skip colour codes if (str[sizeCount] == '{') { sizeCount++; } else if (++(*printLength) >= max_len) break; } // if printed characters are less the the length, return the size. if(*printLength < max_len) return sizeCount; // otherwise be smart where we end the line for (rev = sizeCount; rev > 0; rev--) if (str[rev] == ' ') break; // no spaces? we'll have to split characters if(rev == 0) { rev = sizeCount; } else { *printLength -= sizeCount - rev; } return rev + 1; } /* Add text to the layout. Line and column can be specified. */ void addTextCore(GridLayout *fmt, char *buf, size_t lineSave, size_t columnSave) { size_t index = 0, columnPos = 0; // loop while we have text in buf while(index < strlen(buf)) { char **columnBuf; // are we re-using a line? if(lineSave < fmt->lineCount) { columnBuf = fmt->grid[lineSave]; } else { columnBuf = (char **) calloc(fmt->columnCount, sizeof(char *)); } // try to add all columns for(columnPos = 0; columnPos < fmt->columnCount && index < strlen(buf); columnPos++) { size_t printLength, pos, fill; char tmp[fmt->columnSize], substr[strlen(&buf[index])+1]; // if we are looking for a specific column, skip until its found if(columnSave < fmt->columnCount && columnSave != columnPos) { continue; } // Make the column is empty. if(columnBuf[columnPos]) { continue; } // skip spaces at begining of a new column. while(buf[index] == ' ') index++; // get columnSize position in buf pos = get_line_len(&buf[index], fmt->columnSize-1, &printLength); // no text if(pos == 0) return; // always add a trailing space strcpy(tmp, " "); printLength++; // fill the rest of the column with spaces for(fill = printLength; fill < fmt->columnSize; fill++) strcat(tmp, " "); // make the string strcpy(substr, &buf[index]); strcpy(&substr[pos], tmp); // add it columnBuf[columnPos] = strdup(substr); index += pos; // save the column we added to columnSave = columnPos; } // reassign saved line if(lineSave < fmt->lineCount) { fmt->grid[lineSave++] = columnBuf; } else { fmt->lineCount++; fmt->grid = (char ***) realloc(fmt->grid, fmt->lineCount * sizeof(char ***)); fmt->grid[fmt->lineCount - 1] = columnBuf; } } // add a border if needed. if(fmt->addBorder) { char border[fmt->columnSize+1]; for(columnPos = 0; columnPos < fmt->columnSize-1; columnPos++) border[columnPos] = '-'; border[columnPos] = '\0'; fmt->addBorder = 0; addTextToColumn(fmt, columnSave+1, border); fmt->addBorder = 1; } } /* Adds text to the next available column. */ void addText(GridLayout *fmt, const char *text, ...) { char fmt_buf[bufSize]; va_list args; size_t columnSave = 0, lineSave = 0; va_start(args, text); vsnprintf(fmt_buf, sizeof(fmt_buf), text, args); va_end(args); /* Find the next available column, passing lineCount and columnCount to addTextCore if none are found. */ for(lineSave = 0; lineSave < fmt->lineCount; lineSave++) { for(columnSave = 0; columnSave < fmt->columnCount; columnSave++) { if(!fmt->grid[lineSave][columnSave]) { break; } } // line is incomplete if(columnSave < fmt->columnCount) { break; } } addTextCore(fmt, remove_new_lines(fmt_buf), lineSave, columnSave); } /* Adds text to a specified column. */ void addTextToColumn(GridLayout *fmt, size_t columnSave, const char *text, ...) { char fmt_buf[bufSize]; va_list args; size_t lineSave; if(--columnSave > fmt->columnCount) return; va_start(args, text); vsnprintf(fmt_buf, sizeof(fmt_buf), text, args); va_end(args); /* Find a line with an empty column */ for(lineSave = 0; lineSave < fmt->lineCount; lineSave++) { if(!fmt->grid[lineSave][columnSave]) { break; } } addTextCore(fmt, remove_new_lines(fmt_buf), lineSave, columnSave); } /* Adds text to every column in a row. Useful for column headers. */ void addHeaders(GridLayout *fmt, const char *text, ...) { va_list args; char fmt_buf[bufSize]; size_t count = 0; if(!text || !*text) return; /* finish off any incomplete columns with empty columns so we have a new line. */ if(fmt->grid) { char emptyColumn[fmt->columnSize+1]; int c, l; snprintf(emptyColumn, sizeof(emptyColumn), "%*s", fmt->columnSize, " "); for(l = 0; l < fmt->lineCount; l++) { for(c = 0; c < fmt->columnCount; c++) { // skip columns with text if(fmt->grid[l][c]) continue; // add empty column fmt->grid[l][c] = strdup(emptyColumn); } } } va_start(args, text); vsnprintf(fmt_buf, sizeof(fmt_buf), text, args); va_end(args); // add text to every column while(count++ < fmt->columnCount) { addTextToColumn(fmt, count, fmt_buf); } } /* Prints the grid to stdout. Can easily be modified to print elsewhere. Frees memory stored in the grid as it prints. This function could also be modded to have borders around the text. */ void printText(GridLayout *fmt) { size_t i, j; char emptyColumn[fmt->columnSize+1]; // string of spaces for empty columns snprintf(emptyColumn, sizeof(emptyColumn), "%*s", fmt->columnSize, " "); for(i = 0; i < fmt->lineCount; i++) { for(j = 0; j < fmt->columnCount; j++) { if(fmt->grid[i][j]) { fprintf(stdout, fmt->grid[i][j]); free(fmt->grid[i][j]); } else fprintf(stdout, emptyColumn); } fprintf(stdout, "\n"); free(fmt->grid[i]); } if(fmt->grid) free(fmt->grid); } /* Checks if any text has been added to the grid */ int hasText(GridLayout *fmt) { return (fmt->lineCount && fmt->columnCount); } int main(int argc, char *argv[]) { GridLayout fmt; init_output_format(&fmt, 3, 60); fprintf(stdout, "\nThis is a %d column test with a line length of %d.\n", fmt.columnCount, fmt.columnSize * fmt.columnCount); fmt.addBorder = 1; addHeaders(&fmt, "COLUMN"); addText(&fmt, "TEXT 1: This is the first block of text."); addText(&fmt, "TEXT %d: The second block of text is longer than the column size," " therefore wrapping to the next line in the same column. New text " " added will start on any columns skipped on the next line.", 2); addText(&fmt, "TEXT 3: The third block of text should fill columns left by text two."); addText(&fmt, "TEXT 4: This is the fourth block of text. Cool eh?"); addTextToColumn(&fmt, 3, "TEXT 5: This is fifth block of text. It is set to be in column three no matter what."); addText(&fmt, "AUTHOR: Ryan Jennings <%s>", "ryan78j@gmail.com"); addText(&fmt, "DATE: %s", "Oct 28, 2006"); if(hasText(&fmt)) { fprintf(stdout, "\n"); printText(&fmt); fprintf(stdout, "\n"); } }