Word Wrapping in C

Posted by Ryan Uber | C Programming | Tuesday 22 November 2011 10:40 am

I recently ran into a situation where I needed to wrap text to a certain number of columns to support the standard 80×24 Linux console without making a complete mess of the screen. Since I found no standard way of doing this, I wrote this little function to handle it for me.

There are no dependencies on any external libraries. Pass in your storage, your string, and the number of columns to wrap to, and this will do the rest.

/*
 * This function will wrap large amounts of a text into a manageable and human-readable width
 * word by word. Just specify the number of columns you are working with and feed it a string,
 * and it will return a new string (including added line breaks) to accommodate the area you
 * are working with.
 *
 * {{{ proto( void ) wrap( char out, char str, int columns )
 */
void wrap( char *out, char *str, int columns )
{
    int len, n, w, wordlen=0, linepos=0, outlen=0;

    /*
     * Find length of string 'str' without using string.h
     */
    for( len=0; str[len]; ++len );

    /*
     * Allocate the full space of 'str' to 'word', so there is no possible way that the string
     * could contain a word that does not fit into the 'word' variable.
     */
    char word[len];

    /*
     * Loop through each individual character in the passed array (str) and detect white space
     * and word length to determine how to handle line wrapping.
     */
    for( n=0; n<=len; n++ )
    {
        /*
         * Detect spaces and newlines. We cannot gurantee that the passed string is null-
         * terminated, so we also need to handle cases where we reach the end of the string
         * without encountering wite space characters.
         */
        if( str[n] == ' ' || str[n] == '\n' || n == len )
        {
            /*
             * If the current word will not fit on the current line, add a newline.
             */
            if( linepos > columns )
            {
                out[outlen++] = '\n';
                linepos = wordlen;
            }

            /*
             * Append the found word to the output and reset the word character array in
             * preparation for accepting characters for the next word.
             */
            for( w=0; w<wordlen; w++ )
            {
                out[outlen++] = word[w];
                word[w] = '\0';
            }

            /*
             * If we reach the end of the string, add the null-terminator character.
             */
            if( n == len )
                out[outlen] = '\0';

            /*
             * If we encounter a newline character, append it to the string as usual, but
             * set the line position counter back to 0 (new line = start count from 0 again).
             */
            else if( str[n] == '\n' )
            {
                out[outlen] = str[n];
                linepos=0;
            }

            /*
             * If the word fits in the current line without trouble, just add the space.
             */
            else
            {
                out[outlen] = ' ';
                linepos++;
            }

            /*
             * Increment the final output length for the next loop, and set the word length
             * counter back to 0 (newline or space = new word).
             */
            outlen++;
            wordlen=0;
        }

        /*
         * If the current character is in the middle of a word somewhere, just append it and
         * move on, incrementing counters.
         */
        else
        {
            word[wordlen++] = str[n];
            linepos++;
        }
    }
}
/* }}} */

Examples

Wrap at 50 columns:

$ ./a.out
Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Maecenas pulvinar blandit diam nec mattis.
Sed a ipsum nec ante porttitor feugiat. Morbi
ipsum lacus, dignissim at bibendum in, consectetur
eget ipsum. Donec dolor nibh, scelerisque ac
sodales et, posuere ac nulla. Aliquam eget
tincidunt ante. Vestibulum justo leo, congue ut
luctus ut, venenatis non dui. Cras sapien risus,
blandit at semper eu, cursus eu est. In hac
habitasse platea dictumst. Fusce libero dolor,
commodo nec rhoncus eu, rutrum vel arcu. Vivamus
ultrices faucibus tellus ac feugiat. Ut imperdiet
metus sed erat feugiat sed porta lorem tristique.
Mauris quis erat vel ante cursus pretium.
Curabitur arcu erat, consequat ac ornare et,
venenatis eu diam.

Curabitur posuere dui vitae tortor cursus cursus.
Pellentesque adipiscing lobortis sem at varius.
Mauris pellentesque sollicitudin ultricies.
Maecenas in tellus turpis. Integer convallis
mollis elit eu placerat. Nunc volutpat consectetur
facilisis. Curabitur eros arcu, dapibus ut
convallis non, rhoncus non magna.

Wrap at 20 columns:

$ ./a.out
Lorem ipsum dolor
sit amet,
consectetur
adipiscing elit.
Maecenas pulvinar
blandit diam nec
mattis. Sed a ipsum
nec ante porttitor
feugiat. Morbi ipsum
lacus, dignissim at
bibendum in,
consectetur eget
ipsum. Donec dolor
nibh, scelerisque ac
sodales et, posuere
ac nulla. Aliquam
eget tincidunt ante.
Vestibulum justo
leo, congue ut
luctus ut, venenatis
non dui. Cras sapien
risus, blandit at
semper eu, cursus eu
est. In hac
habitasse platea
dictumst. Fusce
libero dolor,
commodo nec rhoncus
eu, rutrum vel arcu.
Vivamus ultrices
faucibus tellus ac
feugiat. Ut
imperdiet metus sed
erat feugiat sed
porta lorem
tristique. Mauris
quis erat vel ante
cursus pretium.
Curabitur arcu erat,
consequat ac ornare
et, venenatis eu
diam.

Curabitur posuere
dui vitae tortor
cursus cursus.
Pellentesque
adipiscing lobortis
sem at varius.
Mauris pellentesque
sollicitudin
ultricies. Maecenas
in tellus turpis.
Integer convallis
mollis elit eu
placerat. Nunc
volutpat consectetur
facilisis. Curabitur
eros arcu, dapibus
ut convallis non,
rhoncus non magna.

*Update 11/24/2011: This solution is useful if you need to word wrap inside of a C program. If you are shell scripting, you can use the “fold” command, which comes standard with the “coreutils” package in EL-based distributions.

Usage: fold [OPTION]... [FILE]...
Wrap input lines in each FILE (standard input by default), writing to
standard output.

Mandatory arguments to long options are mandatory for short options too.
  -b, --bytes         count bytes rather than columns
  -c, --characters    count characters rather than columns
  -s, --spaces        break at spaces
  -w, --width=WIDTH   use WIDTH columns instead of 80
      --help     display this help and exit
      --version  output version information and exit

Report bugs to <bug-coreutils@gnu.org>.

Configuring console blanking on RHEL5-based distributions

Posted by Ryan Uber | C Programming,Kernel,Linux | Tuesday 8 February 2011 9:35 pm

As the title states, the following patch will give you the ability to configure screen blanking persistently on a RHEL5-compatible kernel, without having to make a bash loop on startup. If you aren’t modifying the RHEL kernel already, then I’d recommend you just do some bash-fu on startup, however, if you already have some custom patches or extensions / modules in your RHEL-derivative kernel, may as well stick this patch into your build repository as well.

Feedback would be greatly appreciated. I’m not really a kernel developer but would love to know if you have any suggestions.

The "consoleblank=" is a very handy little addition to the kernel boot arguments,
especially nowadays with virtual machines growing in number quite rapidly.

There is, generally speaking, no damageable screen attached to most Linux machines
these days. Since using "setterm" does not make customizations persistent, the only
way to disable screen blanking without this patch is by adding some script-fu
to /etc/rc.local or similar start script, with "setterm -blank N", for each TTY
you do not wish to activate a screensaver for.

Rather, let's add this functionality (which is already available in later versions
of the Linux kernel) as a configurable argument to the kernel boot line.

The "consoleblank" kernel argument will take an integer value, which will specify
the blanking timeout in seconds. Set it to "0" to disable screen blanking entirely.

Ryan R. Uber <ryan@blankbmx.com>

--- a/Documentation/kernel-parameters.txt	2011-02-08 23:30:42.758108332 +0000
+++ b/Documentation/kernel-parameters.txt	2011-02-08 23:31:14.309331657 +0000
@@ -425,6 +425,10 @@
 			switching to the matching ttyS device later.  The
 			options are the same as for ttyS, above.

+   consoleblank=   [KNL] The console blank (screen saver) timeout in
+           seconds. Defaults to 10*60 = 10mins. A value of 0
+           disables the blank timer.
+
 	cpcihp_generic=	[HW,PCI] Generic port I/O CompactPCI driver
 			Format:
 			<first_slot>,<last_slot>,<port>,<enum_bit>[,<debug>]
--- a/drivers/char/vt.c	2011-02-08 23:30:39.667988520 +0000
+++ b/drivers/char/vt.c	2011-02-09 00:16:56.715662063 +0000
@@ -171,8 +173,24 @@
 int console_blanked;

 static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */
-static int blankinterval = 10*60*HZ;
 static int vesa_off_interval;
+static unsigned long blankinterval = 10*60;
+
+/*
+ *  Newer kernel releases use core_param here, as seen below:
+ *  core_param(consoleblank, blankinterval, int, 0444);
+ *  The EL5 kernel does not yet have the core_param() function,
+ *  so we backport by using module_param_named() instead, add
+ *  a call to __setup(), and provide a function to set the
+ *  proper value.
+ */
+static int __init consoleblank_config(char *val)
+{
+    blankinterval = simple_strtoul(val,NULL,0);
+    return 0;
+}
+__setup("consoleblank=", consoleblank_config);
+module_param_named(consoleblank, blankinterval, int, 0444);

 static DECLARE_WORK(console_work, console_callback, NULL);

@@ -1374,7 +1392,7 @@
 			update_attr(vc);
 			break;
 		case 9:	/* set blanking interval */
-			blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+			blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60;
 			poke_blanked_console();
 			break;
 		case 10: /* set bell frequency in Hz */
@@ -3374,7 +3392,7 @@
 		return; /* but leave console_blanked != 0 */

 	if (blankinterval) {
-		mod_timer(&console_timer, jiffies + blankinterval);
+		mod_timer(&console_timer, jiffies + (blankinterval * HZ));
 		blank_state = blank_normal_wait;
 	}

@@ -3408,7 +3426,7 @@
 static void blank_screen_t(unsigned long dummy)
 {
 	if (unlikely(!keventd_up())) {
-		mod_timer(&console_timer, jiffies + blankinterval);
+		mod_timer(&console_timer, jiffies + (blankinterval * HZ));
 		return;
 	}
 	blank_timer_expired = 1;
@@ -3438,7 +3456,7 @@
 	if (console_blanked)
 		unblank_screen();
 	else if (blankinterval) {
-		mod_timer(&console_timer, jiffies + blankinterval);
+		mod_timer(&console_timer, jiffies + (blankinterval * HZ));
 		blank_state = blank_normal_wait;
 	}
 }

entab – A utility to convert from white-space to tabbed output

Posted by Ryan Uber | C Programming | Friday 19 November 2010 2:05 pm

The following program was another exercise from “The C Programming Language” by Brian Kerrigan and Dennis Ritchie. This exercise was presented at the end of the first chapter, and no starting point is provided. Utilizing what I had learned from reading and previous exercises, I was able to get through the whole program, and it seems to work perfectly so far.

Here is the description of the exercise, directly from the book:

Write a program entab that replaces strings of blanks by the minimum number of tabs and blanks to achieve the same spacing. Use the same tab stops as for detab*. When either a tab or a single blank would suffice to reach a tab stop, which should be given preference?

Some additional thoughts / potential TODO’s before you read the program:

  • Should the tab-stop length (in spaces) be hard-set? Perhaps this would better be written as a command-line option for usability on programs and files where the programmer has set their editor to use, say, 4 spaces in place of a ‘\t’.
  • Possibly add white space trimming to the end of the line, as in my previous getline.c program. This should likely happen before converting to tabbed output.
  • Should the end-of-line white space trimming be in a function of its own? Perhaps there is already such a function available in string.h.
/*
 *  entab.c - Convert white space into tabbed output, being mindful that
 *            a tab stop is not simply a fixed number of consecutive
 *            white space.
 *
 *  Author: Ryan R. Uber <ryan@blankbmx.com>
 *  Date:   Fri Nov 19 04:39:29 CST 2010
 *
 */

#include <stdio.h>

#define MAXLINE 1000    /*  Maximum input per line */
#define TAB     8       /*  Tab stop interval */
#define TRUE    1       /*  Just symbolic names for readability. */
#define FALSE   0       /*  These could have just as effectively been
                            written as a 1 or 0 in the program */

void copy (char from, char to[]);
int getline (char line[], int lim);

/*  Replace white space with proper tabbing */
int main (void)
{
    int len, i, j, nspaces, stop;
    char line[MAXLINE], output[MAXLINE];

    nspaces = 0;

    while ((len = getline(line, MAXLINE)) > 0)
    {
        for (i = 0; i < len; ++i)
        {
            /*  This defines whether we are at a tab stop position. The
             *  division operation returns a zero if the quotient is an
             *  even number. As an example, try:
             *      ( 8 % 8 )
             *  in a separate C program. This would be the exact operation
             *  run if TAB is set to '8' and you are on the 8th character
             *  of input.
             */
            if ( i != 0 && i % TAB == 0 )
            {
                stop = TRUE;
            }

            else
            {
                stop = FALSE;
            }

            /*  We will not be adding any detected white space to our array
             *  if we are not currently at a tab stop. However, we need to
             *  keep track of the detected white space until to later determine
             *  if we need to replace it with the '\t' character.
             */
            if (line[i] == ' ' && stop == FALSE)
            {
                ++nspaces;
            }

            /*  The following tests that white space was detected all the way
             *  from the last non-white space up to the next tab stop. If this
             *  condition is true, we replace the white space with a tab stop.
             */
            else if (nspaces > 0 && stop == TRUE)
            {
                /*  Copy the '\t' character to the output array, and set our
                 *  space counter back to 0 in preparation for the next test.
                 */
                copy ('\t', output);
                nspaces = 0;

                /*  The current position we are at (i) has not yet had its
                 *  corresponding character added to the output array. We only
                 *  want to copy this character if it is not white space.
                 */
                if ( line[i] != ' ' )
                {
                    copy (line[i], output);
                }

                /*  As before, keep track of the white space if detected for
                 *  accurate white space replacement in the next tab stop.
                 */
                else
                {
                    ++nspaces;
                }
            }

            /*  If we have detected 1 or more white spaces that do not lead us
             *  all the way up until a tab stop, we need to add the actual white
             *  space to our output array so they are not lost.
             */
            else
            {
                /*  Add white space for each ' ' character counted */
                for (j = 0; j < nspaces; ++j)
                {
                    copy (' ', output);
                }

                /*  Set space count to 0 to count any remaining space before we
                 *  encounter the next tab stop. Copy non-white space characters.
                 */
                nspaces = 0;
                copy (line[i], output);
            }
        }
    }

    /*  Display the newly-formatted string and return */
    printf("%s", output);
    return 0;
}

/*  Append provided output to a designated character array */
void copy (char from, char to[])
{
    /*  Set character position to 0 */
    int pos;
    pos = 0;

    /*  Determine where we will append characters in the pre-existing array */
    while ( to[pos] != '\0' )
    {
        ++pos;
    }

    /*  Copy provided character to the last position in the array */
    to[pos] = from;
    to[pos+1] = '\0';
}

/*  Read input from stdin */
int getline (char s[], int lim)
{
    int c, i;

    /*  Read in characters until a newline '\n' is encountered */
    for (i = 0; i < lim-1 && (c=getchar()) != EOF && c != '\n'; ++i)
        s[i] = c;

    /*  If a newline was encountered, add to the array and increment counter */
    if (c == '\n')
    {
        s[i] = c;
        i++;
    }

    /*  Terminate input line, return number of characters found in the line */
    s[i] = '\0';
    return i;
}

/* EOF */
Next Page »