/****************************************************************************** * BTextViewer.cpp - HTML Renderer for rendering biblical texts on Pocket PC * devices. * Author: David C Trotz Jr. * e-mail: dtrotzjr@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 #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 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 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: // > < & // 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" ", 6) == 0)) ){ dwWordIndex += m_lpszBuff[dwWordIndex] == '&' ? 6 : 1; // Did we find a   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 i = 0; if(m_dwBuffEnd == 0){ do{ while(szText[i] != '<' && i < dwSize) i++; if(wcsnicmp(&szText[i], L"", 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"", 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; }