/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: textline.cpp,v 1.1.2.1 2004/07/09 01:50:20 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

/////////////////////////////////////////////////////////////////////////////
//
//  textline.cpp
//
//  class TextLine methods.
//


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "hxtypes.h"
#include "hxslist.h" //for base class CHXSimpleList.
#include "hxstack.h" //for base class CHXStack.

#include "rt_types.h"
#include "fontdefs.h"
#include "fontinfo.h"
#include "txtattrb.h"
#include "txtcntnr.h"
#include "textline.h"

#include "hxstrutl.h"

#include "parsing.h"

#include "netbyte.h"

#ifdef _WINDOWS
#ifdef _WIN16
#include <windows.h>
#endif /* _WIN16 */
#endif /* _WINDOWS */

#include "txtwindw.h"

#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE		
static char HX_THIS_FILE[] = __FILE__;
#endif


/////////////////////////////////////////////////////////////////////////////
//
// Constructor:
//
// Note: if ulEndByteInFile is 0, this means it's not yet known and must be
// set later.
//
TextLine::TextLine(TextLine& refTL) :
	TextAttributes(refTL),
	m_ulLineNumOfFile(0L)
	, m_ulStartByteInFile(0L)
	, m_ulEndByteInFile(0L)
	, m_ulTimeOfLastClear(TIME_INVALID)
	, m_ulStartByteOfMatchingFontTagInFile(0L)
	, m_bSomethingChanged(TRUE)
{
    //let the default copy constructor do its thing (memcopy the members of
    // refTL into *this).  There are no members that are pointers, so this
    // will work just fine.
}


//This constructor allows us to create a font-attribute only T.L. for
// keeping track of what the state of the font stacks is right after
// a </FONT..> tag is seen:
TextLine::TextLine(
	ULONG32 ulStartByteOfFontUndoTagInFile,
	ULONG32 ulEndByteOfFontUndoTagInFile,
	ULONG32 ulStartByteOfMatchingFontTagInFile,
	ULONG32 ulFontPointSize,
	COLORTYPE ulTextColor,
	COLORTYPE ulTextBGColor,
	ULONG32 ulFontCharset,
	ULONG32 ulFontFace,
	ULONG32 ulMinorContentVersion,
	ULONG32 ulMajorContentVersion)
{
    m_ulStartByteInFile = ulStartByteOfFontUndoTagInFile;
    m_ulEndByteInFile = ulEndByteOfFontUndoTagInFile;
    m_ulStartByteOfMatchingFontTagInFile =ulStartByteOfMatchingFontTagInFile;
    setFontPointSize(ulFontPointSize);
    setTextColor(ulTextColor);
    setTextBackgroundColor(ulTextBGColor);
    setFontCharset(ulFontCharset); 
    setFontFace(ulFontFace);
}



/////////////////////////////////////////////////////////////////////////////
//
// Decides what's different from the default vals and adds it to what
// will be sent at the start of a packet so that everything in the
// packet gets rendered properly, even if prior packet was lost.
// Fills pPacketHeaderBuf, with these & other values, in marked-up text form.
//
// Note: if a val is the same as the default val for the stream, the
// fact that the val was not sent tells the renderer to use the default.
//
ULONG32 TextLine::OutputPacketHeaderString(
	ULONG32 ulDataID,
	TextWindow* pTextWin,
	BOOL bStateIsGetPacketSeekBackReadPending,
	char* pPacketHeaderBuf,	ULONG32 packetHeaderBufSize,
	TextLineList* pFontUndoTagList,
	ULONG32 ulPacketLen)
{
//Pkt hdr starts with <nnnn> where nnnn is a 4-digit, base 10 number telling
// the size of the entire header in bytes (so the renderer doesn't have to
// parse it to find the end):
#define NUM_DIGITS_IN_START_TAG_OF_PKT_HDR  4
//Add 2 for the '<' and '>':
#define LEN_OF_START_TAG_OF_PKT_HDR	NUM_DIGITS_IN_START_TAG_OF_PKT_HDR+2
//Add 1 more for the '/' (</nnnn> ends the header, but is not really needed):
#define LEN_OF_END_TAG_OF_PKT_HDR	LEN_OF_START_TAG_OF_PKT_HDR+1

#define MIN_TEMPBUF_LEN			128

    if(!pTextWin)
    {
	return 0;
    }
    
    ULONG32 ulTextWindowType = pTextWin->getType();
    BOOL bIsLiveSource = pTextWin->isLiveSource();
    
    if(packetHeaderBufSize <
	    LEN_OF_START_TAG_OF_PKT_HDR+LEN_OF_END_TAG_OF_PKT_HDR)
    {
	return 0L;
    }

    ULONG32 ulStartByteOfThisPacket = getStartByteInFile();
    ULONG32 ulEndByteOfThisPacket = ulStartByteOfThisPacket + ulPacketLen-1;

    ULONG32 HREFbufLen = getHrefBufLen();
    if(HREFbufLen  &&  !getHrefBuf())
    {
	HREFbufLen = 0L;
    }
    //This is needed for hrefs that have target="_player" (and possibly
    // other parameters to the <A> (anchor) tag in addition to the URL
    // specified in href="URL" string:
    ULONG32 ulExtraLen = 128;
    char* pTmp = new char[HREFbufLen+1>MIN_TEMPBUF_LEN?
	    ulExtraLen+HREFbufLen+1:ulExtraLen+MIN_TEMPBUF_LEN];
    if(!pTmp)
    {
	return 0L; //Mem alloc failed.
    }
    pTmp[0] = '\0';

#define TMP_FONT_INFO_BUF_SIZE	32
    char pTmpFontInfoBuf[TMP_FONT_INFO_BUF_SIZE]; /* Flawfinder: ignore */
    pTmpFontInfoBuf[0] = '\0';

    const char pFontIntroStr[] = "<FONT ";
    const char pTimeIntroStr[] = "<TIME ";
    BOOL bFontTagWasStarted = FALSE;
    BOOL bTimeTagWasStarted = FALSE;

    pPacketHeaderBuf[0] = '\0';
/// if(!bStateIsGetPacketSeekBackReadPending) //XXXEH-do this for ALL states?
    {
	//fill with NUM_DIGITS_IN_START_TAG_OF_PKT_HDR zeros for now:
	SafeStrCat(pPacketHeaderBuf, "<0000>", packetHeaderBufSize);
    }

    ULONG32 pktHdrLen = strlen(pPacketHeaderBuf) + LEN_OF_END_TAG_OF_PKT_HDR;
    ULONG32 ulSprintfLen = 0L;

    //These are an internal set of values used by the renderer.
    // (1) the <RESET> tag tells the renderer to kill all prior text whose
    // time|position is no longer valid.
    // (2) the <DATA ID= > is used by the renderer to know what data this
    // is (NOT what packet it is) because the live encoder may resend data
    // in a new packet and this ID identifies it so that the renderer can
    // ignore the packet if it has already seen the data with that ID.
    // (3) <POS X0= Y0= > is used by the renderer to figure out where to draw
    // the first data in this packet; X0 and Y0 are where to draw it at
    // time zero (or at time of last clear if <TIME LC=> is sent); the 
    // renderer then uses the scrollrate and crawlrate to figure out where
    // to draw this text at its starttime; 
    if(/* //XXXEH- RESET *all* data, not just back-seeking (...right???):
	    bStateIsGetPacketSeekBackReadPending  || */
	    !pTextWin->isLooping())
    {
	SafeStrCat(pPacketHeaderBuf, "<RESET>", packetHeaderBufSize);
	pktHdrLen = strlen(pPacketHeaderBuf) + LEN_OF_END_TAG_OF_PKT_HDR;
    }
    //For now, data ID must be non-zero if live, else zero:
    HX_ASSERT((ulDataID  &&  bIsLiveSource)  ||
	    (!ulDataID  &&  !bIsLiveSource));
    if(ulDataID) //(else is invalid ID so don't insert a DATA tag)
    {
	ulSprintfLen = sprintf(pTmp, "<DATA ID=%ld>", /* Flawfinder: ignore */
		ulDataID);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	}
    }
    
    //Do this before doing the "<POS ..>" below or else both will force
    // 2 line breaks for a total of 4, since POS already accounts for the
    // line breaks that the POS tag caused:
    if(isPreFormatted())
    {
	if(pktHdrLen+8 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<PRE>", packetHeaderBufSize);
	    pktHdrLen+=5;
	}
    }

    {
	ULONG32 ulNewLinesAtStart = getNumNewlinesAtStart();
	ulSprintfLen = sprintf(pTmp, "<POS X0=%ld Y0=%ld NEWLINES=%ld>", /* Flawfinder: ignore */
		getXAtTimeZeroUpperLeftCorner(),
		getYAtTimeZeroUpperLeftCorner(),
		ulNewLinesAtStart);
	//"NEWLINES=", above, tells renderer whether or not the text of this
	// TextLine, which is starting a packet, immediately followed a line
	// break (so the renderer can properly handle CENTERing this text,
	// which is not considered in the file format when it computes the
	// X-POS of the text); a TextLine does not necessarily start a new
	// line since it may be merely a piece of a line that was too large.

	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	}
    }

    COLORTYPE defaultTextColor = DEFAULT_TEXT_COLOR;
    COLORTYPE defaultTextBGColor = DEFAULT_TEXT_BGCOLOR;
    ULONG32 defaultFontPointSize = DEFAULT_FONT_PTSIZE;
    ULONG32 defaultFontCharset = CHARSET__default;
    ULONG32 defaultFontFaceIndex = DEFAULT_FONT_FACE_INDX;
    //Go through the pFontUndoTagList and see if any of them are between the
    // ulStartByteOfThisPacket and ulEndByteOfThisPacket of this packet and,
    // if so, prepend their values to the packet header:
    //XXXEH- a more efficient thing to do would be to only do this for any
    // </FONT> tags in this packet that do not have their matching <FONT>
    // tags preceding them in this packet, but we'll need to keep track of
    // where each <FONT> tag is first...
    //Search from the end of the pFontUndoTagList and look for the first
    // node whose startByteInFile is between ulStartByteOfThisPacket and
    // ulEndByteOfThisPacket:
    ULONG32 listSize;
    ULONG32 ulNumFontUndoTagsInThisPacket = 0L;
    if(pFontUndoTagList  &&  (listSize = pFontUndoTagList->size())>0L)
    {

	LISTPOSITION pos = pFontUndoTagList->GetEndPosition();

	while(pos)
	{
	    TextLine* pTL = (TextLine*)pFontUndoTagList->GetAt(pos);

	    HX_ASSERT_VALID_PTR(pTL);
	    if(pTL)
	    {
		//Added bIsLiveSource check because live version might not
		// keep track properly (since it's not necessarily reading
		// from a file) and the matching <FONT> may truly be AFTER
		// the </FONT> since each packet is considered to start at
		// position 0 of the virtual file:
		HX_ASSERT(bIsLiveSource? TRUE :
			pTL->getStartByteOfMatchingFontTagInFile() < 
			pTL->getStartByteInFile());

		BOOL bMatchingFontTagIsInAPreviousPacket = 
			pTL->getStartByteOfMatchingFontTagInFile() < 
			ulStartByteOfThisPacket;
		if(bIsLiveSource)
		{
		    //Note: live version might not keep track of locations in
		    // the "file" properly (since it is not necessarily
		    // reading from a file) and the matching <FONT> may truly
		    // be AFTER the </FONT> since each packet is considered
		    // to start at position 0 of the virtual file, e.g., a
		    // "<FONT>" at location 400 of the prior packet could be
		    // followed by the "</FONT>" at location 300 of the
		    // current packet:
		    if(pTL->getStartByteOfMatchingFontTagInFile() >= 
			    pTL->getStartByteInFile())
		    {
			bMatchingFontTagIsInAPreviousPacket = TRUE;
		    }
		}

		if(pTL->getStartByteInFile() < ulEndByteOfThisPacket  &&
			pTL->getEndByteInFile() > ulStartByteOfThisPacket  &&
			bMatchingFontTagIsInAPreviousPacket)
		{
		    ulNumFontUndoTagsInThisPacket++;

		    COLORTYPE textColor = pTL->getTextColor();
		    COLORTYPE textBGColor = pTL->getTextBackgroundColor();
		    ULONG32 ptSize = pTL->getFontPointSize();
		    ULONG32 fontCharset = pTL->getFontCharset();
		    ULONG32 fontFaceIndex = pTL->getFontFace();

		    if(textColor != BAD_RGB_COLOR)
		    {
			if(pTL->getSomethingChanged())
			{
			    defaultTextColor = textColor;
			}

			textColor = DwToNet(textColor);
			UCHAR* pc = (UCHAR *)&textColor;

			if(textBGColor != TRANSPARENT_COLOR)
			{
			    ulSprintfLen = sprintf(pTmp, /* Flawfinder: ignore */
				    "%scolor=#%02X%02X%02X",
				    bFontTagWasStarted?" ":pFontIntroStr,
				    pc[1],pc[2],pc[3]);
			}
			else
			{
			    ulSprintfLen = sprintf(pTmp, /* Flawfinder: ignore */
				    "%scolor=transparent",
				    bFontTagWasStarted?" ":pFontIntroStr);
			}

			if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
			{
			    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
			    pktHdrLen += ulSprintfLen;
			    bFontTagWasStarted = TRUE;
			}
		    }
		    if(textBGColor != BAD_RGB_COLOR)
		    {
			if(pTL->getSomethingChanged())
			{
			    defaultTextBGColor = textBGColor;
			}

			textBGColor = DwToNet(textBGColor);
			UCHAR* pc = (UCHAR *)&textBGColor;

			if(textBGColor != TRANSPARENT_COLOR)
			{
			    ulSprintfLen=sprintf(pTmp, /* Flawfinder: ignore */
				    "%sbgcolor=#%02X%02X%02X",
				    bFontTagWasStarted?" ":pFontIntroStr,
				    pc[1],pc[2],pc[3]);
			}
			else
			{
			    ulSprintfLen = sprintf(pTmp, /* Flawfinder: ignore */
				    "%sbgcolor=transparent",
				    bFontTagWasStarted?" ":pFontIntroStr);
			}

			if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
			{
			    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
			    pktHdrLen += ulSprintfLen;
			    bFontTagWasStarted = TRUE;
			}
		    }

		    if(ptSize != 0L)  //(0L is invalid pt size.)
		    {
			if(pTL->getSomethingChanged())
			{
			    defaultFontPointSize = ptSize;
			}

			getFontPointSizeStringFromPtSize(ptSize,
				pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE);
			ulSprintfLen = sprintf(pTmp, "%ssize=%s", /* Flawfinder: ignore */
				bFontTagWasStarted?" ":pFontIntroStr,
				pTmpFontInfoBuf);
			if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
			{
			    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
			    pktHdrLen += ulSprintfLen;
			    bFontTagWasStarted = TRUE;
			}
		    }

		    if(fontCharset != INVALID_CHARSET)
		    {
			getFontCharsetStringFromCharsetVal(fontCharset,
				pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE);
			ulSprintfLen = sprintf(pTmp, "%scharset=\"%s\"", /* Flawfinder: ignore */
				bFontTagWasStarted?" ":pFontIntroStr,
				pTmpFontInfoBuf);
			if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
			{
			    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
			    pktHdrLen += ulSprintfLen;
			    bFontTagWasStarted = TRUE;
			}
		    }

		    if(fontFaceIndex != INVALID_FONT_INDEX)
		    {
			getFontFaceStringFromFaceIndex(fontFaceIndex,
				pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE,
				pTextWin->getMajorContentVersion(),
				pTextWin->getMinorContentVersion());
			ulSprintfLen = sprintf(pTmp, "%sface=\"%s\"", /* Flawfinder: ignore */
				bFontTagWasStarted?" ":pFontIntroStr,
				pTmpFontInfoBuf);
			if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
			{
			    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
			    pktHdrLen += ulSprintfLen;
			    bFontTagWasStarted = TRUE;
			}
		    }

		    if(bFontTagWasStarted)
		    {
			SafeStrCat(pPacketHeaderBuf, ">", packetHeaderBufSize);
			pktHdrLen++;
		    }
		}
	    }

	    bFontTagWasStarted = FALSE;

	    pFontUndoTagList->GetPrev(pos);
	} //end "while(pos)".
    }
    bFontTagWasStarted = FALSE;
    
    //examine all of *this's vals against default vals and
    // append diffs, in text form, to pPacketHeaderBuf:
    COLORTYPE c = getTextColor();

    BOOL bAnchorTagShouldComeBeforeFontTag =
	    (getNumLinkColorOverrides() != 0);

    if(HREFbufLen  &&  bAnchorTagShouldComeBeforeFontTag)
    {
	ulSprintfLen = sprintf(pTmp, "<A HREF=\"%s\"%s>", getHrefBuf(), /* Flawfinder: ignore */
		 //Add target=_player if that's the case:
		 (getTargetOfURL()==URL_TARGET_PLAYER?
		 " target=\"_player\"":"") );
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	}
    }

    if(c != defaultTextColor)
    {
	if(TRANSPARENT_COLOR != c)
	{
	    c = DwToNet(c);
	    UCHAR* pc = (UCHAR *)&c;

	    ulSprintfLen = sprintf(pTmp, "%scolor=#%02X%02X%02X", /* Flawfinder: ignore */
		    bFontTagWasStarted?" ":pFontIntroStr,
		    pc[1],pc[2],pc[3]);
	}
	else
	{
	    ulSprintfLen = sprintf(pTmp, "%scolor=transparent", /* Flawfinder: ignore */
		    bFontTagWasStarted?" ":pFontIntroStr);
	}

	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bFontTagWasStarted = TRUE;
	}
    }

    COLORTYPE bgc = getTextBackgroundColor();
    if(bgc != defaultTextBGColor)
    {
	if(TRANSPARENT_COLOR != bgc)
	{
	    bgc = DwToNet(bgc);
	    UCHAR* pBgc = (UCHAR *)&bgc;

	    ulSprintfLen = sprintf(pTmp, "%sbgcolor=#%02X%02X%02X", /* Flawfinder: ignore */
		    bFontTagWasStarted?" ":pFontIntroStr,
		    pBgc[1],pBgc[2],pBgc[3]);
	}
	else
	{
	    ulSprintfLen = sprintf(pTmp, "%sbgcolor=transparent", /* Flawfinder: ignore */
		    bFontTagWasStarted?" ":pFontIntroStr);
	}

	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bFontTagWasStarted = TRUE;
	}
    }

    if(getFontPointSize() != defaultFontPointSize)
    {
	getFontPointSizeStringFromPtSize(getFontPointSize(),
		pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE);
	ulSprintfLen = sprintf(pTmp, "%ssize=%s", /* Flawfinder: ignore */
		bFontTagWasStarted?" ":pFontIntroStr,
		pTmpFontInfoBuf);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bFontTagWasStarted = TRUE;
	}
    }

    if(getFontCharset() != defaultFontCharset)
    {
	getFontCharsetStringFromCharsetVal(getFontCharset(),
		pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE);
	ulSprintfLen = sprintf(pTmp, "%scharset=\"%s\"", /* Flawfinder: ignore */
		bFontTagWasStarted?" ":pFontIntroStr,
		pTmpFontInfoBuf);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bFontTagWasStarted = TRUE;
	}
    }

    if(getFontFace() != defaultFontFaceIndex)
    {
	getFontFaceStringFromFaceIndex(getFontFace(),
		pTmpFontInfoBuf, TMP_FONT_INFO_BUF_SIZE,
		pTextWin->getMajorContentVersion(),
		pTextWin->getMinorContentVersion());
	ulSprintfLen = sprintf(pTmp, "%sface=\"%s\"", /* Flawfinder: ignore */
		bFontTagWasStarted?" ":pFontIntroStr,
		pTmpFontInfoBuf);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bFontTagWasStarted = TRUE;
	}
    }

    if(bFontTagWasStarted)
    {
	SafeStrCat(pPacketHeaderBuf, ">", packetHeaderBufSize);
	pktHdrLen++;
    }

    if(isBold())
    {
	if(pktHdrLen+3 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<B>", packetHeaderBufSize);
	    pktHdrLen+=3;
	}
    }
    if(isItalicized())
    {
	if(pktHdrLen+3 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<I>", packetHeaderBufSize);
	    pktHdrLen+=3;
	}
    }
    if(isUnderlined())
    {
	if(pktHdrLen+3 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<U>", packetHeaderBufSize);
	    pktHdrLen+=3;
	}
    }
    if(isStruckOut())
    {
	if(pktHdrLen+3 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<S>", packetHeaderBufSize);
	    pktHdrLen+=3;
	}
    }
    if(isCentered())
    {
	if(pktHdrLen+8 < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, "<CENTER>", packetHeaderBufSize);
	    pktHdrLen+=8;
	}
    }

    {
	ulSprintfLen = sprintf(pTmp, "%sstart=%ld.%03d", /* Flawfinder: ignore */
		bTimeTagWasStarted?" ":pTimeIntroStr,
		getStartTime()/1000, getStartTime()%1000);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bTimeTagWasStarted = TRUE;
	}
    }
    //One more seek-related bug fix; don't set "time end="
    // in packet header based on endtime of first Text in
    // the packet because all further text will die at that
    // end time, you idiot!  Added member to TextAttributes that
    // keeps track of when the last <TIME ... end=t> was seen and,
    // if it's not MAX_ULONG32, then we'll send it in the packet
    // header:
    ULONG32 ulMostRecentTimeTagEndTime = getMostRecentTimeTagEndTime();
    ///By commenting out this line, resend of live packets works properly
    // in cases where <TIME end=...> is in the data;
    // if(ulMostRecentTimeTagEndTime < (ULONG32)ULONG_MAX)
    {
	ulSprintfLen = sprintf(pTmp, "%send=%ld.%03d", /* Flawfinder: ignore */
		bTimeTagWasStarted?" ":pTimeIntroStr,
		ulMostRecentTimeTagEndTime/1000,
		ulMostRecentTimeTagEndTime%1000);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bTimeTagWasStarted = TRUE;
	}
    }

    if(m_ulTimeOfLastClear !=0L)
    {
	//this is an internal tag val that tells renderer when the
	// last <CLEAR> was done so position can be calculated using
	// the x and y from <POS X0=x Y0=y> where time "zero" becomes
	// the time of the last <CLEAR> tag:
	ulSprintfLen = sprintf(pTmp, "%slc=%ld.%03d", /* Flawfinder: ignore */
		bTimeTagWasStarted?" ":pTimeIntroStr,
		m_ulTimeOfLastClear/1000, m_ulTimeOfLastClear%1000);
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	    bTimeTagWasStarted = TRUE;
	}
    }

    if(bTimeTagWasStarted)
    {
	SafeStrCat(pPacketHeaderBuf, ">", packetHeaderBufSize);
	pktHdrLen ++;
    }

    //Added this to handle indenting caused by <UL>(s) &/or <OL>(s):
    UINT16 uLineIndentAmt = getLineIndentAmtInPixels();
    if(uLineIndentAmt)
    {
	//Count the number of indents (how many <UL>s &/or <OL>s we've had
	// since the last </UL? or </OL>) and insert that many <UL>s:
	UINT16 uNumberOfOLsOrULs =uLineIndentAmt / UNORDERED_LIST_INDENT_AMT;
	for(UINT16 i=0; i<uNumberOfOLsOrULs; i++)
	{
	    if(pktHdrLen+4 >= packetHeaderBufSize)
	    {
		break;
	    }
	    SafeStrCat(pPacketHeaderBuf, "<UL>", packetHeaderBufSize);
	    pktHdrLen += 4;
	}
    }
    
    if(HREFbufLen  &&  !bAnchorTagShouldComeBeforeFontTag)
    {
	ulSprintfLen = sprintf(pTmp, "<A HREF=\"%s\"%s>", getHrefBuf(), /* Flawfinder: ignore */
		 //add target=_player if that's the case:
		 (getTargetOfURL()==URL_TARGET_PLAYER?
		 " target=\"_player\"":"") );
	if(pktHdrLen+ulSprintfLen < packetHeaderBufSize)
	{
	    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
	    pktHdrLen += ulSprintfLen;
	}
    }

    if(ulTextWindowType == TYPE_TICKERTAPE)
    {
        COLORTYPE tlc = getTickerLowerColor();
        COLORTYPE origTlc = tlc;
	tlc = DwToNet(tlc);
	UCHAR* ptlc = (UCHAR *)&tlc;
        COLORTYPE tuc = getTickerUpperColor();
        COLORTYPE origTuc = tuc;
	tuc = DwToNet(tuc);
	UCHAR* ptuc = (UCHAR *)&tuc;

	if(isTickerUpperText())	//then do lower first, then upper:
	{
	    if(origTlc != DEFAULT_TICKER_LOWERCOLOR)
	    {	
		ulSprintfLen = sprintf(pTmp, "<TL color=#%02X%02X%02X>", /* Flawfinder: ignore */
			ptlc[1],ptlc[2],ptlc[3]);

		if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
		{
		    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
		    pktHdrLen += ulSprintfLen;
		}
	    }
	    if(pktHdrLen+3 < packetHeaderBufSize-1)
	    {
		SafeStrCat(pPacketHeaderBuf, "<TU", packetHeaderBufSize);
		pktHdrLen += 3;
	    }
	    if(origTuc != DEFAULT_TICKER_UPPERCOLOR)
	    {	
		ulSprintfLen = sprintf(pTmp, " color=#%02X%02X%02X", /* Flawfinder: ignore */
			ptuc[1],ptuc[2],ptuc[3]);

		if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
		{
		    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
		    pktHdrLen += ulSprintfLen;
		}
	    }
	    SafeStrCat(pPacketHeaderBuf, ">", packetHeaderBufSize);
	    pktHdrLen ++;
	}
	else  //do ticker upper first:
	{
	    if(origTuc != DEFAULT_TICKER_UPPERCOLOR)
	    {	
		ulSprintfLen = sprintf(pTmp, "<TU color=#%02X%02X%02X>", /* Flawfinder: ignore */
			ptuc[1],ptuc[2],ptuc[3]);

		if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
		{
		    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
		    pktHdrLen += ulSprintfLen;
		}
	    }
	    if(pktHdrLen+3 < packetHeaderBufSize-1)
	    {
		SafeStrCat(pPacketHeaderBuf, "<TL", packetHeaderBufSize);
		pktHdrLen += 3;
	    }
	    if(origTlc != DEFAULT_TICKER_LOWERCOLOR)
	    {	
		ulSprintfLen = sprintf(pTmp, " color=#%02X%02X%02X", /* Flawfinder: ignore */
			ptlc[1],ptlc[2],ptlc[3]);

		if(pktHdrLen+ulSprintfLen < packetHeaderBufSize-1)
		{
		    SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);
		    pktHdrLen += ulSprintfLen;
		}
	    }
	    SafeStrCat(pPacketHeaderBuf, ">", packetHeaderBufSize);
	    pktHdrLen ++;
	}
    }

/// if(!bStateIsGetPacketSeekBackReadPending) //XXXEH-do this for ALL states?
    {
	//Now, change the zeros in bytes 1 through
	// NUM_DIGITS_IN_START_TAG_OF_PKT_HDR of pPacketHeaderBuf to the
	// actual numbers in pktHdrLen:
	sprintf(pTmp, "%04d", pktHdrLen); /* Flawfinder: ignore */
	for(ULONG32 ii=0; ii<NUM_DIGITS_IN_START_TAG_OF_PKT_HDR; ii++)
	{
	    pPacketHeaderBuf[ii+1] = pTmp[ii];
	}
	//fill with NUM_DIGITS_IN_START_TAG_OF_PKT_HDR digits of pktHdrLen:
	sprintf(pTmp, "</%04d>", pktHdrLen); /* Flawfinder: ignore */
	SafeStrCat(pPacketHeaderBuf, pTmp, packetHeaderBufSize);

	HX_ASSERT(strlen(pPacketHeaderBuf) == pktHdrLen);
    }

    delete [] pTmp;
    return pktHdrLen;

} //end of OutputPacketHeaderString method.




/////////////////////////////////////////////////////////////////////////////
//class TextLineList methods:
/////////////////////////////////////////////////////////////////////////////
//
LISTPOSITION TextLineList::GetEndPosition()
{
    if (GetCount() > 0)
    {
	return (TextLine*)GetTailPosition();
    }
    else
    {
	return NULL;
    }
}


/////////////////////////////////////////////////////////////////////////////
//
LISTPOSITION TextLineList::GetStartPosition()
{
    if (GetCount() > 0)
    {
	return (TextLine*)GetHeadPosition();
    }
    else
    {
	return NULL;
    }
}


/////////////////////////////////////////////////////////////////////////////
//
TextLine* TextLineList::end() //returns NULL if list is empty.
{
    if (GetCount() > 0)
    {
	return (TextLine*)GetTail();
    }
    else
    {
	return NULL;
    }
}


/////////////////////////////////////////////////////////////////////////////
//
TextLine* TextLineList::start() //returns NULL if list is empty.
{
    if (GetCount() > 0)
    {
	return (TextLine*)GetHead();
    }
    else
    {
	return NULL;
    }
}


/////////////////////////////////////////////////////////////////////////////
//
BOOL TextLineList::insertAtEndOfList(TextLine* tc)
{
    HX_ASSERT_VALID_PTR(tc);
    if(NULL == tc)
    {
	return FALSE;
    }
    else
    {
	LISTPOSITION lpos = AddTail(tc);
	return (lpos!=NULL);
    }
}


/////////////////////////////////////////////////////////////////////////////
//
ULONG32 TextLineList::flush() //Returns number of nodes deleted.
{
    ULONG32 numDeleted = 0L;
    while(nodeCount())
    {
	TextLine* pTLHead = (TextLine*)CHXSimpleList::RemoveHead();
	HX_ASSERT_VALID_PTR(pTLHead);
	if(pTLHead) 
	{
	    delete pTLHead;
	    pTLHead = NULL;
	    numDeleted++;
	}
    }

    return numDeleted;
}


/////////////////////////////////////////////////////////////////////////////
//
//  If <CLEAR> tag is sent, effective at time t, then this function is
//  called to change all in this list whose m_ulEndTimeOfData is > t;
//  Returns the number of nodes in the list whose endTimes were reduced to t:
//
ULONG32 TextLineList::MarkAllForClear(ULONG32 ulTimeOfClear,
				      BOOL bIsLiveSource)
{
    ULONG32 listSize = size();
    ULONG32 numTLsWhoseEndTimesWereReduced = 0L;

    if(listSize)
    {
	LISTPOSITION pos = GetStartPosition();

	while(pos)
	{
	    TextLine* pTL = (TextLine*)GetAt(pos);

	    HX_ASSERT_VALID_PTR(pTL);
	    if(pTL)
	    {
		if(pTL->MarkForClear(ulTimeOfClear, bIsLiveSource))
		{
		    numTLsWhoseEndTimesWereReduced++;
		}
	    }

	    GetNext(pos);
	} //end "while(pos)".
    } //end "if(listSize)".	
    return numTLsWhoseEndTimesWereReduced;
}


/////////////////////////////////////////////////////////////////////////////
//
// Added this so seek() can calculate what to send;
//
// returns 0 if there are no active TextLines at ulTime or if error:
//
ULONG32 TextLineList::findBoundingStartAndEndBytesOfActiveTextLines(
	ULONG32 ulTime, ULONG32* p_ulPktStartByte, ULONG32* p_ulPktEndByte,
	TextLine** ppFirstTextLineInPkt)
{
    if(!p_ulPktStartByte  ||  !p_ulPktEndByte)
    {
	return 0L;
    }

    *p_ulPktStartByte = 0L;
    *p_ulPktEndByte = 0L;
    *ppFirstTextLineInPkt = NULL;

    BOOL bNoPktStartByteFoundYet = TRUE;

    ULONG32 ulNextPktStartByte = 0L;
    ULONG32 ulNextPktEndByte = 0L;

    ULONG32 listSize = size();
    ULONG32 numTLsActiveAtThisTime = 0L;

    ULONG32 numTLsActiveAtLaterTime = 0L;

    ULONG32 ul_totalLaterBytes = 0L;

    TextLine* pFirstTextLineInNextPkt = NULL;

    if(listSize)
    {
	LISTPOSITION pos = GetStartPosition();

	while(pos)
	{
	    TextLine* pTL = (TextLine*)GetAt(pos);

	    HX_ASSERT_VALID_PTR(pTL);
	    if(pTL)
	    {
		if(pTL->getStartTime() <= ulTime  &&
			pTL->getEndTime() >= ulTime)
		{
		    if(pTL->getStartByteInFile() < *p_ulPktStartByte  ||
			((0L == *p_ulPktStartByte && bNoPktStartByteFoundYet)
			&& !numTLsActiveAtThisTime))
		    {
			*p_ulPktStartByte = pTL->getStartByteInFile();
			bNoPktStartByteFoundYet = FALSE;
			*ppFirstTextLineInPkt = pTL;
		    }
		    if(pTL->getEndByteInFile() > *p_ulPktEndByte)
		    {
			*p_ulPktEndByte = pTL->getEndByteInFile();
		    }
		    		    
		    numTLsActiveAtThisTime++;
		}
		//Keep track of T.L. that is the first one with a start time
		// that is just after ulTime in case none (or very few bytes)
		// are found above:
		if(pTL->getEndTime() >= ulTime  &&
			((1+ulNextPktEndByte-ulNextPktStartByte) <
			IDEAL_SIZE_OF_NONHEADER_PART_OF_PKT)    )
		{
		    if(pTL->getStartByteInFile() < ulNextPktStartByte  ||
			    0L == ulNextPktStartByte)
		    {
			ulNextPktStartByte = pTL->getStartByteInFile();
			pFirstTextLineInNextPkt = pTL;
		    }
		    if(pTL->getEndByteInFile() > ulNextPktEndByte)
		    {
			ulNextPktEndByte = pTL->getEndByteInFile();
		    }
		    		    
		    numTLsActiveAtLaterTime++;
		}
	    }

	    GetNext(pos);
	} //end "while(pos)".
    } //end "if(listSize)".
    
    if(!numTLsActiveAtThisTime) //none were found, so send later ones:
    {
	if(!numTLsActiveAtLaterTime)
	{
	    return 0L;
	}
	*p_ulPktStartByte = ulNextPktStartByte;
	bNoPktStartByteFoundYet = FALSE;
	*p_ulPktEndByte = ulNextPktEndByte;
	*ppFirstTextLineInPkt = pFirstTextLineInNextPkt;
    }
    else if(numTLsActiveAtLaterTime  &&
	    ((1 + *p_ulPktEndByte - *p_ulPktStartByte) <
	    MIN_SIZE_OF_NONHEADER_PART_OF_PKT) )
    {
	//include some more text to round out the pkt to a better size:
	*p_ulPktEndByte = ulNextPktEndByte;
    }

    //Note: p_ulPktStartByte can be zero if tagless "plaintext"-type file:
    HX_ASSERT(!bNoPktStartByteFoundYet  &&  *p_ulPktEndByte);
    //This if should never be TRUE, but...
    if(bNoPktStartByteFoundYet  &&  !*p_ulPktEndByte)
    {
	return 0L; 
    }

    return (numTLsActiveAtThisTime>0L?
	    numTLsActiveAtThisTime:numTLsActiveAtLaterTime);
}



/////////////////////////////////////////////////////////////////////////////
//
//Added this so GetPacket() can calculate how many
// bytes need to be read; returns 0 if there are no already-read 
// TextLines at startByte or later, or if other error:
ULONG32 TextLineList::makeReasonableSizedPacketFromTextLinesAtStartByte(
	ULONG32 ulStartByte, ULONG32* p_ulPktEndByte,
	BOOL* p_bCurPacketHasREQUIREDContents,
	TextLine** ppFirstTextLineInPkt)
{
    if(!p_ulPktEndByte  ||  !p_bCurPacketHasREQUIREDContents)
    {
	return 0L;
    }

    *p_ulPktEndByte = 0L;
    *ppFirstTextLineInPkt = NULL;

    ULONG32 ulNextPktEndByte = 0L;

    ULONG32 listSize = size();
    ULONG32 numTLsInThisPacket = 0L;

    if(listSize)
    {
	LISTPOSITION pos = GetStartPosition();

	while(pos)
	{
	    TextLine* pTL = (TextLine*)GetAt(pos);

	    HX_ASSERT_VALID_PTR(pTL);
	    if(pTL)
	    {
		if(pTL->getStartByteInFile() >= ulStartByte)
		{
		    if(!(*ppFirstTextLineInPkt))
		    {
			*ppFirstTextLineInPkt = pTL;
		    }

		    if(pTL->getEndByteInFile() > *p_ulPktEndByte)
		    {
			*p_ulPktEndByte = pTL->getEndByteInFile();
			numTLsInThisPacket++;

			if(pTL->isRequired())
			{
			    *p_bCurPacketHasREQUIREDContents = TRUE;
			}
		    }
		    
		    if(*p_ulPktEndByte > ulStartByte  &&
			    *p_ulPktEndByte - ulStartByte >
			    IDEAL_SIZE_OF_NONHEADER_PART_OF_PKT)
		    {
			break;
		    }
		}
	    }

	    GetNext(pos);
	} //end "while(pos)".
    } //end "if(listSize)".
    
    return numTLsInThisPacket;
}


