
/*===========================================================================
   GNU UnRTF, a command-line program to convert RTF documents to other formats.
   Copyright (C) 2000,2001,2004 Zachary Thayer Smith

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

   The maintainer is reachable by electronic mail at daved@physiol.usyd.edu.au
===========================================================================*/


/*----------------------------------------------------------------------
 * Module name:    convert
 * Author name:    Zachary Smith
 * Create date:    24 Jul 01
 * Purpose:        Performs conversion from RTF to other formats.
 *----------------------------------------------------------------------
 * Changes:
 * 24 Jul 01, tuorfa@yahoo.com: moved code over from word.c
 * 24 Jul 01, tuorfa@yahoo.com: fixed color table reference numbering.
 * 30 Jul 01, tuorfa@yahoo.com: moved hex convert to util.c
 * 30 Jul 01, tuorfa@yahoo.com: moved special expr tables to special.c
 * 30 Jul 01, tuorfa@yahoo.com: moved attribute stack to attr.c
 * 31 Jul 01, tuorfa@yahoo.com: began addition of hash of rtf commands
 * 01 Aug 01, tuorfa@yahoo.com: finished bulk of rtf command hash
 * 03 Aug 01, tuorfa@yahoo.com: removed no-op hash entries to save space
 * 03 Aug 01, tuorfa@yahoo.com: code to ignore rest of groups for \*, etc
 * 03 Aug 01, tuorfa@yahoo.com: fixed para-alignnot being cleared by \pard
 * 03 Aug 01, tuorfa@yahoo.com: added support for \keywords group
 * 03 Aug 01, tuorfa@yahoo.com: added dummy funcs for header/footer
 * 03 Aug 01, tuorfa@yahoo.com: began addition of hyperlink support
 * 04 Aug 01, tuorfa@yahoo.com: fixed debug string printing
 * 05 Aug 01, tuorfa@yahoo.com: added support for hyperlink data with \field
 * 06 Aug 01, tuorfa@yahoo.com: added support for several font attributes
 * 08 Aug 01, gommer@gmx.net: bugfix for picture storing mechanism
 * 08 Sep 01, tuorfa@yahoo.com: added use of UnRTF
 * 11 Sep 01, tuorfa@yahoo.com: added support for JPEG and PNG pictures
 * 19 Sep 01, tuorfa@yahoo.com: added output personality support
 * 22 Sep 01, tuorfa@yahoo.com: added function-level comment blocks 
 * 23 Sep 01, tuorfa@yahoo.com: fixed translation of \'XX expressions
 * 08 Oct 03, daved@physiol.usyd.edu.au: more special character code
 * 29 Mar 05, daved@physiol.usyd.edu.au: changes requested by ZT Smith
 * 29 Mar 05, daved@physiol.usyd.edu.au: more unicode support
 * 31 Mar 05, daved@physiol.usyd.edu.au: strcat security bug fixed
 * 06 Jan 06, marcossamaral@terra.com.br: patch from debian 0.19.3-1.1
 * 03 Mar 06, daved@physiol.usyd.edu.au: fixed creation date spelling
		and added support for accented characters in titles from
		Laurent Monin
 * 09 Mar 06, daved@physiol.usyd.edu.au: don't print null post_trans
 * 18 Jun 06, daved@physiol.usyd.edu.au: fixed some incorrect comment_end
 * 18 Jun 06, frolovs@internet2.ru: codepage support
 * 16 Dec 07, daved@physiol.usyd.edu.au: updated to GPL v3
 * 17 Dec 07, daved@physiol.usyd.edu.au: Italian month name spelling corrections
 *		from David Santinoli
 * 09 Nov 08, arkadiusz.firus@gmail.com: adopt iconv
 * 04 Jan 10, arkadiusz.firus@gmail.com: deal with (faulty) negative unicodes
 * 04 Jan 10, daved@physiol.usyd.edu.au: suppress <font face=Symbol>
 * 07 Oct 11, jf@dockes.org: major improvement to code pages and to unicode
 * 	related code
 * 09 Jun 13, jf@dockes.org: ignore nonshppict flag to avoid useless output
 * 09 Jun 13, peterli@salk.edu, added code to cope with bold/italic
 			requests that are not properly nested.  Change
			is ifdef'd SUPPORT_UNNESTED.  At present only applied
			to cmd_b, but if generally accepted could/should be
			more widely applied
 * 27 Jun 13, daved@physiol.usyd.edu.au: added a few more cmds to
 		SUPPORT_UNNESTED
 *--------------------------------------------------------------------*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_CTYPE_H
#include <ctype.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include <errno.h>

#include "defs.h"
#include "parse.h"
#include "util.h"
#include "malloc.h"
#include "main.h"
#include "error.h"
#include "word.h"
#include "hash.h"
#include "convert.h"
#include "attr.h"


extern int nopict_mode;

/*
#define BINARY_ATTRS
*/

my_iconv_t desc = MY_ICONV_T_CLEAR;

/* Nested tables aren't supported.
 */
static int coming_pars_that_are_tabular = 0;
static int within_table = FALSE;
static int have_printed_row_begin=FALSE;
static int have_printed_cell_begin=FALSE;
static int have_printed_row_end=FALSE;
static int have_printed_cell_end=FALSE;


/* Previously in word_print_core function 
 */
static int total_chars_this_line=0; /* for simulating \tab */


/* Paragraph alignment (kludge)
 */
enum {
	ALIGN_LEFT=0,
	ALIGN_RIGHT,
	ALIGN_CENTER,
	ALIGN_JUSTIFY
};



/* This value is set by attr_push and attr_pop 
 */
int simulate_smallcaps;
int simulate_allcaps;


/* Most pictures must be written to files. */
enum {
	PICT_UNKNOWN=0,
	PICT_WM,
	PICT_MAC,
	PICT_PM,
	PICT_DI,
	PICT_WB,
	PICT_JPEG,
	PICT_PNG,
};
static int within_picture=FALSE;
static int picture_file_number=1;
static char picture_path[255];
static int picture_width;
static int picture_height;
static int picture_bits_per_pixel=1;
static int picture_type=PICT_UNKNOWN;
static int picture_wmetafile_type;
static char *picture_wmetafile_type_str;


static int have_printed_body=FALSE;
static int within_header=TRUE;


static char *hyperlink_base = NULL;


void starting_body();
void starting_text();
void print_with_special_exprs (char *s);

static int banner_printed=FALSE;


/*========================================================================
 * Name:	print_banner
 * Purpose:	Writes program-identifying text to the output stream.
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void
print_banner () {
	if (!banner_printed) {
		if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
	  	printf(" Translation from RTF performed by ");
		printf("UnRTF, version ");
		printf("%s ", PACKAGE_VERSION);
		if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
	}
	banner_printed=TRUE;
}


/*========================================================================
 * Name:	starting_body
 * Purpose:	Switches output stream for writing document contents.
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void
starting_body ()
{
	if (!have_printed_body) {
		if (!inline_mode) {
			if (safe_printf(0, op->header_end)) fprintf(stderr, TOO_MANY_ARGS, "header_end");
			if (safe_printf(0, op->body_begin)) fprintf(stderr, TOO_MANY_ARGS, "body_begin");
		}
		within_header = FALSE;
		have_printed_body = TRUE;
	}
}


/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/


static char *month_strings[12]= {
#ifdef ENGLISH
  "January","February","March","April","May","June","July","August",
  "September","October","November","December"
#endif
#ifdef FRANCAIS
  "Janvier","Fevrier","Mars","Avril","Mai","Juin","Juillet","Aout","Septembre",
  "Octobre","Novembre","Decembre"
#endif
#ifdef ITALIANO
  "Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno",
  "Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"
#endif
#ifdef ESPANOL /* amaral - 0.19.2 */
  "Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto",
  "Septiembre","Octubre","Noviembre","Diciembre"
#endif
#ifdef DEUTCH
  "?","?","?","?","?","?","?","?",
  "?","?","?","?"
#endif
#ifdef PORTUGUES /* amaral - 0.19.2 */
  "Janeiro","Fevereiro","Marco","Abril","Maio","Junho","Julho","Agosto",
  "Setembro","Outubro","Novembro","Dezembro"
#endif
};


/*========================================================================
 * Name:	word_dump_date
 * Purpose:	Extracts date from an RTF input stream, writes it to
 *              output stream.
 * Args:	Word*, buffered RTF stream
 * Returns:	None.
 *=======================================================================*/

void 
word_dump_date (Word *w)
{
	int year=0, month=0, day=0, hour=0, minute=0;
	CHECK_PARAM_NOT_NULL(w);
	while (w) {
	 	char *s = word_string (w);
		if (*s == '\\') {
			++s;
			if (!strncmp (s, "yr", 2) && isdigit(s[2])) {
				year = atoi (&s[2]);
			}
			else if (!strncmp (s, "mo", 2) && isdigit(s[2])) {
				month= atoi (&s[2]);
			}
			else if (!strncmp (s, "dy", 2) && isdigit(s[2])) {
				day= atoi (&s[2]);
			}
			else if (!strncmp (s, "min", 3) && isdigit(s[3])) {
				minute= atoi (&s[3]);
			}
			else if (!strncmp (s, "hr", 2) && isdigit(s[2])) {
				hour= atoi (&s[2]);
			}
		}
		w=w->next;
	}
	if (year && month && day) {
		printf("%d %s %d ", day, month_strings[month-1], year);
	}
	if (hour && minute) {
		printf("%02d:%02d ", hour, minute);
	}
}



/*-------------------------------------------------------------------*/

typedef struct {
	int num;
	char *name;
        char *encoding;
} FontEntry;

static char *default_encoding = "CP1252";
/* current_encoding not NULL to be safe. "" is detected to avoid
   free() on 1st call */
static char *current_encoding = ""; 
/* Encoding expected by op_translate_buffer. Until we find a way to specify
   transparent UTF-8 passthrough */
static char *output_encoding = "UTF-32BE";
static int default_font_number = 0; // Set by \deffx command
static int had_ansicpg;
#define MAX_FONTS (8192)
static FontEntry font_table[MAX_FONTS];
static int total_fonts=0;

static void flush_iconv_input();
static void accumulate_iconv_input(int ch);

static void 
set_current_encoding(char *encoding)
{
    if (current_encoding && *current_encoding)
        my_free(current_encoding);
    current_encoding = my_strdup(encoding);
}

static void 
maybeopeniconv()
{
    if (!my_iconv_is_valid(desc)) {
        /* This may happen if output begins without a font command */
        char *encoding = attr_get_param(ATTR_ENCODING);
        if (!encoding || !*encoding) 
            encoding = default_encoding;
        desc = my_iconv_open(output_encoding, encoding);
        set_current_encoding(encoding);
    }
}


/*========================================================================
 * Name:	lookup_font
 * Purpose:	Fetches the font entry from the already-read font table.
 * Args:	Font#.
 * Returns:	Font name.
 *=======================================================================*/
FontEntry *
lookup_font (int num) 
{
    int i;
    if (total_fonts) 
	for(i=0;i<total_fonts;i++) {
            if (font_table[i].num==num) 
                return &font_table[i];
	}
    return NULL;
}
char* 
lookup_fontname (int num) {
    FontEntry *e = lookup_font(num);
    if (e == NULL)
        return NULL;
    return e->name;
}

// Extract name and parameter from command. RTF spec says name is max
// 32 chars (inc. param I think), let's be conservative:
#define MAX_CONTROL_LEN 50
// Returns 0 for error, 1 if ok no parm, 0 else
static int controlToNameParm(const char *s, char *name, int maxlen, int *parm)
{
    const char *cp = s;
    int len = 0;

    if (s == 0)
        return 0;
    while (isalpha(*cp) && len < maxlen) {
        *name++ = *cp++;
        len++;
    }
    if (len == maxlen)
        return 0;
    *name = 0;
    if (!*cp)
        return 1;
    if (!(*cp == '-') && !isdigit(*cp))
        return 0;
    *parm=atoi(cp);
    return 2;
}

// translate \fcharset parameter value to code page. See MS RTF doc
static const int fcharsetparmtocp(int parm)
{
    switch (parm) {
    case 0: return 1252;
    case 1: return 0;
    case 2: return 42; 
    case 77: return 10000;
    case 78: return 10001;
    case 79: return 10003;
    case 80: return 10008;
    case 81: return 10002;
    case 83: return 10005;
    case 84: return 10004;
    case 85: return 10006;
    case 86: return 10081;
    case 87: return 10021;
    case 88: return 10029;
    case 89: return 10007;
    case 128: return 932;
    case 129: return 949;
    case 130: return 1361;
    case 134: return 936;
    case 136: return 950;
    case 161: return 1253;
    case 162: return 1254;
    case 163: return 1258;
    case 177: return 1255;
    case 178: return 1256;
    case 186: return 1257;
    case 204: return 1251;
    case 222: return 874;
    case 238: return 1250;
    case 254: return 437;
    default: return 1252;
    }
}

// Translate code page to encoding name hopefully suitable as iconv input
static char *cptoencoding(parm)
{
    // Note that CP0 is supposed to mean current system default, which does
    // not make any sense as a stored value, we don't handle it.

    // It's quite possible that some of the CPxx values had better be
    // replaced in some case by an equivalent better understood by
    // iconv. Need testing
    switch (parm) {
    case 42: return "SYMBOL";
    case 437: return "CP437"; /* United States IBM */
    case 708: return "CP708"; /* Arabic (ASMO 708) */
    case 709: return "CP709"; /* Arabic (ASMO 449+, BCON V4) */
    case 710: return "CP710"; /* Arabic (transparent Arabic) */
    case 711: return "CP711"; /* Arabic (Nafitha Enhanced) */
    case 720: return "CP720"; /* Arabic (transparent ASMO) */
    case 819: return "CP819"; /* Windows 3.1 (United States and Western Europe) */
    case 850: return "CP850"; /* IBM multilingual */
    case 852: return "CP852"; /* Eastern European */
    case 860: return "CP860"; /* Portuguese */
    case 862: return "CP862"; /* Hebrew */
    case 863: return "CP863"; /* French Canadian */
    case 864: return "CP864"; /* Arabic */
    case 865: return "CP865"; /* Norwegian */
    case 866: return "CP866"; /* Soviet Union */
    case 874: return "CP874"; /* Thai */
    case 932: return "CP932"; /* Japanese */
    case 936: return "CP936"; /* Simplified Chinese */
    case 949: return "CP949"; /* Korean */
    case 950: return "CP950"; /* Traditional Chinese */
    case 1250: return "CP1250"; /* Eastern European */
    case 1251: return "CP1251"; /* Cyrillic */
    case 1252: return "CP1252"; /* Western European */
    case 1253: return "CP1253"; /* Greek */
    case 1254: return "CP1254"; /* Turkish */
    case 1255: return "CP1255"; /* Hebrew */
    case 1256: return "CP1256"; /* Arabic */
    case 1257: return "CP1257"; /* Baltic */
    case 1258: return "CP1258"; /* Vietnamese */
    case 1361: return "CP1361"; /* Johab */
    case 10000: return "MAC"; /* MAC Roman */
    case 10001: return "CP10001"; /* MAC Japan ?? Iconv does not know this*/
    case 10004: return "MACARABIC"; /* MAC Arabic */
    case 10005: return "MACHEBREW"; /* MAC Hebrew */
    case 10006: return "MACGREEK"; /* MAC Greek */
    case 10007: return "MACCYRILLIC"; /* MAC Cyrillic */
    case 10029: return "MACCENTRALEUROPE"; /* MAC Latin2 */
    case 10081: return "MACTURKISH"; /* MAC Turkish */
    case 57002: return "CP57002"; /* Devanagari */
    case 57003: return "CP57003"; /* Bengali */
    case 57004: return "CP57004"; /* Tamil */
    case 57005: return "CP57005"; /* Telugu */
    case 57006: return "CP57006"; /* Assamese */
    case 57007: return "CP57007"; /* Oriya */
    default: return "CP1252";
    }
}

/*========================================================================
 * Name:	process_font_table
 * Purpose:	Processes the font table of an RTF file.
 * Args:	Tree of words.
 * Returns:	None.
 *=======================================================================*/

void
process_font_table (Word *w)
{
    Word *w2;
    CHECK_PARAM_NOT_NULL(w);

    if (safe_printf(0, op->fonttable_begin)) fprintf(stderr, TOO_MANY_ARGS, "fonttable_begin");

    while (w) {
        int num;
        char name[BUFSIZ];
        char *tmp;

        if ((w2 = w->child)) {
            tmp = word_string(w2);
            if (!strncmp("\\f", tmp, 2)) {
                num = atoi(&tmp[2]);
                name[0] = 0;
                int cpgcp = -1;
                int fcharsetcp = -1;
                w2 = w2->next;
                while (w2) {
                    tmp = word_string (w2);
                    if (tmp && tmp[0] != '\\') {
                        if (strlen(tmp) + strlen(name) > BUFSIZ - 1) {
                            fprintf(stderr, "Invalid font table entry\n");
                            name[0] = 0;
                        }
                        else
                            strncat(name,tmp,sizeof(name) - strlen(name) - 1);
                    } else if (tmp) {
                        char nm[MAX_CONTROL_LEN+1];
                        int parm;
                        int ret = 
                            controlToNameParm(tmp+1,nm, MAX_CONTROL_LEN, &parm);
                        if (ret == 2) {
                            if (!strcmp(nm, "fcharset")) {
                                fcharsetcp = fcharsetparmtocp(parm);
                            } else if (!strcmp(nm, "cpg")) {
                                cpgcp = parm;
                            }
                        }
                    }
                    w2 = w2->next;
                }

                /* Chop the gall-derned semicolon. */
                if ((tmp = strchr(name, ';')))
                    *tmp = 0;

                font_table[total_fonts].num=num;
                font_table[total_fonts].name=my_strdup(name);
                // Explicit cpg parameter has priority on fcharset one
                if (cpgcp == -1)
                    cpgcp = fcharsetcp;
                if (cpgcp != -1)
                    font_table[total_fonts].encoding = cptoencoding(cpgcp);
                else {
                    /* If there is "symbol" in the font name, use
                     * symbol encoding, else no local encoding */
                    if (strcasestr(name, "symbol"))
                        font_table[total_fonts].encoding = "SYMBOL";
                    else 
                        font_table[total_fonts].encoding = 0;
                }
		// need to have this outside last block as it must be detected
		// even if cpgcp is set
		if (strcasecmp(name, "symbol") == 0)
			font_table[total_fonts].encoding = "SYMBOL";
                if (safe_printf(0, assemble_string(op->fonttable_fontnr, num))) fprintf(stderr, TOO_MANY_ARGS, "fonttable_fontnr");
                if (safe_printf(1, op->fonttable_fontname, name)) fprintf(stderr, TOO_MANY_ARGS, "fonttable_fontname");
                total_fonts++;
            }
        }
        w=w->next;
    }

    if (safe_printf(0, op->fonttable_end)) fprintf(stderr, TOO_MANY_ARGS, "fonttable_end");

    /*
     * If the default font has an encoding, set it as default
     * encoding. The default font number was set by the \deff command
     * in the header (or not in which case it is o). Don't do it if
     * we had an explicit ansicpg command (no logic in this, just works).
     */
    if (total_fonts > 0 && !had_ansicpg) {
        FontEntry *e = lookup_font(default_font_number);
        if (e && e->encoding && *e->encoding)
            default_encoding = e->encoding;
    }

    if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
    printf("font table contains %d fonts total",total_fonts);
    if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");

    if (debug_mode) {
        int i;

        if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
        printf("font table dump: \n");
        for (i=0; i<total_fonts; i++) {
            printf(" font %d = %s encoding = %s\n", font_table[i].num, 
                   font_table[i].name, font_table[i].encoding);
        }
		
        if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
    }
}


/*========================================================================
 * Name:	process_index_entry
 * Purpose:	Processes an index entry of an RTF file.
 * Args:	Tree of words.
 * Returns:	None.
 *=======================================================================*/

void
process_index_entry (Word *w)
{
	Word *w2;

	CHECK_PARAM_NOT_NULL(w);

	while(w) {
		if ((w2=w->child)) {
			char *str = word_string (w2);

			if (debug_mode && str) {
				if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
				printf("index entry word: %s ", str);			
				if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
			}
		}
		w=w->next;
	}
}


/*========================================================================
 * Name:	process_toc_entry
 * Purpose:	Processes an index entry of an RTF file.
 * Args:	Tree of words, flag to say whether to include a page#.
 * Returns:	None.
 *=======================================================================*/

void
process_toc_entry (Word *w, int include_page_num)
{
	Word *w2;

	CHECK_PARAM_NOT_NULL(w);

	while(w) {
		if ((w2=w->child)) {
			char *str = word_string (w2);

			if (debug_mode && str) {
				
				if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
				printf("toc %s entry word: %s ", 
					include_page_num ? "page#":"no page#",
					str);
				if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
			}
		}
		w=w->next;
	}
}


/*========================================================================
 * Name:	process_info_group
 * Purpose:	Processes the \info group of an RTF file.
 * Args:	Tree of words.
 * Returns:	None.
 *=======================================================================*/

void
process_info_group (Word *w)
{
	Word *child;

        maybeopeniconv();
	/* amaral - 0.19.2 */
	/* CHECK_PARAM_NOT_NULL(w); */
	if (!w) printf("AUTHOR'S COMMENT: \\info command is null!\n"); 

	while(w) {
		child = w->child;
		if (child) {
			Word *w2;
			char *s;

			s = word_string(child);

			if (!inline_mode) {
				if (!strcmp("\\title", s)) {
					
					if (safe_printf(0, op->document_title_begin)) fprintf(stderr, TOO_MANY_ARGS, "document_title_begin");
					w2=child->next;
					while (w2) {
						char *s2 = word_string(w2);
						if (s2[0] != '\\') 
						{
							print_with_special_exprs (s2);
						}
						else
						{
							if (s2[1] == '\'')
							{
								int ch = h2toi (&s2[2]);
                                                                accumulate_iconv_input(ch);
							}
						}

						w2 = w2->next;
					}
                                        flush_iconv_input();
					if (safe_printf(0, op->document_title_end)) fprintf(stderr, TOO_MANY_ARGS, "document_title_end");
				}
				else if (!strcmp("\\keywords", s)) {
					if (safe_printf(0, op->document_keywords_begin)) fprintf(stderr, TOO_MANY_ARGS, "document_keywords_begin");
					w2=child->next;
					while (w2) {
						char *s2 = word_string(w2);
						if (s2[0] != '\\') 
							printf("%s,", s2);
						w2 = w2->next;
					}
					if (safe_printf(0, op->document_keywords_end)) fprintf(stderr, TOO_MANY_ARGS, "document_keywords_end");
				}
				else if (!strcmp("\\author", s)) {
					if (safe_printf(0, op->document_author_begin)) fprintf(stderr, TOO_MANY_ARGS, "document_author_begin");
					w2=child->next;
					while (w2) {
						char *s2 = word_string(w2);
						if (s2[0] != '\\') 
							printf("%s", s2);
						w2 = w2->next;
					}
					if (safe_printf(0, op->document_author_end)) fprintf(stderr, TOO_MANY_ARGS, "document_author_end");
				}
				else if (!strcmp("\\comment", s)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("comments: ");
					w2=child->next;
					while (w2) {
						char *s2 = word_string(w2);
						if (s2[0] != '\\') 
							printf("%s", s2);
						w2 = w2->next;
					}
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strncmp("\\nofpages", s, 9)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("total pages: %s",&s[9]);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strncmp("\\nofwords", s, 9)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("total words: %s",&s[9]);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strncmp("\\nofchars", s, 9) && isdigit(s[9])) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("total chars: %s",&s[9]);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strcmp("\\creatim", s)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("creation date: ");
					if (child->next) word_dump_date (child->next);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strcmp("\\printim", s)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("last printed: ");
					if (child->next) word_dump_date (child->next);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strcmp("\\buptim", s)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("last backup: ");
					if (child->next) word_dump_date (child->next);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
				else if (!strcmp("\\revtim", s)) {
					if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
					printf("revision date: ");
					if (child->next) word_dump_date (child->next);
					if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
				}
			}

			/* Irregardless of whether we're in inline mode,
			 * we want to process the following.
			 */
			if (!strcmp("\\hlinkbase", s)) {
				char *linkstr = NULL;

				if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
				printf("hyperlink base: ");
				if (child->next) {
					Word *nextword = child->next;

					if (nextword) 
						linkstr=word_string (nextword);
				}

				if (linkstr)
					printf("%s", linkstr);
				else
					printf("(none)");
				if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");

				/* Store the pointer, it will remain good. */
				hyperlink_base = linkstr; 
			}
		} 
		w = w->next;
	}
}

/*-------------------------------------------------------------------*/

/* RTF color table colors are RGB */

typedef struct {
	unsigned char r,g,b;
} Color;

#define MAX_COLORS (1024)
static Color color_table[MAX_COLORS];
static int total_colors=0;


/*========================================================================
 * Name:	process_color_table
 * Purpose:	Processes the color table of an RTF file.
 * Args:	Tree of words.
 * Returns:	None.
 *=======================================================================*/

void
process_color_table (Word *w)
{
	int r,g,b;

	CHECK_PARAM_NOT_NULL(w);

	/* Sometimes, RTF color tables begin with a semicolon,
	 * i.e. an empty color entry. This seems to indicate that color 0 
	 * will not be used, so here I set it to black.
	 */
	r=g=b=0;

	while(w) {
		char *s = word_string (w);

		if (!strncmp("\\red",s,4)) {
			r = atoi(&s[4]);
			while(r>255) r>>=8;
		}
		else if (!strncmp("\\green",s,6)) {
			g = atoi(&s[6]);
			while(g>255) g>>=8;
		}
		else if (!strncmp("\\blue",s,5)) {
			b = atoi(&s[5]);
			while(b>255) b>>=8;
		}
		else
		/* If we find the semicolon which denotes the end of
		 * a color entry then store the color, even if we don't
		 * have all of it.
		 */
		if (!strcmp (";", s)) {
			color_table[total_colors].r = r;
			color_table[total_colors].g = g;
			color_table[total_colors++].b = b;
			if (debug_mode) {
				if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
				printf("storing color entry %d: %02x%02x%02x",
					total_colors-1, r,g,b);
				if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
			}
			r=g=b=0;
		}

		w=w->next;
	}

	if (debug_mode) {
		if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
	  	printf("color table had %d entries", total_colors);
		if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
	}
}

/*========================================================================
 * Name:	cmd_cf
 * Purpose:	Executes the \cf command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_cf (Word *w, int align, char has_param, int num) {
	char str[40];

	if (!has_param || num>=total_colors) {
		warning_handler ("font color change attempted is invalid");
	}
	else
	{
		sprintf(str,"#%02x%02x%02x",
			color_table[num].r,
			color_table[num].g,
			color_table[num].b);
		attr_push(ATTR_FOREGROUND,str);
	}
	return FALSE;
}



/*========================================================================
 * Name:	cmd_cb
 * Purpose:	Executes the \cb command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_cb (Word *w, int align, char has_param, int num) {
	char str[40];

	if (!has_param || num>=total_colors) {
		warning_handler ("font color change attempted is invalid");
	}
	else
	{
		sprintf(str,"#%02x%02x%02x",
			color_table[num].r,
			color_table[num].g,
			color_table[num].b);
		attr_push(ATTR_BACKGROUND,str);
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_fs
 * Purpose:	Executes the \fs command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_fs (Word *w, int align, char has_param, int points) {
	char str[20];

	if (!has_param) return FALSE;

	/* Note, fs20 means 10pt */
	points /= 2;

	sprintf(str,"%d",points);
	attr_push(ATTR_FONTSIZE,str);

	return FALSE;
}


/*========================================================================
 * Name:	cmd_field
 * Purpose:	Interprets fields looking for hyperlinks.
 * Comment:	Because hyperlinks are put in \field groups,
 *		we must interpret all \field groups, which is
 *		slow and laborious.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_field (Word *w, int align, char has_param, int num) {
	Word *child;

	CHECK_PARAM_NOT_NULL(w);
        maybeopeniconv();
	while(w) {
		child = w->child;
		if (child) {
			Word *w2;
			char *s;

			s = word_string(child);
#if 1 /* daved experimenting with fldrslt */
			if(!strcmp("\\fldrslt", s))
				return FALSE;
#endif


			if (!strcmp("\\*", s))
			{
			    w2=child->next;
			    while (w2)
			    {
				char *s2 = word_string(w2);
				if (s2 && !strcmp("\\fldinst", s2))
				{
				    Word *w3;
				    char *s;
				    char *s4;
				    Word *w4;
				    w3=w2->next;
				    s = word_string(w3);
				    if (s && !strcmp(s, "SYMBOL") )
				    {
					w4=w3->next;
					while(w4 && !strcmp(word_string(w4), " "))
						w4 = w4->next;
					s4 = word_string(w4);
					if (s4)
					{
					   int char_num;
					   char *string;
					   char_num = atoi(s4);

                                           string = op_translate_char(op, 
                                                     FONT_SYMBOL, char_num);
                                           if (string != NULL)
                                               printf("%s", string);
					    else
						fprintf(stderr, "unrtf: Error in translation SYMBOL character %d\n", char_num);
					}
				    }
				    while (w3 && !w3->child) {
					    w3=w3->next;
				    }
				    if (w3) w3=w3->child;
				    while (w3)
				    {
					    char *s3=word_string(w3);
					    if (s3 && !strcmp("HYPERLINK",s3)) {
						    Word *w4;
						    char *s4;
						    w4=w3->next;
						    while (w4 && !strcmp(" ", word_string(w4)))
							    w4=w4->next;
						    if (w4) {
							    s4=word_string(w4);
							    if (safe_printf(0, op->hyperlink_begin)) fprintf(stderr, TOO_MANY_ARGS, "hyperlink_begin");
							    printf("%s", s4);
							    if (safe_printf(0, op->hyperlink_end)) fprintf(stderr, TOO_MANY_ARGS, "hyperlink_end");
							    return TRUE;
						    }
					    	
					    }
					    w3=w3->next;
				    }
				}
					w2 = w2->next;
			    }
				
			}
		}
		w=w->next;
	}
	return TRUE;
}

/*========================================================================
 * Name:	cmd_f
 * Purpose:	Executes the \f command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_f (Word *w, int align, char has_param, int num) 
{
    char *name;

    /* no param exit early XX */
    if (!has_param) 
        return FALSE;
    FontEntry *e = lookup_font(num);
    name = e ? e->name : NULL;
    if (!e || !name) {
        if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
        printf("invalid font number %d",num);
        if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
    } else {
        if (op->fonttable_begin != NULL)
        {
            // TOBEDONE: WHAT'S THIS ???
            name = my_malloc(12);
            sprintf(name, "%d", num);
        }

        /* we are going to output entities, so should not output font */
        if(strstr(name,"Symbol") == NULL)
            attr_push(ATTR_FONTFACE,name);

        desc = my_iconv_close(desc);
        char *encoding = default_encoding;
        if (e->encoding && *e->encoding) {
            encoding = e->encoding;
            attr_push(ATTR_ENCODING, encoding);
        }
        desc = my_iconv_open(output_encoding, encoding);
        set_current_encoding(encoding);
    }

    return FALSE;
}

/*========================================================================
 * Name:	cmd_deff
 * Purpose:	Executes the \deff command, set default font
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_deff (Word *w, int align, char has_param, int num) 
{
    if (has_param) 
        default_font_number = num;
    return FALSE;
}

/*========================================================================
 * Name:	cmd_highlight
 * Purpose:	Executes the \cf command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_highlight (Word *w, int align, char has_param, int num) 
{
	char str[40];

	if (!has_param || num>=total_colors) {
		warning_handler ("font background color change attempted is invalid");
	}
	else
	{
		sprintf(str,"#%02x%02x%02x",
			color_table[num].r,
			color_table[num].g,
			color_table[num].b);
		attr_push(ATTR_BACKGROUND,str);
	}
	return FALSE;
}



/*========================================================================
 * Name:	cmd_tab
 * Purpose:	Executes the \tab command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_tab (Word *w, int align, char has_param, int param) 
{
	/* Tab presents a genuine problem
	 * since some output formats don't have 
	 * an equivalent. As a kludge fix, I shall 
	 * assume the font is fixed width and that
	 * the tabstops are 8 characters apart.
	 */
	int need= 8-(total_chars_this_line%8);
	total_chars_this_line += need;
	while(need>0) {
		if (safe_printf(0, op->forced_space)) fprintf(stderr, TOO_MANY_ARGS, "forced_space");
		need--;
	}
	printf("\n");
	return FALSE;
}


/*========================================================================
 * Name:	cmd_plain
 * Purpose:	Executes the \plain command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_plain (Word *w, int align, char has_param, int param) {
	attr_pop_all();
	return FALSE;
}


/*========================================================================
 * Name:	cmd_fnil
 * Purpose:	Executes the \fnil command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_fnil (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTNIL_STR);
	return FALSE;
}



/*========================================================================
 * Name:	cmd_froman
 * Purpose:	Executes the \froman command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_froman (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTROMAN_STR);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_fswiss
 * Purpose:	Executes the \fswiss command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_fswiss (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTSWISS_STR);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_fmodern
 * Purpose:	Executes the \fmodern command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_fmodern (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTMODERN_STR);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_fscript
 * Purpose:	Executes the \fscript command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_fscript (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTSCRIPT_STR);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_fdecor
 * Purpose:	Executes the \fdecor command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_fdecor (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTDECOR_STR);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_ftech
 * Purpose:	Executes the \ftech command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_ftech (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_FONTFACE,FONTTECH_STR);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_expand
 * Purpose:	Executes the \expand command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_expand (Word *w, int align, char has_param, int param) {
	char str[10];
	if (has_param) {
		sprintf(str, "%d", param/4);
		if (!param) 
			attr_pop(ATTR_EXPAND);
		else 
			attr_push(ATTR_EXPAND, str);
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_emboss
 * Purpose:	Executes the \embo command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_emboss (Word *w, int align, char has_param, int param) {
	char str[10];
	if (has_param && !param)
#ifdef SUPPORT_UNNESTED
		attr_find_pop(ATTR_EMBOSS);
#else
		attr_pop(ATTR_EMBOSS);
#endif
	else
	{
		sprintf(str, "%d", param);
		attr_push(ATTR_EMBOSS, str);
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_engrave
 * Purpose:	Executes the \impr command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_engrave (Word *w, int align, char has_param, int param) {
	char str[10];
	if (has_param && !param) 
		attr_pop(ATTR_ENGRAVE);
	else
	{
		sprintf(str, "%d", param);
		attr_push(ATTR_ENGRAVE, str);
	}
	return FALSE;
}

/*========================================================================
 * Name:	cmd_caps
 * Purpose:	Executes the \caps command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_caps (Word *w, int align, char has_param, int param) {
	if (has_param && !param)
		attr_pop(ATTR_CAPS);
	else 
		attr_push(ATTR_CAPS,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_scaps
 * Purpose:	Executes the \scaps command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_scaps (Word *w, int align, char has_param, int param) {
	if (has_param && !param)
		attr_pop(ATTR_SMALLCAPS);
	else 
		attr_push(ATTR_SMALLCAPS,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_bullet
 * Purpose:	Executes the \bullet command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_bullet (Word *w, int align, char has_param, int param) {
	if (op->chars.bullet) {
		if (safe_printf(0, op->chars.bullet)) fprintf(stderr, TOO_MANY_ARGS, "chars.bullet");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}

/*========================================================================
 * Name:	cmd_ldblquote
 * Purpose:	Executes the \ldblquote command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_ldblquote (Word *w, int align, char has_param, int param) {
	if (op->chars.left_dbl_quote) {
		if (safe_printf(0, op->chars.left_dbl_quote)) fprintf(stderr, TOO_MANY_ARGS, "chars.left_dbl_quote");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_rdblquote
 * Purpose:	Executes the \rdblquote command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_rdblquote (Word *w, int align, char has_param, int param) {
	if (op->chars.right_dbl_quote) {
		if (safe_printf(0, op->chars.right_dbl_quote)) fprintf(stderr, TOO_MANY_ARGS, "chars.right_dbl_quote");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_lquote
 * Purpose:	Executes the \lquote command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_lquote (Word *w, int align, char has_param, int param) {
	if (op->chars.left_quote) {
		if (safe_printf(0, op->chars.left_quote)) fprintf(stderr, TOO_MANY_ARGS, "chars.left_quote");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_nonbreaking_space
 * Purpose:	Executes the nonbreaking space command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_nonbreaking_space (Word *w, int align, char has_param, int param) {
	if (op->chars.nonbreaking_space) {
		if (safe_printf(0, op->chars.nonbreaking_space)) fprintf(stderr, TOO_MANY_ARGS, "chars.nonbreaking_space");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_nonbreaking_hyphen
 * Purpose:	Executes the nonbreaking hyphen command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_nonbreaking_hyphen (Word *w, int align, char has_param, int param) {
	if (op->chars.nonbreaking_hyphen) {
		if (safe_printf(0, op->chars.nonbreaking_hyphen)) fprintf(stderr, TOO_MANY_ARGS, "chars.nonbreaking_hyphen");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_optional_hyphen
 * Purpose:	Executes the optional hyphen command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_optional_hyphen (Word *w, int align, char has_param, int param) {
	if (op->chars.optional_hyphen) {
		if (safe_printf(0, op->chars.optional_hyphen)) fprintf(stderr, TOO_MANY_ARGS, "chars.optional_hyphen");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_emdash
 * Purpose:	Executes the \emdash command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_emdash (Word *w, int align, char has_param, int param) {
	if (op->chars.emdash) {
		if (safe_printf(0, op->chars.emdash)) fprintf(stderr, TOO_MANY_ARGS, "chars.emdash");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_endash
 * Purpose:	Executes the \endash command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_endash (Word *w, int align, char has_param, int param) {
	if (op->chars.endash) {
		if (safe_printf(0, op->chars.endash)) fprintf(stderr, TOO_MANY_ARGS, "chars.endash");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_rquote
 * Purpose:	Executes the \rquote command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_rquote (Word *w, int align, char has_param, int param) {
	if (op->chars.right_quote) {
		if (safe_printf(0, op->chars.right_quote)) fprintf(stderr, TOO_MANY_ARGS, "chars.right_quote");
		++total_chars_this_line; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_par
 * Purpose:	Executes the \par command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int 
cmd_par (Word *w, int align, char has_param, int param) {
	if (op->line_break) {
		if (safe_printf(0, op->line_break)) fprintf(stderr, TOO_MANY_ARGS, "line_break");
		total_chars_this_line = 0; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_line
 * Purpose:	Executes the \line command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int 
cmd_line (Word *w, int align, char has_param, int param) {
	if (op->line_break) {
		if (safe_printf(0, op->line_break)) fprintf(stderr, TOO_MANY_ARGS, "line_break");
		total_chars_this_line = 0; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_page
 * Purpose:	Executes the \page command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_page (Word *w, int align, char has_param, int param) {
	if (op->page_break) {
		if (safe_printf(0, op->page_break)) fprintf(stderr, TOO_MANY_ARGS, "page_break");
		total_chars_this_line = 0; /* \tab */
	}
	return FALSE;
}


/*========================================================================
 * Name:	cmd_intbl
 * Purpose:	Executes the \intbl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_intbl (Word *w, int align, char has_param, int param) {
	++coming_pars_that_are_tabular;
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulnone
 * Purpose:	Executes the \ulnone command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulnone (Word *w, int align, char has_param, int param) {
	int attr, more=TRUE;

#ifdef BINARY_ATTRS
	attr_remove_underlining()) fprintf(stderr, TOO_MANY_ARGS, );
#else
	do {
		attr = attr_read();
		if (attr==ATTR_UNDERLINE ||
		    attr==ATTR_DOT_UL ||
		    attr==ATTR_DASH_UL ||
		    attr==ATTR_DOT_DASH_UL ||
		    attr==ATTR_2DOT_DASH_UL ||
		    attr==ATTR_WORD_UL ||
		    attr==ATTR_WAVE_UL ||
		    attr==ATTR_THICK_UL ||
		    attr==ATTR_DOUBLE_UL)
		{
		  if (!attr_pop(ATTR_UNDERLINE))
		    ;
		} else
		  more=FALSE;
	} while(more);
#endif
	return FALSE;
}

/*========================================================================
 * Name:	cmd_ul
 * Purpose:	Executes the \ul command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ul (Word *w, int align, char has_param, int param) {
	if (has_param && param == 0) {
		cmd_ulnone(w, align, has_param, param);
	} else {
		attr_push(ATTR_UNDERLINE, NULL);
	}
	return FALSE;
}

/*========================================================================
 * Name:	cmd_uld
 * Purpose:	Executes the \uld command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_uld (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_DOUBLE_UL, NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_uldb
 * Purpose:	Executes the \uldb command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_uldb (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_DOT_UL, NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_uldash
 * Purpose:	Executes the \uldash command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_uldash (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_DASH_UL, NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_uldashd
 * Purpose:	Executes the \cmd_uldashd command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_uldashd (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_DOT_DASH_UL,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_uldashdd
 * Purpose:	Executes the \uldashdd command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_uldashdd (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_2DOT_DASH_UL,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulw
 * Purpose:	Executes the \ulw command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulw (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_WORD_UL,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulth
 * Purpose:	Executes the \ulth command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulth (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_THICK_UL,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulthd
 * Purpose:	Executes the \ulthd command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulthd (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_THICK_UL, NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulthdash
 * Purpose:	Executes the \ulthdash command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulthdash (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_THICK_UL, NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_ulwave
 * Purpose:	Executes the \ulwave command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ulwave (Word *w, int align, char has_param, int param) {
	attr_push(ATTR_WAVE_UL,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_strike
 * Purpose:	Executes the \strike command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_strike (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_STRIKE);
	else
		attr_push(ATTR_STRIKE,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_strikedl
 * Purpose:	Executes the \strikedl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_strikedl (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_DBL_STRIKE);
	else
		attr_push(ATTR_DBL_STRIKE,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_striked
 * Purpose:	Executes the \striked command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_striked (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_DBL_STRIKE);
	else
		attr_push(ATTR_DBL_STRIKE,NULL);
	return FALSE;
}


/*========================================================================
 * Name:	cmd_rtf
 * Purpose:	Executes the \rtf command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_rtf (Word *w, int align, char has_param, int param) {
	return FALSE;
}


/*========================================================================
 * Name:	cmd_up
 * Purpose:	Executes the \up command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_up (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_SUPER);
	else
		attr_push(ATTR_SUPER,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_u
 * Purpose:	Processes a Unicode character
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, always false
 *=======================================================================*/

static int cmd_u (Word *w, int align, char has_param, int param) {
/* TODO: Unicode characters won't be correctly preprocessed if sizeof(int) < 4
 *      and document have unicode character which value is greater than 65536
 */

	short	done=0;
	long unicode_number = (long) param; /* On 16bit architectures int is too small to store unicode characters. - AF */
	char tmp[12]; /* Number of characters that can be in int type (including '\0'). If int size is greater than 4 bytes change this value. - AF */
	char *alias;
#define DEBUG 0
#if DEBUG
	char	*str;
	if (has_param == TRUE)
	{
		fprintf(stderr,"param is %d (x%x) (0%o)\n", param,
			param, param);
	}
	if (w->hash_index)
	{
		str=hash_get_string (w->hash_index);
		fprintf(stderr,"string is %s\n", str);
	}
#endif
	/* 0.20.3 - daved added missing function call for unprocessed chars */
	if ((alias = get_alias(op, param)) != NULL)
	{
		printf("%s", alias);
		done++;
	}
	else
		if(!done && op->unisymbol_print)
		{
			if (unicode_number < 0)
			{
                            /* RTF spec: Unicode values beyond 32767 are represented by negative numbers */
				unicode_number += 65536;
			}
			sprintf(tmp, "%ld", unicode_number);

			if (safe_printf(1, op->unisymbol_print, tmp)) fprintf(stderr, TOO_MANY_ARGS, "unisymbol_print");
			done++;
		}
	
	/*
	** if we know how to represent the unicode character in the
	** output language, we need to skip the next word, otherwise
	** we will output that alternative.
	*/
	if (done)
		return(SKIP_ONE_WORD);
	return(FALSE);
}

/*========================================================================
 * Name:	cmd_dn
 * Purpose:	Executes the \dn command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_dn (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_SUB);
	else
		attr_push(ATTR_SUB,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_nosupersub
 * Purpose:	Executes the \nosupersub command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_nosupersub (Word *w, int align, char has_param, int param) {
	attr_pop(ATTR_SUPER);
	attr_pop(ATTR_SUB);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_super
 * Purpose:	Executes the \super command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_super (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_SUPER);
	else
		attr_push(ATTR_SUPER,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_sub
 * Purpose:	Executes the \sub command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_sub (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_SUB);
	else
		attr_push(ATTR_SUB,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_shad
 * Purpose:	Executes the \shad command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_shad (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_SHADOW);
	else
		attr_push(ATTR_SHADOW,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_b
 * Purpose:	Executes the \b command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
#define SUPPORT_UNNESTED

static int 
cmd_b (Word *w, int align, char has_param, int param) {
	if (has_param && param==0) {
#ifdef SUPPORT_UNNESTED
		attr_find_pop(ATTR_BOLD);
#else
		attr_pop(ATTR_BOLD);
#endif
	}
	else
		attr_push(ATTR_BOLD,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_i
 * Purpose:	Executes the \i command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_i (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
#ifdef SUPPORT_UNNESTED
		attr_find_pop(ATTR_ITALIC);
#else
		attr_pop(ATTR_ITALIC);
#endif
	else
		attr_push(ATTR_ITALIC,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_s
 * Purpose:	Executes the \s command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/
static int cmd_s (Word *w, int align, char has_param, int param) {
	return FALSE;
}

/*========================================================================
 * Name:	cmd_sect
 * Purpose:	Executes the \sect command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_sect (Word *w, int align, char has_param, int param) {
	/* XX kludge */
	if (op->paragraph_begin) {
		if (safe_printf(0, op->paragraph_begin)) fprintf(stderr, TOO_MANY_ARGS, "paragraph_begin");
	}
	return FALSE;
}

/*========================================================================
 * Name:	cmd_shp
 * Purpose:	Executes the \shp command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_shp (Word *w, int align, char has_param, int param) {
	if (op->comment_begin) {
		if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
		printf("Drawn Shape (ignored-not implemented yet)");
		if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
	}

	return FALSE;
}

/*========================================================================
 * Name:	cmd_outl
 * Purpose:	Executes the \outl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_outl (Word *w, int align, char has_param, int param) {
	if (has_param && param==0)
		attr_pop(ATTR_OUTLINE);
	else
		attr_push(ATTR_OUTLINE,NULL);
	return FALSE;
}

/*========================================================================
 * Name:	cmd_ansi
 * Purpose:	Executes the \ansi command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ansi (Word *w, int align, char has_param, int param) {
    default_encoding = "CP1252";
    return FALSE;
}

/*========================================================================
 * Name:	cmd_ansicpg
 * Purpose:	Executes the \ansicpg command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ansicpg (Word *w, int align, char has_param, int param)
{
        default_encoding = cptoencoding(param);
        had_ansicpg = 1;
	return FALSE;
}

/*========================================================================
 * Name:	cmd_pc
 * Purpose:	Executes the \pc command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pc (Word *w, int align, char has_param, int param) 
{
    default_encoding = "CP437";
    return FALSE;
}

/*========================================================================
 * Name:	cmd_pca
 * Purpose:	Executes the \pca command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pca (Word *w, int align, char has_param, int param) 
{
    default_encoding = "CP850";
    return FALSE;
}

/*========================================================================
 * Name:	cmd_mac
 * Purpose:	Executes the \mac command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_mac (Word *w, int align, char has_param, int param) 
{
    default_encoding = "MAC";
    return FALSE;
}

/*========================================================================
 * Name:	cmd_colortbl
 * Purpose:	Executes the \colortbl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_colortbl (Word *w, int align, char has_param, int param) {
	if (w->next) {
		process_color_table(w->next);
	}
	return TRUE;
}

/*========================================================================
 * Name:	cmd_fonttbl
 * Purpose:	Executes the \fonttbl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_fonttbl (Word *w, int align, char has_param, int param) {
	if (w->next) {
		process_font_table(w->next);
	}
	return TRUE;
}

/*========================================================================
 * Name:	cmd_header
 * Purpose:	Executes the \header command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_header (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_headerl
 * Purpose:	Executes the \headerl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_headerl (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_headerr
 * Purpose:	Executes the \headerr command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_headerr (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_headerf
 * Purpose:	Executes the \headerf command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_headerf (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_footer
 * Purpose:	Executes the \footer command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_footer (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_footerl
 * Purpose:	Executes the \footerl command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_footerl (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_footerr
 * Purpose:	Executes the \footerr command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_footerr (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_footerf
 * Purpose:	Executes the \footerf command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_footerf (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_ignore
 * Purpose:	Dummy function to get rid of subgroups 
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_ignore (Word *w, int align, char has_param, int param) {
	return TRUE;
}

/*========================================================================
 * Name:	cmd_info
 * Purpose:	Executes the \info command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_info (Word *w, int align, char has_param, int param) {
	process_info_group (w->next);
	return TRUE;
}

/*========================================================================
 * Name:	cmd_pict
 * Purpose:	Executes the \pict command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pict (Word *w, int align, char has_param, int param) {
	within_picture=TRUE;
	picture_width = picture_height = 0;
	picture_type = PICT_WB;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_bin
 * Purpose:	Executes the \bin command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_bin (Word *w, int align, char has_param, int param) {
	return FALSE;		
}


/*========================================================================
 * Name:	cmd_macpict
 * Purpose:	Executes the \macpict command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_macpict (Word *w, int align, char has_param, int param) {
	picture_type = PICT_MAC;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_jpegblip
 * Purpose:	Executes the \jpegblip command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_jpegblip (Word *w, int align, char has_param, int param) {
	picture_type = PICT_JPEG;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_pngblip
 * Purpose:	Executes the \pngblip command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pngblip (Word *w, int align, char has_param, int param) {
	picture_type = PICT_PNG;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_pnmetafile
 * Purpose:	Executes the \pnmetafile command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pnmetafile (Word *w, int align, char has_param, int param) {
	picture_type = PICT_PM;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_wmetafile
 * Purpose:	Executes the \wmetafile command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_wmetafile (Word *w, int align, char has_param, int param) {
	picture_type = PICT_WM;
	if (within_picture && has_param) {
		picture_wmetafile_type=param;
		switch(param) {
		case 1: picture_wmetafile_type_str="MM_TEXT"; break;
		case 2: picture_wmetafile_type_str="MM_LOMETRIC"; break;
		case 3: picture_wmetafile_type_str="MM_HIMETRIC"; break;
		case 4: picture_wmetafile_type_str="MM_LOENGLISH"; break;
		case 5: picture_wmetafile_type_str="MM_HIENGLISH"; break;
		case 6: picture_wmetafile_type_str="MM_TWIPS"; break;
		case 7: picture_wmetafile_type_str="MM_ISOTROPIC"; break;
		case 8: picture_wmetafile_type_str="MM_ANISOTROPIC"; break;
		default: picture_wmetafile_type_str="default:MM_TEXT"; break;
		}
	}
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_wbmbitspixel
 * Purpose:	Executes the \wbmbitspixel command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_wbmbitspixel (Word *w, int align, char has_param, int param) {
	if (within_picture && has_param) 
		picture_bits_per_pixel = param;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_picw
 * Purpose:	Executes the \picw command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_picw (Word *w, int align, char has_param, int param) {
	if (within_picture && has_param) 
		picture_width = param;
	return FALSE;		
}

/*========================================================================
 * Name:	cmd_pich
 * Purpose:	Executes the \pich command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_pich (Word *w, int align, char has_param, int param) {
	if (within_picture && has_param) 
		picture_height = param;
	return FALSE;		
}


/*========================================================================
 * Name:	cmd_xe
 * Purpose:	Executes the \xe (index entry) command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_xe (Word *w, int align, char has_param, int param) {
	process_index_entry (w);
	return TRUE;		
}

/*========================================================================
 * Name:	cmd_tc
 * Purpose:	Executes the \tc (TOC entry) command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_tc (Word *w, int align, char has_param, int param) {
	process_toc_entry (w, TRUE);
	return TRUE;		
}

/*========================================================================
 * Name:	cmd_tcn
 * Purpose:	Executes the \tcn (TOC entry, no page #) command.
 * Args:	Word, paragraph align info, and numeric param if any.
 * Returns:	Flag, true only if rest of Words on line should be ignored.
 *=======================================================================*/

static int cmd_tcn (Word *w, int align, char has_param, int param) {
	process_toc_entry (w, FALSE);
	return TRUE;		
}


typedef struct {
	char *name;
	int (*func)(Word*, int, char, int);
	char *debug_print;
} HashItem;


/* All of the possible commands that RTF might recognize. */
static HashItem hashArray_other [] = {
	{ "*", cmd_ignore, NULL },
	{ "-", cmd_optional_hyphen, "optional hyphen" },
	{ "_", cmd_nonbreaking_hyphen, "nonbreaking hyphen" },
	{ "~", cmd_nonbreaking_space, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_a [] = {
	{ "ansi", &cmd_ansi , NULL },
	{ "ansicpg", &cmd_ansicpg , NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_b [] = {
	{ "b", &cmd_b, NULL },
	{ "bullet", &cmd_bullet, NULL },
	{ "bin", &cmd_bin, "picture is binary" },
#if 0
	{ "bgbdiag", NULL, NULL },
	{ "bgcross", NULL, NULL },
	{ "bgdcross", NULL, NULL },
	{ "bgfdiag", NULL, NULL },
	{ "bghoriz", NULL, NULL },
	{ "bgkbdiag", NULL, NULL },
	{ "bgkcross", NULL, NULL },
	{ "bgkdcross", NULL, NULL },
	{ "bgkfdiag", NULL, NULL },
	{ "bgkhoriz", NULL, NULL },
	{ "bgkvert", NULL, NULL },
	{ "bgvert", NULL, NULL },
	{ "brdrcf", NULL, NULL },
	{ "brdrdb", NULL, NULL },
	{ "brdrdot", NULL, NULL },
	{ "brdrhair", NULL, NULL },
	{ "brdrs", NULL, NULL },
	{ "brdrsh", NULL, NULL },
	{ "brdrth", NULL, NULL },
	{ "brdrw", NULL, NULL },
#endif
	{ NULL, NULL, NULL}
};
static HashItem hashArray_c [] = {
	{ "caps", &cmd_caps, NULL },
	{ "cb", cmd_cb, NULL },
	{ "cf", cmd_cf, NULL },
	{ "colortbl", &cmd_colortbl, "color table" },
	{ "cols", NULL, "columns (not implemented)" },
	{ "column", NULL, "column break (not implemented)" },
	{ "cbpat", NULL, "Paragraph Shading" },
	{ "cellx", NULL, "Table Definitions" },
	{ "cfpat", NULL, NULL },
	{ "cgrid", NULL, NULL },
	{ "charrsid", NULL, "Revision Mark (ignore)" },
	{ "clbgbcross", NULL, NULL },
	{ "clbgbdiag", NULL, NULL },
	{ "clbgbkbdiag", NULL, NULL },
	{ "clbgbkcross", NULL, NULL },
	{ "clbgbkdcross", NULL, NULL },
	{ "clbgbkfdiag", NULL, NULL },
	{ "clbgbkhor", NULL, NULL },
	{ "clbgbkvert", NULL, NULL },
	{ "clbgdcross", NULL, NULL },
	{ "clbgfdiag", NULL, NULL },
	{ "clbghoriz", NULL, NULL },
	{ "clbgvert", NULL, NULL },
	{ "clbrdrb", NULL, NULL },
	{ "clbrdrl", NULL, NULL },
	{ "clbrdrr", NULL, NULL },
	{ "clbrdrt", NULL, NULL },
	{ "clcbpat", NULL, NULL },
	{ "clcfpat", NULL, NULL },
	{ "clmgf", NULL, NULL },
	{ "clmrg", NULL, NULL },
	{ "clshdng", NULL, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_d [] = {
	{ "deff", cmd_deff, "Default Font" },
	{ "dn", &cmd_dn, NULL },
#if 0
	{ "dibitmap", NULL, NULL },
#endif
	{ NULL, NULL, NULL}
};
static HashItem hashArray_e [] = {
	{ "emdash", cmd_emdash, NULL },
	{ "endash", cmd_endash, NULL },
	{ "embo", &cmd_emboss, NULL },
	{ "expand", &cmd_expand, NULL },
	{ "expnd", &cmd_expand, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_f [] = {
	{ "f", cmd_f, NULL },
	{ "fdecor", cmd_fdecor, NULL },
	{ "fmodern", cmd_fmodern, NULL },
	{ "fnil", cmd_fnil, NULL },
	{ "fonttbl", cmd_fonttbl, "font table" },
	{ "froman", cmd_froman, NULL },
	{ "fs", cmd_fs, NULL },
	{ "fscript", cmd_fscript, NULL },
	{ "fswiss", cmd_fswiss, NULL },
	{ "ftech", cmd_ftech, NULL },
	{ "field", cmd_field, NULL },
	{ "footer", cmd_footer, NULL },
	{ "footerf", cmd_footerf, NULL },
	{ "footerl", cmd_footerl, NULL },
	{ "footerr", cmd_footerr, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_h [] = {
	{ "highlight", &cmd_highlight, NULL },
	{ "header", cmd_header, NULL },
	{ "headerf", cmd_headerf, NULL },
	{ "headerl", cmd_headerl, NULL },
	{ "headerr", cmd_headerr, NULL },
	{ "hl", cmd_ignore, "hyperlink within object" },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_i [] = {
	{ "i", &cmd_i, NULL },
	{ "info", &cmd_info, NULL },
	{ "insrsid", NULL, "Revision Mark (ignore)" },
	{ "intbl", &cmd_intbl, NULL },
	{ "impr", &cmd_engrave, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_j [] = {
	{ "jpegblip", &cmd_jpegblip, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_l [] = {
	{ "ldblquote", &cmd_ldblquote, NULL },
	{ "line", &cmd_line, NULL },
	{ "lquote", &cmd_lquote, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_m [] = {
	{ "mac", &cmd_mac , NULL },
	{ "macpict", &cmd_macpict, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_n [] = {
	{ "nosupersub", &cmd_nosupersub, NULL },
	{ "nonshppict", &cmd_ignore, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_o [] = {
	{ "outl", &cmd_outl, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_p [] = {
	{ "page", &cmd_page, NULL },
	{ "par", &cmd_par, NULL },
	{ "pc", &cmd_pc , NULL },
	{ "pca", &cmd_pca , NULL },
	{ "pich", &cmd_pich, NULL },
	{ "pict", &cmd_pict, "picture" },
	{ "picw", &cmd_picw, NULL },
	{ "plain", &cmd_plain, NULL },
	{ "pngblip", &cmd_pngblip, NULL },
	{ "pnmetafile", &cmd_pnmetafile, NULL },
#if 0
	{ "piccropb", NULL, NULL },
	{ "piccropl", NULL, NULL },
	{ "piccropr", NULL, NULL },
	{ "piccropt", NULL, NULL },
	{ "pichgoal", NULL, NULL },
	{ "pichgoal", NULL, NULL },
	{ "picscaled", NULL, NULL },
	{ "picscalex", NULL, NULL },
	{ "picwgoal", NULL, NULL },
#endif
	{ NULL, NULL, NULL}
};
static HashItem hashArray_r [] = {
	{ "rdblquote", &cmd_rdblquote, NULL },
	{ "rquote", &cmd_rquote, NULL },
	{ "rtf", &cmd_rtf, NULL },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_s [] = {
	{ "s", cmd_s, "style" },
	{ "sect", &cmd_sect, "section break"},
	{ "scaps", &cmd_scaps, NULL },
	{ "super", &cmd_super, NULL },
	{ "sub", &cmd_sub, NULL },
	{ "shad", &cmd_shad, NULL },
	{ "strike", &cmd_strike, NULL },
	{ "striked", &cmd_striked, NULL },
	{ "strikedl", &cmd_strikedl, NULL },
	{ "stylesheet", &cmd_ignore, "style sheet" },
	{ "shp", cmd_shp, "drawn shape" },
#if 0
	{ "shading", NULL, NULL },
#endif
	{ NULL, NULL, NULL}
};
static HashItem hashArray_t [] = {
	{ "tab", &cmd_tab, NULL },
	{ "tc", cmd_tc, "TOC entry" },
	{ "tcn", cmd_tcn, "TOC entry" },
	{ NULL, NULL, NULL}
};
static HashItem hashArray_u [] = {
	{ "u", &cmd_u, NULL },
	{ "ul", &cmd_ul, NULL },
	{ "up", &cmd_up, NULL },
	{ "uld", &cmd_uld, NULL },
	{ "uldash", &cmd_uldash, NULL },
	{ "uldashd", &cmd_uldashd, NULL },
	{ "uldashdd", &cmd_uldashdd, NULL },
	{ "uldb", &cmd_uldb, NULL },
	{ "ulnone", &cmd_ulnone, NULL },
	{ "ulth", &cmd_ulth, NULL },
	{ "ulthd", &cmd_ulthd, NULL },
	{ "ulthdash", &cmd_ulthdash, NULL },
	{ "ulw", &cmd_ulw, NULL },
	{ "ulwave", &cmd_ulwave, NULL },
	{ NULL, NULL, NULL}
};

static HashItem hashArray_v [] = {
	{ "v", NULL, "Hidden Text" },
	{ NULL, NULL, NULL }
};

static HashItem hashArray_w [] = {
	{ "wbmbitspixel", &cmd_wbmbitspixel, NULL },
	{ "wmetafile", &cmd_wmetafile, NULL },
	{ NULL, NULL, NULL}
};

static HashItem hashArray_x [] = {
	{ "xe", cmd_xe, "index entry" },
	{ NULL, NULL, NULL}
};

static HashItem *hash [26] = {
	hashArray_a,
	hashArray_b,
	hashArray_c,
	hashArray_d,
	hashArray_e,
	hashArray_f,
	NULL,
	hashArray_h,
	hashArray_i,
	hashArray_j,
	NULL,
	hashArray_l,
	hashArray_m,
	hashArray_n,
	hashArray_o,
	hashArray_p,
	NULL,
	hashArray_r,
	hashArray_s,
	hashArray_t,
	hashArray_u,
	hashArray_v,
	hashArray_w,
	hashArray_x, 
	NULL, NULL
};


/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/



/*-------------------------------------------------------------------*/
/*-------------------------------------------------------------------*/


/*========================================================================
 * Name:	print_with_special_exprs
 * Purpose: print "normal" content string, obtained as "as-is" text,
 *          not through an \' or \u or other command. The input is
            guaranteed to be a string of 7-8 bit bytes representing
            single-byte characters in the current encoding. The things
            we do in there (with the caps conversion) would be seriously
            wrong if the input was an encoding not
            apparented to ascii, which hopefully is never the case.
            Also we just drop all chars outside [0x20-0x80[
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void 
print_with_special_exprs (char *s) {
	int ch;
	int state;

enum { SMALL=0, BIG=1 };

	CHECK_PARAM_NOT_NULL(s);

        // Not sure that there are cases where the flush is needed, 
        // but this is conceivable, and can't hurt in any case.
        flush_iconv_input();

        maybeopeniconv();

	state=SMALL;  /* Pacify gcc,  st001906 - 0.19.6 */
	if (simulate_smallcaps) {
		if (*s >= 'a' && *s <= 'z') {
			state=SMALL;
			if (safe_printf(0, op->smaller_begin)) fprintf(stderr, TOO_MANY_ARGS, "smaller_begin");
		}
		else
			state=BIG;
	}

	while ((ch=*s)) {
		char *post_trans = NULL;

		if (simulate_allcaps || simulate_smallcaps)
			ch = toupper (ch);

		if (ch >= 0x20 && ch < 0x80) {
			post_trans= op_translate_char(op, current_encoding, ch);
			if(post_trans)
				printf("%s",post_trans);
		}

		s++;

		if (simulate_smallcaps) {
			ch = *s;
			if (ch >= 'a' && ch <= 'z') {
				if (state==BIG)
					if (safe_printf(0, op->smaller_begin)) fprintf(stderr, TOO_MANY_ARGS, "smaller_begin");
				state=SMALL;
			}
			else
			{
				if (state==SMALL)
					if (safe_printf(0, op->smaller_end)) fprintf(stderr, TOO_MANY_ARGS, "smaller_end");
				state=BIG;
			}
		}
	}
}



/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

static void 
begin_table()
{
	within_table=TRUE;
	have_printed_row_begin = FALSE;
	have_printed_cell_begin = FALSE;
	have_printed_row_end = FALSE;
	have_printed_cell_end = FALSE;
	attrstack_push();
	starting_body();
	if (safe_printf(0, op->table_begin)) fprintf(stderr, TOO_MANY_ARGS, "table_begin");
}



/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void 
end_table ()
{
	if (within_table) {
		if (!have_printed_cell_end) {
			attr_pop_dump();
			if (safe_printf(0, op->table_cell_end)) fprintf(stderr, TOO_MANY_ARGS, "table_cell_end");
		}
		if (!have_printed_row_end) {
			if (safe_printf(0, op->table_row_end)) fprintf(stderr, TOO_MANY_ARGS, "table_row_end");
		}
		if (safe_printf(0, op->table_end)) fprintf(stderr, TOO_MANY_ARGS, "table_end");
		within_table=FALSE;
		have_printed_row_begin = FALSE;
		have_printed_cell_begin = FALSE;
		have_printed_row_end = FALSE;
		have_printed_cell_end = FALSE;
	}
}



/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void 
starting_text() {
	if (within_table) {
		if (!have_printed_row_begin) {
			if (safe_printf(0, op->table_row_begin)) fprintf(stderr, TOO_MANY_ARGS, "table_row_begin");
			have_printed_row_begin=TRUE;
			have_printed_row_end=FALSE;
			have_printed_cell_begin=FALSE;
		}
		if (!have_printed_cell_begin) {
			if (safe_printf(0, op->table_cell_begin)) fprintf(stderr, TOO_MANY_ARGS, "table_cell_begin");
			attrstack_express_all();
			have_printed_cell_begin=TRUE;
			have_printed_cell_end=FALSE;
		}
	}
}




/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

static void
starting_paragraph_align (int align)
{
	if (within_header && align != ALIGN_LEFT)
		starting_body();

	switch (align) 
	{
	case ALIGN_CENTER:
		if (safe_printf(0, op->center_begin)) fprintf(stderr, TOO_MANY_ARGS, "center_begin");
		break;
	case ALIGN_LEFT:
		break;
	case ALIGN_RIGHT:
		if (safe_printf(0, op->align_right_begin)) fprintf(stderr, TOO_MANY_ARGS, "align_right_begin");
		break;
	case ALIGN_JUSTIFY:
		if (safe_printf(0, op->justify_begin)) fprintf(stderr, TOO_MANY_ARGS, "justify_begin"); /* But this is correct */
		break;
	}
}



/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

static void
ending_paragraph_align (int align)
{
	switch (align) {
	case ALIGN_CENTER:
		if (safe_printf(0, op->center_end)) fprintf(stderr, TOO_MANY_ARGS, "center_end");
		break;
	case ALIGN_LEFT:
		break;
	case ALIGN_RIGHT:
		if (safe_printf(0, op->align_right_end)) fprintf(stderr, TOO_MANY_ARGS, "align_right_end");
		break;
	case ALIGN_JUSTIFY:
		if (safe_printf(0, op->justify_end)) fprintf(stderr, TOO_MANY_ARGS, "justify_end");
		break;
	}
}


#define IIBS 10240
static char iconv_buffer[IIBS];
static int iconv_cur = 0;
static void
flush_iconv_input()
{
/*  fprintf(stderr, "flush_iconv_input: iconv_cur %d\n", iconv_cur);*/
    if (iconv_cur <= 0) {
        iconv_cur = 0;
        return;
    }
    maybeopeniconv();
    if (!my_iconv_is_valid(desc)) {
        fprintf(stderr, "unrtf: flush: iconv not ready!\n");
        return;
    }

    char obuf[IIBS];
    size_t isiz = iconv_cur;
    char *ip = iconv_buffer;
    while (isiz > 0) {
        size_t osiz;
        char *ocp;

        osiz = IIBS;
        ocp = obuf;
        errno = 0;
        if(my_iconv(desc, &ip, &isiz, &ocp, &osiz) == (size_t)-1 && errno != E2BIG) {
            if (errno == EINVAL) {
                // Incomplete input sequence. Copy it to the
                // beginning of the buffer and leave it around
                // (more data probably coming)
                // iconv leaves ip pointing at the
                // beginning of the sequence.
                int cnt = iconv_cur - (ip - iconv_buffer);
/*            fprintf(stderr, "flush_iconv: incomp. input remain %d\n", cnt);*/
                memcpy(obuf, ip, cnt);
                memcpy(iconv_buffer, obuf, cnt);
                iconv_cur = cnt;
                return;
            } else {
/*           fprintf(stderr, "flush_iconv: rem. %d errno %d\n", isiz, errno);*/
                iconv_cur = 0;
                return;
            }
        }
/*        fprintf(stderr, "flush_iconv: ok: isiz %d out %d errno %d\n", 
          isiz, IIBS - osiz, errno); */

        
/*        fwrite(obuf, 1, IIBS - osiz, stdout);*/
        char *out = op_translate_buffer(op, obuf, IIBS - osiz);
        if (out == 0) {
            iconv_cur = 0;
            return;
        }
        fprintf(stdout, "%s", out);
        my_free(out);
    }
    iconv_cur = 0;
}

static void
accumulate_iconv_input(int ch)
{
/*    fprintf(stderr, "accumulate_iconv_input: 0x%x\n", ch);*/
    if (iconv_cur >= IIBS-1)
        flush_iconv_input();
    iconv_buffer[iconv_cur++] = ch;
}

/*========================================================================
 * Name:	
 * Purpose:	Recursive routine to produce the output in the target
 *		format given on a tree of words.
 * Args:	Word* (the tree).
 * Returns:	None.
 *=======================================================================*/

static void
word_print_core (Word *w)
{
	char *s;
	char *alias;
	FILE *pictfile=NULL;
	int is_cell_group=FALSE;
	int paragraph_begined=FALSE;
	int paragraph_align=ALIGN_LEFT;

	CHECK_PARAM_NOT_NULL(w);

	if (!coming_pars_that_are_tabular && within_table) {
		end_table();
	}
	else if (coming_pars_that_are_tabular && !within_table) {
		begin_table();
	}

	/* Mark our place in the stack */
	attrstack_push();

	while (w) {

                s = word_string (w);
                // If we have hex data and we're getting out of the hex area
                // flush it.
                if (iconv_cur > 0 && s && strncmp(s, "\\'", 2))
                    flush_iconv_input();

		if (s) {

			/*--Ignore whitespace in header--------------------*/
			if (*s==' ' && within_header) {
				/* no op */
			} 
			else
			/*--Handle word -----------------------------------*/
			if (s[0] != '\\') 
			{
				starting_body();
				starting_text();

				if (!paragraph_begined) {
					starting_paragraph_align (paragraph_align);
					paragraph_begined=TRUE;
				}

				/*----------------------------------------*/
				if (within_picture) {
					starting_body();
					if (!pictfile && !nopict_mode) {
						char *ext=NULL;
						switch (picture_type) {
						case PICT_WB: ext="bmp"; break;
						case PICT_WM: ext="wmf"; break;
						case PICT_MAC: ext="pict"; break;
						case PICT_JPEG: ext="jpg"; break;
						case PICT_PNG: ext="png"; break;
						case PICT_DI: ext="dib"; break; /* Device independent bitmap=??? */
						case PICT_PM: ext="pmm"; break; /* OS/2 metafile=??? */
						}
						sprintf(picture_path, "pict%03d.%s", 
							picture_file_number++,ext);
						pictfile=fopen(picture_path,"w");
					}

					if (s[0]!=' ') {
						char *s2;
						if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
						printf("picture data found, ");
						if (picture_wmetafile_type_str) {
							printf("WMF type is %s, ",
								picture_wmetafile_type_str);
						}
						printf("picture dimensions are %d by %d, depth %d", 
							picture_width, picture_height, picture_bits_per_pixel);
						if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
						if (picture_width && picture_height && picture_bits_per_pixel) {
							s2=s;
							while (*s2) {
								unsigned int tmp,value;
								tmp=tolower(*s2++);
								if (tmp>'9') tmp-=('a'-10); 
								else tmp-='0';
								value=16*tmp;
								tmp=tolower(*s2++);
								if (tmp>'9') tmp-=('a'-10); 
								else tmp-='0';
								value+=tmp;
								if (pictfile) {
									fprintf(pictfile,"%c", value);
								}
							}
						}
					}
				}
				/*----------------------------------------*/
				else {
					total_chars_this_line += strlen(s);

					if (op->word_begin)
						if (safe_printf(0, op->word_begin)) fprintf(stderr, TOO_MANY_ARGS, "word_begin");

					print_with_special_exprs (s);

					if (op->word_end)
						if (safe_printf(0, op->word_end)) fprintf(stderr, TOO_MANY_ARGS, "word_end");
				}


			}
			/* output an escaped backslash */
			/* do we need special handling for latex? */
			/* we do for troff where we want the string for 92 */
			else if (*(s+1) == '\\')
			{
				s++;
				if ((alias = get_alias(op, 92)) != NULL)
				{
					printf("%s", alias);
				}
				else
				{
					putchar('\\');
				}
			}
			else if (*(s+1) == '{')
			{
				s++;
				putchar('{');
			}
			else if (*(s+1) == '}')
			{
				s++;
				putchar('}');
			}
			/*---Handle RTF keywords---------------------------*/
			else {

				int done=FALSE;
				s++;
/*----Paragraph alignment----------------------------------------------------*/
				if (!strcmp ("ql", s))
					paragraph_align = ALIGN_LEFT;
				else if (!strcmp ("qr", s))
					paragraph_align = ALIGN_RIGHT;
				else if (!strcmp ("qj", s))
					paragraph_align = ALIGN_JUSTIFY;
				else if (!strcmp ("qc", s))
					paragraph_align = ALIGN_CENTER;
				else if (!strcmp ("pard", s)) 
				{
					/* Clear out all font attributes.
					 */
					attr_pop_all();

					if (coming_pars_that_are_tabular) {
						--coming_pars_that_are_tabular;
					}

					/* Clear out all paragraph attributes.
					 */
					ending_paragraph_align(paragraph_align);
					paragraph_align = ALIGN_LEFT;
					paragraph_begined = FALSE;
				}
/*----Table keywords---------------------------------------------------------*/
				else
				if (!strcmp (s, "cell")) {

					is_cell_group=TRUE;
					if (!have_printed_cell_begin) {
						/* Need this with empty cells */
						if (safe_printf(0, op->table_cell_begin)) fprintf(stderr, TOO_MANY_ARGS, "table_cell_begin");
						attrstack_express_all();
					}
					attr_pop_dump();
					if (safe_printf(0, op->table_cell_end)) fprintf(stderr, TOO_MANY_ARGS, "table_cell_end");
					have_printed_cell_begin = FALSE;
					have_printed_cell_end=TRUE;
				}
				else if (!strcmp (s, "row")) {

					if (within_table) {
						if (safe_printf(0, op->table_row_end)) fprintf(stderr, TOO_MANY_ARGS, "table_row_end");
						have_printed_row_begin = FALSE;
						have_printed_row_end=TRUE;
					} else {
						if (debug_mode) {
							if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
							printf("end of table row");
							if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
						}
					}
				}

/*----Special chars---------------------------------------------------------*/
				else if (*s == '\'') {
					/* \'XX is a hex char code expression */

					int ch = h2toi (&s[1]);
                                        accumulate_iconv_input(ch);
				}
				else
/*----Search the RTF command hash-------------------------------------------*/
				{
					int ch;
					int index=0;
					int have_param = FALSE, param = 0;
					HashItem *hip;
					char *p;
					int match = FALSE;  /* Pacify gcc,  st001906 - 0.19.6 */

					/* Look for a parameter */
					p = s;
					while (*p && (!isdigit(*p) && *p != '-')) p++;
					if (*p && (isdigit(*p) || *p == '-')) { 
						have_param = TRUE; 
						param = atoi(p);
					}

					/* Generate a hash index */
					ch = tolower(*s);
					if (ch >= 'a' && ch <= 'z') 
						hip = hash[ch - 'a'];
					else
						hip = hashArray_other;

					if (!hip) {
						if (debug_mode) {
							if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
							printf("Unfamiliar RTF command: %s (HashIndex not found)", s);
							if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
						}
					}
					else {
						while (!done) {
							match = FALSE;

							if (have_param) {
								int len=p-s;
								if (!hip[index].name[len] && !strncmp(s, hip[index].name, len))
									match = TRUE;
							}
							else
								match = !strcmp(s, hip[index].name);

							if (match) {

								char *debug;
								int terminate_group;

								if (hip[index].func) {
//unicode
									terminate_group = hip[index].func (w,paragraph_align, have_param, param);
	/* daved - 0.19.4 - unicode support may need to skip only one word */
									if (terminate_group == SKIP_ONE_WORD)
											w=w->next;
											else
									if (terminate_group)
										while(w) w=w->next;
								}

								debug=hip[index].debug_print;

								if (debug && debug_mode) {
									if (safe_printf(0, op->comment_begin))
										fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
									printf("%s", debug);
									if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
								}

								done=TRUE;
							}
							else {
								index++;
								if (!hip[index].name)
									done = TRUE;
							}
						}
					}
					if (!match) {
						if (debug_mode) {
							if (safe_printf(0, op->comment_begin)) fprintf(stderr, TOO_MANY_ARGS, "comment_begin");
							printf("Unfamiliar RTF command: %s", s);
							if (safe_printf(0, op->comment_end)) fprintf(stderr, TOO_MANY_ARGS, "comment_end");
						}
					}
				}
			}
/*-------------------------------------------------------------------------*/
		} else {

			Word *child;

			child = w->child;

			if (!paragraph_begined) {
				starting_paragraph_align (paragraph_align);
				paragraph_begined=TRUE;
			}

			if (child) 
			  word_print_core (child);
		}

		if (w) 
			w = w->next;
	}

	if (within_picture) {
		if(pictfile) { 
			fclose(pictfile);
			if (safe_printf(0, op->imagelink_begin)) fprintf(stderr, TOO_MANY_ARGS, "imagelink_begin");
			printf("%s", picture_path);
			if (safe_printf(0, op->imagelink_end)) fprintf(stderr, TOO_MANY_ARGS, "imagelink_end");
		}
		within_picture=FALSE;
	}

	/* Undo font attributes UNLESS we're doing table cells
	 * since they would appear between </td> and </tr>.
	 */

	if (!is_cell_group)
		attr_pop_all();
	else
		attr_drop_all();

	/* Undo paragraph alignment
	 */
	if (paragraph_begined)
		ending_paragraph_align (paragraph_align);

	attrstack_drop();

        // Flush iconv input
        flush_iconv_input();
        iconv_cur = 0;

        // If there is an encoding in the stacks, restore it, else
        // restore default.
        desc = my_iconv_close(desc);
        char *encoding = attr_get_param(ATTR_ENCODING);
        if (!encoding || !*encoding) 
            encoding = default_encoding;
        desc = my_iconv_open(output_encoding, encoding);
        set_current_encoding(encoding);
}




/*========================================================================
 * Name:	
 * Purpose:	
 * Args:	None.
 * Returns:	None.
 *=======================================================================*/

void 
word_print (Word *w) 
{
	CHECK_PARAM_NOT_NULL (w);

	if (!inline_mode) {
#if 1 /* AK3 - AF */
		if (safe_printf(0, op->document_begin)) fprintf(stderr, TOO_MANY_ARGS, "document_begin");
		if (safe_printf(0, op->header_begin)) fprintf(stderr, TOO_MANY_ARGS, "header_begin");
		if (safe_printf(0, op->utf8_encoding)) fprintf(stderr, TOO_MANY_ARGS, "utf8_encoding");
#else
		printf(op->document_begin);
		printf(op->header_begin);
#endif
	}

	print_banner ();

	within_header=TRUE;
	have_printed_body=FALSE;
	within_table=FALSE;
	simulate_allcaps=FALSE;
	word_print_core (w);
	end_table();

	if (!inline_mode) {
#if 1 /* AK3 - AF */
		if (safe_printf(0, op->body_end)) fprintf(stderr, TOO_MANY_ARGS, "body_end");
		if (safe_printf(0, op->document_end)) fprintf(stderr, TOO_MANY_ARGS, "document_end");
#else
		printf(op->body_end);
		printf(op->document_end);
#endif
	}
}
