#ifndef __DW_TEXTBLOCK_HH__ #define __DW_TEXTBLOCK_HH__ #include #include "core.hh" #include "outofflowmgr.hh" #include "../lout/misc.hh" // These were used when improved line breaking and hyphenation were // implemented. Should be cleaned up; perhaps reactivate RTFL again. #define PRINTF(fmt, ...) #define PUTCHAR(ch) //#define DEBUG namespace dw { /** * \brief A Widget for rendering text blocks, i.e. paragraphs or sequences * of paragraphs. * *
Info: The recent changes (line breaking and * hyphenation on one hand, floats on the other hand) have not yet * been incorporated into this documentation. See \ref * dw-line-breaking and \ref dw-out-of-flow.
* *

Signals

* * dw::Textblock uses the signals defined in * dw::core::Layout::LinkReceiver, related to links. The coordinates are * always -1. * * *

Collapsing Spaces

* * The idea behind this is that every paragraph has a specific vertical * space around and that they are combined to one space, according to * rules stated below. A paragraph consists either of the lines between * two paragraph breaks within a dw::Textblock, or of a dw::Textblock * within a dw::Textblock, in a single line; the latter is used for * indented boxes and list items. * * The rules: * *
    *
  1. If a paragraph is following by another, the space between them is the * maximum of both box spaces: * * \image html dw-textblock-collapsing-spaces-1-1.png * * are combined like this: * * \image html dw-textblock-collapsing-spaces-1-2.png * *
  2. a) If one paragraph is the first paragraph within another, the upper * space of these paragraphs collapse. b) The analogue is the case for the * last box: * * \image html dw-textblock-collapsing-spaces-2-1.png * * If B and C are put into A, the result is: * * \image html dw-textblock-collapsing-spaces-2-2.png *
* * For achieving this, there are some features of dw::Textblock: * * * * *

Collapsing Margins

* * Collapsing margins, as defined in the CSS2 specification, are, * supported in addition to collapsing spaces. Also, spaces and margins * collapse themselves. I.e., the space between two paragraphs is the * maximum of the space calculated as described in "Collapsing Spaces" * and the space calculated according to the rules for collapsing margins. * * (This is an intermediate hybrid state, collapsing spaces are used in * the current version of dillo, while I implemented collapsing margins * for the CSS prototype and integrated it already into the main trunk. For * a pure CSS-based dillo, collapsing spaces will not be needed anymore, and * may be removed for simplicity.) * * *

Some Internals

* * There are 4 lists, dw::Textblock::words, dw::Textblock::paragraphs, * dw::Textblock::lines, and dw::Textblock::anchors. The word list is * quite static; only new words may be added. A word is either text, a * widget, or a break. * * Lines refer to the word list (first and last). They are completely * redundant, i.e., they can be rebuilt from the words. Lines can be * rewrapped either completely or partially (see "Incremental Resizing" * below). For the latter purpose, several values are accumulated in the * lines. See dw::Textblock::Line for details. * * A recent change was the introduction of the paragraphs list, which * works quite similar, is also redundant, but is used to calculate * the extremes, not the size. * * Anchors associate the anchor name with the index of the next word at * the point of the anchor. * *

Incremental Resizing

* * dw::Textblock makes use of incremental resizing as described in \ref * dw-widget-sizes. The parentRef is, for children of a dw::Textblock, simply * the number of the line. * * Generally, there are three cases which may change the size of the * widget: * * * * The state of the size calculation is stored in wrapRef within * dw::Textblock, which has the value -1 if no rewrapping of lines * necessary, or otherwise the line from which a rewrap is necessary. * */ class Textblock: public core::Widget { private: /** * This class encapsulates the badness/penalty calculation, and so * (i) makes changes (hopefully) simpler, and (ii) hides the * integer arithmetic (floating point arithmetic avoided for * performance reasons). Unfortunately, the value range of the * badness is not well defined, so fiddling with the penalties is a * bit difficult. */ enum { PENALTY_FORCE_BREAK = INT_MIN, PENALTY_PROHIBIT_BREAK = INT_MAX }; class BadnessAndPenalty { private: enum { NOT_STRETCHABLE, QUITE_LOOSE, BADNESS_VALUE, TOO_TIGHT } badnessState; int ratio; // ratio is only defined when badness is defined int badness, penalty[2]; // For debugging: define DEBUG for more informations in print(). #ifdef DEBUG int totalWidth, idealWidth, totalStretchability, totalShrinkability; #endif // "Infinity levels" are used to represent very large numbers, // including "quasi-infinite" numbers. A couple of infinity // level and number can be mathematically represented as // // number * N ^ (infinity level) // // where N is a number which is large enough. Practically, // infinity levels are used to circumvent limited ranges for // integer numbers. // Here, all infinity levels have got special meanings. enum { INF_VALUE = 0, /* simple values */ INF_LARGE, /* large values, like QUITE_LOOSE */ INF_NOT_STRETCHABLE, /* reserved for NOT_STRECTHABLE */ INF_TOO_TIGHT, /* used for lines, which are too tight */ INF_PENALTIES, /* used for penalties */ INF_MAX = INF_PENALTIES // That INF_PENALTIES is the last value means that an // infinite penalty (breaking is prohibited) makes a break // not possible at all, so that pre-formatted text // etc. works. }; void setSinglePenalty (int index, int penalty); int badnessValue (int infLevel); int penaltyValue (int index, int infLevel); public: void calcBadness (int totalWidth, int idealWidth, int totalStretchability, int totalShrinkability); inline void setPenalty (int penalty) { setPenalties (penalty, penalty); } void setPenalties (int penalty1, int penalty2); bool lineLoose (); bool lineTight (); bool lineTooTight (); bool lineMustBeBroken (int penaltyIndex); bool lineCanBeBroken (int penaltyIndex); int compareTo (int penaltyIndex, BadnessAndPenalty *other); void print (); }; enum { PENALTY_HYPHEN, PENALTY_EM_DASH_LEFT, PENALTY_EM_DASH_RIGHT, PENALTY_NUM }; enum { NUM_DIV_CHARS = 4 }; typedef struct { const char *s; bool charRemoved, canBeHyphenated, unbreakableForMinWidth; int penaltyIndexLeft, penaltyIndexRight; } DivChar; static DivChar divChars[NUM_DIV_CHARS]; static const char *hyphenDrawChar; Textblock *containingBlock; OutOfFlowMgr *outOfFlowMgr; protected: struct Paragraph { int firstWord; /* first word's index in word vector */ int lastWord; /* last word's index in word vector */ /* * General remark: all values include the last hyphen width, but * not the last space; these values are, however corrected, when * another word is added. * * Also, as opposed to lines, paragraphs are created with the * first, not the last word, so these values change when new * words are added. */ int parMin; /* The sum of all word minima (plus spaces, hyphen width etc.) since the last possible break within this paragraph. */ int parMax; /* The sum of all word maxima in this * paragraph (plus spaces, hyphen width * etc.). */ int maxParMin; /* Maximum of all paragraph minima (value of * "parMin), including this paragraph. */ int maxParMax; /* Maximum of all paragraph maxima (value of * "parMax"), including this paragraph. */ }; struct Line { int firstWord; /* first word's index in word vector */ int lastWord; /* last word's index in word vector */ /* "top" is always relative to the top of the first line, i.e. * page->lines[0].top is always 0. */ int top, boxAscent, boxDescent, contentAscent, contentDescent, breakSpace, leftOffset, offsetCompleteWidget; /* This is similar to descent, but includes the bottom margins of the * widgets within this line. */ int marginDescent; /* Maximum of all line widths, including this line. Does not * include the last space, but the last hyphen width. Please * notice a change: until recently (before hyphenation and * changed line breaking), the values were accumulated up to the * last line, not this line.*/ int maxLineWidth; /* Set to false at the beginning of addLine(), and to true at * the end. Should be checked by some methods which are called * by addLine(). */ bool finished; }; struct Word { enum { /** Can be hyphenated automatically. (Cleared after * hyphenation.) */ CAN_BE_HYPHENATED = 1 << 0, /** Must be drawn with a hyphen, when at the end of the line. */ DIV_CHAR_AT_EOL = 1 << 1, /** Is or ends with a "division character", which is part of * the word. */ PERM_DIV_CHAR = 1 << 2, /** This word must be drawn, together with the following * word(s), by only one call of View::drawText(), to get * kerning, ligatures etc. right. The last of the words drawn * as one text does *not* have this flag set. */ DRAW_AS_ONE_TEXT = 1 << 3, /* When calculating the minimal width (as part of extremes), * do not consider this word as breakable. This flag is * ignored when the line is actually broken. */ UNBREAKABLE_FOR_MIN_WIDTH = 1 << 4, /* If a word represents a "real" text word, or (after * hyphenation) the first part of a "real" text word, this * flag is set. Plays a role for text transformation. */ WORD_START = 1 << 5, /* If a word represents a "real" text word, or (after * hyphenation) the last part of a "real" text word, this * flag is set. Analogue to WORD_START. */ WORD_END = 1 << 6 }; /* TODO: perhaps add a xLeft? */ core::Requisition size; /* Space after the word, only if it's not a break: */ short origSpace; /* from font, set by addSpace */ short effSpace; /* effective space, set by wordWrap, * used for drawing etc. */ short hyphenWidth; /* Additional width, when a word is part * (except the last part) of a hyphenationed * word. Has to be added to the width, when * this is the last word of the line, and * "hyphenWidth > 0" is also used to decide * whether to draw a hyphen. */ short flags; core::Content content; // accumulated values, relative to the beginning of the line int totalWidth; /* The sum of all word widths; plus all spaces, excluding the one of this word; plus the hyphen width of this word (but of course, no hyphen widths of previous words. In other words: the value compared to the ideal width of the line, if the line would be broken after this word. */ int totalStretchability; // includes all *before* current word int totalShrinkability; // includes all *before* current word BadnessAndPenalty badnessAndPenalty; /* when line is broken after this * word */ core::style::Style *style; core::style::Style *spaceStyle; /* initially the same as of the word, later set by a_Dw_page_add_space */ }; void printWordShort (Word *word); void printWordFlags (short flags); void printWordWithFlags (Word *word); void printWord (Word *word); struct Anchor { char *name; int wordIndex; }; class TextblockIterator: public core::Iterator { private: bool oofm; int index; public: TextblockIterator (Textblock *textblock, core::Content::Type mask, bool atEnd); TextblockIterator (Textblock *textblock, core::Content::Type mask, bool oofm, int index); lout::object::Object *clone(); int compareTo(lout::object::Comparable *other); bool next (); bool prev (); void highlight (int start, int end, core::HighlightLayer layer); void unhighlight (int direction, core::HighlightLayer layer); void getAllocation (int start, int end, core::Allocation *allocation); void print (); }; friend class TextblockIterator; /* These fields provide some ad-hoc-functionality, used by sub-classes. */ bool hasListitemValue; /* If true, the first word of the page is treated specially (search in source). */ int innerPadding; /* This is an additional padding on the left side (used by ListItem). */ int line1Offset; /* This is an additional offset of the first line. May be negative (shift to left) or positive (shift to right). */ int line1OffsetEff; /* The "effective" value of line1_offset, may differ from line1_offset when ignoreLine1OffsetSometimes is set to true. */ /* The following is really hackish: It is used for DwTableCell (see * comment in dw_table_cell.c), to avoid too wide table columns. If * set to true, it has following effects: * * (i) line1_offset is ignored in calculating the minimal width * (which is used by DwTable!), and * (ii) line1_offset is ignored (line1_offset_eff is set to 0), * when line1_offset plus the width of the first word is * greater than the the available witdh. * * \todo Eliminate all these ad-hoc features by a new, simpler and * more elegant design. ;-) */ bool ignoreLine1OffsetSometimes; bool mustQueueResize; /** * The penalties for hyphens and other, multiplied by 100. So, 100 * means 1.0. INT_MAX and INT_MIN are also allowed. See * dw::Textblock::BadnessAndPenalty::setPenalty for more * details. Set from preferences. */ static int penalties[PENALTY_NUM][2]; bool limitTextWidth; /* from preferences */ int redrawY; int lastWordDrawn; /* These values are set by set_... */ int availWidth, availAscent, availDescent; int wrapRefLines, wrapRefParagraphs; /* 0-based. Important: Both are the line numbers, not the value stored in parentRef. */ // These four values are calculated by containingBlock->outOfFlowMgr // (when defined; otherwise, they are false, or 0, respectively), for // the newly constructed line, only when needed: when a new line is // added, or if something in the line currently constucted has // changed, e. g. a float has been added. bool newLineHasFloatLeft, newLineHasFloatRight; int newLineLeftBorder, newLineRightBorder; /* As returned by outOfFlowMgr->get...Border, or 0, if outOfFlowMgr is NULL */ // Ascent and descent of the newly constructed line, i. e. maximum // of all words ascent/descent since the end of the last line. Not // neccessary the ascent and descent of the newly added line, since // not all words are added to it. int newLineAscent, newLineDescent; lout::misc::SimpleVector *lines; lout::misc::SimpleVector *paragraphs; int nonTemporaryLines; lout::misc::NotSoSimpleVector *words; lout::misc::SimpleVector *anchors; struct {int index, nChar;} hlStart[core::HIGHLIGHT_NUM_LAYERS], hlEnd[core::HIGHLIGHT_NUM_LAYERS]; int hoverLink; /* The link under the mouse pointer */ void queueDrawRange (int index1, int index2); void getWordExtremes (Word *word, core::Extremes *extremes); void justifyLine (Line *line, int diff); Line *addLine (int firstWord, int lastWord, bool temporary); void calcWidgetSize (core::Widget *widget, core::Requisition *size); void rewrap (); void fillParagraphs (); void initNewLine (); void showMissingLines (); void removeTemporaryLines (); void decorateText (core::View *view, core::style::Style *style, core::style::Color::Shading shading, int x, int yBase, int width); void drawText (core::View *view, core::style::Style *style, core::style::Color::Shading shading, int x, int y, const char *text, int start, int len, bool isStart, bool isEnd); void drawWord (Line *line, int wordIndex1, int wordIndex2, core::View *view, core::Rectangle *area, int xWidget, int yWidgetBase); void drawWord0 (int wordIndex1, int wordIndex2, const char *text, int totalWidth, bool drawHyphen, core::style::Style *style, core::View *view, core::Rectangle *area, int xWidget, int yWidgetBase); void drawSpace (int wordIndex, core::View *view, core::Rectangle *area, int xWidget, int yWidgetBase); void drawLine (Line *line, core::View *view, core::Rectangle *area); int findLineIndex (int y); int findLineOfWord (int wordIndex); int findParagraphOfWord (int wordIndex); Word *findWord (int x, int y, bool *inSpace); Word *addWord (int width, int ascent, int descent, short flags, core::style::Style *style); void fillWord (Word *word, int width, int ascent, int descent, short flags, core::style::Style *style); void fillSpace (Word *word, core::style::Style *style); void setBreakOption (Word *word, core::style::Style *style, int breakPenalty1, int breakPenalty2); bool isBreakAllowed (Word *word); int textWidth (const char *text, int start, int len, core::style::Style *style, bool isStart, bool isEnd); void calcTextSize (const char *text, size_t len, core::style::Style *style, core::Requisition *size, bool isStart, bool isEnd); /** * Of nested text blocks, only the most inner one must regard the * borders of floats. */ inline bool mustBorderBeRegarded (Line *line) { return getTextblockForLine (line) == NULL; } inline bool mustBorderBeRegarded (int lineNo) { return getTextblockForLine (lineNo) == NULL; } inline int lineYOffsetWidgetAllocation (Line *line, core::Allocation *allocation) { return line->top + (allocation->ascent - lines->getRef(0)->boxAscent); } inline int lineYOffsetWidget (Line *line) { return lineYOffsetWidgetAllocation (line, &allocation); } /** * Like lineYOffsetCanvas, but with the allocation as parameter. */ inline int lineYOffsetCanvasAllocation (Line *line, core::Allocation *allocation) { return allocation->y + lineYOffsetWidgetAllocation(line, allocation); } /** * Returns the y offset (within the canvas) of a line. */ inline int lineYOffsetCanvas (Line *line) { return lineYOffsetCanvasAllocation(line, &allocation); } inline int lineYOffsetWidgetI (int lineIndex) { return lineYOffsetWidget (lines->getRef (lineIndex)); } inline int lineYOffsetCanvasI (int lineIndex) { return lineYOffsetCanvas (lines->getRef (lineIndex)); } inline int calcPenaltyIndexForNewLine () { if (lines->size() == 0) return 0; else return (words->getRef(lines->getLastRef()->lastWord)->flags & (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0; } Textblock *getTextblockForLine (Line *line); Textblock *getTextblockForLine (int lineNo); Textblock *getTextblockForLine (int firstWord, int lastWord); int topOfPossiblyMissingLine (int lineNo); int heightOfPossiblyMissingLine (int lineNo); bool sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event); void accumulateWordExtremes (int firstWord, int lastWord, int *maxOfMinWidth, int *sumOfMaxWidth); void processWord (int wordIndex); virtual bool wordWrap (int wordIndex, bool wrapAll); bool wrapWordInFlow (int wordIndex, bool wrapAll); void checkPossibleLineHeightChange (int wordIndex); bool wrapWordOofRef (int wordIndex, bool wrapAll); void updateBorders (int wordIndex, bool left, bool right); int searchMinBap (int firstWord, int lastWordm, int penaltyIndex, bool correctAtEnd); int considerHyphenation (int firstIndex, int breakPos); bool isHyphenationCandidate (Word *word); void handleWordExtremes (int wordIndex); void correctLastWordExtremes (); static int getShrinkability(struct Word *word); static int getStretchability(struct Word *word); int hyphenateWord (int wordIndex); void accumulateWordForLine (int lineIndex, int wordIndex); void accumulateWordData (int wordIndex); int calcAvailWidth (int lineIndex); void initLine1Offset (int wordIndex); void alignLine (int lineIndex); void sizeRequestImpl (core::Requisition *requisition); void getExtremesImpl (core::Extremes *extremes); void sizeAllocateImpl (core::Allocation *allocation); void resizeDrawImpl (); void markSizeChange (int ref); void markExtremesChange (int ref); void notifySetAsTopLevel(); void notifySetParent(); void setWidth (int width); void setAscent (int ascent); void setDescent (int descent); void draw (core::View *view, core::Rectangle *area); bool buttonPressImpl (core::EventButton *event); bool buttonReleaseImpl (core::EventButton *event); bool motionNotifyImpl (core::EventMotion *event); void enterNotifyImpl (core::EventCrossing *event); void leaveNotifyImpl (core::EventCrossing *event); void removeChild (Widget *child); void addText0 (const char *text, size_t len, short flags, core::style::Style *style, core::Requisition *size); void calcTextSizes (const char *text, size_t textLen, core::style::Style *style, int numBreaks, int *breakPos, core::Requisition *wordSize); static bool isContainingBlock (Widget *widget); public: static int CLASS_ID; static void setPenaltyHyphen (int penaltyHyphen); static void setPenaltyHyphen2 (int penaltyHyphen2); static void setPenaltyEmDashLeft (int penaltyLeftEmDash); static void setPenaltyEmDashRight (int penaltyRightEmDash); static void setPenaltyEmDashRight2 (int penaltyRightEmDash2); Textblock(bool limitTextWidth); ~Textblock(); core::Iterator *iterator (core::Content::Type mask, bool atEnd); void flush (); void addText (const char *text, size_t len, core::style::Style *style); inline void addText (const char *text, core::style::Style *style) { addText (text, strlen(text), style); } void addWidget (core::Widget *widget, core::style::Style *style); bool addAnchor (const char *name, core::style::Style *style); void addSpace (core::style::Style *style); void addBreakOption (core::style::Style *style); void addParbreak (int space, core::style::Style *style); void addLinebreak (core::style::Style *style); core::Widget *getWidgetAtPoint (int x, int y, int level); void handOverBreak (core::style::Style *style); void changeLinkColor (int link, int newColor); void changeWordStyle (int from, int to, core::style::Style *style, bool includeFirstSpace, bool includeLastSpace); void borderChanged (int y, core::Widget *vloat); inline int getAvailWidth () { return availWidth; } inline int getAvailAscent () { return availAscent; } inline int getAvailDescent () { return availDescent; } }; } // namespace dw #endif // __DW_TEXTBLOCK_HH__