[sword-svn] r102 - trunk/src/gui

dtrotzjr at www.crosswire.org dtrotzjr at www.crosswire.org
Tue Mar 18 20:17:26 MST 2008


Author: dtrotzjr
Date: 2008-03-18 20:17:25 -0700 (Tue, 18 Mar 2008)
New Revision: 102

Added:
   trunk/src/gui/BTextViewer.cpp
   trunk/src/gui/BTextViewer.h
Log:
Initial commit of BTextViewer. Files only, not yet plugged into the interface.

Added: trunk/src/gui/BTextViewer.cpp
===================================================================
--- trunk/src/gui/BTextViewer.cpp	                        (rev 0)
+++ trunk/src/gui/BTextViewer.cpp	2008-03-19 03:17:25 UTC (rev 102)
@@ -0,0 +1,1189 @@
+/******************************************************************************
+ *  BTextViewer.cpp - HTML Renderer for rendering biblical texts on Pocket PC 
+ *  devices.
+ *  Author: David C Trotz Jr. 
+ *  e-mail: dtrotzjr at crosswire.org
+ *
+ * $Id$
+ *
+ * Copyright 1998 CrossWire Bible Society (http://www.crosswire.org)
+ *	CrossWire Bible Society
+ *	P. O. Box 2528
+ *	Tempe, AZ  85280-2528
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <Winuser.h>
+#include "stdafx.h"
+#include "BTextViewer.h"
+
+
+VOID LOGTAG(const char *tag)
+{
+#if 0
+    FILE *fp = fopen("\\Storage Card\\tags.txt", "a");
+    fprintf(fp, "%s\n", tag);
+    fclose(fp);
+#endif
+}
+
+BTextViewer::BTextViewer(HINSTANCE hInstance, HWND hWndParent, RECT lRect)
+: m_fDragging(FALSE)
+, m_nTop(0)
+, m_nDragStart(0)
+, m_dwBuffSize(BTEXT_BUFF_INC)
+, m_dwBuffEnd(0)
+, m_fPreRendered(FALSE)
+, m_nRollVelocity(FALSE)
+, m_dwTrueStartPos(0)
+, m_dwTrueStartTime(0)
+, m_hCurFont(NULL)
+{
+    TCHAR   szWindowClass[] = L"BTextViewer";
+    m_ptLastClick.x = 0; m_ptLastClick.y = 0;
+
+    // Init the current font 
+    HFONT hSystemVariableFont = (HFONT ) GetStockObject(SYSTEM_FONT);
+    GetObject(hSystemVariableFont, sizeof(LOGFONT), &m_lfCurFont);
+    m_lfCurFont.lfHeight = -11;
+    m_lfCurFont.lfQuality = CLEARTYPE_QUALITY;
+    lstrcpy(m_lfCurFont.lfFaceName, _T("Veranda"));
+
+    m_lpszBuff = new TCHAR[m_dwBuffSize];
+
+    // Register window class...
+    WNDCLASS    wc;
+    wc.style            = CS_HREDRAW | CS_VREDRAW | CS_PARENTDC;
+    wc.lpfnWndProc      = (WNDPROC) BTextViewer::MessageRoute;
+    wc.cbClsExtra       = 0;
+    wc.cbWndExtra       = 0;
+    wc.hInstance        = hInstance;
+    wc.hIcon            = NULL;
+    wc.hCursor          = 0;
+    wc.hbrBackground    = (HBRUSH) GetStockObject(WHITE_BRUSH);
+    wc.lpszMenuName     = 0;
+    wc.lpszClassName    = szWindowClass;
+
+    RegisterClass(&wc);
+    m_hWnd = CreateWindow(szWindowClass, NULL, WS_CHILD | WS_VISIBLE,
+        lRect.left, lRect.top, lRect.right - lRect.left, lRect.bottom - lRect.top, hWndParent, NULL, hInstance, this);
+    for(DWORD i = 0; i < BTEXT_FONT_CACHE_MAX; i++)
+        m_hFontCache[i] = 0;
+}
+
+BTextViewer::~BTextViewer()
+{
+    if(m_lpszBuff)
+        delete [] m_lpszBuff;
+
+    DestroyWindow(m_hWnd);
+}
+
+VOID BTextViewer::Show()
+{
+    ShowWindow(m_hWnd, SW_SHOW);
+    // FOR COMPARATIVE PERFORMANCE TESTING...
+    // REMOVE ONCE INTEGRATED
+    m_fPreRendered = FALSE;
+    InvalidateRect(m_hWnd, NULL, TRUE);
+    UpdateWindow(m_hWnd);
+}
+
+VOID BTextViewer::Hide()
+{
+    ShowWindow(m_hWnd, SW_HIDE);
+}
+
+/* Routes the messages to the appropiate WindowProcedure by unwrapping the lParam 
+* we associated with this window upon creation.
+*/
+LRESULT CALLBACK BTextViewer::MessageRoute(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    BTextViewer*    wnd = NULL;
+
+    if (message == WM_CREATE) {	
+        ::SetWindowLong (hwnd, GWL_USERDATA, long((LPCREATESTRUCT(lParam))->lpCreateParams));
+    }
+
+    wnd = (BTextViewer*) (::GetWindowLong (hwnd, GWL_USERDATA));
+
+    if (wnd)
+        return wnd->WndProcBText(hwnd, message, wParam, lParam);
+    return ::DefWindowProc(hwnd, message, wParam, lParam);
+}
+
+LRESULT CALLBACK BTextViewer::WndProcBText(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    HDC         hdc;
+    PAINTSTRUCT ps;
+
+    switch (message) 
+    {
+    case WM_PAINT:
+        hdc = BeginPaint(hWnd, &ps);
+        this->Paint(hWnd,hdc,ps);
+        EndPaint(hWnd, &ps);
+        break;
+    case WM_LBUTTONDOWN:
+        this->SetTapState(TRUE, lParam);
+        break;
+    case WM_LBUTTONUP:
+        this->SetTapState(FALSE, lParam);
+        break;
+    case WM_MOUSEMOVE:
+        this->DragScreenToPoint(lParam);
+        break;
+    default:
+        return DefWindowProc(hWnd, message, wParam, lParam);
+    }
+    return 0;
+}
+VOID BTextViewer::MoveWindow(INT x, INT y, INT w, INT h)
+{
+    ::MoveWindow(m_hWnd, x, y, w, h, FALSE);
+}
+
+INT BTextViewer::GetHTMLTags(RenderState& rsState, DWORD &dwWordIndex)
+{
+    BOOL    fDone               = FALSE;
+    DWORD   dwBegin;
+    DWORD   dwEnd;
+    DWORD   dwIdentifyResult;
+    INT     nLineBreaks         = 0;
+
+    while(!fDone){
+        GetSpaces(rsState, dwWordIndex);
+        dwBegin = dwWordIndex + 1;
+        if(m_lpszBuff[dwWordIndex] != 0 && m_lpszBuff[dwWordIndex] == '<'){
+            while(m_lpszBuff[dwWordIndex] != 0 && m_lpszBuff[dwWordIndex] != '>')
+                dwWordIndex++;
+            dwEnd = dwWordIndex - dwBegin;
+            dwIdentifyResult = IndentifyTag(rsState, dwBegin, dwEnd);
+            dwWordIndex++;
+            switch(dwIdentifyResult){
+                case BTEXT_HTML_B_BEG:
+                    rsState.m_wBoldState++;
+                    break;
+                case BTEXT_HTML_B_END:
+                    if(rsState.m_wBoldState)
+                        rsState.m_wBoldState--;
+                    break;
+                case BTEXT_HTML_BR:
+                    nLineBreaks++;
+                    break;
+                case BTEXT_HTML_I_BEG:
+                    rsState.m_wItalicState++;
+                    break;
+                case BTEXT_HTML_I_END:
+                    if(rsState.m_wItalicState)
+                        rsState.m_wItalicState--;
+                    break;
+                case BTEXT_HTML_A_BEG:
+                    rsState.m_wAState++;
+                    rsState.m_dwlWordNum++;
+                    rsState.m_dwSubWordNum = 0;
+                    break;
+                case BTEXT_HTML_A_END:
+                    if(rsState.m_wAState)
+                        rsState.m_wAState--;
+                    rsState.m_dwlWordNum++;
+                    rsState.m_dwSubWordNum = 0;
+                    break;
+                case BTEXT_HTML_SUB_BEG:
+                    rsState.m_wSubState++;
+                    break;
+                case BTEXT_HTML_SUB_END:
+                    if(rsState.m_wSubState)
+                        rsState.m_wSubState--;
+                    break;
+                case BTEXT_HTML_SUP_BEG:
+                    rsState.m_wSuperState++;
+                    break;
+                case BTEXT_HTML_SUP_END:
+                    if(rsState.m_wSuperState)
+                        rsState.m_wSuperState--;
+                    break;
+                case BTEXT_HTML_P_BEG:
+                    rsState.m_wParagraphState++;
+                    nLineBreaks++;
+                    break;
+                case BTEXT_HTML_P_END:
+                    if(rsState.m_wParagraphState)
+                        rsState.m_wParagraphState--;
+                    nLineBreaks++;
+                    break;
+            }
+        }else
+            fDone = TRUE;
+    }
+    return nLineBreaks;
+}
+
+DWORD BTextViewer::IndentifyTag(RenderState& rsState,DWORD dwWordIndex,DWORD dwWordEnd)
+{
+    DWORD dwMask      = 0x00000000;
+    DWORD dwResult    = 0x00000000;
+    DWORD dwLen       = 0;
+    DWORD dwOrigIndex = dwWordIndex;
+    DWORD dwSubIndex  = 0;
+
+    if(!dwWordEnd)
+        return BTEXT_HTML_ILLEGAL_TAG_FORMAT; // There is nothing to do here.
+
+    if((dwWordIndex - dwOrigIndex) >=  dwWordEnd)
+        return BTEXT_HTML_ILLEGAL_TAG_FORMAT;
+
+    if(m_lpszBuff[dwWordIndex] == '/'){
+        dwMask = 0xFFFFFFFF;
+        dwWordIndex++;
+    }
+
+    if((dwWordIndex - dwOrigIndex) >=  dwWordEnd)
+        return BTEXT_HTML_ILLEGAL_TAG_FORMAT;
+    // Get the token
+    while((dwWordIndex  - dwOrigIndex) + dwLen <  dwWordEnd && m_lpszBuff[dwWordIndex + dwLen] != ' '){
+        dwLen++;
+    }
+
+    // Determine the token
+    switch(dwLen){
+        case 0:
+            dwResult = BTEXT_HTML_ILLEGAL_TAG_FORMAT;
+            break;
+        case 1:
+            if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"a", 1) == 0){
+                dwSubIndex = dwWordIndex + 1;
+                while(m_lpszBuff[dwSubIndex] == ' ')
+                    dwSubIndex++;
+                if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"name=\"", 6) == 0){
+                    dwSubIndex += 6;
+                    rsState.m_wVerseNum = (short)_wtoi(&m_lpszBuff[dwSubIndex]);
+                }else if(dwMask == 0){ // This is not an '/a'
+                    while(m_lpszBuff[dwSubIndex] == ' ')
+                        dwSubIndex++;
+                    if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"href=\"", 6) == 0){
+                        dwSubIndex += 6;
+                        rsState.m_lpszHref = &m_lpszBuff[dwSubIndex];
+                        rsState.m_dwHrefLen = 0;
+                        while(m_lpszBuff[dwSubIndex++] != '\"')
+                            rsState.m_dwHrefLen++;
+                    }
+                    dwResult = dwMask ^ BTEXT_HTML_A_BEG;
+                }else
+                    dwResult = dwMask ^ BTEXT_HTML_A_BEG;
+
+            }else if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"b", 1) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_B_BEG;
+            }else if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"i", 1) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_I_BEG;
+            }else if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"p", 1) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_P_BEG;
+            }
+            break;
+        case 2:
+            if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"br", 2) == 0){
+                dwResult = BTEXT_HTML_BR;		
+            }
+            break;
+        case 3:
+            if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"sup", 3) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_SUP_BEG;	
+            }else if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"sub", 3) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_SUB_BEG;
+            }else if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"br/", 3) == 0){
+                dwResult = BTEXT_HTML_BR;		
+            }
+            break;
+        case 4:
+            if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"font", 4) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_FONT_BEG;
+                if(!dwMask){
+                    // Found a <font... element
+                    if(!rsState.m_lpftFontTagTail){
+                        rsState.m_lpftFontTagHead = new FontTagItem();
+                        rsState.m_lpftFontTagTail = rsState.m_lpftFontTagHead;
+                    }else{
+                        rsState.m_lpftFontTagTail->m_lpftNext = new FontTagItem();
+                        rsState.m_lpftFontTagTail = rsState.m_lpftFontTagTail->m_lpftNext;
+                    }
+                    dwSubIndex += dwWordIndex + 4;
+                    while(m_lpszBuff[dwSubIndex] != '>'){
+                        while(m_lpszBuff[dwSubIndex] == ' ')
+                            dwSubIndex++;
+                        if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"size=\"", 6) == 0){
+                            dwSubIndex += 6;
+                            rsState.m_lpftFontTagTail->m_siRelFontSize = (short)_wtoi(&m_lpszBuff[dwSubIndex]);
+                            while(m_lpszBuff[dwSubIndex] != '\"')
+                                dwSubIndex++;
+                            dwSubIndex++;
+                        }else if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"color=\"", 7) == 0){
+                            dwSubIndex += 7;
+                            if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"red\"", 4) == 0){
+                                dwSubIndex += 4;
+                                rsState.m_lpftFontTagTail->m_crFontColor = 0x000000FF;
+                            }else if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"green\"", 6) == 0){
+                                dwSubIndex += 6;
+                                rsState.m_lpftFontTagTail->m_crFontColor = 0x0000FF00;
+                            }else if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"blue\"", 5) == 0){
+                                dwSubIndex += 5;
+                                rsState.m_lpftFontTagTail->m_crFontColor = 0x00FF0000;
+                            }else if(wcsnicmp(&m_lpszBuff[dwSubIndex], L"black\"", 6) == 0){
+                                dwSubIndex += 6;
+                                rsState.m_lpftFontTagTail->m_crFontColor = 0x00000000;
+                            }
+                        }
+                    }
+                }else{ 
+                    // Its a </font pop the last item off the linked list
+                    // this has potential to be memory leak central...
+                    if(rsState.m_lpftFontTagTail){
+                        if(rsState.m_lpftFontTagHead == rsState.m_lpftFontTagTail){
+                            delete rsState.m_lpftFontTagHead;
+                            rsState.m_lpftFontTagHead = NULL;
+                            rsState.m_lpftFontTagTail = NULL;
+                        }else{
+                            FontTagItem *next = rsState.m_lpftFontTagHead;
+                            FontTagItem *prev = next;
+
+                            while(next != rsState.m_lpftFontTagTail){
+                                prev = next;
+                                next = prev->m_lpftNext;
+                            }
+                            delete next;
+                            rsState.m_lpftFontTagTail = prev;
+                        }
+                    }
+                }
+            }
+            break;
+        case 5:
+            if(wcsnicmp(&m_lpszBuff[dwWordIndex], L"small", 5) == 0){
+                dwResult = dwMask ^ BTEXT_HTML_SMALL_BEG;
+            }
+            break;
+        default:
+            dwResult = 0;
+    }
+
+    return dwResult;
+}
+
+
+INT BTextViewer::NextWord(RenderState& rsState,DWORD dwWordIndex, DWORD &dwWordEnd)
+{
+    dwWordEnd = 0;
+    // Special Entities are: 
+    //      &gt; &lt; &amp; 
+    //      and others (later)
+    rsState.m_wTotalSpclEnt = 0;
+
+    while(m_lpszBuff[dwWordIndex + dwWordEnd] != 0 && m_lpszBuff[dwWordIndex + dwWordEnd] != ' ' && m_lpszBuff[dwWordIndex + dwWordEnd] != '\n' && m_lpszBuff[dwWordIndex + dwWordEnd] != '<'){
+        if(m_lpszBuff[dwWordIndex + dwWordEnd] == '&'){
+            dwWordEnd++;
+            while(m_lpszBuff[dwWordIndex + dwWordEnd] != ';'){
+                dwWordEnd++;
+                rsState.m_wTotalSpclEnt++;
+            }
+        }
+        dwWordEnd++;
+    }
+    if(m_lpszBuff[dwWordIndex + dwWordEnd] == '\n')
+        return BTEXT_NEWLINE_ENCOUNTERED;
+    else if(m_lpszBuff[dwWordIndex + dwWordEnd] == '<')
+        return BTEXT_HTML_OPEN_BRACKET;
+
+    return BTEXT_SPACE_ENCOUNTERED;
+}
+VOID BTextViewer::Clear()
+{
+    m_BTLines.ClearLines();
+    m_BTLines.InitLines(0);
+    if(m_lpszBuff)
+        delete [] m_lpszBuff;
+    m_lpszBuff = NULL;
+    m_dwBuffEnd = 0;
+    m_dwBuffSize = 0;
+    m_nTop = 0;
+    m_fPreRendered = FALSE;
+}
+VOID BTextViewer::PreRenderBuff(HDC hdc)
+{
+    BTextWord   thisWord;
+    RenderState rsState;
+    memset(&rsState, 0, sizeof(rsState));
+    RECT        rectDims;
+    INT         nLineX      = BTEXT_MARGIN;
+    INT         nLineY      = BTEXT_MARGIN;
+    DWORD       dwLineNum   = 0;
+    rectDims.top            = 0; 
+    rectDims.left           = 0; 
+    rectDims.right          = 0; 
+    rectDims.bottom         = 0;
+    INT         nLineH      = (INT)(BTEXT_LINE_SPACING*(FLOAT)DrawText(hdc, L" ", 1, &rectDims, DT_CALCRECT | DT_LEFT | DT_BOTTOM | DT_SINGLELINE));
+    DWORD       dwWordIndex = 0;
+    DWORD       dwWordEnd   = 0;
+    INT         nSpaceWidth = rectDims.right - rectDims.left;
+    INT         nScreenWidth= GetSystemMetrics(SM_CXSCREEN) - BTEXT_MARGIN;
+    DWORD       dwResult    = 0;
+    INT         nAddLines   = 0;
+    DWORDLONG   dwlFontState= 0;
+    DWORDLONG dwlFontColor;
+    DWORDLONG dwlFontHeight;
+
+    m_BTLines.ClearLines();
+    m_BTLines.InitLines(nLineH);
+
+    while(TRUE){
+        thisWord.Clear();
+        rsState.m_space_encountered = FALSE;
+
+        GetSpaces(rsState, dwWordIndex);
+        nAddLines = GetHTMLTags(rsState, dwWordIndex);
+        if(nAddLines){
+            dwLineNum += nAddLines;
+            nLineX = BTEXT_MARGIN;
+            nLineY += nAddLines * nLineH;
+            rsState.m_space_encountered = FALSE;
+        }
+        GetSpaces(rsState, dwWordIndex);
+        if(rsState.m_space_encountered){
+            nLineX += nSpaceWidth;
+            rsState.m_space_encountered = FALSE;
+            rsState.m_dwlWordNum++;
+            rsState.m_dwSubWordNum = 0;
+        }else
+            rsState.m_dwSubWordNum++;
+
+        dwResult = NextWord(rsState, dwWordIndex, dwWordEnd);
+        if(dwWordEnd == 0){
+            break;
+        }
+        rectDims.left = nLineX;
+
+        // Font...
+        FontTagItem *tag = rsState.m_lpftFontTagHead;
+        dwlFontColor = BTEXT_DEFAULT_FONT_COLOR;
+        dwlFontHeight = abs(BTEXT_DEFAULT_FONT_HEIGHT);
+        while(tag){
+            if(tag->m_crFontColor != BTEXT_FONT_NOT_A_COLOR)
+                dwlFontColor = tag->m_crFontColor;
+            dwlFontHeight += tag->m_siRelFontSize;
+            tag = tag->m_lpftNext;
+        }
+        if(rsState.m_wSubState || rsState.m_wSuperState)
+            dwlFontHeight -= 3;
+
+        dwlFontState = 
+            ((DWORDLONG)(dwlFontColor  << 40) & 0xFFFFFF0000000000)	| 
+            ((DWORDLONG)(dwlFontHeight << 32) & 0x000000FF00000000)	|
+            (rsState.m_wAState > 0	    ? BTEXT_HTML_A_BEG : 0)	|
+            (rsState.m_wBoldState > 0   ? BTEXT_HTML_B_BEG : 0)	|
+            (rsState.m_wItalicState > 0 ? BTEXT_HTML_I_BEG : 0);
+        SetFont(hdc, dwlFontState);
+
+        rectDims.top = nLineY;
+        // Set the properties for this word.
+        thisWord.m_rect             = rectDims;
+        thisWord.m_dwlfFontState    = dwlFontState;
+        thisWord.m_lpszHref         = rsState.m_lpszHref;
+        thisWord.m_dwHrefLen        = rsState.m_dwHrefLen;
+        thisWord.m_dwlWordNum       = rsState.m_dwlWordNum;
+        thisWord.m_dwSubWordNum     = rsState.m_dwSubWordNum;
+        // Determine if we encountered any special entities...
+        if(rsState.m_wTotalSpclEnt > 0){
+            InterpretSpecialEntities(dwWordIndex, dwWordEnd, &thisWord);
+        }else{
+            thisWord.m_fOwner = FALSE;
+            thisWord.m_lpszWord = &m_lpszBuff[dwWordIndex];
+            thisWord.m_dwWordLen = dwWordEnd;
+        }
+        // Calc the text rect
+        DrawText(hdc, thisWord.m_lpszWord, thisWord.m_dwWordLen, &thisWord.m_rect, 
+            DT_CALCRECT | DT_LEFT | DT_BOTTOM | DT_SINGLELINE);
+
+        if(rsState.m_wSuperState)
+            thisWord.m_rect.top -= 1; // TODO: Determine the '1' value based upon the font size
+        else if(rsState.m_wSubState)
+            thisWord.m_rect.top = 2*thisWord.m_rect.top + nLineH - thisWord.m_rect.bottom + 1; // TODO: Same as above...
+        else
+            thisWord.m_rect.top = 2*thisWord.m_rect.top + nLineH - thisWord.m_rect.bottom; // Baseline justify
+
+        if(thisWord.m_rect.right > (nScreenWidth - BTEXT_MARGIN)){
+            // Place on next line. But...
+            // We may need to move the prefix of this word down, thus we need to adjust the left
+            // edge of this word to make room.
+            int adjustedMargin = BTEXT_MARGIN;
+            if(thisWord.m_dwSubWordNum > 0)
+                adjustedMargin = m_BTLines.ValidateNewLineWord(thisWord.m_dwlWordNum,nLineH, BTEXT_MARGIN);
+
+            dwLineNum++;
+            thisWord.m_rect.right = thisWord.m_rect.right - thisWord.m_rect.left + adjustedMargin;
+            thisWord.m_rect.left = adjustedMargin;
+            thisWord.m_rect.top += nLineH;
+            thisWord.m_rect.bottom += nLineH;
+
+            nLineY += nLineH;
+        }
+
+        // Store the line to be rendered later.
+        m_BTLines.AddWordToLine(dwLineNum, thisWord);
+
+        // Prep for next iteration
+        if(rsState.m_space_encountered)
+            nLineX = thisWord.m_rect.right + nSpaceWidth;
+        else
+            nLineX = thisWord.m_rect.right;
+
+        //curWord++;
+        dwWordIndex += dwWordEnd;
+    }
+    if(rsState.m_lpftFontTagHead) // Properly formatted text would never need this, but that cannot be assumed.
+        delete rsState.m_lpftFontTagHead; // Do not delete Tail it is a convenience pointer...
+
+    m_fPreRendered = TRUE;
+}
+
+// I wanted to use the same font setter for both prerendering and rendering
+// so I wrote this method. The first if statement will try to determine if
+// we can safely skip this call since it is a rather expensive call
+// after several thousand words are pre-rendered and rendered. But,
+// If the text is already pre-rendered and we are calling this during
+// the actual rendering stage we are sure we want to make this call already
+// and thus the !m_bnRendering statement ensures we will change the font - otherwise
+// certain lines get the wrong fonts applied to them.
+VOID BTextViewer::SetFont(HDC hdc, DWORDLONG dwlFontState)
+{
+    INT     lfWeight    = dwlFontState & BTEXT_HTML_B_BEG ? FW_BOLD : FW_NORMAL;
+    UCHAR   lfItalic    = (char)((dwlFontState & BTEXT_HTML_I_BEG) > 0 ? 1 : 0);
+    UCHAR   lfUnderline = (char)((dwlFontState & BTEXT_HTML_A_BEG) > 0 ? 1 : 0); 
+    INT     lfHeight    = -1*((int)(0x000000FF & ((dwlFontState & 0x000000FF00000000) >> 32)));
+    HGDIOBJ oldFont     = 0;
+    // Compute the font cache index...
+    DWORD   dwFontCacheIndex = 0x000000FF & ((dwlFontState & (BTEXT_HTML_B_BEG | BTEXT_HTML_I_BEG | BTEXT_HTML_A_BEG)) | ((dwlFontState & 0x000000FF00000000) >> 24));
+    
+    if(!m_fPreRendered && 
+        m_lfCurFont.lfWeight    == lfWeight && 
+        m_lfCurFont.lfItalic    == lfItalic && 
+        m_lfCurFont.lfHeight    == lfHeight &&
+        m_lfCurFont.lfUnderline == lfUnderline)
+        return;
+
+    m_lfCurFont.lfWeight    = lfWeight;
+    m_lfCurFont.lfItalic    = lfItalic;
+    m_lfCurFont.lfHeight    = lfHeight;
+    m_lfCurFont.lfUnderline = lfUnderline;
+    
+    if(!m_hFontCache[dwFontCacheIndex]){
+        m_hFontCache[dwFontCacheIndex] = CreateFontIndirect(&m_lfCurFont);
+    }
+    SelectObject(hdc, m_hFontCache[dwFontCacheIndex]);
+    /*
+    m_hCurFont = CreateFontIndirect(&m_lfCurFont);
+    oldFont = SelectObject(hdc, m_hCurFont);
+    if(oldFont)
+        DeleteObject(oldFont);
+    */
+}
+
+BOOL BTextViewer::GetSpaces(RenderState& rsState, DWORD &dwWordIndex){
+    DWORD i = 0;
+
+    while(m_lpszBuff[dwWordIndex] != '\0' && (m_lpszBuff[dwWordIndex] == ' ' || (m_lpszBuff[dwWordIndex] == '&' && wcsnicmp(&m_lpszBuff[dwWordIndex], L"&nbsp;", 6) == 0)) ){
+        dwWordIndex += m_lpszBuff[dwWordIndex] == '&' ? 6 : 1; // Did we find a &nbsp; then we need to increment that much.
+        i++;
+
+    }
+
+    if(i) 
+        rsState.m_space_encountered = TRUE;
+    return i > 0;
+}
+
+VOID BTextViewer::InterpretSpecialEntities(DWORD dwWordIndex, DWORD dwWordLen, BTextWord *pbtWord)
+{
+    DWORD dwSubIndex    = 0;
+    DWORD dwBufIndex    = 0;
+    TCHAR szBuf[256]    = {0};
+
+    while(dwSubIndex < dwWordLen){
+        if(m_lpszBuff[dwWordIndex + dwSubIndex] == '&'){
+            dwSubIndex++;
+            if(wcsncmp(&m_lpszBuff[dwWordIndex + dwSubIndex], L"lt;",3) == 0){
+                szBuf[dwBufIndex++] = '<';
+                dwSubIndex += 3;
+            }else if(wcsncmp(&m_lpszBuff[dwWordIndex + dwSubIndex], L"gt;",3) == 0){
+                szBuf[dwBufIndex++] = '>';
+                dwSubIndex += 3;
+            }else if(wcsncmp(&m_lpszBuff[dwWordIndex + dwSubIndex], L"amp;",4) == 0){
+                szBuf[dwBufIndex++] = '&';
+                dwSubIndex += 4;
+            }else if(wcsncmp(&m_lpszBuff[dwWordIndex + dwSubIndex], L"nbsp;",5) == 0){
+                szBuf[dwBufIndex++] = ' ';
+                dwSubIndex += 5;
+            }else{
+                // I have no idea what this is, simply display it...
+                szBuf[dwBufIndex++] = '&';
+                dwSubIndex++;
+            }
+        }
+        else
+            szBuf[dwBufIndex++] = m_lpszBuff[dwWordIndex + dwSubIndex++];
+    }
+
+    pbtWord->OwnWord(szBuf, dwBufIndex);
+}
+
+INT BTextViewer::Paint(HWND hWnd, HDC hdc, PAINTSTRUCT ps)
+{
+    COLORREF    crColor     = 0x00000000;
+    COLORREF    crOldColor  = SetTextColor(hdc, crColor); 
+    INT         nSavedDC    = SaveDC(hdc);
+    RECT        rectDims;
+    rectDims.top            = 0; 
+    rectDims.left           = 0; 
+    rectDims.right          = 0; 
+    rectDims.bottom         = 0;
+
+    SetBkMode(hdc, TRANSPARENT); 
+    if(!m_fPreRendered)
+        PreRenderBuff(hdc);
+
+    for(unsigned int line = 0; line <= m_BTLines.m_dwLastLine; line++){
+        BTextWord *pTWord = &m_BTLines.m_lpLines[line];
+        if((pTWord->m_rect.top + m_BTLines.m_nLineH + m_nTop) <= ps.rcPaint.top)
+            continue; // this line is above the region we are interested in painting
+        else if(pTWord->m_rect.top + m_nTop > ps.rcPaint.bottom)
+            break; // We are done the rest of the lines are below the region we care about.
+        while(pTWord->m_lpszWord){
+            if(!pTWord->m_lpPrevWord || pTWord->m_dwlfFontState != pTWord->m_lpPrevWord->m_dwlfFontState){
+                if(pTWord->m_dwlfFontState & BTEXT_HTML_A_BEG){ // links get priority coloring
+                    crColor = COLORREF(0x00FF0000);
+                }else{
+                    crColor = COLORREF(((pTWord->m_dwlfFontState >> 40) & 0x00FFFFFF));
+                }
+                SetTextColor(hdc, crColor);
+                SetFont(hdc, pTWord->m_dwlfFontState);
+            }
+
+            ExtTextOut(hdc,pTWord->m_rect.left, pTWord->m_rect.top + m_nTop, NULL, NULL, pTWord->m_lpszWord, pTWord->m_dwWordLen, NULL);
+            pTWord = pTWord->m_lpNextWord;
+        }
+    }
+
+    SetTextColor(hdc, crOldColor); 
+    ClearFontCache(hdc);
+    RestoreDC(hdc, nSavedDC);
+    return 0;
+}
+
+VOID BTextViewer::ClearFontCache(HDC hdc)
+{
+    // Clear the font from device context so it can be deleted with the rest...
+    SelectObject(hdc, GetStockObject(SYSTEM_FONT));
+    
+    for(DWORD i = 0; i < BTEXT_FONT_CACHE_MAX; i++){
+        if(m_hFontCache[i]){
+            DeleteObject(m_hFontCache[i]);
+            m_hFontCache[i] = 0;
+
+        }
+    }
+}
+
+VOID BTextViewer::SetTapState(BOOL fMouseDown, LPARAM lParam)
+{
+    POINT pt;
+    DWORD dwDiffTime = 0;
+    pt.x = LOWORD(lParam);
+    pt.y = HIWORD(lParam);
+    ScreenToClient(m_hWnd, &pt);
+    // Test if the mouse moved more than some threshold value.
+    BOOL mouseMoved = (abs(m_ptLastClick.y - pt.y) > 5) || (abs(m_ptLastClick.x - pt.x) > 5);
+
+    if(!m_fDragging){	
+        m_nDragStart =  pt.y;
+    }
+
+    // If we are about to go into a dragging mode, 
+    // or we are about to leave a dragging mode,
+    // we should calculate a rolling velocity.
+    if(fMouseDown && !m_fDragging){
+        m_dwTrueStartPos = pt.y;
+        m_dwTrueStartTime = GetTickCount();
+        m_nRollVelocity = 0;
+    }else if(!fMouseDown && m_fDragging){
+        dwDiffTime = GetTickCount() - m_dwTrueStartTime;
+        if(dwDiffTime > 200)
+            m_nRollVelocity = 0;
+        else
+            m_nRollVelocity = (80*(int)(pt.y - m_dwTrueStartPos))/((int)dwDiffTime);
+    }
+
+    if(fMouseDown){
+        m_dwClickTime = GetTickCount();
+        m_fDragging = TRUE;
+        SetCapture(m_hWnd);
+    }else{
+        m_fDragging = FALSE;
+        ReleaseCapture();
+    }
+
+    // If we are absolutely positive the user wants to click...
+    if(!fMouseDown && !mouseMoved && (GetTickCount() - m_dwClickTime) < 120)
+        GetWordAtPoint(lParam);
+    m_ptLastClick = pt;
+
+    // If there is a velocity set this will use it up, otherwise it drops out nicely.
+    RollTillStopped();
+}
+
+VOID BTextViewer::GetWordAtPoint(LPARAM lParam)
+{
+    POINT pt;
+    pt.x = LOWORD(lParam);
+    pt.y = HIWORD(lParam);
+    BTextWord *pbtWord;
+    DWORD dwLineNum = 0;
+    TCHAR szWordFound[128] = {0};
+
+    while(dwLineNum <= m_BTLines.m_dwLastLine && (m_BTLines.m_lpLines[dwLineNum].m_rect.bottom + m_nTop) < pt.y)
+        dwLineNum++;
+    if(dwLineNum > m_BTLines.m_dwLastLine)
+        return;
+    pbtWord = &m_BTLines.m_lpLines[dwLineNum]; 
+    while(pbtWord->m_lpszWord && pbtWord->m_rect.right < pt.x)
+        pbtWord = pbtWord->m_lpNextWord;
+    if(pbtWord->m_lpszWord){
+        if(pbtWord->m_dwlfFontState & BTEXT_HTML_A_BEG && pbtWord->m_lpszHref){
+            wcsncpy(szWordFound, pbtWord->m_lpszHref, pbtWord->m_dwHrefLen);
+        }else{
+            wcsncpy(szWordFound, pbtWord->m_lpszWord, pbtWord->m_dwWordLen);
+        }
+        MessageBox(m_hWnd, szWordFound, L"Found...", MB_OK);
+    }
+}
+
+VOID BTextViewer::DragScreenToPoint(LPARAM lParam)
+{
+    POINT   pt;
+    RECT    rectUpdateRect;
+    INT     nDragDist = 0;
+
+    if(m_fDragging){
+        pt.x = 0;
+        pt.y = HIWORD(lParam);
+        ScreenToClient(m_hWnd, &pt);
+        nDragDist = pt.y - m_nDragStart;
+        m_nTop += nDragDist;
+        // Prevent rolling past top or bottom
+        if(m_nTop > 0){
+            nDragDist -= m_nTop;
+            m_nTop = 0;
+        }else if(m_nTop < -m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom){
+            // nDragDist does not matter here because we are Scrolling a white window, 
+            // Keeping track of m_nTop is important as it is our reference point for 
+            // scrolling later
+            m_nTop = -(m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom);
+        }
+
+        ScrollWindowEx(m_hWnd, 0, nDragDist, NULL, NULL, NULL, &rectUpdateRect, NULL);
+        InvalidateRect(m_hWnd, &rectUpdateRect, TRUE);
+        UpdateWindow(m_hWnd);
+
+        m_nDragStart = pt.y;
+    }
+}
+
+VOID BTextViewer::RollTillStopped()
+{
+    RECT    rectUpdateRect;
+    BOOL    fDone       = FALSE;
+    INT     nDirection  = m_nRollVelocity > 0 ? 1 : -1;
+
+    while(!fDone && nDirection*m_nRollVelocity > 0){
+        m_nTop += m_nRollVelocity;
+
+        // Prevent rolling beyond the top of the page.
+        if(m_nTop > 0){
+            m_nRollVelocity -= m_nTop;
+            m_nTop = 0;
+            fDone = TRUE;
+        }else if(m_nTop < -m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom){
+            // dist does not matter here because we are Scrolling a white window, 
+            // Keeping track of m_nTop is important as it is our reference point for 
+            // scrolling later
+            m_nTop = -(m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom);
+            fDone = TRUE;
+        }
+
+        ScrollWindowEx(m_hWnd, 0, m_nRollVelocity, NULL, NULL, NULL, &rectUpdateRect, NULL);
+        InvalidateRect(m_hWnd, &rectUpdateRect, TRUE);
+        UpdateWindow(m_hWnd);
+        m_nRollVelocity = (m_nRollVelocity - m_nRollVelocity/6) - ((nDirection*m_nRollVelocity < 10) ? nDirection : 0); // the nDirection part ensures continual degrading in the velocity
+    }
+}
+
+VOID BTextViewer::ScrollFullPage(INT nDirection)
+{
+    RECT    rectUpdateRect;
+    RECT    rectClientRect;
+    INT     nDist       = 0;
+    BOOL    fDone       = FALSE;
+    GetClientRect(m_hWnd,&rectClientRect);
+    INT     nRollBy     = rectClientRect.bottom - rectClientRect.top;
+    INT     nCalcDist   = 0;
+    CONST INT UPPER_SB  = 15; // Upper bound on the scrolling per iteration.
+    nDirection          = (nDirection > 0) ? 1 : -1;
+    
+    for(INT i = 0;!fDone && i < nRollBy; i+=nCalcDist){
+        nCalcDist = UPPER_SB - (int)(UPPER_SB*((float)i/(float)nRollBy)) + 1;
+        nDist = nDirection*nCalcDist;
+        m_nTop += nDist;
+        // Prevent rolling beyond the top of the page.
+        if(m_nTop > 0){
+            nDist -= m_nTop;
+            m_nTop = 0;
+            fDone = TRUE;
+        }else if(m_nTop < -m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom){
+            // nDist does not matter here because we are Scrolling a white window, 
+            // Keeping track of m_nTop is important as it is our reference point for 
+            // scrolling later
+            m_nTop = -(m_BTLines.m_lpLines[m_BTLines.m_dwLastLine].m_rect.bottom);
+            fDone = TRUE;
+        }
+        ScrollWindowEx(m_hWnd, 0, nDist, NULL, NULL, NULL, &rectUpdateRect, NULL);
+        InvalidateRect(m_hWnd, &rectUpdateRect, TRUE);
+        UpdateWindow(m_hWnd);
+    }
+}
+
+VOID BTextViewer::AddText(TCHAR *szText, DWORD dwSize)
+{
+    DWORD i = 0;
+
+    // Do we need to make room for the new szText
+    if((m_dwBuffEnd + dwSize) > m_dwBuffSize){
+        DWORD dwNewSize = BTEXT_BUFF_INC > dwSize ? BTEXT_BUFF_INC : dwSize;
+        TCHAR *lpszTmp = new TCHAR[dwNewSize];
+        for(i = 0; i < m_dwBuffSize; i++){
+            lpszTmp[i] = m_lpszBuff[i];
+        }
+        delete [] m_lpszBuff;
+        m_lpszBuff = lpszTmp;
+        m_dwBuffSize = dwNewSize;
+    }
+
+    // If this is a new buffer we need to
+    // discard HTML header stuff by pointing to the beginning of the <body></body>
+    i = 0;
+    if(m_dwBuffEnd == 0){ 
+        do{
+            while(szText[i] != '<' && i < dwSize) i++;
+            if(wcsnicmp(&szText[i], L"<body>", 6) == 0){
+                i += 6;
+                break;
+            }
+            while(szText[i++] != '>' && i < dwSize);
+        }while(TRUE);
+    }
+
+    // Copy it in, and ignore the closing tags, if any
+    for(; i < dwSize; i++){
+        // We arbitrarily choose to start looking at the last 100 characters for the tail end 
+        // of this HTML stream. If we fail to chop it off its not the end of the world, but
+        // it would be nice to get out of the way now.
+        if(dwSize > 100 && i > (dwSize - 100) && wcsnicmp(&szText[i], L"</body>", 7) == 0)
+            break; 
+        if(szText[i] == 0x09) // Horizontal tab, replace with a single space. (for now at least.)
+            m_lpszBuff[m_dwBuffEnd++] = ' ';
+        else if(szText[i] != '\n') // Ignore new-lines
+            m_lpszBuff[m_dwBuffEnd++] = szText[i];
+    }
+    for(i = 0; i + m_dwBuffEnd < m_dwBuffSize; i++)
+        m_lpszBuff[i + m_dwBuffEnd] = 0;
+}
+
+/*****************************************************************************
+ * BTextWord                                                                 *
+ *****************************************************************************/
+
+BTextViewer::BTextWord::BTextWord()
+: m_lpNextWord(0)
+, m_lpPrevWord(0)
+, m_lpszWord(NULL)
+, m_dwWordLen(0)
+, m_lpszHref(NULL)
+, m_dwHrefLen(0)
+, m_fOwner(FALSE)
+, m_dwlfFontState(0)
+, m_dwlWordNum(0)
+, m_dwSubWordNum(0)
+{
+    m_rect.top      = 0;
+    m_rect.left     = 0;
+    m_rect.bottom   = 0;
+    m_rect.right    = 0;
+}
+
+VOID BTextViewer::BTextWord::Clear()
+{
+    if(m_fOwner && m_lpszWord)
+        delete [] m_lpszWord;
+    m_fOwner            = FALSE;
+    m_dwlfFontState     = 0;
+    m_dwHrefLen         = 0;
+    m_dwWordLen         = 0;
+    m_lpszWord          = NULL;
+    m_lpszHref          = NULL;
+    m_rect.top          = 0;
+    m_rect.bottom       = 0;
+    m_rect.left         = 0;
+    m_rect.right        = 0;
+    m_dwlWordNum        = 0;
+    m_dwSubWordNum      = 0;
+    m_lpNextWord        = NULL;
+    m_lpPrevWord        = NULL;
+}
+
+VOID BTextViewer::BTextWord::OwnWord(TCHAR *lpszWord, DWORD dwWordLen)
+{
+    if(m_fOwner && m_lpszWord){
+        delete [] m_lpszWord;
+    }
+    m_lpszWord = new TCHAR[dwWordLen + 1];
+    for(DWORD i = 0; i < dwWordLen; i++)
+        m_lpszWord[i] = lpszWord[i];
+
+    m_lpszWord[dwWordLen]   = 0;
+    m_dwWordLen             = dwWordLen;
+    m_fOwner                = TRUE;
+}
+
+BTextViewer::BTextWord::BTextWord(const BTextViewer::BTextWord &rhs)
+{
+    m_dwlfFontState = rhs.m_dwlfFontState;
+    m_dwHrefLen     = rhs.m_dwHrefLen;
+    m_dwWordLen     = rhs.m_dwWordLen;
+    m_lpszHref      = rhs.m_lpszHref;
+    m_lpNextWord    = rhs.m_lpNextWord;
+    m_lpPrevWord    = rhs.m_lpPrevWord;
+    m_fOwner        = rhs.m_fOwner;
+    m_rect          = rhs.m_rect;
+    m_dwlWordNum    = rhs.m_dwlWordNum;
+    m_dwSubWordNum  = rhs.m_dwSubWordNum;
+
+    if(m_fOwner && rhs.m_lpszWord){
+        m_lpszWord = new TCHAR[m_dwWordLen + 1];
+        for(DWORD i = 0; i < m_dwWordLen; i++)
+            m_lpszWord[i] = rhs.m_lpszWord[i];
+        m_lpszWord[m_dwWordLen] = 0;
+    }else{
+        m_lpszWord = rhs.m_lpszWord;
+    }
+}
+BTextViewer::BTextWord & BTextViewer::BTextWord::operator=(const BTextViewer::BTextWord &rhs)
+{
+    if(this == &rhs)
+        return *this; // We detect self assignment
+
+    m_dwlfFontState = rhs.m_dwlfFontState;
+    m_dwHrefLen     = rhs.m_dwHrefLen;
+    m_dwWordLen     = rhs.m_dwWordLen;
+    m_lpszHref      = rhs.m_lpszHref;
+    m_lpNextWord    = rhs.m_lpNextWord;
+    m_lpPrevWord    = rhs.m_lpPrevWord;
+    m_rect          = rhs.m_rect;
+    m_dwlWordNum    = rhs.m_dwlWordNum;
+    m_dwSubWordNum  = rhs.m_dwSubWordNum;
+
+    if(m_fOwner && m_lpszWord)
+        delete [] m_lpszWord;
+
+    m_fOwner        = rhs.m_fOwner;
+
+    if(m_fOwner && rhs.m_lpszWord){
+        m_lpszWord = new TCHAR[m_dwWordLen + 1];
+        for(DWORD i = 0; i < m_dwWordLen; i++)
+            m_lpszWord[i] = rhs.m_lpszWord[i];
+        m_lpszWord[m_dwWordLen] = 0;
+    }else{
+        m_lpszWord = rhs.m_lpszWord;
+    }
+    return *this;
+}
+
+BTextViewer::BTextWord::~BTextWord()
+{
+    if(m_fOwner && m_lpszWord){
+        delete [] m_lpszWord;
+    }
+}
+/*****************************************************************************
+ * BTextLines                                                                *
+ *****************************************************************************/
+
+BTextViewer::BTextLines::BTextLines()
+: m_nLineH(0)
+, m_dwLastLine(0)
+{
+    InitLines(0);
+}
+
+VOID BTextViewer::BTextLines::InitLines(CONST INT nLineH)
+{
+    m_dwLines           = BTEXT_LINE_INC;
+    m_lpLines           = new BTextWord[m_dwLines];
+    m_lppLinesLastWord  = new BTextWord*[m_dwLines];
+    
+    for(DWORD i = 0; i < m_dwLines; i++)
+        m_lppLinesLastWord[i] = &m_lpLines[i];
+    m_nLineH = nLineH;
+}
+
+BTextViewer::BTextLines::BTextLines(CONST BTextViewer::BTextLines &rhs)
+{
+    m_dwLastLine        = rhs.m_dwLastLine;
+    m_dwLines           = rhs.m_dwLines;
+    m_lpLines           = new BTextWord[m_dwLines];
+    m_lppLinesLastWord  = new BTextWord*[m_dwLines];
+
+    for(DWORD i = 0; i < m_dwLines; i++){
+        m_lpLines[i]            = rhs.m_lpLines[i];
+        m_lppLinesLastWord[i]   = rhs.m_lppLinesLastWord[i];
+    }
+}
+
+BTextViewer::BTextLines & BTextViewer::BTextLines::operator=(CONST BTextViewer::BTextLines &rhs)
+{
+    if(this == &rhs)
+        return *this; // We detect self assignment
+
+    ClearLines();
+
+    m_dwLastLine    = rhs.m_dwLastLine;
+    m_dwLines       = rhs.m_dwLines;
+
+    m_lpLines           = new BTextWord[m_dwLines];
+    m_lppLinesLastWord  = new BTextWord*[m_dwLines];
+
+    for(DWORD i = 0; i < m_dwLines; i++){
+        m_lpLines[i]            = rhs.m_lpLines[i];
+        m_lppLinesLastWord[i]   = rhs.m_lppLinesLastWord[i];
+    }
+
+    return *this;
+}
+
+BTextViewer::BTextLines::~BTextLines()
+{
+    ClearLines();
+}
+
+VOID BTextViewer::BTextLines::ClearLines()
+{
+    DWORD dwCurLine = 0;
+    BTextWord *pbtNextWord = NULL;
+    BTextWord *pbtTempWord = NULL;
+
+    for(dwCurLine = 0; dwCurLine < m_dwLines; dwCurLine++){
+        pbtNextWord = m_lpLines[dwCurLine].m_lpNextWord;
+        // Delete all the words in this line
+        while(pbtNextWord){
+            pbtTempWord = pbtNextWord->m_lpNextWord;
+            delete pbtNextWord;
+            pbtNextWord = pbtTempWord;
+        }
+    }
+    m_dwLastLine = 0;
+    m_dwLines = 0;
+
+    delete [] m_lpLines;
+    m_lpLines = NULL;
+    delete [] m_lppLinesLastWord;
+    m_lppLinesLastWord = NULL;
+}
+
+INT BTextViewer::BTextLines::ValidateNewLineWord(CONST DWORDLONG dwlWordNum,CONST INT nLineH, CONST INT nMargin)
+{
+    if(!m_lppLinesLastWord[m_dwLastLine]->m_lpPrevWord)
+        return nMargin; // There is no word on the last line, nothing to do...
+
+    BTextViewer::BTextWord *pbtWord = m_lppLinesLastWord[m_dwLastLine]->m_lpPrevWord;
+
+    if(pbtWord->m_dwlWordNum != dwlWordNum)
+        return nMargin; // Different word, nothing to do...
+
+    pbtWord->m_rect.top     += nLineH;
+    pbtWord->m_rect.bottom  += nLineH;
+    pbtWord->m_rect.right   = nMargin + (pbtWord->m_rect.right - pbtWord->m_rect.left);
+    pbtWord->m_rect.left    = nMargin;
+
+    // break this word off the old line's chain
+    BTextViewer::BTextWord *pbtChainPrev = pbtWord->m_lpPrevWord;
+    BTextViewer::BTextWord *pbtChainNext = pbtWord->m_lpNextWord;
+
+    if(pbtWord->m_lpPrevWord)
+        pbtWord->m_lpPrevWord->m_lpNextWord = pbtChainNext;
+    if(pbtWord->m_lpNextWord)
+        pbtWord->m_lpNextWord->m_lpPrevWord = pbtChainPrev;
+
+    m_lppLinesLastWord[m_dwLastLine] = pbtChainNext;
+    pbtWord->m_lpPrevWord = NULL;
+    pbtWord->m_lpNextWord = NULL;
+    AddWordToLine(m_dwLastLine + 1, *pbtWord);
+    delete pbtWord;
+
+    return m_lppLinesLastWord[m_dwLastLine]->m_lpPrevWord->m_rect.right;
+}
+
+VOID BTextViewer::BTextLines::AddWordToLine(DWORD dwLine, CONST BTextViewer::BTextWord &btWord)
+{
+    DWORD dwLines   = 0;
+    DWORD i         = 0;
+
+    // Check if we are about to overflow the allocated lines
+    // If so resize...
+    if(dwLine >= m_dwLines){
+        dwLines = m_dwLines + BTEXT_LINE_INC;
+        //unsigned int nLastLine = m_dwLastLine;
+        i = 0;
+        BTextWord *pbtTemp1     = new BTextWord[dwLines];
+        BTextWord **ppbtTemp2   = new BTextWord*[dwLines];
+
+        for(i = 0; i <= m_dwLastLine; i++){
+            pbtTemp1[i] = m_lpLines[i];
+            // Now the next btWord doesn't know it has a new prev btWord
+            // Fixing...
+            if(pbtTemp1[i].m_lpNextWord)
+                pbtTemp1[i].m_lpNextWord->m_lpPrevWord = &pbtTemp1[i];
+            ppbtTemp2[i] = m_lppLinesLastWord[i];
+        }
+        for(;i < dwLines; i++){
+            ppbtTemp2[i] = &pbtTemp1[i];
+        }
+        m_dwLastLine = dwLine;
+        m_dwLines = dwLines;
+        delete [] m_lpLines;
+        delete [] m_lppLinesLastWord;
+        m_lpLines = pbtTemp1;
+        m_lppLinesLastWord = ppbtTemp2;
+    }else if(dwLine > m_dwLastLine){
+        m_dwLastLine = dwLine;
+    }
+    BTextViewer::BTextWord *pbtPrevWord = m_lppLinesLastWord[dwLine]->m_lpPrevWord;
+    *m_lppLinesLastWord[dwLine] = btWord;
+    m_lppLinesLastWord[dwLine]->m_lpNextWord = new BTextWord();
+    m_lppLinesLastWord[dwLine]->m_lpPrevWord = pbtPrevWord;
+    // Create the relationship between this btWord and the next 
+    // potential btWord...
+    m_lppLinesLastWord[dwLine]->m_lpNextWord->m_lpPrevWord = m_lppLinesLastWord[dwLine];
+    m_lppLinesLastWord[dwLine] = m_lppLinesLastWord[dwLine]->m_lpNextWord;
+}
\ No newline at end of file

Added: trunk/src/gui/BTextViewer.h
===================================================================
--- trunk/src/gui/BTextViewer.h	                        (rev 0)
+++ trunk/src/gui/BTextViewer.h	2008-03-19 03:17:25 UTC (rev 102)
@@ -0,0 +1,668 @@
+/******************************************************************************
+*  BTextViewer.h - HTML Renderer for rendering biblical texts on Pocket PC 
+*  devices.
+*  Author: David C Trotz Jr. 
+*  e-mail: dtrotzjr at crosswire.org
+*
+* $Id$
+*
+* Copyright 1998 CrossWire Bible Society (http://www.crosswire.org)
+*	CrossWire Bible Society
+*	P. O. Box 2528
+*	Tempe, AZ  85280-2528
+*
+* This program is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License as published by the
+* Free Software Foundation version 2.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* General Public License for more details.
+*
+*/
+
+#pragma once
+
+//! BTextViewer::m_lpszBuff gets re-sized in increments based upon this number.
+#define BTEXT_BUFF_INC              2000
+//! BTextViewer::m_BTLines gets re-sized based upon this number
+#define BTEXT_LINE_INC              400
+//!  Determines the margin around the border of the display. 
+//!  May be broken into Top, Bottom, Left, and Right margins in the future.
+#define BTEXT_MARGIN                4
+
+#define BTEXT_FONT_CACHE_MAX        0x7FFF
+
+
+//! Determines the default font height. 
+//! @note Future implementations may read this from a config file.
+#define BTEXT_DEFAULT_FONT_HEIGHT   -11
+//! Flags an uninitialized color state.
+//! This is actually an invalid COLOREF Color, which makes it ideal for
+//! this type of use, as it will never come up naturally.
+#define BTEXT_FONT_NOT_A_COLOR      0xFF000000
+//! Determines the default font face color.
+//! @note Future implementations may read this from a config file.
+#define BTEXT_DEFAULT_FONT_COLOR    0x00000000
+//! Determines the line spacing factor.
+//! @note Future implementations may read this from a config file.
+#define BTEXT_LINE_SPACING          1.1
+
+//! Flag indicating a space of some sort was encountered.
+//! @deprecated Future implementations will move away from this flag
+#define BTEXT_SPACE_ENCOUNTERED     0
+//! Flag indicating a new line has been encountered
+//! @deprecated Use of this flag may not be needed in future releases.
+#define BTEXT_NEWLINE_ENCOUNTERED   -1
+//! Flag indicating we have encountered an opening bracket for HTML processing.
+#define BTEXT_HTML_OPEN_BRACKET     -2
+
+//! Flag indicating a &lt;br&gt; element was encountered
+#define BTEXT_HTML_BR               0x00000001
+//! Flag used to mask against the ..._BEG counterpart of an ..._END.
+#define BTEXT_HTML_MASK             0xFFFFFFFF
+
+//!Flag indicating we encountered a &lt;b&gt; element
+#define BTEXT_HTML_B_BEG            0x00000002
+//!Flag indicating we encountered a &lt;/b&gt; element
+#define BTEXT_HTML_B_END            BTEXT_HTML_MASK ^ BTEXT_HTML_B_BEG
+
+//!Flag indicating we encountered a &lt;i&gt; element
+#define BTEXT_HTML_I_BEG            0x00000004
+//!Flag indicating we encountered a &lt;/i&gt; element
+#define BTEXT_HTML_I_END            BTEXT_HTML_MASK ^ BTEXT_HTML_I_BEG
+
+//!Flag indicating we encountered a &lt;sup&gt; element
+#define BTEXT_HTML_SUP_BEG          0x00000008
+//!Flag indicating we encountered a &lt;/sup&gt; element
+#define BTEXT_HTML_SUP_END          BTEXT_HTML_MASK ^ BTEXT_HTML_SUP_BEG
+
+//!Flag indicating we encountered a &lt;sub&gt; element
+#define BTEXT_HTML_SUB_BEG          0x00000010
+//!Flag indicating we encountered a &lt;/sub&gt; element
+#define BTEXT_HTML_SUB_END          BTEXT_HTML_MASK ^ BTEXT_HTML_SUB_BEG
+
+//!Flag indicating we encountered a &lt;small&gt; element
+#define BTEXT_HTML_SMALL_BEG        0x00000020
+//!Flag indicating we encountered a &lt;/small&gt; element
+#define BTEXT_HTML_SMALL_END        BTEXT_HTML_MASK ^ BTEXT_HTML_SMALL_BEG
+
+//!Flag indicating we encountered a &lt;font ...&gt; element
+#define BTEXT_HTML_FONT_BEG         0x00000040
+//!Flag indicating we encountered a &lt;/font&gt; element
+#define BTEXT_HTML_FONT_END         BTEXT_HTML_MASK ^ BTEXT_HTML_FONT_BEG
+
+//!Flag indicating we encountered a &lt;a ...&gt; element
+#define BTEXT_HTML_A_BEG            0x00000080
+//!Flag indicating we encountered a &lt;/a&gt; element
+#define BTEXT_HTML_A_END            BTEXT_HTML_MASK ^ BTEXT_HTML_A_BEG
+
+//!Flag indicating we encountered a &lt;p&gt; element
+#define BTEXT_HTML_P_BEG            0x00000100
+//!Flag indicating we encountered a &lt;/p&gt; element
+#define BTEXT_HTML_P_END            BTEXT_HTML_MASK ^ BTEXT_HTML_P_BEG
+
+//! Flag indicating we encountered an illegal tag format.
+#define BTEXT_HTML_ILLEGAL_TAG_FORMAT   0x80000000
+//! Flag indicating we encountered an unknown tag.
+#define BTEXT_HTML_UNKNOWN_TAG          0x40000000
+
+//! BTextViewer - A custom HTML Renderer for Biblical Texts on WinCE Platforms.
+/*!
+    BTextViewer was designed to be a replacement for the built in HTML Renderer
+    on Windows CE platforms. This replacement was decided necessary due to the
+    limitations and performance issues encountered when designing a front end
+    for The SWORD Project on the Windows CE platform.
+
+    When implementing this class I have taken every precaution I could to ensure
+    the class could be easily manipulated in the future to add support for new
+    features necessary to display biblical and supporting texts. Whether I have
+    been successful at this or not is unfounded, only time will tell.
+ */
+class BTextViewer
+{
+private:
+
+    //! BTextWord - The most basic element of this text display. 
+    /*!	BTextWord represents a single (whole or partial) word. Each word 
+    			contains information to describe its placement on the screen and any
+    			display characteristics it may possess such as bold, italic, font 
+    			size, etc. In order to preserve memory and time copying text from the 
+    			main buffer BTextWord objects typically only point to the main text 
+    			buffer BTextViewer::m_lpszBuff 
+    			
+    			On rare occasions it is necessary for the word to store its own copy 
+    			of the word it represents, in these cases m_fOwner will be true and 
+    			special care is taken to manage its memory.
+    			
+    			This Class also represents a doubly linked list, care must be taken 
+    			whenever a single node is removed from the list or else you risk
+    			memory leaks or dangling pointers, both of which are a death sentence
+    			on the WinCE platform.
+    				
+     */
+    class BTextWord
+    {
+    public:
+    		//! Default Constructor
+        BTextWord();
+        //! Copy Constructor
+        BTextWord(CONST BTextViewer::BTextWord &rhs);
+        //! Destructor
+        ~BTextWord();
+        //! Assignment operator
+        BTextViewer::BTextWord &operator=(CONST BTextViewer::BTextWord &rhs);
+        //! Clears the contents of this word.
+        //! This may include deleting any memory it may own.
+        VOID Clear();
+        //! Copy the pWord object internally and manage it internally.
+        //! Tells this instance of BTextWord that it needs to make a copy of 
+        //! this word and "own" the word.
+        //! @param pWord pointer to the word being copied in.
+        //! @param dwWordLen length of the word being copied in.
+        VOID OwnWord(TCHAR *lpszWord, DWORD dwWordLen);
+        //! Points to a specific word inside a buffered string stored and 
+        //! managed outside of this class.
+        //! Thus we do not try to manage the memory, unless the m_fOwner 
+        //! flag is set.
+        TCHAR   *       m_lpszWord;
+        //! Indicates how many characters this word is in length.
+        DWORD           m_dwWordLen;
+        //! Points to a specific href inside a buffered string stored and 
+        //! managed outside of this class.
+        //! Thus we do not try to manage the memory, not even if m_fOwner is set
+        TCHAR   *       m_lpszHref;
+        //! Indicates how many characters this href is in length.
+        DWORD           m_dwHrefLen;
+        //! Indicates the on screen bounds of this word.
+        RECT            m_rect;
+        //! Flags indicating the font status of this word.
+        //! @code 
+        //! X = Reserved     H = Font Height
+        //! M = Small        B = Subscript
+        //! U = Superscript  O = Bold
+        //! I = Italic       A = Link
+        //! P = Paragraph
+        //! F = Font (not used here)
+        //! RR = Red	GG = Green BB = Blue
+        //! - = Line-break bit (not used here)
+        //! BYTE: 7[7654 3210] 6[7654 3210] 5[7654 3210] 4[7654 3210]
+        //!        [BBBB BBBB]  [GGGG GGGG]  [RRRR RRRR]  [HHHH HHHH]
+        //! BYTE: 3[7654 3210] 2[7654 3210] 1[7654 3210] 0[7654 3210]
+        //!        [XXXX XXXX]  [XXXX XXXX]  [XXXX XXXP]  [AFMB UIO-]
+        //! @endcode
+        DWORDLONG       m_dwlfFontState;
+
+				//! Indicates that this BTextWord object should manage the memory
+				//! pointed to by m_lpszWord.
+        //! There are times when this BTextWord needs to clean up the 
+        //! character allocation it points to, such as when Special
+        //! Entities are interpreted and stored separately from the main
+        //! text buffer.
+        BOOL            m_fOwner;
+        //! Points to the next word in the list.
+        BTextWord *     m_lpNextWord;
+        //! Points to the previous word in the list.
+        BTextWord *     m_lpPrevWord;
+        //! Indicates the unique number assigned to each word.
+        //! This number is helpful in keeping words together when a
+        //! html element is introduced mid-word such as 
+        //! L&lt;font size="-1"&gt;ORD&lt;/font&gt; In this case two BTextWord
+        //! objects are created and all that ties them together is this 
+        //! variable. 
+        //! @see m_dwSubWordNum.
+        DWORDLONG       m_dwlWordNum;
+        //! Indicates which piece of a word this word is (most often it will be 0)
+        DWORD           m_dwSubWordNum;
+    };
+
+    //! BTextLines - Stores all the words in an array of lines.
+    /*!	BTextLines is responsible for storing the BTextWord objects in a way that 
+    			is convenient for rendering on the display, and for x,y coordinate to 
+    			BTextWord object look-up.
+     */
+    class BTextLines
+    {
+    public:
+    		//! Default Constructor
+        BTextLines();
+        // Copy Constructor
+        BTextLines(CONST BTextViewer::BTextLines &rhs);
+        //! Destructor
+        ~BTextLines();
+        //! Assignment operator.
+        BTextViewer::BTextLines &operator=(CONST BTextViewer::BTextLines &rhs);
+				
+				//! Adds the given word to the end of the line indicated by the line number.
+				/*! If dwLine is larger than the current m_dwLastLine property, then 
+						m_dwLastLine is changed accordingly (hence it grows). Since we do 
+						not want to thrash the heap by reallocating every time m_dwLastLine
+						increments (and we expect it will often), we instead allocate in 
+						increments of BTEXT_LINE_INC.
+						@param dwLine Indicates the line number where the new btWord is being 
+						added.
+						@param btWord The BTextWord object we will copy into the BTextLines
+						object.
+				*/
+        VOID    AddWordToLine(DWORD dwLine, CONST BTextViewer::BTextWord &btWord);
+        //! Clears all lines from this instance of BTextLines. 
+        /*! BEWARE - Once this is called the object is not usable again until, 
+        		InitLines is called to re-initialize the memory for m_lpLines and 
+        		m_lppLinesLastWord
+         */	
+        VOID    ClearLines();
+        //! Initializes (or allocates) memory to store the words.
+        /*! @param nLineH indicates the physical line height in pixels. 
+        		@note Not sure how appropriate nLineH is here. Expect this to change
+        		in future releases.
+        */
+        VOID    InitLines(CONST INT nLineH);
+        //! Validates the incoming word's position on a new line.
+        /*!
+        		Checks to see if this word might be part of a word that ended on the
+        		previous line. If it is part of the previous line's word, it moves 
+        		the previous word down to the new line and prepares this word to be 
+        		inserted immediately following.
+        		@param dwWordNum indicates the word's unique number to determine if it 
+        			belongs to the word in the line above.
+        		@param nLineH indicates how far down to move the word (physically in 
+        			pixels) if it is determined that it needs to be moved.
+        		@param nMargin indicates the margin to obey when placing the word on 
+        			a new line if it is determined that it needs to be moved.
+        		@return the position in pixels that the new word can start at. If it 
+        			was determined that the word above did not need to be moved, it 
+        			simply returns nMargin as its value, if it did move down its 
+        			rightmost extent is returned instead.
+         */
+        INT     ValidateNewLineWord(CONST DWORDLONG dwlWordNum,CONST INT nLineH, CONST INT nMargin);
+        //! Indicates the total number of lines currently allocated.
+        DWORD           m_dwLines;
+        //! Indicates the last line actually being used,
+        //! hence m_dwLastLine < m_dwLines is always true!
+        DWORD           m_dwLastLine;
+        //! Indicates the physical line height in pixels. Useful for 
+        //! determining line boundaries.
+        int             m_nLineH;
+        //! Pointer to an array of BTextWords, where each index into the array 
+        //! represents a single line of text.
+        BTextWord*      m_lpLines;
+        //! Convenience pointer to the last word in each line.
+        //! @note Do not manage this memory!!! It is already managed 
+        //! as m_lpLines.
+        BTextWord**     m_lppLinesLastWord;
+    };
+
+    //! FontTagItem - Tracks font tag elements and their respected properties.
+    /*! This simple class is a basic queue for tracking font tags and the properties related to those tags. 
+     */
+    class FontTagItem{
+    public:
+        //! Default Constructor
+        FontTagItem(){ m_siAbsFontSize = BTEXT_DEFAULT_FONT_HEIGHT; m_siRelFontSize = 0; m_crFontColor = BTEXT_FONT_NOT_A_COLOR; m_lpftNext = NULL; };
+        //! Destructor
+        ~FontTagItem() { if(m_lpftNext) delete m_lpftNext; };
+
+        //! Represents the absolute font size related to this font tag.
+        //! @note not fully implemented.
+        SHORT           m_siAbsFontSize;
+        //! Represents the relative font size related to this font tag.
+        //! Examples: &lt;font size="+2"&gt; -- indicates a font size +2 
+        //! greater than the current font size.
+        SHORT           m_siRelFontSize;
+        //! Represents the fore color of the font related to this font tag.
+        COLORREF        m_crFontColor;
+        //! Points to the next font tag item (if there exists one.)
+        FontTagItem*    m_lpftNext;
+    };
+
+    //! RenderState - Keeps track of the current rendering state.
+    /*! As new tags are encountered this structure is useful for keeping
+        track of such changes so that each word is rendered with the correct 
+        formatting tags applied.
+     */
+    struct RenderState{
+        //! Indicates that we encountered a space outside of a tag element.
+        BOOL            m_space_encountered;
+        //! Indicates the bold state.
+        /*! \code
+            m_wBoldState == 0 indicates not bold; 
+            m_wBoldState >  0 indicates bold
+            \endcode
+         */
+        WORD            m_wBoldState;
+        //! Indicates the italic state. 
+        /*! \code
+            m_wItalicState == 0 indicates not italicized; 
+            m_wItalicState >  0 indicates italicized
+            \endcode
+         */
+        WORD            m_wItalicState;
+        //! Indicates the super script state.
+        /*! \code
+            m_wSuperState == 0 indicates not super scripted; 
+            m_wSuperState >  0 indicates super scripted
+            \endcode
+         */
+        WORD            m_wSuperState;
+        //! Indicates the anchor (link) state.
+        /*! \code
+            m_wAState == 0 indicates not in anchor state
+            m_wAState >  0 indicates anchor state
+            \endcode
+            @note it is assumed that anchors are not nested 
+            thus: \code -1 < m_wAState > 1 \endcode
+         */
+        WORD            m_wAState;
+        //! Indicates the sub script state.
+        /*! \code
+            m_wSubState == 0 indicates not sub scripted
+            m_wSubState >  0 indicates sub scripted
+            \endcode
+         */
+        WORD            m_wSubState;
+        //! Indicates the paragraph state.
+        /*! \code
+            m_wParagraphState == 0 indicates not in paragraph
+            m_wParagraphState > indicates in a paragraph
+            \endcode
+         */
+        WORD            m_wParagraphState;
+        //! Tracks the font tag elements that help make the current font 
+        //! state. This linked list is treated as a last in first out (LIFO)
+        //! queue.
+        FontTagItem*    m_lpftFontTagHead;
+        //! This is where new elements are pushed/popped on to the queue.
+        FontTagItem*    m_lpftFontTagTail;
+        //! Pointer to an anchor href property the current font state
+        //! may reference.
+        TCHAR*          m_lpszHref; 
+        //! The length of that anchor href property ( m_lpszHref ) string
+        DWORD           m_dwHrefLen;
+        //! Indicates the current verse we believe we are representing.
+        WORD            m_wVerseNum;
+        //! Indicates how many HTML Special entities we have encountered and 
+        //! need to be interpreted later.
+        WORD            m_wTotalSpclEnt;
+        //! Used to compose a word that may be broken up by rendering
+        //! tags introduced mid-word such as the very common
+        //! L<font size="-1">ORD</font>
+        DWORDLONG       m_dwlWordNum;
+        //! Further helps compose words broken apart by tags.
+        DWORD           m_dwSubWordNum;
+    };
+
+public:
+    // Public Methods
+    //! Default Constructor
+    BTextViewer(HINSTANCE hInstance, HWND hWndParent, RECT lRect);
+    //! Destructor
+    virtual ~BTextViewer();
+    //! Adds the given text to the window control for rendering.
+    /*! The method appends the given text string to the text currently being 
+        stored in m_lpszBuff. To save on constantly resizing m_lpszBuff at 
+        each call to this method we instead re-size at increments of 
+        BTEXT_BUFF_INC
+        @param szText the text being added to this control.
+        @param dwSize the length of the text being added.
+     */
+    VOID    AddText(TCHAR *szText, DWORD dwSize);
+    //! Scrolls the window a full height in a given direction.
+    /*! @param nDirection the direction to scroll 
+        \code
+        nDirection > 0 indicates up.
+        nDirection <= 0 indicates down.
+        \endcode
+     */
+    VOID    ScrollFullPage(INT nDirection);
+    //! Shows the window control.
+    //! Calls ShowWindow with the SW_SHOW parameter for the underlying window.
+    VOID    Show();
+    //! Hides the window control.
+    //! Calls ::ShowWindow with the SW_HIDE parameter for the underlying window.
+    VOID    Hide();
+    //! Changes the position and or size of the window.
+    //! Calls ::MoveWindow for the underlying window.
+    VOID    MoveWindow(INT x, INT y, INT w, INT h);
+    //! Clears the text from m_lpszBuff and removes the lines from m_BTLines.
+    VOID    Clear();
+private:
+    // Private Methods
+    //! Handles the WM_PAINT message for this window control. 
+    /*! This is the heart and soul of this window control. Well in theory it 
+        is, except most of the real work is done in PreRenderBuff. This method 
+        checks to see if the buffer has been pre-rendered, if not it calls 
+        PreRenderBuff. Once it is established that the buffer has been 
+        pre-rendered the method continues to actually render the text on 
+        screen, skipping any lines not expected to be showing on the screen.
+        @param hWnd handle to the window being painted
+        @param hdc  device context to do our rendering on.
+        @param ps   contains information about what to paint.
+        @note 
+            It is expected that 
+            \code hWnd == m_hWnd \endcode 
+            is always true. 
+            Given this assumption this first parameter may be removed in 
+            future releases.
+     */
+    INT     Paint(HWND hWnd, HDC hdc, PAINTSTRUCT ps);
+    //! Drags the text across the screen.
+    /*! Drags the text across the screen according to the point given and the 
+        current value of m_nDragStart.
+        @param lParam contains information about the pointer's position.
+        @note
+        \code
+        xPos = LOWORD(lParam); 
+        yPos = HIWORD(lParam);
+        \endcode
+     */
+    VOID    DragScreenToPoint(LPARAM lParam);
+    //! Set's the state of the pen. 
+    /*! The pen's state includes its current position and whether the pen is 
+        considered up or down. This method also tries to detect what the user 
+        is intending to do, such as tap a word or scroll a few lines or send the
+        window in a rolling motion.
+        @param fMouseDown   indicates the pen is in the down position.
+        @param lParam       contains information about the pointer's position.
+        @note
+        \code
+        xPos = LOWORD(lParam); 
+        yPos = HIWORD(lParam);
+        \endcode 
+     */
+    VOID    SetTapState(BOOL fMouseDown, LPARAM lParam);
+    //! Gets the word (if any) at the given point on the screen.
+    /*! Does a methodical search for the word that exists under the given point.
+        @param lParam  contains information about the position to be searched.
+        @note
+        \code
+        xPos = LOWORD(lParam); 
+        yPos = HIWORD(lParam);
+        \endcode 
+        
+        @par
+        @note Future implementations will return the word found. The current 
+        implementation only displays a MessageBox containing the word.
+     */
+    VOID    GetWordAtPoint(LPARAM lParam);
+    //! Rolls the screen until the m_nRollVelocity member has reached a value of 0.
+    /*! @note Future implementations will work a little differently to allow the 
+        cancellation of a roll by replacing the main loop in this method with 
+        sending a user defined message indicating a roll is requested which will 
+        call this method until either m_nRollVelocity is 0 at which point this 
+        method will no longer send the user defined message to itself, or the 
+        user taps the screen in the middle of a roll resetting the 
+        m_nRollVelocity data member.
+     */
+    VOID    RollTillStopped();
+    //! The actual call back function for this window.
+    /*! This only acts as a mediator between the callback function we really 
+        want and the call back the Win API is expecting.
+        @param hwnd     Handle to the window.
+        @param message  Specifies the message. 
+        @param wParam   Specifies additional message information. The contents 
+                        of this parameter depend on the value of the message 
+                        parameter. 
+        @param lParam   Specifies additional message information. The contents 
+                        of this parameter depend on the value of the message 
+                        parameter. 
+     */
+    static LRESULT CALLBACK MessageRoute(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
+    //! The expected WinProc callback function.
+    /*! Handles any messages sent to this window.
+        @param hwnd     Handle to the window.
+        @param message  Specifies the message. 
+        @param wParam   Specifies additional message information. The contents 
+                        of this parameter depend on the value of the message 
+                        parameter. 
+        @param lParam   Specifies additional message information. The contents 
+                        of this parameter depend on the value of the message 
+                        parameter. 
+     */
+    LRESULT CALLBACK WndProcBText(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
+    //! Sets the Font for the given device context. 
+    /*! Tries to determine if changing the font is even necessary. If not it 
+        returns immediately otherwise it proceeds to change the font based 
+        upon the dwlFontState parameter.
+        @param hdc          the device context getting the font change.
+        @param dwlFontState contains flags indicating the new state of the 
+                            font.
+        @note If called, be sure to free the last created font prior to 
+                releasing the device context otherwise a resource leak will 
+                occur. Sequential calls to this method do take care of any 
+                fonts created in prior calls. Its the last call that needs 
+                the extra attention.
+        @see BTextViewer::BTextWord::m_dwlfFontState
+     */
+    VOID    SetFont(HDC hdc, DWORDLONG dwlFontState);
+    //! Tries to detect any spaces at the current stream position.
+    /*! Spaces are then discarded (by incrementing the dwWordIndex) and their 
+        existence is recorded in the rsState.m_space_encountered parameter.
+        @param rsState      contains current rendering information. This is 
+                            where we store our 'space' encounters.
+        @param dwWordIndex  Index into the current stream m_lpszBuff indicating 
+                            where to be looking for spaces.
+        @return A BOOL value where a non-zero value indicates that spaces were 
+                encountered.
+        @note The return value is redundant information, it was the original 
+                way I dealt with acknowledging spaces. Since then I have moved 
+                onto a structure containing the overall state of the renderer, 
+                as such the return value here may change to void in future 
+                implementations.
+     */
+    BOOL    GetSpaces(RenderState& rsState, DWORD &dwWordIndex);
+    //! Determines location the next word in the stream.
+    /*! Based upon the current stream position this method will determine the 
+        extents of the word assumed to have its origin at dwWordIndex.
+        @param  rsState     the current state of the renderer.
+        @param  dwWordIndex where in m_lpszBuff to begin looking.
+        @param  dwWordEnd   the determined length of the word will land here.
+        @return An INT value indicating the character that terminated the search.
+        @note The return value was originally designed help determine what state 
+                we should be in next, but is currently unused and may end up 
+                going unused and removed in future releases.
+     */
+    INT     NextWord(RenderState& rsState,DWORD dwWordIndex, DWORD &dwWordEnd);
+    //! Determines what HTML tags are at the location in the stream.
+    /*! As HTML tags are encountered they are logged in the rsState parameter 
+        for future rendering.
+        @param rsState      the current state of the renderer
+        @param dwWordIndex  where in m_lpszBuff to begin looking. This value is
+                            also updated as the tags are being encountered and 
+                            recorded.
+        @return An INT indicating how many line breaks &lt;br&gt; were 
+                encountered during this call.
+     */
+    INT     GetHTMLTags(RenderState& rsState, DWORD &dwWordIndex);
+    //! Identifies what tag element is at the stream position.
+    /*! Once GetHTMLTags encounters a new tag it needs to know what tag it is, 
+        and that job falls to this method. This method is also the one 
+        responsible for building the FontTagItem linked list representing any 
+        &lt;font ...&gt; tags it encounters.
+        @param rsState      the current state of the renderer, and the recipient 
+                            of any changes to that state as a result of the tag 
+                            encountered. i.e An anchor tag may have an href 
+                            property attached to it, as such this state will 
+                            reflect that property upon return.
+        @param dwWordIndex  where in m_lpszBuff to begin looking
+        @param dwWordEnd    the predetermined length of the tag element being 
+                            examined.
+        @return A DWORD value indicating which tag was identified. 
+     */
+    DWORD   IndentifyTag(RenderState& rsState, DWORD dwWordIndex, DWORD dwWordEnd);
+    //! Looks for HTML special entities and translates them into their single character equivalents.
+    /*! @param dwWordIndex  where in m_lpszBuff to begin looking.
+        @param dwWordLen    where to stop translating
+        @param pbtWord      where the new translated string will be copied.
+        @note This causes the given pbtWord object to "own" its own copy of the 
+                newly translated string.
+     */
+    VOID    InterpretSpecialEntities(DWORD dwWordIndex, DWORD dwWordLen, BTextWord *pbtWord);
+    //! Pre-Renders the internal buffer stream.
+    /*! @par
+        This is the big boss!
+        @par
+        This method is responsible for interpreting how the text stream is 
+        intended to be rendered.
+        @par
+        The main body of this function is a loop. At each iteration of the loop 
+        a single word is extracted. However every word has the possibility of 
+        being preceded with spaces, and/or html tags so we try to parse these 
+        out first. Once we have determined that we are at the start of a new 
+        word we call NextWord and get the actual word. If it happens that 
+        dwWordEnd is 0 after this call we exit, this is the only exit condition 
+        from this loop. Once we have the word we then set the font according 
+        to the current rendering state and do a "fake" draw of the word to 
+        determine its bounding rectangle. Now that we have the word, its font 
+        settings, and its boundaries, we store the word for actual rendering 
+        later. The loop continues to the next word, and so on...
+        @param  hdc the current device context to base our "fake" drawing on.
+     */
+    VOID    PreRenderBuff(HDC hdc);
+
+    VOID ClearFontCache(HDC hdc);
+    
+    // Private data members
+
+    //! Indicates whether we are in a dragging state.
+    BOOL                        m_fDragging;
+    //! 
+    POINT                       m_ptLastClick;
+    //! Indicates the yPos that the current dragging motion should start from.
+    INT                         m_nDragStart;
+    /*! Like m_nDragStart except it keeps track of the true start point 
+        as opposed to the m_nDragStart which is updated upon each WM_MOUSEMOVE 
+        message sent to this window.
+     */
+    DWORD                       m_dwTrueStartPos;
+    //! The start time related to m_dwTrueStartPos
+    DWORD                       m_dwTrueStartTime;
+    //! A virtual velocity used to do the fancy rolling motion.
+    INT                         m_nRollVelocity;
+    //! Indicates where the top of the "canvas" is in relationship to the top 
+    //! of the physical screen.
+    INT                         m_nTop;
+    //! The actual text stream is stored here.
+    TCHAR*                      m_lpszBuff;
+    //! Indicates how much memory has been allocated to the text buffer pointed 
+    //! to by m_lpszBuff
+    DWORD                       m_dwBuffSize;
+    //! Indicates the end of the text stream and the actual memory used in 
+    //! m_lpszBuff
+    DWORD                       m_dwBuffEnd;
+    //! Indicates when the time when the pen down event was first registered
+    DWORD                       m_dwClickTime;
+    //! A handle to the currently selected font.
+    HFONT                       m_hCurFont;
+    //! A LOGFONT description of the current font.
+    LOGFONT                     m_lfCurFont;
+    //! Represents rendered words sorted by line.
+    BTextViewer::BTextLines     m_BTLines;
+    //! A handle to the window that this object represents.
+    HWND                        m_hWnd;
+    //! Indicates whether the text stream has been pre-rendered according to 
+    //! the current window layout.
+    BOOL                        m_fPreRendered;
+
+    HFONT                       m_hFontCache[BTEXT_FONT_CACHE_MAX];        
+};




More information about the sword-cvs mailing list