filehandling/richtexttohtmlconverter/Src/RT2HTMCV.CPP
changeset 0 2e3d3ce01487
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/filehandling/richtexttohtmlconverter/Src/RT2HTMCV.CPP	Tue Feb 02 10:12:00 2010 +0200
@@ -0,0 +1,865 @@
+// 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);
+	}
+
+