/* 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.
*/

#include <string.h>

#include "pbm.h"
#include "pbmfont.h"

static void fix_control_chars ARGS(( char* buf, struct font* fn ));
static void fill_rect ARGS(( bit** bits, int row0, int col0, int height, int width, bit color ));



static void
get_line_dimensions(const char line[], struct font *font_p, 
                    int * const bwid_p, int * const backup_space_needed_p) {
/*----------------------------------------------------------------------------
   Determine the widith 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 */
    unsigned char lastch;  /* line[cursor] */

    int no_chars_yet; 
        /* logical: we haven't seen any renderable characters yet in 
           the line.
        */
    no_chars_yet = TRUE;   /* initial value */
    *bwid_p = 0;  /* initial value */

    for (cursor = 0; line[cursor] != '\0'; cursor++) {
        lastch = line[cursor];
        if (font_p->glyph[(unsigned char)lastch]) {
            if (no_chars_yet) {
                no_chars_yet = FALSE;
                if (font_p->glyph[lastch]->x < 0) 
                    *backup_space_needed_p = -font_p->glyph[lastch]->x;
                else {
                    *backup_space_needed_p = 0;
                    *bwid_p += font_p->glyph[lastch]->x;
                }
            }
            *bwid_p += font_p->glyph[lastch]->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 -= font_p->glyph[lastch]->xadd;
        *bwid_p += font_p->glyph[lastch]->width + font_p->glyph[lastch]->x;
    }
}



static void
insert_characters(bit ** const bits, const char ** const lp, const int lines,
                  const struct font *font_p, 
                  const int topmargin, const int leftmargin) {
/*----------------------------------------------------------------------------
   Put the lines of text 'lp' (array of 'lines' strings) into the image 'bits'
   using font *font_p.
-----------------------------------------------------------------------------*/
    int line;  /* Line number in input text */

    for ( line = 0; line < lines; ++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 */

        row = topmargin + line * font_p->maxheight;
        leftcol = leftmargin;
    
        for ( cursor = 0; lp[line][cursor] != '\0'; ++cursor ) {
            struct glyph* glyph;   /* the glyph for this character */
            int glyph_y, glyph_x;  /* position within the glyph */

            glyph = font_p->glyph[(unsigned char)lp[line][cursor]];
            if (glyph != NULL) {
                int toprow;  /* row number in image of top row in glyph */
                toprow = row + font_p->maxheight + font_p->y 
                    - glyph->height - glyph->y;

                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;
                    }
                }
                leftcol += glyph->xadd;
            }
        }
    }
}


static void
flow_text(char *lp[], const char input_text[]) {
/* under construction */
}


static void
truncate_text(char *lp[], const char *input_text[]) {
/* under construction */
}



int
main( argc, argv )
    int argc;
    char* argv[];
    {
    bit** bits;
    int argn, rows, cols;
    struct font* fn;
    char* fontname;
    int fitwidth;
        /* value of -width option, or zero if none */
    int dump;
    int vmargin, hmargin;
    char buf[5000];
    char** input_text;
    char** lp;
    int lines, maxlines, line;
    int maxwidth, maxleftb;
    char* usage = "[-font <fontfile>] [-builtin <fontname>] [text]";

    pbm_init( &argc, argv );

    /* Set up default parameters. */
    argn = 1;
    fn = 0;
    fontname = "bdf";
    dump = 0;

    /* Check for flags. */
    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
	{
	if ( pm_keymatch( argv[argn], "-font", 2 ) )
	    {
	    ++argn;
	    if ( argn == argc )
		pm_usage( usage );
	    
	    fn = pbm_loadfont( argv[argn] );
	    }
	else if ( pm_keymatch( argv[argn], "-builtin", 2 ) )
	    {
	    ++argn;
	    if ( argn == argc )
		pm_usage( usage );
	    fontname = argv[argn];
	    }
	else if ( pm_keymatch( argv[argn], "-dump", 2 ) )
	    /* Undocumented dump flag for installing a new built-in font. */
	    dump = 1;
	else
	    pm_usage( usage );
	++argn;
	}

    if (fn == 0)
	fn = pbm_defaultfont( fontname );

    if ( dump )
	{
	pbm_dumpfont( fn );
	exit( 0 );
	}
    
    maxlines = 50;
    input_text = (char**) malloc( maxlines * sizeof(char*) );
    if ( input_text == (char**) 0 )
	pm_error( "out of memory" );

    if ( argn < argc )
	{ /* Get text from the command line. */
	(void) strcpy( buf, argv[argn] );
	++argn;
	while ( argn < argc )
	    {
	    (void) strcat( buf, " " );
	    (void) strcat( buf, argv[argn] );
	    ++argn;
	    }
	fix_control_chars( buf, fn );
	input_text[0] = buf;
	lines = 1;
	}
    else
	{ /* Read text from stdin. */
	lines = 0;
	while ( fgets( buf, sizeof(buf), stdin ) != NULL )
	    {
	    int l;
        
	    fix_control_chars( buf, fn );
	    l = strlen( buf );
	    if ( lines >= maxlines )
		{
		maxlines *= 2;
		input_text = (char**) realloc( (char*) input_text, 
                                       maxlines * sizeof(char*) );
		if ( input_text == (char**) 0 )
		    pm_error( "out of memory" );
		}
	    input_text[lines] = (char*) malloc( l + 1 );
	    if ( input_text[lines] == 0 )
		pm_error( "out of memory" );
	    (void) strcpy( input_text[lines], buf );
	    ++lines;
	    }
	}

    if ( lines == 1 )
	{
	vmargin = fn->maxheight / 2;
	hmargin = fn->maxwidth;
	}
    else
	{
	vmargin = fn->maxheight;
	hmargin = 2 * fn->maxwidth;
	}

    /* The total height is easy to figure out */
    rows = 2 * vmargin + lines * fn->maxheight;

    /* The total width is not so easy */

    fitwidth = 0;  /* For future expansion */

    if (fitwidth > 0) {
        maxwidth = fitwidth;
        if (lines == 1)
            flow_text(lp, input_text[0]);
        else 
            truncate_text(lp, (const char **) input_text);
    } else {
        lp = input_text;
    
        maxwidth = 0;  /* initial value */
        maxleftb = 0;  /* initial value */
        for ( line = 0; line < lines; ++line ) {
            int bwid, backup_space_needed;
            
            get_line_dimensions(lp[line], fn, &bwid, &backup_space_needed);
            
            maxwidth = max(maxwidth, bwid);
            maxleftb = max(maxleftb, backup_space_needed);
        }
    }
    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, (const char **) lp, lines, fn, 
                      vmargin, hmargin + maxleftb);

    /* All done. */
    pbm_writepbm( stdout, bits, cols, rows, 0 );
    pm_close( stdout );

    exit( 0 );
    }



static void
fix_control_chars( buf, fn )
    char* buf;
    struct font* fn;
    {
    int i, j, n, l;

    /* 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. */
	    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] = ' ';
	}
    }

#if __STDC__
static void
fill_rect( bit** bits, int row0, int col0, int height, int width, bit color )
#else /*__STDC__*/
static void
fill_rect( bits, row0, col0, height, width, color )
    bit** bits;
    int row0, col0, height, width;
    bit color;
#endif /*__STDC__*/
    {
    int row, col;

    for ( row = row0; row < row0 + height; ++row )
	for ( col = col0; col < col0 + width; ++col )
	    bits[row][col] = color;
    }

