/*
* blocklist.c
* Copyright (C) 1998-2005 A.J. van Os; Released under GNU GPL
*
* Description:
* Build, read and destroy the lists of Word "text" blocks
*/

#include <stdlib.h>
#include "antiword.h"


/*
* Private structure to hide the way the information
* is stored from the rest of the program
*/
typedef struct list_mem_tag {
       text_block_type         tInfo;
       struct list_mem_tag     *pNext;
} list_mem_type;

typedef struct readinfo_tag {
       list_mem_type           *pBlockCurrent;
       ULONG                   ulBlockOffset;
       size_t                  tByteNext;
       UCHAR                   aucBlock[BIG_BLOCK_SIZE];
} readinfo_type;

/* Variables to describe the start of the block lists */
static list_mem_type    *pTextAnchor = NULL;
static list_mem_type    *pFootnoteAnchor = NULL;
static list_mem_type    *pHdrFtrAnchor = NULL;
static list_mem_type    *pMacroAnchor = NULL;
static list_mem_type    *pAnnotationAnchor = NULL;
static list_mem_type    *pEndnoteAnchor = NULL;
static list_mem_type    *pTextBoxAnchor = NULL;
static list_mem_type    *pHdrTextBoxAnchor = NULL;
/* Variable needed to build the block list */
static list_mem_type    *pBlockLast = NULL;
/* Variable needed to read the block lists */
static readinfo_type    tOthers = { NULL, 0, 0, };
static readinfo_type    tHdrFtr = { NULL, 0, 0, };
static readinfo_type    tFootnote = { NULL, 0, 0, };


/*
* pFreeOneList - free a text block list
*
* Will always return NULL
*/
static list_mem_type *
pFreeOneList(list_mem_type *pAnchor)
{
       list_mem_type   *pCurr, *pNext;

       pCurr = pAnchor;
       while (pCurr != NULL) {
               pNext = pCurr->pNext;
               pCurr = xfree(pCurr);
               pCurr = pNext;
       }
       return NULL;
} /* end of pFreeOneList */

/*
* vDestroyTextBlockList - destroy the text block lists
*/
void
vDestroyTextBlockList(void)
{
       DBG_MSG("vDestroyTextBlockList");

       /* Free the lists one by one */
       pTextAnchor = pFreeOneList(pTextAnchor);
       pFootnoteAnchor = pFreeOneList(pFootnoteAnchor);
       pHdrFtrAnchor = pFreeOneList(pHdrFtrAnchor);
       pMacroAnchor = pFreeOneList(pMacroAnchor);
       pAnnotationAnchor = pFreeOneList(pAnnotationAnchor);
       pEndnoteAnchor = pFreeOneList(pEndnoteAnchor);
       pTextBoxAnchor = pFreeOneList(pTextBoxAnchor);
       pHdrTextBoxAnchor = pFreeOneList(pHdrTextBoxAnchor);
       /* Reset all the controle variables */
       pBlockLast = NULL;
       tOthers.pBlockCurrent = NULL;
       tHdrFtr.pBlockCurrent = NULL;
       tFootnote.pBlockCurrent = NULL;
} /* end of vDestroyTextBlockList */

/*
* bAdd2TextBlockList - add an element to the text block list
*
* returns: TRUE when successful, otherwise FALSE
*/
BOOL
bAdd2TextBlockList(const text_block_type *pTextBlock)
{
       list_mem_type   *pListMember;

       fail(pTextBlock == NULL);
       fail(pTextBlock->ulFileOffset == FC_INVALID);
       fail(pTextBlock->ulCharPos == CP_INVALID);
       fail(pTextBlock->ulLength == 0);
       fail(pTextBlock->bUsesUnicode && odd(pTextBlock->ulLength));

       NO_DBG_MSG("bAdd2TextBlockList");
       NO_DBG_HEX(pTextBlock->ulFileOffset);
       NO_DBG_HEX(pTextBlock->ulCharPos);
       NO_DBG_HEX(pTextBlock->ulLength);
       NO_DBG_DEC(pTextBlock->bUsesUnicode);
       NO_DBG_DEC(pTextBlock->usPropMod);

       if (pTextBlock->ulFileOffset == FC_INVALID ||
           pTextBlock->ulCharPos == CP_INVALID ||
           pTextBlock->ulLength == 0 ||
           (pTextBlock->bUsesUnicode && odd(pTextBlock->ulLength))) {
               werr(0, "Software (textblock) error");
               return FALSE;
       }
       /*
        * Check for continuous blocks of the same character size and
        * the same properties modifier
        */
       if (pBlockLast != NULL &&
           pBlockLast->tInfo.ulFileOffset +
            pBlockLast->tInfo.ulLength == pTextBlock->ulFileOffset &&
           pBlockLast->tInfo.ulCharPos +
            pBlockLast->tInfo.ulLength == pTextBlock->ulCharPos &&
           pBlockLast->tInfo.bUsesUnicode == pTextBlock->bUsesUnicode &&
           pBlockLast->tInfo.usPropMod == pTextBlock->usPropMod) {
               /* These are continous blocks */
               pBlockLast->tInfo.ulLength += pTextBlock->ulLength;
               return TRUE;
       }
       /* Make a new block */
       pListMember = xmalloc(sizeof(list_mem_type));
       /* Add the block to the list */
       pListMember->tInfo = *pTextBlock;
       pListMember->pNext = NULL;
       if (pTextAnchor == NULL) {
               pTextAnchor = pListMember;
       } else {
               fail(pBlockLast == NULL);
               pBlockLast->pNext = pListMember;
       }
       pBlockLast = pListMember;
       return TRUE;
} /* end of bAdd2TextBlockList */

/*
* vSpitList - Split the list in two
*/
static void
vSpitList(list_mem_type **ppAnchorCurr, list_mem_type **ppAnchorNext,
       ULONG ulListLen)
{
       list_mem_type   *pCurr;
       long            lCharsToGo, lBytesTooFar;

       fail(ppAnchorCurr == NULL);
       fail(ppAnchorNext == NULL);
       fail(ulListLen > (ULONG)LONG_MAX);

       pCurr = NULL;
       lCharsToGo = (long)ulListLen;
       lBytesTooFar = -1;
       if (ulListLen != 0) {
               DBG_DEC(ulListLen);
               for (pCurr = *ppAnchorCurr;
                    pCurr != NULL;
                    pCurr = pCurr->pNext) {
                       NO_DBG_DEC(pCurr->tInfo.ulLength);
                       fail(pCurr->tInfo.ulLength == 0);
                       fail(pCurr->tInfo.ulLength > (ULONG)LONG_MAX);
                       if (pCurr->tInfo.bUsesUnicode) {
                               fail(odd(pCurr->tInfo.ulLength));
                               lCharsToGo -= (long)(pCurr->tInfo.ulLength / 2);
                               if (lCharsToGo < 0) {
                                       lBytesTooFar = -2 * lCharsToGo;
                               }
                       } else {
                               lCharsToGo -= (long)pCurr->tInfo.ulLength;
                               if (lCharsToGo < 0) {
                                       lBytesTooFar = -lCharsToGo;
                               }
                       }
                       if (lCharsToGo <= 0) {
                               break;
                       }
               }
       }
/* Split the list */
       if (ulListLen == 0) {
               /* Current blocklist is empty */
               *ppAnchorNext = *ppAnchorCurr;
               *ppAnchorCurr = NULL;
       } else if (pCurr == NULL) {
               /* No blocks for the next list */
               *ppAnchorNext = NULL;
       } else if (lCharsToGo == 0) {
               /* Move the integral number of blocks to the next list */
               *ppAnchorNext = pCurr->pNext;
               pCurr->pNext = NULL;
       } else {
               /* Split the part current block list, part next block list */
               DBG_DEC(lBytesTooFar);
               fail(lBytesTooFar <= 0);
               *ppAnchorNext = xmalloc(sizeof(list_mem_type));
               DBG_HEX(pCurr->tInfo.ulFileOffset);
               (*ppAnchorNext)->tInfo.ulFileOffset =
                               pCurr->tInfo.ulFileOffset +
                               pCurr->tInfo.ulLength -
                               lBytesTooFar;
               DBG_HEX((*ppAnchorNext)->tInfo.ulFileOffset);
               DBG_HEX(pCurr->tInfo.ulCharPos);
               (*ppAnchorNext)->tInfo.ulCharPos =
                               pCurr->tInfo.ulCharPos +
                               pCurr->tInfo.ulLength -
                               lBytesTooFar;
               DBG_HEX((*ppAnchorNext)->tInfo.ulCharPos);
               (*ppAnchorNext)->tInfo.ulLength = (ULONG)lBytesTooFar;
               pCurr->tInfo.ulLength -= (ULONG)lBytesTooFar;
               (*ppAnchorNext)->tInfo.bUsesUnicode = pCurr->tInfo.bUsesUnicode;
               (*ppAnchorNext)->tInfo.usPropMod = pCurr->tInfo.usPropMod;
               /* Move the integral number of blocks to the next list */
               (*ppAnchorNext)->pNext = pCurr->pNext;
               pCurr->pNext = NULL;
       }
} /* end of vSpitList */

#if defined(DEBUG) || defined(__riscos)
/*
* ulComputeListLength - compute the length of a list
*
* returns the list length in characters
*/
static ULONG
ulComputeListLength(const list_mem_type *pAnchor)
{
       const list_mem_type     *pCurr;
       ULONG           ulTotal;

       ulTotal = 0;
       for (pCurr = pAnchor; pCurr != NULL; pCurr = pCurr->pNext) {
               fail(pCurr->tInfo.ulLength == 0);
               if (pCurr->tInfo.bUsesUnicode) {
                       fail(odd(pCurr->tInfo.ulLength));
                       ulTotal += pCurr->tInfo.ulLength / 2;
               } else {
                       ulTotal += pCurr->tInfo.ulLength;
               }
       }
       return ulTotal;
} /* end of ulComputeListLength */
#endif /* DEBUG || __riscos */

#if defined(DEBUG)
/*
* vCheckList - check the number of bytes in a block list
*/
static void
vCheckList(const list_mem_type *pAnchor, ULONG ulListLen, char *szMsg)
{
       ULONG           ulTotal;

       ulTotal = ulComputeListLength(pAnchor);
       DBG_DEC(ulTotal);
       if (ulTotal != ulListLen) {
               DBG_DEC(ulListLen);
               werr(1, szMsg);
       }
} /* end of vCheckList */
#endif /* DEBUG */

/*
* bIsEmptyBox - check to see if the given text box is empty
*/
static BOOL
bIsEmptyBox(FILE *pFile, const list_mem_type *pAnchor)
{
       const list_mem_type     *pCurr;
       size_t  tIndex, tSize;
       UCHAR   *aucBuffer;
       char    cChar;

       fail(pFile == NULL);

       if (pAnchor == NULL) {
               return TRUE;
       }

       aucBuffer = NULL;
       for (pCurr = pAnchor; pCurr != NULL; pCurr = pCurr->pNext) {
               fail(pCurr->tInfo.ulLength == 0);
               tSize = (size_t)pCurr->tInfo.ulLength;
#if defined(__dos) && !defined(__DJGPP__)
               if (pCurr->tInfo.ulLength > 0xffffUL) {
                       tSize = 0xffff;
               }
#endif /* __dos && !__DJGPP__ */
               fail(aucBuffer != NULL);
               aucBuffer = xmalloc(tSize);
               if (!bReadBytes(aucBuffer, tSize,
                               pCurr->tInfo.ulFileOffset, pFile)) {
                       aucBuffer = xfree(aucBuffer);
                       return FALSE;
               }
               for (tIndex = 0; tIndex < tSize; tIndex++) {
                       cChar = (char)aucBuffer[tIndex];
                       switch (cChar) {
                       case '\0': case '\r': case '\n':
                       case '\f': case '\t': case '\v':
                       case ' ':
                               break;
                       default:
                               aucBuffer = xfree(aucBuffer);
                               return FALSE;
                       }
               }
               aucBuffer = xfree(aucBuffer);
       }
       fail(aucBuffer != NULL);
       return TRUE;
} /* end of bIsEmptyBox */

/*
* vSplitBlockList - split the block list in the various parts
*
* Split the blocklist in a Text block list, a Footnote block list, a
* HeaderFooter block list, a Macro block list, an Annotation block list,
* an Endnote block list, a TextBox list and a HeaderTextBox list.
*
* NOTE:
* The various ul*Len input parameters are given in characters, but the
* length of the blocks are in bytes.
*/
void
vSplitBlockList(FILE *pFile, ULONG ulTextLen, ULONG ulFootnoteLen,
       ULONG ulHdrFtrLen, ULONG ulMacroLen, ULONG ulAnnotationLen,
       ULONG ulEndnoteLen, ULONG ulTextBoxLen, ULONG ulHdrTextBoxLen,
       BOOL bMustExtend)
{
       list_mem_type   *apAnchors[8];
       list_mem_type   *pGarbageAnchor, *pCurr;
       size_t          tIndex;

       DBG_MSG("vSplitBlockList");

       pGarbageAnchor = NULL;

       DBG_MSG_C(ulTextLen != 0, "Text block list");
       vSpitList(&pTextAnchor, &pFootnoteAnchor, ulTextLen);
       DBG_MSG_C(ulFootnoteLen != 0, "Footnote block list");
       vSpitList(&pFootnoteAnchor, &pHdrFtrAnchor, ulFootnoteLen);
       DBG_MSG_C(ulHdrFtrLen != 0, "Header/Footer block list");
       vSpitList(&pHdrFtrAnchor, &pMacroAnchor, ulHdrFtrLen);
       DBG_MSG_C(ulMacroLen != 0, "Macro block list");
       vSpitList(&pMacroAnchor, &pAnnotationAnchor, ulMacroLen);
       DBG_MSG_C(ulAnnotationLen != 0, "Annotation block list");
       vSpitList(&pAnnotationAnchor, &pEndnoteAnchor, ulAnnotationLen);
       DBG_MSG_C(ulEndnoteLen != 0, "Endnote block list");
       vSpitList(&pEndnoteAnchor, &pTextBoxAnchor, ulEndnoteLen);
       DBG_MSG_C(ulTextBoxLen != 0, "Textbox block list");
       vSpitList(&pTextBoxAnchor, &pHdrTextBoxAnchor, ulTextBoxLen);
       DBG_MSG_C(ulHdrTextBoxLen != 0, "HeaderTextbox block list");
       vSpitList(&pHdrTextBoxAnchor, &pGarbageAnchor, ulHdrTextBoxLen);

       /* Free the garbage block list, this should not be needed */
       DBG_DEC_C(pGarbageAnchor != NULL, pGarbageAnchor->tInfo.ulLength);
       pGarbageAnchor = pFreeOneList(pGarbageAnchor);

#if defined(DEBUG)
       vCheckList(pTextAnchor, ulTextLen, "Software error (Text)");
       vCheckList(pFootnoteAnchor, ulFootnoteLen, "Software error (Footnote)");
       vCheckList(pHdrFtrAnchor, ulHdrFtrLen, "Software error (Hdr/Ftr)");
       vCheckList(pMacroAnchor, ulMacroLen, "Software error (Macro)");
       vCheckList(pAnnotationAnchor, ulAnnotationLen,
                                               "Software error (Annotation)");
       vCheckList(pEndnoteAnchor, ulEndnoteLen, "Software error (Endnote)");
       vCheckList(pTextBoxAnchor, ulTextBoxLen, "Software error (TextBox)");
       vCheckList(pHdrTextBoxAnchor, ulHdrTextBoxLen,
                                               "Software error (HdrTextBox)");
#endif /* DEBUG */

       /* Remove the list if the text box is empty */
       if (bIsEmptyBox(pFile, pTextBoxAnchor)) {
               pTextBoxAnchor = pFreeOneList(pTextBoxAnchor);
       }
       if (bIsEmptyBox(pFile, pHdrTextBoxAnchor)) {
               pHdrTextBoxAnchor = pFreeOneList(pHdrTextBoxAnchor);
       }

       if (!bMustExtend) {
               return;
       }
       /*
        * All blocks (except the last one) must have a length that
        * is a multiple of the Big Block Size
        */

       apAnchors[0] = pTextAnchor;
       apAnchors[1] = pFootnoteAnchor;
       apAnchors[2] = pHdrFtrAnchor;
       apAnchors[3] = pMacroAnchor;
       apAnchors[4] = pAnnotationAnchor;
       apAnchors[5] = pEndnoteAnchor;
       apAnchors[6] = pTextBoxAnchor;
       apAnchors[7] = pHdrTextBoxAnchor;

       for (tIndex = 0; tIndex < elementsof(apAnchors); tIndex++) {
               for (pCurr = apAnchors[tIndex];
                    pCurr != NULL;
                    pCurr = pCurr->pNext) {
                       if (pCurr->pNext != NULL &&
                           pCurr->tInfo.ulLength % BIG_BLOCK_SIZE != 0) {
                               DBG_DEC(tIndex);
                               DBG_HEX(pCurr->tInfo.ulFileOffset);
                               DBG_HEX(pCurr->tInfo.ulCharPos);
                               DBG_DEC(pCurr->tInfo.ulLength);
                               pCurr->tInfo.ulLength /= BIG_BLOCK_SIZE;
                               pCurr->tInfo.ulLength++;
                               pCurr->tInfo.ulLength *= BIG_BLOCK_SIZE;
                               DBG_DEC(pCurr->tInfo.ulLength);
                       }
               }
       }
} /* end of vSplitBlockList */

#if defined(__riscos)
/*
* ulGetDocumentLength - get the total character length of the printable lists
*
* returns: The total number of characters
*/
ULONG
ulGetDocumentLength(void)
{
       long            ulTotal;

       DBG_MSG("ulGetDocumentLength");

       ulTotal = ulComputeListLength(pTextAnchor);
       ulTotal += ulComputeListLength(pFootnoteAnchor);
       ulTotal += ulComputeListLength(pEndnoteAnchor);
       ulTotal += ulComputeListLength(pTextBoxAnchor);
       ulTotal += ulComputeListLength(pHdrTextBoxAnchor);
       DBG_DEC(ulTotal);
       return ulTotal;
} /* end of ulGetDocumentLength */
#endif /* __riscos */

#if 0
/*
* bExistsHdrFtr - are there headers and/or footers?
*/
BOOL
bExistsHdrFtr(void)
{
       return pHdrFtrAnchor != NULL &&
               pHdrFtrAnchor->tInfo.ulLength != 0;
} /* end of bExistsHdrFtr */
#endif

/*
* bExistsTextBox - is there a text box?
*/
BOOL
bExistsTextBox(void)
{
       return pTextBoxAnchor != NULL &&
               pTextBoxAnchor->tInfo.ulLength != 0;
} /* end of bExistsTextBox */

/*
* bExistsHdrTextBox - is there a header text box?
*/
BOOL
bExistsHdrTextBox(void)
{
       return pHdrTextBoxAnchor != NULL &&
               pHdrTextBoxAnchor->tInfo.ulLength != 0;
} /* end of bExistsHdrTextBox */

/*
* usGetNextByte - get the next byte from the specified block list
*/
static USHORT
usGetNextByte(FILE *pFile, readinfo_type *pInfoCurrent, list_mem_type *pAnchor,
       ULONG *pulFileOffset, ULONG *pulCharPos, USHORT *pusPropMod)
{
       ULONG   ulReadOff;
       size_t  tReadLen;

       fail(pInfoCurrent == NULL);

       if (pInfoCurrent->pBlockCurrent == NULL ||
           pInfoCurrent->tByteNext >= sizeof(pInfoCurrent->aucBlock) ||
           pInfoCurrent->ulBlockOffset + pInfoCurrent->tByteNext >=
                               pInfoCurrent->pBlockCurrent->tInfo.ulLength) {
               if (pInfoCurrent->pBlockCurrent == NULL) {
                       /* First block, first part */
                       pInfoCurrent->pBlockCurrent = pAnchor;
                       pInfoCurrent->ulBlockOffset = 0;
               } else if (pInfoCurrent->ulBlockOffset +
                               sizeof(pInfoCurrent->aucBlock) <
                               pInfoCurrent->pBlockCurrent->tInfo.ulLength) {
                       /* Same block, next part */
                       pInfoCurrent->ulBlockOffset +=
                                       sizeof(pInfoCurrent->aucBlock);
               } else {
                       /* Next block, first part */
                       pInfoCurrent->pBlockCurrent =
                                       pInfoCurrent->pBlockCurrent->pNext;
                       pInfoCurrent->ulBlockOffset = 0;
               }
               if (pInfoCurrent->pBlockCurrent == NULL) {
                       /* Past the last part of the last block */
                       return (USHORT)EOF;
               }
               tReadLen = (size_t)
                       (pInfoCurrent->pBlockCurrent->tInfo.ulLength -
                        pInfoCurrent->ulBlockOffset);
               if (tReadLen > sizeof(pInfoCurrent->aucBlock)) {
                       tReadLen = sizeof(pInfoCurrent->aucBlock);
               }
               ulReadOff = pInfoCurrent->pBlockCurrent->tInfo.ulFileOffset +
                               pInfoCurrent->ulBlockOffset;
               if (!bReadBytes(pInfoCurrent->aucBlock,
                                               tReadLen, ulReadOff, pFile)) {
                       /* Don't read from this list any longer */
                       pInfoCurrent->pBlockCurrent = NULL;
                       return (USHORT)EOF;
               }
               pInfoCurrent->tByteNext = 0;
       }
       if (pulFileOffset != NULL) {
               *pulFileOffset =
                       pInfoCurrent->pBlockCurrent->tInfo.ulFileOffset +
                       pInfoCurrent->ulBlockOffset +
                       pInfoCurrent->tByteNext;
       }
       if (pulCharPos != NULL) {
               *pulCharPos =
                       pInfoCurrent->pBlockCurrent->tInfo.ulCharPos +
                       pInfoCurrent->ulBlockOffset +
                       pInfoCurrent->tByteNext;
       }
       if (pusPropMod != NULL) {
               *pusPropMod = pInfoCurrent->pBlockCurrent->tInfo.usPropMod;
       }
       return (USHORT)pInfoCurrent->aucBlock[pInfoCurrent->tByteNext++];
} /* end of usGetNextByte */


/*
* usGetNextChar - get the next character from the specified block list
*/
static USHORT
usGetNextChar(FILE *pFile, list_id_enum eListID,
       ULONG *pulFileOffset, ULONG *pulCharPos, USHORT *pusPropMod)
{
       readinfo_type   *pReadinfo;
       list_mem_type   *pAnchor;
       USHORT  usLSB, usMSB;

       switch (eListID) {
       case text_list:
               pReadinfo = &tOthers;
               pAnchor = pTextAnchor;
               break;
       case footnote_list:
               pReadinfo = &tFootnote;
               pAnchor = pFootnoteAnchor;
               break;
       case hdrftr_list:
               pReadinfo = &tHdrFtr;
               pAnchor = pHdrFtrAnchor;
               break;
       case endnote_list:
               pReadinfo = &tOthers;
               pAnchor = pEndnoteAnchor;
               break;
       case textbox_list:
               pReadinfo = &tOthers;
               pAnchor = pTextBoxAnchor;
               break;
       case hdrtextbox_list:
               pReadinfo = &tOthers;
               pAnchor = pHdrTextBoxAnchor;
               break;
       default:
               DBG_DEC(eListID);
               return (USHORT)EOF;
       }

       usLSB = usGetNextByte(pFile, pReadinfo, pAnchor,
                               pulFileOffset, pulCharPos, pusPropMod);
       if (usLSB == (USHORT)EOF) {
               return (USHORT)EOF;
       }
       fail(pReadinfo->pBlockCurrent == NULL);

       if (pReadinfo->pBlockCurrent->tInfo.bUsesUnicode) {
               usMSB = usGetNextByte(pFile,
                               pReadinfo, pAnchor, NULL, NULL, NULL);
       } else {
               usMSB = 0x00;
       }
       if (usMSB == (USHORT)EOF) {
               DBG_MSG("usGetNextChar: Unexpected EOF");
               DBG_HEX_C(pulFileOffset != NULL, *pulFileOffset);
               DBG_HEX_C(pulCharPos != NULL, *pulCharPos);
               return (USHORT)EOF;
       }
       return (usMSB << 8) | usLSB;
} /* end of usGetNextChar */

/*
* usNextChar - get the next character from the given block list
*/
USHORT
usNextChar(FILE *pFile, list_id_enum eListID,
       ULONG *pulFileOffset, ULONG *pulCharPos, USHORT *pusPropMod)
{
       USHORT  usRetVal;

       fail(pFile == NULL);

       usRetVal = usGetNextChar(pFile, eListID,
                               pulFileOffset, pulCharPos, pusPropMod);
       if (usRetVal == (USHORT)EOF) {
               if (pulFileOffset != NULL) {
                       *pulFileOffset = FC_INVALID;
               }
               if (pulCharPos != NULL) {
                       *pulCharPos = CP_INVALID;
               }
               if (pusPropMod != NULL) {
                       *pusPropMod = IGNORE_PROPMOD;
               }
       }
       return usRetVal;
} /* end of usNextChar */

/*
* usToHdrFtrPosition - Go to a character position in header/foorter list
*
* Returns the character found on the specified character position
*/
USHORT
usToHdrFtrPosition(FILE *pFile, ULONG ulCharPos)
{
       ULONG   ulCharPosCurr;
       USHORT  usChar;

       tHdrFtr.pBlockCurrent = NULL;   /* To reset the header/footer list */
       do {
               usChar = usNextChar(pFile,
                               hdrftr_list, NULL, &ulCharPosCurr, NULL);
       } while (usChar != (USHORT)EOF && ulCharPosCurr != ulCharPos);
       return usChar;
} /* end of usToHdrFtrPosition */

/*
* usToFootnotePosition - Go to a character position in footnote list
*
* Returns the character found on the specified character position
*/
USHORT
usToFootnotePosition(FILE *pFile, ULONG ulCharPos)
{
       ULONG   ulCharPosCurr;
       USHORT  usChar;

       tFootnote.pBlockCurrent = NULL; /* To reset the footnote list */
       do {
               usChar = usNextChar(pFile,
                               footnote_list, NULL, &ulCharPosCurr, NULL);
       } while (usChar != (USHORT)EOF && ulCharPosCurr != ulCharPos);
       return usChar;
} /* end of usToFootnotePosition */

/*
* Convert a character position to an offset in the file.
* Logical to physical offset.
*
* Returns:     FC_INVALID: in case of error
*              otherwise: the computed file offset
*/
ULONG
ulCharPos2FileOffsetX(ULONG ulCharPos, list_id_enum *peListID)
{
       static list_id_enum     eListIDs[8] = {
               text_list,      footnote_list,          hdrftr_list,
               macro_list,     annotation_list,        endnote_list,
               textbox_list,   hdrtextbox_list,
       };
       list_mem_type   *apAnchors[8];
       list_mem_type   *pCurr;
       list_id_enum    eListGuess;
       ULONG           ulBestGuess;
       size_t          tIndex;

       fail(peListID == NULL);

       if (ulCharPos == CP_INVALID) {
               *peListID = no_list;
               return FC_INVALID;
       }

       apAnchors[0] = pTextAnchor;
       apAnchors[1] = pFootnoteAnchor;
       apAnchors[2] = pHdrFtrAnchor;
       apAnchors[3] = pMacroAnchor;
       apAnchors[4] = pAnnotationAnchor;
       apAnchors[5] = pEndnoteAnchor;
       apAnchors[6] = pTextBoxAnchor;
       apAnchors[7] = pHdrTextBoxAnchor;

       eListGuess = no_list;     /* Best guess is no list */
       ulBestGuess = FC_INVALID; /* Best guess is "file offset not found" */

       for (tIndex = 0; tIndex < elementsof(apAnchors); tIndex++) {
               for (pCurr = apAnchors[tIndex];
                    pCurr != NULL;
                    pCurr = pCurr->pNext) {
                       if (ulCharPos == pCurr->tInfo.ulCharPos +
                            pCurr->tInfo.ulLength &&
                           pCurr->pNext != NULL) {
                               /*
                                * The character position is one beyond this
                                * block, so we guess it's the first byte of
                                * the next block (if there is a next block)
                                */
                               eListGuess= eListIDs[tIndex];
                               ulBestGuess = pCurr->pNext->tInfo.ulFileOffset;
                       }

                       if (ulCharPos < pCurr->tInfo.ulCharPos ||
                           ulCharPos >= pCurr->tInfo.ulCharPos +
                            pCurr->tInfo.ulLength) {
                               /* Character position is not in this block */
                               continue;
                       }

                       /* The character position is in the current block */
                       *peListID = eListIDs[tIndex];
                       return pCurr->tInfo.ulFileOffset +
                               ulCharPos - pCurr->tInfo.ulCharPos;
               }
       }
       /* Passed beyond the end of the last list */
       NO_DBG_HEX(ulCharPos);
       NO_DBG_HEX(ulBestGuess);
       *peListID = eListGuess;
       return ulBestGuess;
} /* end of ulCharPos2FileOffsetX */

/*
* Convert a character position to an offset in the file.
* Logical to physical offset.
*
* Returns:     FC_INVALID: in case of error
*              otherwise: the computed file offset
*/
ULONG
ulCharPos2FileOffset(ULONG ulCharPos)
{
       list_id_enum    eListID;

       return ulCharPos2FileOffsetX(ulCharPos, &eListID);
} /* end of ulCharPos2FileOffset */

/*
* Convert an offset in the header/footer list to a character position.
*
* Returns:     CP_INVALID: in case of error
*              otherwise: the computed character position
*/
ULONG
ulHdrFtrOffset2CharPos(ULONG ulHdrFtrOffset)
{
       list_mem_type   *pCurr;
       ULONG           ulOffset;

       ulOffset = ulHdrFtrOffset;
       for (pCurr = pHdrFtrAnchor; pCurr != NULL; pCurr = pCurr->pNext) {
               if (ulOffset >= pCurr->tInfo.ulLength) {
                       /* The offset is not in this block */
                       ulOffset -= pCurr->tInfo.ulLength;
                       continue;
               }
               return pCurr->tInfo.ulCharPos + ulOffset;
       }
       return CP_INVALID;
} /* end of ulHdrFtrOffset2CharPos */

/*
* Get the sequence number beloning to the given file offset
*
* Returns the sequence number
*/
ULONG
ulGetSeqNumber(ULONG ulFileOffset)
{
       list_mem_type   *pCurr;
       ULONG           ulSeq;

       if (ulFileOffset == FC_INVALID) {
               return FC_INVALID;
       }

       ulSeq = 0;
       for (pCurr = pTextAnchor; pCurr != NULL; pCurr = pCurr->pNext) {
               if (ulFileOffset >= pCurr->tInfo.ulFileOffset &&
                   ulFileOffset < pCurr->tInfo.ulFileOffset +
                    pCurr->tInfo.ulLength) {
                       /* The file offset is within the current textblock */
                       return ulSeq + ulFileOffset - pCurr->tInfo.ulFileOffset;
               }
               ulSeq += pCurr->tInfo.ulLength;
       }
       return FC_INVALID;
} /* end of ulGetSeqNumber */