filehandling/richtexttohtmlconverter/Src/RT2HTMCV.CPP
author fimarlaht2 <>
Mon, 18 Oct 2010 15:01:14 +0300
branchRCL_3
changeset 85 32f887d619a0
parent 0 2e3d3ce01487
permissions -rw-r--r--
Bug 3556 - Not possible to restore factory settings

// Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// ConArc plug-in converter.
// Converts EPOC Rich Text files (i.e. externalised CRichText objects) to HTML files.
// 1. Does not support object->object conversions
// 2. Therefore does not support embedded objects
// 3. No output encoding for UNICODE has beed decided upon
// 
//

#include <e32base.h>
#include <e32def.h>
#include <e32std.h>
#include <s32file.h>
#include <txtuids.h>
#include <txtetext.h>
#include <txtrich.h>
#include <conarc.h>
#include <bautils.h>

#include "RT2HTMCV.H"
#include "HTMLDEFS.H"
#include <ecom/ecom.h>
#include <ecom/implementationproxy.h>


//-------------------------------------------------------------------------------------------------
const TInt  KMaxTagLength		= 256;
const TText KCharacterSpace		= 0x20;
//-------------------------------------------------------------------------------------------------
#ifdef _UNICODE
	HBufC8* ConvertUnicodeToUTF8LC(const TDesC16& uniText);
#endif // _UNICODE


_LIT(KTempFileExt, ".dat");

typedef TPckgBuf<CRichText*> TRTPtrBuffer;

/**
-------------------------------------------------------------------------------------------------
 The required ConArc classes: CCtrToHTMLConverter and CCrtToHTMLLibrary
-------------------------------------------------------------------------------------------------
 Class CCrtToHTMLConverter.
 The CConverterBase derived class which is responsable for performing the conversion operation.
 1. Supports File->File conversions and output to a RWriteStream
 2. For the stream interface a temporary file store is created for reading the rich text object.
    this is deleted once the conversion is complete
 3. The conversion is performed on a paragraph by paragraph basis
 4. iHTMLOut is used to output the resulting HTML. For File->File conversions a new file stream store
    is opened based on the user provided file name. For the streaming interface iHTMLOut is assigned 
    to a RWriteStream& provided by the user
*/
CCrtToHTMLConverter::CCrtToHTMLConverter() : iHTMLOut(iFileStream)
	{ 
	}

void CCrtToHTMLConverter::ConvertL(const TFileName& aSourceFile, const TFileName& aTargetFile, MConverterUiObserver* aObserver /*= NULL*/)
//
// Configures and performs a file -> file conversion synchronously
//
	{
	iObserver = aObserver;
	PrepForConversionL(aSourceFile, aTargetFile);

	while(DoConvertL())
		{
		}
	ASSERT(iParaRemain == 0);
	}

void CCrtToHTMLConverter::ConvertAL(const TFileName& aSourceFile, const TFileName& aTargetFile, MConverterUiObserver* aObserver /*= NULL*/)
//
// Configures the object to perform a file -> file conversion asynchronously
//
	{
	// Cast away the const-ness - for now...
	iObserver = aObserver;
	PrepForConversionL(aSourceFile, aTargetFile);

	if(iObserver)
		{
		iObserver->MaxSteps(iParaRemain, 0);
		}
	}

/**
 To create the RReadStream you package a pointer to the CRichText object and create a RDesReadStream:
 TPckgBuf<CRichText*> buffer(text); //text is the CRichText* to be converted
 RDesReadStream readStream(buffer); //readStream can now be passed to ConvertObjectL/ConvertObjectAL
*/
void CCrtToHTMLConverter::ConvertObjectL(RReadStream& aReadStream, RWriteStream& aWriteStream, MConverterUiObserver* aObserver)
	{
	iObserver = aObserver;
	PrepForConversionL(aReadStream, aWriteStream);

	while(DoConvertL());
	
	ASSERT(iParaRemain == 0);
	}

/**
 To create the RReadStream you package a pointer to the CRichText object and create a RDesReadStream:
 TPckgBuf<CRichText*> buffer(text); //text is the CRichText* to be converted
 RDesReadStream readStream(buffer); //readStream can now be passed to ConvertObjectL/ConvertObjectAL
*/
void CCrtToHTMLConverter::ConvertObjectAL(RReadStream& aReadStream, RWriteStream& aWriteStream, MConverterUiObserver* aObserver)
	{
	iObserver = aObserver;
	PrepForConversionL(aReadStream, aWriteStream);

	if(iObserver)
		{
		iObserver->MaxSteps(iParaRemain, 0);
		}
	}


TUid CCrtToHTMLConverter::Uid()
//
// Returns the UID associated with the conversion operation
//
	{
	return KCrt2HTMLConverterUid;
	}

TInt CCrtToHTMLConverter::Capabilities()
//
// Returns EConvertsFiles. Only File->File conversion is supported.
//
	{
	return EConvertsFiles;
	}

void CCrtToHTMLConverter::CancelConvert()
//
// Clean up. Doesn't remove half finished file.
//
	{
	ResetMembers();
	}

CCrtToHTMLConverter::~CCrtToHTMLConverter()
	{
	ResetMembers();
	iFs.Close();
	}

/**
 Performs a single step of the conversion. Converts a single paragraph from Rich Text -> HTML
 THe final aparagrahp of a RichText doc contains a single end of document marker. When we 
 reach this we can delete the output sink object, which will flush and close the output file.
*/
TBool CCrtToHTMLConverter::DoConvertL()
	{
	TBool closePara = ETrue;

	if(iParaNum == -1)				// We're at the start
		{
		StartDocumentL();			// Write the HTML header

	    iParaNum   = 0;
	    iParaLen   = 0;
		iParaPos   = 0;
		closePara  = EFalse;		// Coz we don't want a </P> tag a the start...
		}	
	
	// Do a single paragraph...
	if((iParaPos = iInputText->CharPosOfParagraph(iParaLen, iParaNum++)) >= 0)
		{
		// Construct a formatting object
		CParaFormat* format = CParaFormat::NewLC();

		TParaFormatMask  mask;

		// Cue, Mr Hack...
		// Knock 1 off the document length as a panic prevention measure, as the iParaLength of
		// the last paragraph is _beyond_ the end of the document. Doh!
		if(iParaNum == iParaCount)
			iParaLen -= 1;

		iInputText->GetParaFormatL(format, mask, iParaPos, iParaLen);

		ASSERT(mask.IsNull());

		ProcessParagraphFormatL(format, closePara);	// Put paragraph level formatting
		ProcessParagraphTextL(iParaPos, iParaLen);	// Process the paragraph itself
		--iParaRemain;

		CleanupStack::PopAndDestroy(); // CParaFormat format
		if(iParaNum == iParaCount)  // We're at the end of the document, so finish up
			{
			ASSERT(iParaPos + iParaLen == iInputText->DocumentLength());
			EndDocumentL();
			// Commit the file
			iHTMLOut.CommitL();
			iHTMLOut.Close();
			return EFalse;
			}
		}
	else
		{
		Panic(EHTMLBeyondLastPara);
		}
	return ETrue;
}

/**
 Both File->File and stream converstions use a file store to read the input CRichText object
 In File->File conversions the user creates the rich text file store prior to calling ConvertL()/ConvertAL()
 In stream conversions CCrtToHTMLConverter creates a temporary file store
*/
void CCrtToHTMLConverter::RestoreInputTextL(const TFileName& aSourceFile)
	{
	delete iInputText;
	iInputText = NULL;
	
	iParaLayer = CParaFormatLayer::NewL();
	iCharLayer = CCharFormatLayer::NewL();
	iInputText = CRichText::NewL(iParaLayer, iCharLayer, CEditableText::EFlatStorage);

	// Get the store and restore from it to the CRichText object
	iStore = CDirectFileStore::OpenL(iFs, aSourceFile, EFileRead | EFileShareReadersOnly);

	if(iStore->Type()[0] != KDirectFileStoreLayoutUid)
          User::Leave(KErrUnknown);     
	
   	iInputText->RestoreL(*iStore, iStore->Root());
	
	iParaRemain = iInputText->ParagraphCount();
	iParaCount  = iParaRemain;
	}

/**
  Configures the object prior to performing a conversion. 
  1. Connects to the file server
  2. Restores the input CRichText object from the given file
  3. Prepares the output context
*/
void CCrtToHTMLConverter::PrepForConversionL(const TFileName& aSourceFile, const TFileName& aTargetFile)
	{
	ResetMembers();
	// ResetMembers() will close the file server session to ensure it's flushed and any temporary files are deleted 
	User::LeaveIfError(iFs.Connect());

	iHTMLOut = iFileStream;
	RestoreInputTextL(aSourceFile);

	// Set up the output context
	User::LeaveIfError(((RFileWriteStream&)iHTMLOut).Replace(iFs, aTargetFile, EFileStream|EFileWrite|EFileShareExclusive));
	}
	
/**
  Configures the object prior to performing a conversion. 
  1. Connects to the file server
  2. Assign the output stream
  3. Copys the read CRichText object to a file store
  4. Restores our copy of the input CRichText

 There's no way to copy a CRichText object without first externalizing it in some way. The normal mechanism for 
 saving and loading CRichText objects is to use Store and Restore. Using Externalize and Internalize is non-trivial
 with CRichText. The stream interface only gives us a RReadStream& making Restore very difficult. The way around 
 this is for the user to just stream a pointer to the actual CRichText object. This way the caller dosn't need to 
 worry about setting up any stores. CCrtToHTMLConverter does the copy.
 To create the RReadStream you package a pointer to the CRichText object and create a RDesReadStream:
 TPckgBuf<CRichText*> buffer(text); //text is the CRichText* to be converted
 RDesReadStream readStream(buffer); //readStream can now be passed to ConvertObjectL/ConvertObjectAL
*/ 
void CCrtToHTMLConverter::PrepForConversionL(RReadStream& aReadStream, RWriteStream& aWriteStream)
	{
	ResetMembers();
	// ResetMembers() will close the file server session to ensure it's flushed and any temporary files are deleted 
	User::LeaveIfError(iFs.Connect());
	
	iHTMLOut = aWriteStream;
	
	// Unpackage the source rich text pointer
	TRTPtrBuffer readBuffer;
	aReadStream.ReadL(readBuffer);
	CRichText* sourceText = readBuffer();

	// The temporary file needs to be unique to allow for concurrent conversions
	// This file is deleted on a call to ResetMembers()
	TFileName newTempFile;
	User::LeaveIfError(iFs.PrivatePath(newTempFile));

	newTempFile.AppendNum((TInt)sourceText);
	newTempFile.Append(KTempFileExt);
	BaflUtils::EnsurePathExistsL(iFs, newTempFile);
	iInternalFile = newTempFile.AllocL();
		
	CDirectFileStore* fileStore = CDirectFileStore::ReplaceL(iFs, *iInternalFile, EFileWrite|EFileShareAny);
	CleanupStack::PushL( fileStore );
	fileStore->SetTypeL( KDirectFileStoreLayoutUid );
	TStreamId streamID = sourceText->StoreL(*fileStore);
	fileStore->SetRootL(streamID);
	fileStore->CommitL();
	CleanupStack::PopAndDestroy( fileStore );
	
	// Restore our copy of the input rich text object from the newly created file store
	RestoreInputTextL(*iInternalFile);
	}

/**
 Ensures that any contained objects are desstroyed and sets all member variable to zero(NULL)
*/
void CCrtToHTMLConverter::ResetMembers()
	{
	iStyleIndex = -1;
	iParaNum    = -1;
	iParaLen    = 0;
	iParaPos    = 0;
	iParaCount  = 0;
	iParaRemain = 0;
	iIndent     = 0;
	iOldFmtCount = 0;

	iHTMLOut.Close();
	delete iInputText;
	iInputText = NULL;
	delete iParaLayer;
	iParaLayer = NULL;
	delete iCharLayer;
	iCharLayer = NULL;
	delete iBullet;
	iBullet = NULL;
	delete iStore;
	iStore = NULL;
	if (iFs.Handle()) 
		{
		if (iInternalFile && BaflUtils::FileExists(iFs,*iInternalFile))
			{
			iFs.Delete(*iInternalFile);
			delete iInternalFile;
			iInternalFile = NULL;			
			}
		// I'm closing the file server to ensure it flushes.
		// Otherwise internal files are not always deleted
		iFs.Close();
		}
	}

/**
 Scans the passed buffer, replacing special characters in the source with relevant HTML tags
 before sending them to be output
*/
void CCrtToHTMLConverter::TranslateL(const TDesC& aBuf)
	{
	int i = 0;
	while(i < aBuf.Length())
		{
		TText ch = aBuf[i++];
		switch(ch)
			{
			// !! am I picking up all possibles here?
			case CEditableText::EPageBreak:
			case CEditableText::EPotentialHyphen:
			case CEditableText::ENonBreakingHyphen:
				break;  // These characters are not emitted.
			case CEditableText::EPictureCharacter:
				break;
			case CEditableText::ELineBreak:
				WriteTagL(KHtmlLineBreak);
				break;
			case CEditableText::ENonBreakingSpace:
			case CEditableText::ETabCharacter:
				WriteContentL(TPtrC(&KCharacterSpace, 1));
				break;
			case CEditableText::EParagraphDelimiter:
				break;
			case KLessThan:
				WriteTagL(KHtmlLessThan);
				break;
			case KGreaterThan:
				WriteTagL(KHtmlGreaterThan);
				break;
			case KAmpersand:
				WriteTagL(KHtmlAmpersand);
				break;
			default:
				WriteContentL(TPtrC(&ch, 1));
				break;
			}
		}
	}

void CCrtToHTMLConverter::WriteTagL(const TDesC8& aTagText)
	{
	iHTMLOut.WriteL(aTagText);
	}

void CCrtToHTMLConverter::WriteContentL(const TDesC& aText)
	{
#ifdef _UNICODE
	HBufC8* pBuf = ConvertUnicodeToUTF8LC(aText);
	User::LeaveIfNull(pBuf);
	iHTMLOut.WriteL(*pBuf);
	CleanupStack::PopAndDestroy();
#else
	iHTMLOut.WriteL(aText);
#endif
	}

void CCrtToHTMLConverter::ProcessParagraphFormatL(const CParaFormat* aFormat, TBool aClosePara)
//
// Processes any paragraph level formatting (paragraph alignment, list bullets, indentation)
//
	{
	// Close indents
	for( ; iIndent ; iIndent--)
		WriteTagL(KHtmlBlockquoteEnd);

	if(!iBullet && aClosePara)
		{
		if ( iInsertBlankDivClose )
			{
			WriteTagL(KHtmlDivBlankEnd);
			}
		else
			{
			WriteTagL(KHtmlDivEnd);
			}
		}

	// Process unordered (bulleted) lists
	if(iBullet)
		{
		// Previous paragraph was bulleted
		if(!aFormat->iBullet)
			{
			// End of list
			delete iBullet;
			iBullet = NULL;
			WriteTagL(KHtmlBulletListPointEnd);
			WriteTagL(KHtmlBulletListEnd);
			}
		else
			{
			if(*iBullet == *(aFormat->iBullet))
				{
				WriteTagL(KHtmlBulletListPointEnd);
				WriteTagL(KHtmlBulletListPointStart);
				}
			else
				{
				// A _new_ list
				WriteTagL(KHtmlBulletListPointEnd);
				WriteTagL(KHtmlBulletListEnd);
				WriteTagL(KHtmlBulletListStart);
				WriteTagL(KHtmlBulletListPointStart);
				*iBullet = *(aFormat->iBullet);
				}
			}
		}
	else
		{
		// Previous paragraph was _not_ bulleted
		if(aFormat->iBullet)
			{
			// But this one is: start a new list
			iBullet = new (ELeave) TBullet();
			*iBullet = *(aFormat->iBullet);
			WriteTagL(KHtmlBulletListStart);
			WriteTagL(KHtmlBulletListPointStart);
			}
		}

	// Process paragraph alignment
	switch(aFormat->iHorizontalAlignment)
		{
	case CParaFormat::ELeftAlign:   // Paragraph aligned flush with left margin
		WriteTagL(KHtmlDivAlignLeftStart);
		break;
	case CParaFormat::ECenterAlign: // Paragraph center aligned
		WriteTagL(KHtmlDivAlignCentreStart);
		break;
	case CParaFormat::ERightAlign:  // Paragraph aligned flush with right margin
		WriteTagL(KHtmlDivAlignRightStart);
		break;
	case CParaFormat::EJustifiedAlign: // Justified text
		WriteTagL(KHtmlDivAlignJustifyStart);
		break;
	default:
		WriteTagL(KHtmlDivAlignNoneStart);
		break;
		}

	// Open indents
    iIndent = (aFormat->iLeftMarginInTwips) / KTwipsToBlockQuote;
	for(TInt i = 0; i < iIndent; i++)
  		WriteTagL(KHtmlBlockquoteStart);
	}

void CCrtToHTMLConverter::ProcessParagraphTextL(TInt aPos, TInt aLength)
//
// Processes a paragraph of text
//
	{
	TInt			pos = aPos;
	TCharFormat		oldFormat,
		            newFormat;

	TCharFormatMask maskChar;
	// Set up initial character formatting
	iInputText->GetSpecificCharFormat(newFormat, maskChar, pos);
	DiffCharFormats(newFormat, oldFormat, maskChar);
	OpenCharFormatL (maskChar, newFormat);
	oldFormat = newFormat;

	// reset blank paragraph flag
	iInsertBlankDivClose = EFalse;
	// Scan the paragraph 1 char at a time...
	while((pos - aPos) < aLength)
		{
		ASSERT(pos < iInputText->DocumentLength());
		TPtrC str = iInputText->Read(pos++, 1);

		if(str[0] == CEditableText::EParagraphDelimiter)
			{
			// only insert a blank div if we have a blank paragraph
			// ie aLength == 1 && it's only contents are CEditableText::EParagraphDelimiter
			iInsertBlankDivClose = ( aLength == 1 );
			continue;
			}

		TCharFormatMask testMask;
		TCharFormat		tstFormat;

		iInputText->GetCharFormat(tstFormat, testMask, pos-1, 2);

		if(!tstFormat.IsEqual(oldFormat))
			{
			// Something has changed...
			DiffCharFormats(tstFormat, oldFormat, maskChar);
			CloseCharFormatL(maskChar, oldFormat);
			OpenCharFormatL (maskChar, tstFormat);
			}
		oldFormat = tstFormat;
		TranslateL(str);
		}
	// End of paragraph, reset formatting to base...
	TCharFormat	closeFormat;
	DiffCharFormats(oldFormat, closeFormat, maskChar);
	CloseCharFormatL(maskChar, oldFormat);
	}

void CCrtToHTMLConverter::DiffCharFormats(const TCharFormat& aFormatA, const TCharFormat& aFormatB, TCharFormatMask& aMask)
//
// Compare two TCharFormat and set flags in the mask which descrbe the differences
// (Would be quite nice if TCharFormat knew how to do this itself...)
//
	{
	aMask.ClearAll();

	if(aFormatA.iLanguage!=aFormatB.iLanguage)
		aMask.SetAttrib(EAttCharLanguage);

	if(aFormatA.iFontSpec.iHeight != aFormatB.iFontSpec.iHeight)
		aMask.SetAttrib(EAttFontHeight);

	if(!(aFormatA.iFontSpec.iTypeface == aFormatB.iFontSpec.iTypeface))
		aMask.SetAttrib(EAttFontTypeface);

	if(aFormatA.iFontSpec.iFontStyle.Posture() != aFormatB.iFontSpec.iFontStyle.Posture())
		aMask.SetAttrib(EAttFontPosture);

	if(aFormatA.iFontSpec.iFontStyle.StrokeWeight() != aFormatB.iFontSpec.iFontStyle.StrokeWeight())
		aMask.SetAttrib(EAttFontStrokeWeight);

	if(aFormatA.iFontSpec.iFontStyle.PrintPosition() != aFormatB.iFontSpec.iFontStyle.PrintPosition())
		aMask.SetAttrib(EAttFontPrintPos);

	if(aFormatA.iFontPresentation.iUnderline != aFormatB.iFontPresentation.iUnderline)
		aMask.SetAttrib(EAttFontUnderline);

	if (aFormatA.iFontPresentation.iStrikethrough != aFormatB.iFontPresentation.iStrikethrough)
		aMask.SetAttrib(EAttFontStrikethrough);

	if(aFormatA.iFontPresentation.iTextColor != aFormatB.iFontPresentation.iTextColor)
		aMask.SetAttrib(EAttColor);

	if(!(aFormatA.iFontSpec.iTypeface == aFormatB.iFontSpec.iTypeface))
		aMask.SetAttrib(EAttFontTypeface);
	}

void CCrtToHTMLConverter::OpenCharFormatL(const TCharFormatMask& aMask, const TCharFormat& aFormat)
//
//	Open the formating tags as set in aMask using parameters gleened from aFormat
//
	{
	if(aMask.IsNull())
		return;
	// Bold text ?
	if(aMask.AttribIsSet(EAttFontStrokeWeight) && (aFormat.iFontSpec.iFontStyle.StrokeWeight() == EStrokeWeightBold))
		WriteTagL(KHtmlBoldStart);
	// Underlined ?
	if(aMask.AttribIsSet(EAttFontUnderline)  && (aFormat.iFontPresentation.iUnderline == EUnderlineOn))
		WriteTagL(KHtmlUnderlineStart);
	// Italic ?
	if(aMask.AttribIsSet(EAttFontPosture) && (aFormat.iFontSpec.iFontStyle.Posture() == EPostureItalic))
		WriteTagL(KHtmlItalicStart);
	// Strike through ?
	if(aMask.AttribIsSet(EAttFontStrikethrough) && aFormat.iFontPresentation.iStrikethrough)
		WriteTagL(KHtmlStrikeoutStart);
	// Sub/Super-script ?
	if(aMask.AttribIsSet(EAttFontPrintPos))
		{
		switch(aFormat.iFontSpec.iFontStyle.PrintPosition())
			{
		case EPrintPosSuperscript:	WriteTagL(KHtmlSuperscriptStart);
			break;
		case EPrintPosSubscript  :	WriteTagL(KHtmlSubscriptStart);
			break;
		case EPrintPosNormal     :	break;
			}
		}
	// Font typeface
	if(aMask.AttribIsSet(EAttFontTypeface) && !aFormat.iFontSpec.iTypeface.IsProportional())
		WriteTagL(KHtmlTeletypeStart);


	//	if both font height and colour are set.
		if(aMask.AttribIsSet(EAttFontHeight) && aMask.AttribIsSet(EAttColor))
		{
			TInt htmlHeight = ((aFormat.iFontSpec.iHeight - KHtmlTwipsToHeightBaseAdjust) / KHtmlTwipsToHeight) + 1;
			TBuf8<KMaxTagLength > tag;
			if(htmlHeight > KHtmlMaxFontSize)
				htmlHeight = KHtmlMaxFontSize;
			tag.Format(KHtmlFontStartClrNSize, htmlHeight, aFormat.iFontPresentation.iTextColor.Red(),
												 aFormat.iFontPresentation.iTextColor.Green(),
												 aFormat.iFontPresentation.iTextColor.Blue());
			WriteTagL(tag);
			//if(!iOldFmtCount)
			iOldFmtCount = 1;
		}
		else
		{
			// Font height
			if(aMask.AttribIsSet(EAttFontHeight))
			{
				TInt htmlHeight = ((aFormat.iFontSpec.iHeight - KHtmlTwipsToHeightBaseAdjust) / KHtmlTwipsToHeight) + 1;
				TBuf8<KMaxTagLength > tag;
				if(htmlHeight > KHtmlMaxFontSize)
					htmlHeight = KHtmlMaxFontSize;
				tag.Format(KHtmlFontSizeStart, htmlHeight);
				WriteTagL(tag);
				//if(!iOldFmtCount)
				iOldFmtCount = 1;

			}
			// Font colour
			if(aMask.AttribIsSet(EAttColor))
			{
				TBuf8<KMaxTagLength > tag;
				tag.Format(KHtmlFontColourStart, aFormat.iFontPresentation.iTextColor.Red(),
												 aFormat.iFontPresentation.iTextColor.Green(),
												 aFormat.iFontPresentation.iTextColor.Blue());
				WriteTagL(tag);
				//if(!iOldFmtCount)
				iOldFmtCount = 1;
			}
		}



	}

void CCrtToHTMLConverter::CloseCharFormatL(const TCharFormatMask& aMask, const TCharFormat& aFormat)
//
// Open the formating tags as set in aMask using parameters gleened from aFormat
// These tags should be checked in the exact _reverse_ order as when they were opened
// to ensure that they nest properly.
//
	{
	if(aMask.IsNull())
		return;

	//Font color or height
	if(iOldFmtCount && (aMask.AttribIsSet(EAttColor)	|| aMask.AttribIsSet(EAttFontHeight)))
	{
		WriteTagL(KHtmlFontEnd);
		iOldFmtCount = 0;
	}

	// Font typeface
if(aMask.AttribIsSet(EAttFontTypeface) && !aFormat.iFontSpec.iTypeface.IsProportional())
		WriteTagL(KHtmlTeletypeEnd);
	// Sub/Super-script ?
	if(aMask.AttribIsSet(EAttFontPrintPos))
		{
		switch(aFormat.iFontSpec.iFontStyle.PrintPosition())
			{
		case EPrintPosSuperscript:	WriteTagL(KHtmlSuperscriptEnd);
			break;
		case EPrintPosSubscript  :	WriteTagL(KHtmlSubscriptEnd);
			break;
		case EPrintPosNormal     :	break;
			}
		}
	// Strike through ?
	if(aMask.AttribIsSet(EAttFontStrikethrough) && aFormat.iFontPresentation.iStrikethrough)
		WriteTagL(KHtmlStrikeoutEnd);
	// Italic ?
	if(aMask.AttribIsSet(EAttFontPosture) && (aFormat.iFontSpec.iFontStyle.Posture() == EPostureItalic))
		WriteTagL(KHtmlItalicEnd);
	// Underlined ?
	if(aMask.AttribIsSet(EAttFontUnderline)  && (aFormat.iFontPresentation.iUnderline == EUnderlineOn))
		WriteTagL(KHtmlUnderlineEnd);
	// Bold text ?
	if(aMask.AttribIsSet(EAttFontStrokeWeight) && (aFormat.iFontSpec.iFontStyle.StrokeWeight() == EStrokeWeightBold))
		WriteTagL(KHtmlBoldEnd);
	}

//-------------------------------------------------------------------------------------------------
// Output functions
//
void CCrtToHTMLConverter::StartDocumentL()
//
// Put the <HTML><HEAD>...</HEAD> tags and open the >BODY> tag.
//
	{
	// Do some validity checks ?				// TTD:	Change this to 4.0 xxxx - or skip it!
	WriteTagL(KHTMLDocType32);				// <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">
	WriteTagL(KHtmlStartTag);				// <HTML>
	WriteTagL(KHtmlHeadStartTag); 			// <HEAD>

	WriteTagL(KHTMLHeadTitleStartTag);		// <TITLE>
	TranslateL(_L("Converted from Rich Text"));	//   ...
	WriteTagL(KHTMLHeadTitleEndTag);		// </TITLE>
#ifdef _UNICODE
	WriteTagL(KHTMLMetaCharSetUTF8);		// <META HTTP-EQUIV ="Content-Type" CONTENT = "text/html; charset=UTF-8">
#endif //_UNICODE
	WriteTagL(KHTMLHeadMetaGenTag);  		// <META NAME = "generator" CONTENT = "rt2html converter">
	WriteTagL(KHtmlHeadEndTag);	  		// </HEAD>

	// Get default background colour...
	CParaFormat* formatPara = CParaFormat::NewLC();
	iInputText->GlobalParaFormatLayer()->SenseEffectiveL(formatPara);
	TRgb backColour = formatPara->iFillColor;
	CleanupStack::PopAndDestroy(); // CParaFormat formatPara

	// Get default foreground colour...
	TCharFormat formatChar;
	iInputText->GlobalCharFormatLayer()->SenseEffective(formatChar);
	TRgb foreColour = formatChar.iFontPresentation.iTextColor;

	TBuf8<KMaxTagLength > tag;
	tag.Format(KHtmlBodyStartTag, backColour.Red(),
								  backColour.Green(),
								  backColour.Blue(),
								  foreColour.Red(),
								  foreColour.Green(),
								  foreColour.Blue());

	WriteTagL(tag);
	}


void CCrtToHTMLConverter::EndDocumentL()
//
// Close the document (write the </BODY> and </HTML> tags
//
	{
	if(iOldFmtCount)
		{
		WriteTagL(KHtmlFontEnd);
		}

	if(iBullet)
		{
		// End of list
		delete iBullet;
		iBullet = NULL;
		WriteTagL(KHtmlBulletListPointEnd);
		WriteTagL(KHtmlBulletListEnd);
		}
	else if (iInsertBlankDivClose)
		{
		WriteTagL(KHtmlDivBlankEnd);//&nbsp</DIV>
		}
	else
		{
		WriteTagL(KHtmlDivEnd);//</DIV>
		}
	
	WriteTagL(KHtmlBodyEndTag);	// </BODY>
	WriteTagL(KHtmlEndTag);		// </HTML>
	}

CConverterBase2* CCrtToHTMLConverter::NewL()
	{
	CConverterBase2* crtToHtmlConverter=new (ELeave) CCrtToHTMLConverter();
	return crtToHtmlConverter;
	}

const TImplementationProxy ImplementationTable[] =
    {
	IMPLEMENTATION_PROXY_ENTRY(0x1000071c,CCrtToHTMLConverter::NewL)
    };

EXPORT_C const TImplementationProxy* ImplementationGroupProxy(TInt& aTableCount)
    {
    aTableCount = sizeof(ImplementationTable) / sizeof(TImplementationProxy);
    return ImplementationTable;
    }
//-------------------------------------------------------------------------------------------------
// Conversion helper
//-------------------------------------------------------------------------------------------------
#ifdef _UNICODE
#include <utf.h>
// Stole most of this from Phil :-)
HBufC8* ConvertUnicodeToUTF8LC(const TDesC16& uniText)
	{
    // Final UTF8 destination buffer.
	TInt    len     = uniText.Length() * sizeof(TText);
	HBufC8* utfText = HBufC8::NewL(len); // Probably be enough...
    CleanupStack::PushL(utfText);

    // Keep going until there are no unconverted characters left.
    FOREVER
		{
        TPtr8 destination = utfText->Des();
		      destination.FillZ();
        TInt  charsLeft   = CnvUtfConverter::ConvertFromUnicodeToUtf8(destination, uniText);

        if(charsLeft < 0)
			User::Leave(KErrCorrupt);       // Conversion error due to input stream.
        else if(0==charsLeft)
			{
			return utfText;
			}
            else
            {
            // There are characters left to convert due to running out of destination buffer space.
			len += charsLeft * sizeof(TText);
			utfText = utfText->ReAlloc(len);
            }
		}
//    return NULL;  PFD - removed unreachable code as no warnings are acceptable.
	}
#endif // _UNICODE

//-------------------------------------------------------------------------------------------------
// Globals
//-------------------------------------------------------------------------------------------------

GLDEF_C void Panic(TTextToHTMLPanic aPanic)
// Panic the process with HTML converter as the category.
//
	{
	User::Panic(_L("CRT2HTML"),aPanic);
	}