Variable Tab Width, Part II

Last week’s Lesson discussed the tab character and how it’s used to line up text in a terminal window. This Lesson shows you how such a calculation is made and coded.

Tabs are a topic of confusion mostly when it comes to word processing. The issue there is that you’re really dealing with two separate items: The tab character itself and the tab stop.

The tab character is just that: a character. Its code is 0x09, escape sequence is \t. When that character is encountered, the word processor or terminal window reacts by jumping the cursor in a horizontal direction to the next tab stop position. The tab character doesn’t make that jump; the jump is calculated and either the cursor position is updated or spaces are output.

For C standard output, the tabs and tab stops are calculated by the terminal. If you want to code the tab yourself, say to set your own tab stop position, you have to translate the tab character into the appropriate number of spaces to have it line up with the next tab stop. Therefore it’s necessary for your code to calculate the proper number of spaces.

The code below is a modification of the last Lesson’s final code example, It does the superficially obvious thing, which is to translate a single tab character into 8 consecutive spaces:

#include <stdio.h>

void tabby(void);

int main()
{
    char *text[3] = {
      "\tHello\tHello",
      "\tHi\tHi",
      "\tFelicitations\tFelicitations"
    };
    int x,i;

    for(x=0;x<3;x++)
    {
        i = 0;
        while(*(text[x]+i))
        {
            if( *(text[x]+i) == '\t')
                tabby();
            else
                putchar(*(text[x]+i));
            i++;
            i++;
        }
        putchar('\n');
    }

    return(0);
}

/* calculate and display a tab */
void tabby(void)
{
    int t;

    for(t=0;t<8;t++)
        putchar(' ');
}

A simple for loop in the tabby() function generates 8 spaces each time a tab character is encountered. Here's the output:

        Hello        Hello
        Hi        Hi
        Felicitations        Felicitations

That looks radically different from this output, where the terminal handles the tab stops:

	Hello	Hello
	Hi	Hi
	Felicitations	Felicitations

In the code's output, tab stops are essentially ignored. Instead, each tab simply translated into 8 spaces. That's probably not the best solution.

To best handle the tab character's output you need to code the second half of the tab equation: the tab stop.

As an example, and what is true for most terminals, assume that tab stops are set at every eighth character position. Therefore, to calculate the tab stop, you must track the current horizontal text position. If the code can read the terminal directly, that's easy; just fetch the cursor position. Otherwise, the code has to keep track of the position virtually. Here's my solution:

#include <stdio.h>

int tabby(int);

int main()
{
    char *text[3] = {
      "\tHello\tHello",
      "\tHi\tHi",
      "\tFelicitations\tFelicitations"
    };
    int x,i,column;

    for(x=0;x<3;x++)
    {
        i = column = 0;
        while(*(text[x]+i))
        {
            if( *(text[x]+i) == '\t')
                column += tabby(column);
            else
            {
                putchar(*(text[x]+i));
                column++;
            }
            i++;
        }
        putchar('\n');
    }

    return(0);
}

/* calculate and display a tab */
int tabby(int p)
{
    int t,s;

    s = 8 - (p % 8);    /* number of spaces to jump */
    for(t=0;t<s;t++)
        putchar(' ');

    return(s);
}

To keep track of the text's horizontal position, I added the int variable column (Line 12), and initialized it to zero at Line 16. You might think that you could use variable i, but that's an index into the string displayed. Because of the tab characters, the index won't always accurately reflect the text's column position. That's why another variable had to be created.

The column variable's is updated at Lines 20 and 24. That's how the code keeps track of the text's virtual position across the screen or page.

The tabby() function had to be re-written (and re-prototyped at Line 3) to account for the text's column position. The function accepts the current position as an argument. That value is used to calculate the distance to the next tab stop. After that calculation is made, the function returns the number of spaces jumped, which is how the code keeps track of the virtual column position.

Within the tabby() function, the number of spaces to jump is calculated at Line 39. I hard-coded 8 as the tab stop, although the function could be modified to reset the tab stop to any distance. The modulus operator is used to determine the distance to the next tab stop based on the text's current position. The result, in the range of 0 to 7, is placed in the s variable.

A for loop at Line 40 displays the proper number of spaces. Line 43 then returns that number of spaces, so that the code can continue to properly keep track of the column position.

In the end, the output from this modified code is the same as generated by the terminal. The bonus is that you can adjust the tab stop, setting it to a value such as 15. Here's the output in that case:

               Hello          Hello
               Hi             Hi
               Felicitations  Felicitations

I'll leave it up to you to modify the tabby() function to accept variable tab stop widths. You can edit the tab stop value directly (Line 39), or make it part of the function's arguments (hint), which would be far more flexible.

Leave a Reply