/*
 * word2text.c
 * Copyright (C) 1998-2001 A.J. van Os; Released under GPL
 *
 * Description:
 * MS Word to text functions
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#if defined(__riscos)
#include "visdelay.h"
#endif /* __riscos */
#include "antiword.h"


#define INITIAL_SIZE		40
#define EXTENTION_SIZE		20


/* Macros to make sure all such statements will be identical */
#define OUTPUT_LINE()		\
	do {\
		vAlign2Window(pDiag, pAnchor, lWidthMax, ucAlignment);\
		pAnchor = pStartNewOutput(pAnchor, NULL);\
		pOutput = pAnchor;\
	} while(0)

#define RESET_LINE()		\
	do {\
		pAnchor = pStartNewOutput(pAnchor, NULL);\
		pOutput = pAnchor;\
	} while(0)

#if defined(__riscos)
/* Length of the document in characters */
static long	lDocumentLength;
/* Number of characters processed so far */
static long	lCharCounter;
static int	iCurrPct, iPrevPct;
#endif /* __riscos */
/* The document is in the format belonging to this version of Word */
static int	iWordVersion = -1;
/* Special treatment for files from Word 6 on an Apple Macintosh */
static BOOL	bWord6MacFile = FALSE;
/* All the (command line) options */
static options_type	tOptions;
/* Needed for reading a complete table row */
static const row_block_type	*pRowInfo = NULL;
static BOOL	bStartRow = FALSE;
static BOOL	bEndRowNorm = FALSE;
static BOOL	bEndRowFast = FALSE;
static BOOL	bIsTableRow = FALSE;
/* Needed for finding the start of a style */
static const style_block_type	*pStyleInfo = NULL;
static BOOL	bStartStyle = FALSE;
/* Needed for finding the start of a font */
static const font_block_type	*pFontInfo = NULL;
static BOOL	bStartFont = FALSE;
/* Needed for finding an image */
static long	lFileOffsetImage = -1;


/*
 * vUpdateCounters - Update the counters for the hourglass
 */
static void
vUpdateCounters(void)
{
#if defined(__riscos)
	lCharCounter++;
	iCurrPct = (int)((lCharCounter * 100) / lDocumentLength);
	if (iCurrPct != iPrevPct) {
		visdelay_percent(iCurrPct);
		iPrevPct = iCurrPct;
	}
#endif /* __riscos */
} /* end of vUpdateCounters */

/*
 * bOutputContainsText - see if the output contains more than white space
 */
static BOOL
bOutputContainsText(output_type *pAnchor)
{
	output_type	*pTmp;
	int	iIndex;

	fail(pAnchor == NULL);

	for (pTmp = pAnchor; pTmp != NULL; pTmp = pTmp->pNext) {
		fail(pTmp->lStringWidth < 0);
		for (iIndex = 0; iIndex < pTmp->iNextFree; iIndex++) {
			if (isspace((int)pTmp->szStorage[iIndex])) {
				continue;
			}
#if defined(DEBUG)
			if (pTmp->szStorage[iIndex] == FILLER_CHAR) {
				continue;
			}
#endif /* DEBUG */
			return TRUE;
		}
	}
	return FALSE;
} /* end of bOutputContainsText */

/*
 * lTotalStringWidth - compute the total width of the output string
 */
static long
lTotalStringWidth(output_type *pAnchor)
{
	output_type	*pTmp;
	long		lTotal;

	lTotal = 0;
	for (pTmp = pAnchor; pTmp != NULL; pTmp = pTmp->pNext) {
		fail(pTmp->lStringWidth < 0);
		lTotal += pTmp->lStringWidth;
	}
	return lTotal;
} /* end of lTotalStringWidth */

/*
 * vStoreByte - store one byte
 */
static void
vStoreByte(unsigned char ucChar, output_type *pOutput)
{
	fail(pOutput == NULL);

	if (ucChar == 0) {
		pOutput->szStorage[pOutput->iNextFree] = '\0';
		return;
	}
	while (pOutput->iNextFree + 2 > (int)pOutput->tStorageSize) {
		pOutput->tStorageSize += EXTENTION_SIZE;
		pOutput->szStorage = xrealloc(pOutput->szStorage,
					pOutput->tStorageSize);
	}
	pOutput->szStorage[pOutput->iNextFree] = (char)ucChar;
	pOutput->szStorage[pOutput->iNextFree + 1] = '\0';
	pOutput->iNextFree++;
} /* end of vStoreByte */

/*
 * vStoreCharacter - store one character
 */
static void
vStoreCharacter(unsigned long ulChar, output_type *pOutput)
{
	int	iLen;

	fail(pOutput == NULL);

	if (tOptions.eEncoding == encoding_utf8) {
		fail(ulChar > 0xffff);
		if (ulChar < 0x80) {
			vStoreByte((unsigned char)ulChar, pOutput);
			iLen = 1;
		} else if (ulChar < 0x800) {
			vStoreByte((unsigned char)(0xc0 | ulChar >> 6),
								pOutput);
			vStoreByte((unsigned char)(0x80 | (ulChar & 0x3f)),
								pOutput);
			iLen = 2;
		} else {
			vStoreByte((unsigned char)(0xe0 | ulChar >> 12),
								pOutput);
			vStoreByte((unsigned char)(0x80 | (ulChar >> 6 & 0x3f)),
								pOutput);
			vStoreByte((unsigned char)(0x80 | (ulChar & 0x3f)),
								pOutput);
			iLen = 3;
		}
	} else {
		fail(ulChar > 0xff);
		vStoreByte((unsigned char)ulChar, pOutput);
		iLen = 1;
	}
	pOutput->lStringWidth += lComputeStringWidth(
				pOutput->szStorage + pOutput->iNextFree - iLen,
				iLen,
				pOutput->tFontRef,
				pOutput->sFontsize);
} /* end of vStoreCharacter */

/*
 * vStoreString - store a string
 */
static void
vStoreString(const char *szString, int iStringLength, output_type *pOutput)
{
	int	iIndex;

	fail(szString == NULL || iStringLength < 0 || pOutput == NULL);

	for (iIndex = 0; iIndex < iStringLength; iIndex++) {
		vStoreCharacter(szString[iIndex], pOutput);
	}
} /* end of vStoreString */

/*
 * vStoreIntegerAsDecimal - store an integer as a decimal number
 */
static void
vStoreIntegerAsDecimal(int iNumber, output_type *pOutput)
{
	int	iLen;
	char	szString[15];

	fail(pOutput == NULL);

	iLen = sprintf(szString, "%d", iNumber);
	vStoreString(szString, iLen, pOutput);
} /* end of vStoreIntegerAsDecimal */

/*
 * vStoreIntegerAsRoman - store an integer as a roman numerical
 */
static void
vStoreIntegerAsRoman(int iNumber, output_type *pOutput)
{
	int	iLen;
	char	szString[15];

	fail(iNumber <= 0);
	fail(pOutput == NULL);

	iLen = iInteger2Roman(iNumber, FALSE, szString);
	vStoreString(szString, iLen, pOutput);
} /* end of vStoreIntegerAsRoman */

/*
 * vStoreStyle - store a style
 */
static void
vStoreStyle(output_type *pOutput)
{
	int	iLen;
	char	szString[120];

	fail(pOutput == NULL);
	iLen = iStyle2Window(szString, pStyleInfo);
	vStoreString(szString, iLen, pOutput);
} /* end of vStoreStyle */

/*
 * Create an empty line by adding a extra "newline"
 */
static void
vEmptyLine2Diagram(diagram_type *pDiag, draw_fontref tFontRef, int iFontsize)
{
	fail(pDiag == NULL);
	fail(iFontsize < MIN_FONT_SIZE || iFontsize > MAX_FONT_SIZE);

	if (pDiag->lXleft > 0) {
		/* To the start of the line */
		vMove2NextLine(pDiag, tFontRef, iFontsize);
	}
	/* Empty line */
	vMove2NextLine(pDiag, tFontRef, iFontsize);
} /* end of vEmptyLine2Diagram */

/*
 * vPutIndentation - output the given amount of indentation
 */
static void
vPutIndentation(diagram_type *pDiag, output_type *pOutput, BOOL bUnmarked,
	int iListNumber, unsigned char ucListType, char cListCharacter,
	int iLeftIndent)
{
	long	lWidth, lLeftIndentation;
	int	iNextFree;
	char	szLine[30];

	fail(pDiag == NULL || pOutput == NULL);
	fail(iListNumber < 0);
	fail(iLeftIndent < 0);

	if (iLeftIndent <= 0) {
		return;
	}
	lLeftIndentation = lTwips2MilliPoints(iLeftIndent);
	if (bUnmarked) {
		vSetLeftIndentation(pDiag, lLeftIndentation);
		return;
	}
	fail(iListNumber <= 0);
	fail(iscntrl((int)cListCharacter));

	switch (ucListType) {
	case LIST_ARABIC_NUM:
		iNextFree = sprintf(szLine, "%d", iListNumber);
		break;
	case LIST_ROMAN_NUM_UPPER:
	case LIST_ROMAN_NUM_LOWER:
		iNextFree = iInteger2Roman(iListNumber,
			ucListType == LIST_ROMAN_NUM_UPPER, szLine);
		break;
	case LIST_UPPER_ALPHA:
	case LIST_LOWER_ALPHA:
		iNextFree = iInteger2Alpha(iListNumber,
			ucListType == LIST_UPPER_ALPHA, szLine);
		break;
	case LIST_ORDINAL_NUM:
		if (iListNumber % 10 == 1 && iListNumber != 11) {
			iNextFree = sprintf(szLine, "%dst", iListNumber);
		} else if (iListNumber % 10 == 2 && iListNumber != 12) {
			iNextFree = sprintf(szLine, "%dnd", iListNumber);
		} else if (iListNumber % 10 == 3 && iListNumber != 13) {
			iNextFree = sprintf(szLine, "%drd", iListNumber);
		} else {
			iNextFree = sprintf(szLine, "%dth", iListNumber);
		}
		break;
	case LIST_BULLETS:
		iNextFree = 0;
		break;
	default:
		DBG_DEC(ucListType);
		DBG_FIXME();
		iNextFree = sprintf(szLine, "%d", iListNumber);
		break;
	}
	szLine[iNextFree++] = cListCharacter;
	szLine[iNextFree++] = ' ';
	szLine[iNextFree] = '\0';
	lWidth = lComputeStringWidth(szLine, iNextFree,
				pOutput->tFontRef, pOutput->sFontsize);
	lLeftIndentation -= lWidth;
	if (lLeftIndentation > 0) {
		vSetLeftIndentation(pDiag, lLeftIndentation);
	}
	vStoreString(szLine, iNextFree, pOutput);
} /* end of vPutIndentation */

/*
 * vPutNoteSeparator - output a note separator
 *
 * A note separator is a horizontal line two inches long.
 * Two inches equals 144000 millipoints.
 */
static void
vPutNoteSeparator(output_type *pOutput)
{
	long	lCharWidth;
	int	iCounter, iChars;
	char	szOne[2];

	fail(pOutput == NULL);

	szOne[0] = OUR_EM_DASH;
	szOne[1] = '\0';
	lCharWidth = lComputeStringWidth(szOne, 1,
				pOutput->tFontRef, pOutput->sFontsize);
	DBG_DEC(lCharWidth);
	iChars = (int)((144000 + lCharWidth / 2) / lCharWidth);
	DBG_DEC(iChars);
	for (iCounter = 0; iCounter < iChars; iCounter++) {
		vStoreCharacter(OUR_EM_DASH, pOutput);
	}
} /* end of vPutNoteSeparator */

/*
 *
 */
static output_type *
pStartNextOutput(output_type *pCurrent)
{
	output_type	*pNew;

	if (pCurrent->iNextFree == 0) {
		/* The current record is empty, re-use */
		fail(pCurrent->szStorage[0] != '\0');
		fail(pCurrent->lStringWidth != 0);
		return pCurrent;
	}
	/* The current record is in use, make a new one */
	pNew = xmalloc(sizeof(*pNew));
	pCurrent->pNext = pNew;
	pNew->tStorageSize = INITIAL_SIZE;
	pNew->szStorage = xmalloc(pNew->tStorageSize);
	pNew->szStorage[0] = '\0';
	pNew->iNextFree = 0;
	pNew->lStringWidth = 0;
	pNew->iColor = FONT_COLOR_DEFAULT;
	pNew->ucFontstyle = FONT_REGULAR;
	pNew->tFontRef = (draw_fontref)0;
	pNew->sFontsize = DEFAULT_FONT_SIZE;
	pNew->pPrev = pCurrent;
	pNew->pNext = NULL;
	return pNew;
} /* end of pStartNextOutput */

/*
 * pStartNewOutput
 */
static output_type *
pStartNewOutput(output_type *pAnchor, output_type *pLeftOver)
{
	output_type	*pCurr, *pNext;
	int		iColor;
	short		sFontsize;
	draw_fontref	tFontRef;
	unsigned char	ucFontstyle;

	iColor = FONT_COLOR_DEFAULT;
	ucFontstyle = FONT_REGULAR;
	tFontRef = (draw_fontref)0;
	sFontsize = DEFAULT_FONT_SIZE;
	/* Free the old output space */
	pCurr = pAnchor;
	while (pCurr != NULL) {
		pNext = pCurr->pNext;
		pCurr->szStorage = xfree(pCurr->szStorage);
		if (pCurr->pNext == NULL) {
			iColor = pCurr->iColor;
			ucFontstyle = pCurr->ucFontstyle;
			tFontRef = pCurr->tFontRef;
			sFontsize = pCurr->sFontsize;
		}
		pCurr = xfree(pCurr);
		pCurr = pNext;
	}
	if (pLeftOver == NULL) {
		/* Create new output space */
		pLeftOver = xmalloc(sizeof(*pLeftOver));
		pLeftOver->tStorageSize = INITIAL_SIZE;
		pLeftOver->szStorage = xmalloc(pLeftOver->tStorageSize);
		pLeftOver->szStorage[0] = '\0';
		pLeftOver->iNextFree = 0;
		pLeftOver->lStringWidth = 0;
		pLeftOver->iColor = iColor;
		pLeftOver->ucFontstyle = ucFontstyle;
		pLeftOver->tFontRef = tFontRef;
		pLeftOver->sFontsize = sFontsize;
		pLeftOver->pPrev = NULL;
		pLeftOver->pNext = NULL;
	}
	fail(!bCheckDoubleLinkedList(pLeftOver));
	return pLeftOver;
} /* end of pStartNewOutput */

/*
 * ulGetChar - get the next character from the given list
 *
 * returns the next character of EOF
 */
static unsigned long
ulGetChar(FILE *pFile, list_id_enum eListID)
{
	const font_block_type	*pCurr;
	unsigned long		ulChar;
	long		lFileOffset, lTextOffset;
	row_info_enum	eRowInfo;
	unsigned short	usChar, usPropMod;
	BOOL	bSkip;

	fail(pFile == NULL);

	pCurr = pFontInfo;
	bSkip = FALSE;
	for (;;) {
		usChar = usNextChar(pFile, eListID,
					&lFileOffset, &lTextOffset, &usPropMod);
		if (usChar == (unsigned short)EOF) {
			return (unsigned long)EOF;
		}

		vUpdateCounters();

		eRowInfo = ePropMod2RowInfo(usPropMod, iWordVersion);
		if (!bStartRow) {
#if 0
			bStartRow = eRowInfo == found_a_cell ||
				(pRowInfo != NULL &&
				 lFileOffset == pRowInfo->lFileOffsetStart &&
				 eRowInfo != found_not_a_cell);
#else
			bStartRow = pRowInfo != NULL &&
				lFileOffset == pRowInfo->lFileOffsetStart;
#endif
			NO_DBG_HEX_C(bStartRow, pRowInfo->lFileOffsetStart);
		}
		if (!bEndRowNorm) {
#if 0
			bEndRow = eRowInfo == found_end_of_row ||
				(pRowInfo != NULL &&
				 lFileOffset == pRowInfo->lFileOffsetEnd &&
				 eRowInfo != found_not_end_of_row);
#else
			bEndRowNorm = pRowInfo != NULL &&
				lFileOffset == pRowInfo->lFileOffsetEnd;
#endif
			NO_DBG_HEX_C(bEndRowNorm, pRowInfo->lFileOffsetEnd);
		}
		if (!bEndRowFast) {
			bEndRowFast = eRowInfo == found_end_of_row;
			NO_DBG_HEX_C(bEndRowFast, pRowInfo->lFileOffsetEnd);
		}

		if (!bStartStyle) {
			bStartStyle = pStyleInfo != NULL &&
				lFileOffset == pStyleInfo->lFileOffset;
			NO_DBG_HEX_C(bStartStyle, lFileOffset);
		}
		if (pCurr != NULL && lFileOffset == pCurr->lFileOffset) {
			bStartFont = TRUE;
			NO_DBG_HEX(lFileOffset);
			pFontInfo = pCurr;
			pCurr = pGetNextFontInfoListItem(pCurr);
		}

		/* Skip embedded characters */
		if (usChar == START_EMBEDDED) {
			bSkip = TRUE;
			continue;
		}
		if (usChar == END_IGNORE || usChar == END_EMBEDDED) {
			bSkip = FALSE;
			continue;
		}
		if (bSkip) {
			continue;
		}
		ulChar = ulTranslateCharacters(usChar,
					lFileOffset,
					tOptions.eEncoding == encoding_utf8,
					bWord6MacFile);
		if (ulChar == IGNORE_CHARACTER) {
			continue;
		}
		if (ulChar == PICTURE) {
			lFileOffsetImage = lGetPicInfoListItem(lFileOffset);
		} else {
			lFileOffsetImage = -1;
		}
		return ulChar;
	}
} /* end of ulGetChar */

/*
 * vWord2Text
 */
void
vWord2Text(FILE *pFile, long lFilesize, diagram_type *pDiag)
{
	imagedata_type	tImage;
	output_type	*pAnchor, *pOutput, *pLeftOver;
	unsigned long	ulChar;
	long	lWidthCurr, lWidthMax, lRightIndentation;
	long	lDefaultTabWidth, lTmp;
	list_id_enum 	eListID;
	image_info_enum	eRes;
	int	iFootnoteNumber, iEndnoteNumber, iLeftIndent;
	int	iListNumber;
	BOOL	bWasTableRow, bTableFontClosed, bUnmarked;
	BOOL	bAllCapitals, bHiddenText, bSuccess;
	short	sFontsize;
	unsigned char	ucFontnumber, ucFontcolor, ucTmp;
	unsigned char	ucFontstyle, ucFontstyleMinimal;
	unsigned char	ucListType, ucAlignment;
	char	cListChar;

	fail(pFile == NULL || lFilesize <= 0 || pDiag == NULL);

	DBG_MSG("vWord2Text");

	iWordVersion = iInitDocument(pFile, lFilesize);
	if (iWordVersion < 6) {
		return;
	}
	vAddFonts2Diagram(pDiag);

	/* Initialisation */
#if defined(__riscos)
	lCharCounter = 0;
	iCurrPct = 0;
	iPrevPct = -1;
	lDocumentLength = lGetDocumentLength();
#endif /* __riscos */
	bWord6MacFile = bIsWord6MacFile();
	lDefaultTabWidth = lGetDefaultTabWidth();
	DBG_DEC_C(lDefaultTabWidth != 36000, lDefaultTabWidth);
	pRowInfo = pGetNextRowInfoListItem();
	DBG_HEX_C(pRowInfo != NULL, pRowInfo->lFileOffsetStart);
	DBG_HEX_C(pRowInfo != NULL, pRowInfo->lFileOffsetEnd);
	DBG_MSG_C(pRowInfo == NULL, "No rows at all");
	bStartRow = FALSE;
	bEndRowNorm = FALSE;
	bEndRowFast = FALSE;
	bIsTableRow = FALSE;
	bWasTableRow = FALSE;
	vResetStyles();
	pStyleInfo = pGetNextStyleInfoListItem();
	bStartStyle = FALSE;
	pAnchor = NULL;
	pFontInfo = pGetNextFontInfoListItem(NULL);
	DBG_HEX_C(pFontInfo != NULL, pFontInfo->lFileOffset);
	DBG_MSG_C(pFontInfo == NULL, "No fonts at all");
	bStartFont = FALSE;
	ucFontnumber = 0;
	ucFontstyleMinimal = FONT_REGULAR;
	ucFontstyle = FONT_REGULAR;
	sFontsize = DEFAULT_FONT_SIZE;
	ucFontcolor = FONT_COLOR_DEFAULT;
	pAnchor = pStartNewOutput(pAnchor, NULL);
	pOutput = pAnchor;
	pOutput->iColor = ucFontcolor;
	pOutput->ucFontstyle = ucFontstyle;
	pOutput->tFontRef = tOpenFont(ucFontnumber, ucFontstyle, sFontsize);
	pOutput->sFontsize = sFontsize;
	bTableFontClosed = TRUE;
	iLeftIndent = 0;
	lRightIndentation = 0;
	bUnmarked = TRUE;
	ucListType = LIST_BULLETS;
	cListChar = OUR_BULLET;
	iListNumber = 0;
	ucAlignment = ALIGNMENT_LEFT;
	bAllCapitals = FALSE;
	bHiddenText = FALSE;
	vGetOptions(&tOptions);
	fail(tOptions.iParagraphBreak < 0);
	if (tOptions.iParagraphBreak == 0) {
		lWidthMax = LONG_MAX;
	} else if (tOptions.iParagraphBreak < MIN_SCREEN_WIDTH) {
		lWidthMax = lChar2MilliPoints(MIN_SCREEN_WIDTH);
	} else if (tOptions.iParagraphBreak > MAX_SCREEN_WIDTH) {
		lWidthMax = lChar2MilliPoints(MAX_SCREEN_WIDTH);
	} else {
		lWidthMax = lChar2MilliPoints(tOptions.iParagraphBreak);
	}
	NO_DBG_DEC(lWidthMax);

	visdelay_begin();

	iFootnoteNumber = 0;
	iEndnoteNumber = 0;
	eListID = text_list;
	for(;;) {
		ulChar = ulGetChar(pFile, eListID);
		if (ulChar == (unsigned long)EOF) {
			if (bOutputContainsText(pAnchor)) {
				OUTPUT_LINE();
			} else {
				RESET_LINE();
			}
			switch (eListID) {
			case text_list:
				eListID = footnote_list;
				if (iFootnoteNumber > 0) {
					vPutNoteSeparator(pAnchor);
					OUTPUT_LINE();
					iFootnoteNumber = 0;
				}
				break;
			case footnote_list:
				eListID = endnote_list;
				if (iEndnoteNumber > 0) {
					vPutNoteSeparator(pAnchor);
					OUTPUT_LINE();
					iEndnoteNumber = 0;
				}
				break;
			case endnote_list:
			default:
				eListID = end_of_lists;
				break;
			}
			if (eListID == end_of_lists) {
				break;
			}
			continue;
		}

		if (ulChar == UNKNOWN_NOTE_CHAR) {
			switch (eListID) {
			case footnote_list:
				ulChar = FOOTNOTE_CHAR;
				break;
			case endnote_list:
				ulChar = ENDNOTE_CHAR;
				break;
			default:
				break;
			}
		}

		if (bStartRow) {
			/* Begin of a tablerow found */
			if (bOutputContainsText(pAnchor)) {
				OUTPUT_LINE();
			} else {
				RESET_LINE();
			}
			fail(pAnchor != pOutput);
			if (bTableFontClosed) {
				/* Start special table font */
				vCloseFont();
				/*
				 * Compensate for the fact that Word uses
				 * proportional fonts for its tables and we
				 * only one fixed-width font
				 */
				pOutput->sFontsize =
					(sFontsize <= DEFAULT_FONT_SIZE ?
					 (DEFAULT_FONT_SIZE * 5 + 3) / 6 :
					 (sFontsize * 5 + 3) / 6);
				pOutput->tFontRef =
					tOpenTableFont(pOutput->sFontsize);
				pOutput->ucFontstyle = FONT_REGULAR;
				pOutput->iColor = FONT_COLOR_BLACK;
				bTableFontClosed = FALSE;
			}
			bIsTableRow = TRUE;
			bStartRow = FALSE;
		}

		if (bWasTableRow &&
		    !bIsTableRow &&
		    ulChar != PAR_END &&
		    ulChar != HARD_RETURN &&
		    ulChar != FORM_FEED &&
		    ulChar != COLUMN_FEED) {
			/*
			 * The end of a table should be followed by an
			 * empty line, like the end of a paragraph
			 */
			OUTPUT_LINE();
			vEndOfParagraph2Diagram(pDiag,
						pOutput->tFontRef,
						pOutput->sFontsize);
		}

		switch (ulChar) {
		case FORM_FEED:
		case COLUMN_FEED:
			if (bIsTableRow) {
				vStoreCharacter('\n', pOutput);
				break;
			}
			if (bOutputContainsText(pAnchor)) {
				OUTPUT_LINE();
			} else {
				RESET_LINE();
			}
			if (ulChar == FORM_FEED) {
				vEndOfPage2Diagram(pDiag,
						pOutput->tFontRef,
						pOutput->sFontsize);
			} else {
				vEndOfParagraph2Diagram(pDiag,
						pOutput->tFontRef,
						pOutput->sFontsize);
			}
			break;
		default:
			break;
		}

		if (bStartFont) {
			/* Begin of a font found */
			fail(pFontInfo == NULL);
			bAllCapitals = bIsCapitals(pFontInfo->ucFontstyle);
			bHiddenText = bIsHidden(pFontInfo->ucFontstyle);
			ucTmp = pFontInfo->ucFontstyle &
			(FONT_BOLD|FONT_ITALIC|FONT_UNDERLINE|FONT_STRIKE);
			if (!bIsTableRow &&
			    (sFontsize != pFontInfo->sFontsize ||
			    ucFontnumber != pFontInfo->ucFontnumber ||
			    ucFontstyleMinimal != ucTmp ||
			    ucFontcolor != pFontInfo->ucFontcolor)) {
				pOutput = pStartNextOutput(pOutput);
				vCloseFont();
				pOutput->iColor = pFontInfo->ucFontcolor;
				pOutput->ucFontstyle = pFontInfo->ucFontstyle;
				pOutput->sFontsize = pFontInfo->sFontsize;
				pOutput->tFontRef = tOpenFont(
						pFontInfo->ucFontnumber,
						pFontInfo->ucFontstyle,
						pFontInfo->sFontsize);
				fail(!bCheckDoubleLinkedList(pAnchor));
			}
			ucFontnumber = pFontInfo->ucFontnumber;
			sFontsize = pFontInfo->sFontsize;
			ucFontcolor = pFontInfo->ucFontcolor;
			ucFontstyle = pFontInfo->ucFontstyle;
			ucFontstyleMinimal = ucTmp;
			pFontInfo = pGetNextFontInfoListItem(pFontInfo);
			NO_DBG_HEX_C(pFontInfo != NULL, pFontInfo->lFileOffset);
			DBG_MSG_C(pFontInfo == NULL, "No more fonts");
			bStartFont = FALSE;
		}

		if (bStartStyle) {
			/* Begin of a style found */
			fail(pStyleInfo == NULL);
			if (!bIsTableRow) {
				vStoreStyle(pOutput);
			}
			iLeftIndent = pStyleInfo->sLeftIndent;
			lRightIndentation =
				lTwips2MilliPoints(pStyleInfo->sRightIndent);
			bUnmarked = !pStyleInfo->bInList ||
						pStyleInfo->bUnmarked;
			ucListType = pStyleInfo->ucListType;
			cListChar = (char)pStyleInfo->ucListCharacter;
			ucAlignment = pStyleInfo->ucAlignment;
			if (pStyleInfo->bInList) {
				if (!pStyleInfo->bUnmarked) {
					iListNumber++;
				}
			} else {
				iListNumber = 0;
			}
			pStyleInfo = pGetNextStyleInfoListItem();
			NO_DBG_HEX_C(pStyleInfo != NULL,
						pStyleInfo->lFileOffset);
			bStartStyle = FALSE;
		}

		if (!bIsTableRow &&
		    lTotalStringWidth(pAnchor) == 0) {
			vPutIndentation(pDiag, pAnchor, bUnmarked,
					iListNumber, ucListType, cListChar,
					iLeftIndent);
			/* One mark per paragraph will do */
			bUnmarked = TRUE;
		}

		switch (ulChar) {
		case PICTURE:
			(void)memset(&tImage, 0, sizeof(tImage));
			eRes = eExamineImage(pFile, lFileOffsetImage, &tImage);
			switch (eRes) {
			case image_no_information:
				bSuccess = FALSE;
				break;
			case image_minimal_information:
			case image_full_information:
#if 0
				if (bOutputContainsText(pAnchor)) {
					OUTPUT_LINE();
				} else {
					RESET_LINE();
				}
#endif
				bSuccess = bTranslateImage(pDiag, pFile,
					eRes == image_minimal_information,
					lFileOffsetImage, &tImage);
				break;
			default:
				DBG_DEC(eRes);
				bSuccess = FALSE;
				break;
			}
			if (!bSuccess) {
				vStoreString("[pic]", 5, pOutput);
			}
			break;
		case FOOTNOTE_CHAR:
			iFootnoteNumber++;
			vStoreCharacter('[', pOutput);
			vStoreIntegerAsDecimal(iFootnoteNumber, pOutput);
			vStoreCharacter(']', pOutput);
			break;
		case ENDNOTE_CHAR:
			iEndnoteNumber++;
			vStoreCharacter('[', pOutput);
			vStoreIntegerAsRoman(iEndnoteNumber, pOutput);
			vStoreCharacter(']', pOutput);
			break;
		case UNKNOWN_NOTE_CHAR:
			vStoreString("[?]", 3, pOutput);
			break;
		case PAR_END:
			if (bIsTableRow) {
				vStoreCharacter('\n', pOutput);
				break;
			}
			if (bOutputContainsText(pAnchor)) {
				OUTPUT_LINE();
			} else {
				RESET_LINE();
			}
			vEndOfParagraph2Diagram(pDiag,
						pOutput->tFontRef,
						pOutput->sFontsize);
			/*
			 * End of paragraph seen, reset indentation,
			 * marking and alignment
			 */
			iLeftIndent = 0;
			lRightIndentation = 0;
			bUnmarked = TRUE;
			ucAlignment = ALIGNMENT_LEFT;
			break;
		case HARD_RETURN:
			if (bIsTableRow) {
				vStoreCharacter('\n', pOutput);
				break;
			}
			if (bOutputContainsText(pAnchor)) {
				OUTPUT_LINE();
			}
			vEmptyLine2Diagram(pDiag,
					pOutput->tFontRef,
					pOutput->sFontsize);
			break;
		case FORM_FEED:
		case COLUMN_FEED:
			/* Already dealt with */
			break;
		case TABLE_SEPARATOR:
			if (bIsTableRow) {
				vStoreCharacter(ulChar, pOutput);
				break;
			}
			vStoreCharacter(' ', pOutput);
			vStoreCharacter(TABLE_SEPARATOR_CHAR, pOutput);
			break;
		case TAB:
			if (bIsTableRow) {
				vStoreCharacter(' ', pOutput);
				break;
			}
			if (tOptions.iParagraphBreak == 0 &&
			    !tOptions.bUseOutlineFonts) {
				/* No logical lines, so no tab expansion */
				vStoreCharacter(TAB, pOutput);
				break;
			}
			lTmp = lTotalStringWidth(pAnchor);
			lTmp += lDrawUnits2MilliPoints(pDiag->lXleft);
			lTmp /= lDefaultTabWidth;
			do {
				vStoreCharacter(FILLER_CHAR, pOutput);
				lWidthCurr = lTotalStringWidth(pAnchor);
				lWidthCurr +=
					lDrawUnits2MilliPoints(pDiag->lXleft);
			} while (lTmp == lWidthCurr / lDefaultTabWidth &&
				 lWidthCurr < lWidthMax + lRightIndentation);
			break;
		default:
			if (bHiddenText && tOptions.bHideHiddenText) {
				continue;
			}
			if (bAllCapitals) {
				ulChar = ulToUpper(ulChar);
			}
			vStoreCharacter(ulChar, pOutput);
			break;
		}

		if (bWasTableRow && !bIsTableRow) {
			/* End of a table, resume normal font */
			NO_DBG_MSG("End of table font");
			vCloseFont();
			bTableFontClosed = TRUE;
			pOutput->iColor = ucFontcolor;
			pOutput->ucFontstyle = ucFontstyle;
			pOutput->sFontsize = sFontsize;
			pOutput->tFontRef =
				tOpenFont(ucFontnumber, ucFontstyle, sFontsize);
		}
		bWasTableRow = bIsTableRow;

		if (bIsTableRow) {
			fail(pAnchor != pOutput);
			if (!bEndRowNorm && !bEndRowFast) {
				continue;
			}
			/* End of a table row */
			if (bEndRowNorm) {
				fail(pRowInfo == NULL);
				vTableRow2Window(pDiag, pAnchor, pRowInfo);
			} else {
				fail(!bEndRowFast);
			}
			/* Reset */
			pAnchor = pStartNewOutput(pAnchor, NULL);
			pOutput = pAnchor;
			if (bEndRowNorm) {
				pRowInfo = pGetNextRowInfoListItem();
			}
			bIsTableRow = FALSE;
			bEndRowNorm = FALSE;
			bEndRowFast = FALSE;
			DBG_HEX_C(pRowInfo != NULL, pRowInfo->lFileOffsetStart);
			DBG_HEX_C(pRowInfo != NULL, pRowInfo->lFileOffsetEnd);
			continue;
		}
		lWidthCurr = lTotalStringWidth(pAnchor);
		lWidthCurr += lDrawUnits2MilliPoints(pDiag->lXleft);
		if (lWidthCurr < lWidthMax + lRightIndentation) {
			continue;
		}
		pLeftOver = pSplitList(pAnchor);
		vJustify2Window(pDiag, pAnchor,
				lWidthMax, lRightIndentation, ucAlignment);
		pAnchor = pStartNewOutput(pAnchor, pLeftOver);
		for (pOutput = pAnchor;
		     pOutput->pNext != NULL;
		     pOutput = pOutput->pNext)
			;	/* EMPTY */
		fail(pOutput == NULL);
		if (lTotalStringWidth(pAnchor) > 0) {
			vSetLeftIndentation(pDiag,
					lTwips2MilliPoints(iLeftIndent));
		}
	}

	pAnchor = pStartNewOutput(pAnchor, NULL);
	pAnchor->szStorage = xfree(pAnchor->szStorage);
	pAnchor = xfree(pAnchor);
	vCloseFont();
	vFreeDocument();
	visdelay_end();
} /* end of vWord2Text */
