about summary refs log tree commit diff
path: root/generator/pbmtext.c
diff options
context:
space:
mode:
authorgiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
committergiraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8>2006-08-19 03:12:28 +0000
commit1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch)
tree64c8c96cf54d8718847339a403e5e67b922e8c3f /generator/pbmtext.c
downloadnetpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz
netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'generator/pbmtext.c')
-rw-r--r--generator/pbmtext.c732
1 files changed, 732 insertions, 0 deletions
diff --git a/generator/pbmtext.c b/generator/pbmtext.c
new file mode 100644
index 00000000..e1d132d0
--- /dev/null
+++ b/generator/pbmtext.c
@@ -0,0 +1,732 @@
+/* pbmtext.c - render text into a bitmap
+**
+** Copyright (C) 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+*/
+
+#define _BSD_SOURCE 1      /* Make sure strdup() is in string.h */
+#define _XOPEN_SOURCE 500  /* Make sure strdup() is in string.h */
+
+#include <string.h>
+
+#include "pbm.h"
+#include "pbmfont.h"
+#include "shhopt.h"
+#include "mallocvar.h"
+
+struct cmdline_info {
+    /* All the information the user supplied in the command line,
+       in a form easy for the program to use.
+    */
+    const char *text;    /* text from command line or NULL if none */
+    const char *font;    /* -font option value or NULL if none */
+    const char *builtin; /* -builtin option value or NULL if none */
+    unsigned int dump;   
+        /* undocumented dump option for installing a new built-in font */
+    float space;   /* -space option value or default */
+    unsigned int width;     /* -width option value or zero */
+    int lspace;    /* lspace option value or default */
+    unsigned int nomargins;     /* -nomargins */
+};
+
+
+
+
+static void
+parse_command_line(int argc, char ** argv,
+                   struct cmdline_info *cmdline_p) {
+/*----------------------------------------------------------------------------
+   Note that the file spec array we return is stored in the storage that
+   was passed to us as the argv array.
+-----------------------------------------------------------------------------*/
+    optEntry *option_def = malloc(100*sizeof(optEntry));
+        /* Instructions to OptParseOptions3 on how to parse our options.
+         */
+    optStruct3 opt;
+
+    unsigned int option_def_index;
+
+    option_def_index = 0;   /* incremented by OPTENTRY */
+    OPTENT3(0, "font",      OPT_STRING, &cmdline_p->font, NULL,        0);
+    OPTENT3(0, "builtin",   OPT_STRING, &cmdline_p->builtin, NULL,     0);
+    OPTENT3(0, "dump",      OPT_FLAG,   NULL, &cmdline_p->dump,        0);
+    OPTENT3(0, "space",     OPT_FLOAT,  &cmdline_p->space, NULL,       0);
+    OPTENT3(0, "width",     OPT_UINT,   &cmdline_p->width, NULL,       0);
+    OPTENT3(0, "lspace",    OPT_INT,    &cmdline_p->lspace, NULL,      0);
+    OPTENT3(0, "nomargins", OPT_FLAG,   NULL, &cmdline_p->nomargins,   0);
+
+    /* Set the defaults */
+    cmdline_p->font = NULL;
+    cmdline_p->builtin = NULL;
+    cmdline_p->space = 0.0;
+    cmdline_p->width = 0;
+    cmdline_p->lspace = 0;
+
+    opt.opt_table = option_def;
+    opt.short_allowed = FALSE;  /* We have no short (old-fashioned) options */
+    opt.allowNegNum = FALSE;  /* We have no parms that are negative numbers */
+
+    optParseOptions3(&argc, argv, opt, sizeof(opt), 0);
+        /* Uses and sets argc, argv, and some of *cmdline_p and others. */
+
+    if (argc-1 == 0)
+        cmdline_p->text = NULL;
+    else {
+        char *text;
+        int i;
+        int totaltextsize;
+
+        totaltextsize = 1;  /* initial value */
+        
+        text = malloc(totaltextsize);  /* initial allocation */
+        text[0] = '\0';
+        
+        for (i = 1; i < argc; i++) {
+            if (i > 1) {
+                totaltextsize += 1;
+                text = realloc(text, totaltextsize);
+                if (text == NULL)
+                    pm_error("out of memory allocating space for input text");
+                strcat(text, " ");
+            } 
+            totaltextsize += strlen(argv[i]);
+            text = realloc(text, totaltextsize);
+            if (text == NULL)
+                pm_error("out of memory allocating space for input text");
+            strcat(text, argv[i]);
+        }
+        cmdline_p->text = text;
+    }
+}
+
+
+struct text {
+    char **      textArray;  /* malloc'ed */
+    unsigned int allocatedLineCount;
+    unsigned int lineCount;
+};
+
+
+static void
+allocTextArray(struct text * const textP,
+               unsigned int  const maxLineCount,
+               unsigned int  const maxColumnCount) {
+
+    unsigned int line;
+
+    textP->allocatedLineCount = maxLineCount;
+
+    MALLOCARRAY_NOFAIL(textP->textArray, maxLineCount);
+    
+    for (line = 0; line < maxLineCount; ++line)
+        MALLOCARRAY_NOFAIL(textP->textArray[line], maxColumnCount+1);
+
+    textP->lineCount = 0;
+}
+
+
+static void
+freeTextArray(struct text const text) {
+
+    unsigned int line;
+
+    for (line = 0; line < text.allocatedLineCount; ++line)
+        free((char **)text.textArray[line]);
+
+    free(text.textArray);
+}
+
+
+
+static void
+fix_control_chars(char * const buf, struct font * const fn) {
+
+    int i;
+
+    /* chop off terminating newline */
+    if (strlen(buf) >= 1 && buf[strlen(buf)-1] == '\n')
+        buf[strlen(buf)-1] = '\0';
+    
+    for ( i = 0; buf[i] != '\0'; ++i ) {
+        if ( buf[i] == '\t' ) { 
+            /* Turn tabs into the right number of spaces. */
+            int j, n, l;
+            n = ( i + 8 ) / 8 * 8;
+            l = strlen( buf );
+            for ( j = l; j > i; --j )
+                buf[j + n - i - 1] = buf[j];
+            for ( ; i < n; ++i )
+                buf[i] = ' ';
+            --i;
+        }
+        else if ( !fn->glyph[(unsigned char)buf[i]] )
+            /* Turn unknown chars into a single space. */
+            buf[i] = ' ';
+    }
+}
+
+
+
+static void
+fill_rect(bit** const bits, 
+          int   const row0, 
+          int   const col0, 
+          int   const height, 
+          int   const width, 
+          bit   const color) {
+
+    int row;
+
+    for (row = row0; row < row0 + height; ++row) {
+        int col;
+        for (col = col0; col < col0 + width; ++col)
+            bits[row][col] = color;
+    }
+}
+
+
+
+static void
+get_line_dimensions(const char line[], const struct font * const font_p, 
+                    const float intercharacter_space,
+                    int * const bwid_p, int * const backup_space_needed_p) {
+/*----------------------------------------------------------------------------
+   Determine the width in pixels of the line of text line[] in the font
+   *font_p, and return it as *bwid_p.  Also determine how much of this
+   width goes to the left of the nominal starting point of the line because
+   the first character in the line has a "backup" distance.  Return that
+   as *backup_space_needed_p.
+-----------------------------------------------------------------------------*/
+    int cursor;  /* cursor into the line of text */
+    float accumulated_ics;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+    int no_chars_yet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+    struct glyph * lastGlyphP;
+        /* Glyph of last character processed so far.  Undefined if
+           'no_chars_yet'.
+        */
+
+    no_chars_yet = TRUE;   /* initial value */
+    *bwid_p = 0;  /* initial value */
+    accumulated_ics = 0.0;  /* initial value */
+
+    for (cursor = 0; line[cursor] != '\0'; cursor++) {
+        struct glyph * const glyphP = 
+            font_p->glyph[(unsigned char)line[cursor]];
+
+        if (glyphP) {
+            if (no_chars_yet) {
+                no_chars_yet = FALSE;
+                if (glyphP->x < 0) 
+                    *backup_space_needed_p = -glyphP->x;
+                else {
+                    *backup_space_needed_p = 0;
+                    *bwid_p += glyphP->x;
+                }
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                int full_pixels;  /* integer part of accumulated_ics */
+                accumulated_ics += intercharacter_space;
+                full_pixels = (int) accumulated_ics;
+                if (full_pixels > 0) {
+                    *bwid_p += full_pixels;
+                    accumulated_ics -= full_pixels;
+                }
+                lastGlyphP = glyphP;
+            }
+            *bwid_p += glyphP->xadd;
+        }
+    }
+    if (no_chars_yet)
+        /* Line has no renderable characters */
+        *backup_space_needed_p = 0;
+    else {
+        /* Line has at least one renderable character.
+           Recalculate width of last character in line so it ends
+           right at the right edge of the glyph (no extra space to
+           anticipate another character).
+        */
+        *bwid_p -= lastGlyphP->xadd;
+        *bwid_p += lastGlyphP->width + lastGlyphP->x;
+    }
+}
+
+
+
+static void
+insert_character(const struct glyph * const glyph, 
+                 int                  const toprow, 
+                 int                  const leftcol,
+                 bit **               const bits) {
+/*----------------------------------------------------------------------------
+   Insert one character (whose glyph is 'glyph') into the image bits[].
+   Its top left corner shall be row 'toprow', column 'leftcol'.
+-----------------------------------------------------------------------------*/
+
+    int glyph_y, glyph_x;  /* position within the glyph */
+
+    for (glyph_y = 0; glyph_y < glyph->height; glyph_y++) {
+        for (glyph_x = 0; glyph_x < glyph->width; glyph_x++) {
+            if (glyph->bmap[glyph_y * glyph->width + glyph_x])
+                bits[toprow+glyph_y][leftcol+glyph->x+glyph_x] = 
+                    PBM_BLACK;
+        }
+    }
+}    
+
+
+
+static void
+insert_characters(bit **        const bits, 
+                  struct text   const lp,
+                  struct font * const fontP, 
+                  int           const topmargin, 
+                  int           const leftmargin,
+                  float         const intercharacter_space,
+                  int           const lspace) {
+/*----------------------------------------------------------------------------
+   Render the text 'lp' into the image 'bits' using font *fontP and
+   putting 'intercharacter_space' pixels between characters and
+   'lspace' pixels between the lines.
+-----------------------------------------------------------------------------*/
+    int line;  /* Line number in input text */
+
+    for (line = 0; line < lp.lineCount; ++line) {
+        int row;  /* row in image of top of current typeline */
+        int leftcol;  /* Column in image of left edge of current glyph */
+        int cursor;  /* cursor into a line of input text */
+        float accumulated_ics;
+            /* accumulated intercharacter space so far in the line we
+               are building.  Because the intercharacter space might
+               not be an integer, we accumulate it here and realize
+               full pixels whenever we have more than one pixel. 
+            */
+
+        row = topmargin + line * (fontP->maxheight + lspace);
+        leftcol = leftmargin;
+        accumulated_ics = 0.0;  /* initial value */
+    
+        for (cursor = 0; lp.textArray[line][cursor] != '\0'; ++cursor) {
+            unsigned int const glyphIndex = 
+                (unsigned char)lp.textArray[line][cursor];
+            struct glyph* glyph;   /* the glyph for this character */
+
+            glyph = fontP->glyph[glyphIndex];
+            if (glyph != NULL) {
+                const int toprow = row + fontP->maxheight + fontP->y 
+                    - glyph->height - glyph->y;
+                    /* row number in image of top row in glyph */
+                
+                insert_character(glyph, toprow, leftcol, bits);
+
+                leftcol += glyph->xadd;
+                {
+                    /* handle extra intercharacter space (-space option) */
+                    int full_pixels;  /* integer part of accumulated_ics */
+                    accumulated_ics += intercharacter_space;
+                    full_pixels = (int) accumulated_ics;
+                    if (full_pixels > 0) {
+                        leftcol += full_pixels;
+                        accumulated_ics -= full_pixels;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+struct outputTextCursor {
+    struct text text;
+        /* The output text.  The lineCount field of this represents
+           the number of lines we have completed.  The line after that
+           is the one we are currently filling.
+        */
+    unsigned int maxWidth;
+        /* A line of output can't be wider than this many pixels */
+    float intercharacterSpace;
+        /* The amount of extra space, in characters, that should be added
+           between every two characters (Pbmtext -space option)
+        */
+    unsigned int columnNo;
+        /* The column Number (starting at 0) in the current line that we are
+           filling where the next character goes.
+        */
+    bool noCharsYet;
+        /* We haven't put any renderable characters yet in the
+           output line. 
+        */
+    unsigned int widthSoFar;
+        /* The accumulated width, in pixels, of all the characters now
+           in the current output line 
+        */
+    float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we
+           are stepping through.  Because the intercharacter space
+           might not be an integer, we accumulate it here and
+           realize full pixels whenever we have more than one
+           pixel.  
+        */
+};
+
+
+
+static void
+initializeFlowedOutputLine(struct outputTextCursor * const cursorP) {
+
+    cursorP->columnNo = 0;
+    cursorP->noCharsYet = TRUE;
+    cursorP->widthSoFar = 0.0;
+    cursorP->accumulatedIcs = 0.0;
+}
+
+
+
+static void
+initializeFlowedOutput(struct outputTextCursor * const cursorP,
+                       unsigned int              const maxLines,
+                       unsigned int              const maxWidth,
+                       float                     const intercharacterSpace) {
+    
+    allocTextArray(&cursorP->text, maxLines, maxWidth);
+    cursorP->maxWidth = maxWidth;
+    cursorP->intercharacterSpace = intercharacterSpace;
+    initializeFlowedOutputLine(cursorP);
+}
+
+
+
+static void
+finishOutputLine(struct outputTextCursor * const cursorP) {
+
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        char * const currentLine = 
+            cursorP->text.textArray[cursorP->text.lineCount];
+        currentLine[cursorP->columnNo++] = '\0';
+        ++cursorP->text.lineCount;
+    }
+}
+
+
+
+static void
+placeCharacterInOutput(char                      const lastch,
+                       struct font *             const fontP, 
+                       struct outputTextCursor * const cursorP) {
+/*----------------------------------------------------------------------------
+   Place a character of text in the text array at the position indicated
+   by *cursorP, keeping track of what space this character will occupy
+   when this text array is ultimately rendered using font *fontP.
+
+   Note that while we compute how much space the character will take when
+   rendered, we don't render it.
+-----------------------------------------------------------------------------*/
+    if (cursorP->text.lineCount < cursorP->text.allocatedLineCount) {
+        unsigned int const glyphIndex = (unsigned char)lastch;
+        if (fontP->glyph[glyphIndex]) {
+            if (cursorP->noCharsYet) {
+                cursorP->noCharsYet = FALSE;
+                if (fontP->glyph[glyphIndex]->x > 0) 
+                    cursorP->widthSoFar += fontP->glyph[glyphIndex]->x;
+            } else {
+                /* handle extra intercharacter space (-space option) */
+                unsigned int fullPixels;  /* integer part of accumulatedIcs */
+                cursorP->accumulatedIcs += cursorP->intercharacterSpace;
+                fullPixels = (int) cursorP->accumulatedIcs;
+                if (fullPixels > 0) {
+                    cursorP->widthSoFar += fullPixels;
+                    cursorP->accumulatedIcs -= fullPixels;
+                }
+            }
+            cursorP->widthSoFar += fontP->glyph[glyphIndex]->xadd;
+        }
+        if (cursorP->widthSoFar < cursorP->maxWidth) {
+            char * const currentLine = 
+                cursorP->text.textArray[cursorP->text.lineCount];
+            currentLine[cursorP->columnNo++] = lastch;
+        } else {
+            /* Line is full; finish it off, start the next one, and
+               place the character there.
+            */
+            /* TODO: We really should back up to the previous white space
+               character and move the rest of the line to the next line
+            */
+            finishOutputLine(cursorP);
+            initializeFlowedOutputLine(cursorP);
+            placeCharacterInOutput(lastch, fontP, cursorP);
+        }
+    }
+}
+
+
+
+static void
+flowText(struct text    const inputText,
+         int            const width, 
+         struct font *  const fontP, 
+         float          const intercharacterSpace,
+         struct text *  const outputTextP) {
+    
+    unsigned int const maxLineCount = 50;
+
+    unsigned int inputLine;  
+        /* Input line number on which we are currently working */
+    struct outputTextCursor outputCursor;
+
+    for (inputLine = 0; inputLine < inputText.lineCount; ++inputLine) {
+        unsigned int incursor;   /* cursor into the line we are reading */
+
+        initializeFlowedOutput(&outputCursor, maxLineCount,
+                               width, intercharacterSpace);
+        
+        for (incursor = 0; 
+             inputText.textArray[inputLine][incursor] != '\0'; 
+             ++incursor)
+            placeCharacterInOutput(inputText.textArray[inputLine][incursor],
+                                   fontP, &outputCursor);
+        finishOutputLine(&outputCursor);
+    }
+    *outputTextP = outputCursor.text;
+}
+
+
+
+static void
+truncateText(struct text   const inputText, 
+             unsigned int  const width, 
+             struct font * const fontP, 
+             float         const intercharacterSpace,
+             struct text * const outputTextP) {
+
+    struct text truncatedText;
+    int line;  /* Line number on which we are currently working */
+
+    allocTextArray(&truncatedText, inputText.lineCount, width);
+
+    for (line = 0; line < inputText.lineCount; ++line){
+        int cursor;  /* cursor into the line of text */
+        unsigned char lastch;  /* line[cursor] */
+        int widthSoFar;
+            /* How long the line we've built, in pixels, is so far */
+        float accumulatedIcs;
+        /* accumulated intercharacter space so far in the line we are 
+           stepping through.  Because the intercharacter space might not be
+           an integer, we accumulate it here and realize full pixels whenever
+           we have more than one pixel.
+           */
+
+        int noCharsYet; 
+        /* logical: we haven't seen any renderable characters yet in 
+           the line.
+        */
+        noCharsYet = TRUE;   /* initial value */
+        widthSoFar = 0;  /* initial value */
+        accumulatedIcs = 0.0;  /* initial value */
+
+        truncatedText.textArray[line][0] = '\0';  /* Start with empty line */
+
+        for (cursor = 0; 
+             inputText.textArray[line][cursor] != '\0' && widthSoFar < width; 
+             cursor++) {
+            lastch = inputText.textArray[line][cursor];
+            if (fontP->glyph[(unsigned char)lastch]) {
+                if (noCharsYet) {
+                    noCharsYet = FALSE;
+                    if (fontP->glyph[lastch]->x > 0) 
+                        widthSoFar += fontP->glyph[lastch]->x;
+                } else {
+                    /* handle extra intercharacter space (-space option) */
+                    int fullPixels;  /* integer part of accumulatedIcs */
+                    accumulatedIcs += intercharacterSpace;
+                    fullPixels = (int) intercharacterSpace;
+                    if (fullPixels > 0) {
+                        widthSoFar += fullPixels;
+                        accumulatedIcs -= fullPixels;
+                    }
+                }
+                widthSoFar += fontP->glyph[lastch]->xadd;
+            }
+            if (widthSoFar < width) {
+                truncatedText.textArray[line][cursor] = 
+                    inputText.textArray[line][cursor];
+                truncatedText.textArray[line][cursor+1] = '\0';
+            }
+        }
+    }
+    truncatedText.lineCount = inputText.lineCount;
+    *outputTextP = truncatedText;
+}
+
+
+
+static void
+getText(const char          cmdline_text[], 
+        struct font * const fn, 
+        struct text * const input_textP) {
+
+    struct text input_text;
+
+    if (cmdline_text) {
+        allocTextArray(&input_text, 1, strlen(cmdline_text));
+        strcpy(input_text.textArray[0], cmdline_text);
+        fix_control_chars(input_text.textArray[0], fn);
+        input_text.lineCount = 1;
+    } else {
+        /* Read text from stdin. */
+
+        unsigned int maxlines;  
+            /* Maximum number of lines for which we presently have space
+               in the text array 
+            */
+        char buf[5000];
+        char ** text_array;
+        unsigned int lineCount;
+
+        maxlines = 50;  /* initial value */
+        MALLOCARRAY_NOFAIL(text_array, maxlines);
+        
+        lineCount = 0;  /* initial value */
+        while (fgets(buf, sizeof(buf), stdin) != NULL) {
+            fix_control_chars(buf, fn);
+            if (lineCount >= maxlines) {
+                maxlines *= 2;
+                text_array = (char**) realloc((char*) text_array, 
+                                              maxlines * sizeof(char*));
+                if (text_array == NULL)
+                    pm_error("out of memory");
+            }
+            text_array[lineCount] = strdup(buf);
+            if (text_array[lineCount] == NULL)
+                pm_error("out of memory");
+            ++lineCount;
+        }
+        input_text.textArray = text_array;
+        input_text.lineCount = lineCount;
+        input_text.allocatedLineCount = lineCount;
+    }
+    *input_textP = input_text;
+}
+
+
+
+static void
+compute_image_width(struct text         const lp, 
+                    const struct font * const fn,
+                    float               const intercharacter_space,
+                    int *               const maxwidthP, 
+                    int *               const maxleftbP) {
+    int line;
+    
+    *maxwidthP = 0;  /* initial value */
+    *maxleftbP = 0;  /* initial value */
+    for (line = 0; line < lp.lineCount; ++line) {
+        int bwid, backup_space_needed;
+        
+        get_line_dimensions(lp.textArray[line], fn, intercharacter_space,
+                            &bwid, &backup_space_needed);
+        
+        *maxwidthP = MAX(*maxwidthP, bwid);
+        *maxleftbP = MAX(*maxleftbP, backup_space_needed);
+    }
+}
+
+
+
+int
+main(int argc, char *argv[]) {
+
+    struct cmdline_info cmdline;
+    bit** bits;
+    int rows, cols;
+    struct font* fontP;
+    int vmargin, hmargin;
+    struct text inputText;
+    struct text formattedText;
+    int maxwidth;
+        /* width in pixels of the longest line of text in the image */
+    int maxleftb;
+
+    pbm_init( &argc, argv );
+
+    parse_command_line(argc, argv, &cmdline);
+    
+    if (cmdline.font)
+        fontP = pbm_loadfont(cmdline.font);
+    else {
+        if (cmdline.builtin)
+            fontP = pbm_defaultfont(cmdline.builtin);
+        else
+            fontP = pbm_defaultfont("bdf");
+    }
+
+    if (cmdline.dump) {
+        pbm_dumpfont(fontP);
+        exit(0);
+    }
+
+    getText(cmdline.text, fontP, &inputText);
+    
+    if (cmdline.nomargins) {
+        vmargin = 0;
+        hmargin = 0;
+    } else {
+        if (inputText.lineCount == 1) {
+            vmargin = fontP->maxheight / 2;
+            hmargin = fontP->maxwidth;
+        } else {
+            vmargin = fontP->maxheight;
+            hmargin = 2 * fontP->maxwidth;
+        }
+    }
+
+    if (cmdline.width > 0) {
+        /* Flow or truncate lines to meet user's width request */
+        if (inputText.lineCount == 1) 
+            flowText(inputText, cmdline.width, fontP, cmdline.space,
+                     &formattedText);
+        else
+            truncateText(inputText, cmdline.width, fontP, cmdline.space,
+                         &formattedText);
+        freeTextArray(inputText);
+    } else
+        formattedText = inputText;
+    
+    rows = 2 * vmargin + 
+        formattedText.lineCount * fontP->maxheight + 
+        (formattedText.lineCount-1) * cmdline.lspace;
+
+    compute_image_width(formattedText, fontP, cmdline.space,
+                        &maxwidth, &maxleftb);
+
+    cols = 2 * hmargin + maxwidth;
+    bits = pbm_allocarray(cols, rows);
+
+    /* Fill background with white */
+    fill_rect(bits, 0, 0, rows, cols, PBM_WHITE);
+
+    /* Put the text in  */
+    insert_characters(bits, formattedText, fontP, vmargin, hmargin + maxleftb, 
+                      cmdline.space, cmdline.lspace);
+
+    /* All done. */
+    pbm_writepbm(stdout, bits, cols, rows, 0);
+
+    freeTextArray(formattedText);
+    pm_close(stdout);
+
+    return 0;
+}