/****************************************************************************** * BTextViewer.h - 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. * */ #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 <br> 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 <b> element #define BTEXT_HTML_B_BEG 0x00000002 //!Flag indicating we encountered a </b> element #define BTEXT_HTML_B_END BTEXT_HTML_MASK ^ BTEXT_HTML_B_BEG //!Flag indicating we encountered a <i> element #define BTEXT_HTML_I_BEG 0x00000004 //!Flag indicating we encountered a </i> element #define BTEXT_HTML_I_END BTEXT_HTML_MASK ^ BTEXT_HTML_I_BEG //!Flag indicating we encountered a <sup> element #define BTEXT_HTML_SUP_BEG 0x00000008 //!Flag indicating we encountered a </sup> element #define BTEXT_HTML_SUP_END BTEXT_HTML_MASK ^ BTEXT_HTML_SUP_BEG //!Flag indicating we encountered a <sub> element #define BTEXT_HTML_SUB_BEG 0x00000010 //!Flag indicating we encountered a </sub> element #define BTEXT_HTML_SUB_END BTEXT_HTML_MASK ^ BTEXT_HTML_SUB_BEG //!Flag indicating we encountered a <small> element #define BTEXT_HTML_SMALL_BEG 0x00000020 //!Flag indicating we encountered a </small> element #define BTEXT_HTML_SMALL_END BTEXT_HTML_MASK ^ BTEXT_HTML_SMALL_BEG //!Flag indicating we encountered a <font ...> element #define BTEXT_HTML_FONT_BEG 0x00000040 //!Flag indicating we encountered a </font> element #define BTEXT_HTML_FONT_END BTEXT_HTML_MASK ^ BTEXT_HTML_FONT_BEG //!Flag indicating we encountered a <a ...> element #define BTEXT_HTML_A_BEG 0x00000080 //!Flag indicating we encountered a </a> element #define BTEXT_HTML_A_END BTEXT_HTML_MASK ^ BTEXT_HTML_A_BEG //!Flag indicating we encountered a <p> element #define BTEXT_HTML_P_BEG 0x00000100 //!Flag indicating we encountered a </p> 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<font size="-1">ORD</font> 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: <font size="+2"> -- 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 //! LORD 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 <br> 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 <font ...> 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]; };