diff options
Diffstat (limited to 'dw/textblock.cc')
-rw-r--r-- | dw/textblock.cc | 1472 |
1 files changed, 685 insertions, 787 deletions
diff --git a/dw/textblock.cc b/dw/textblock.cc index 6ded2413..6aa75a13 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -17,20 +17,21 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + #include "textblock.hh" #include "table.hh" // Yes, this is ugly. -- SG #include "../lout/msg.h" #include "../lout/misc.hh" #include <stdio.h> -#include <math.h> // remove again +#include <math.h> // remove again? #include <limits.h> /* * Local variables */ - /* The tooltip under mouse pointer in current textblock. No ref. hold. - * (having one per view looks not worth the extra clutter). */ +/* The tooltip under mouse pointer in current textblock. No ref. hold. + * (having one per view looks not worth the extra clutter). */ static dw::core::style::Tooltip *hoverTooltip = NULL; @@ -51,7 +52,6 @@ Textblock::Textblock (bool limitTextWidth) hasListitemValue = false; innerPadding = 0; line1Offset = 0; - line1OffsetEff = 0; ignoreLine1OffsetSometimes = false; mustQueueResize = false; redrawY = 0; @@ -67,21 +67,19 @@ Textblock::Textblock (bool limitTextWidth) * TODO: Some tests would be useful. */ lines = new misc::SimpleVector <Line> (1); - words = new misc::SimpleVector <Word> (1); - leftFloatSide = rightFloatSide = NULL; + nonTemporaryLines = 0; + words = new misc::NotSoSimpleVector <Word> (1); anchors = new misc::SimpleVector <Anchor> (1); + leftFloatSide = rightFloatSide = NULL; - //DBG_OBJ_SET_NUM(page, "num_lines", num_lines); + //DBG_OBJ_SET_NUM(this, "num_lines", num_lines); - lastLineWidth = 0; - lastLineParMin = 0; - lastLineParMax = 0; wrapRef = -1; - //DBG_OBJ_SET_NUM(page, "last_line_width", last_line_width); - //DBG_OBJ_SET_NUM(page, "last_line_par_min", last_line_par_min); - //DBG_OBJ_SET_NUM(page, "last_line_par_max", last_line_par_max); - //DBG_OBJ_SET_NUM(page, "wrap_ref", wrap_ref); + //DBG_OBJ_SET_NUM(this, "last_line_width", last_line_width); + //DBG_OBJ_SET_NUM(this, "last_line_par_min", last_line_par_min); + //DBG_OBJ_SET_NUM(this, "last_line_par_max", last_line_par_max); + //DBG_OBJ_SET_NUM(this, "wrap_ref", wrap_ref); hoverLink = -1; @@ -125,7 +123,7 @@ Textblock::~Textblock () delete lines; delete words; delete anchors; - + if(leftFloatSide) delete leftFloatSide; if(rightFloatSide) @@ -135,7 +133,7 @@ Textblock::~Textblock () parent class destructor. (???) */ words = NULL; - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); + //DBG_OBJ_SET_NUM(this, "num_lines", lines->size ()); } /** @@ -145,12 +143,27 @@ Textblock::~Textblock () */ void Textblock::sizeRequestImpl (core::Requisition *requisition) { + PRINTF ("[%p] SIZE_REQUEST: ...\n", this); + rewrap (); + showMissingLines (); if (lines->size () > 0) { Line *lastLine = lines->getRef (lines->size () - 1); - requisition->width = - misc::max (lastLine->maxLineWidth, lastLineWidth); + requisition->width = lastLine->maxLineWidth; + + PRINTF ("[%p] SIZE_REQUEST: lastLine->maxLineWidth = %d\n", + this, lastLine->maxLineWidth); + + PRINTF ("[%p] SIZE_REQUEST: lines[0]->boxAscent = %d\n", + this, lines->getRef(0)->boxAscent); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->top = %d\n", + this, lines->size () - 1, lastLine->top); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxAscent = %d\n", + this, lines->size () - 1, lastLine->boxAscent); + PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxDescent = %d\n", + this, lines->size () - 1, lastLine->boxDescent); + /* Note: the breakSpace of the last line is ignored, so breaks at the end of a textblock are not visible. */ requisition->ascent = lines->getRef(0)->boxAscent; @@ -158,17 +171,23 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) + lastLine->boxAscent + lastLine->boxDescent - lines->getRef(0)->boxAscent; } else { - requisition->width = lastLineWidth; + requisition->width = 0; // before: lastLineWidth; requisition->ascent = 0; requisition->descent = 0; } + PRINTF ("[%p] SIZE_REQUEST: inner padding = %d, boxDiffWidth = %d\n", + this, innerPadding, getStyle()->boxDiffWidth ()); + requisition->width += innerPadding + getStyle()->boxDiffWidth (); requisition->ascent += getStyle()->boxOffsetY (); requisition->descent += getStyle()->boxRestHeight (); if (requisition->width < availWidth) requisition->width = availWidth; + + PRINTF ("[%p] SIZE_REQUEST: %d x %d + %d\n", this, requisition->width, + requisition->ascent, requisition->descent); } /** @@ -206,147 +225,125 @@ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) void Textblock::getExtremesImpl (core::Extremes *extremes) { - core::Extremes wordExtremes; - Line *line; - Word *word; - int wordIndex, lineIndex; - int parMin, parMax; - bool nowrap; + PRINTF ("[%p] GET_EXTREMES: ...\n", this); - //DBG_MSG (widget, "extremes", 0, "Dw_page_get_extremes"); - //DBG_MSG_START (widget); + showMissingLines (); if (lines->size () == 0) { /* empty page */ extremes->minWidth = 0; extremes->maxWidth = 0; + + PRINTF ("GET_EXTREMES: empty (but %d words)\n", words->size()); } else if (wrapRef == -1) { /* no rewrap necessary -> values in lines are up to date */ - line = lines->getRef (lines->size () - 1); - /* Historical note: The former distinction between lines with and without - * words[first_word]->nowrap set is no longer necessary, since - * Dw_page_real_word_wrap sets max_word_min to the correct value in any - * case. */ - extremes->minWidth = line->maxWordMin; - extremes->maxWidth = misc::max (line->maxParMax, lastLineParMax); - //DBG_MSG (widget, "extremes", 0, "simple case"); + Line *lastLine = lines->getRef (lines->size () - 1); + extremes->minWidth = lastLine->maxParMin; + Word *lastWord = words->getRef (lastLine->lastWord); + extremes->maxWidth = + misc::max (lastLine->maxParMax, + // parMax includes the last space, which we ignore here + lastLine->parMax - lastWord->origSpace + + lastWord->hyphenWidth); + + PRINTF ("GET_EXTREMES: no rewrap => %d, %d\n", + lastLine->maxParMin, lastLine->maxParMax); } else { + int parMax; /* Calculate the extremes, based on the values in the line from where a rewrap is necessary. */ - //DBG_MSG (widget, "extremes", 0, "complex case"); + + PRINTF ("GET_EXTREMES: complex case ...\n"); if (wrapRef == 0) { extremes->minWidth = 0; extremes->maxWidth = 0; - parMin = 0; parMax = 0; } else { - line = lines->getRef (wrapRef); - extremes->minWidth = line->maxWordMin; - extremes->maxWidth = line->maxParMax; - parMin = line->parMin; + // Line [wrapRef - 1], not [wrapRef], because maxParMin and + // maxParMax include the respective values *in* any line + // (accumulated up to the *end*), but we start at line + // [wrapRef], so these are not needed. + + Line *line = lines->getRef (wrapRef - 1); + Word *lastWord = words->getRef (line->lastWord); + extremes->minWidth = line->maxParMin; + // consider also accumulated next value of maxParMax: parMax + extremes->maxWidth = + misc::max (line->maxParMax, + // parMax includes the last space, which we ignore here + line->parMax - lastWord->origSpace + + lastWord->hyphenWidth); parMax = line->parMax; - - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); } - //_MSG ("*** parMin = %d\n", parMin); - - int prevWordSpace = 0; - for (lineIndex = wrapRef; lineIndex < lines->size (); lineIndex++) { - //DBG_MSGF (widget, "extremes", 0, "line %d", lineIndex); - //DBG_MSG_START (widget); - core::style::WhiteSpace ws; - - line = lines->getRef (lineIndex); - ws = words->getRef(line->firstWord)->style->whiteSpace; - nowrap = ws == core::style::WHITE_SPACE_PRE || - ws == core::style::WHITE_SPACE_NOWRAP; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL, " line %d (of %d), nowrap = %d\n", - // lineIndex, page->num_lines, nowrap); - - for (wordIndex = line->firstWord; wordIndex <= line->lastWord; - wordIndex++) { - word = words->getRef (wordIndex); + if (wrapRef < lines->size()) { + int parMin = 0; + + for (int wordIndex = lines->getRef(wrapRef)->firstWord; + wordIndex < words->size(); wordIndex++) { + Word *word = words->getRef (wordIndex); + bool atLastWord = wordIndex == words->size() - 1; + + //printf (" word: "); + //printWord (word); + //printf ("\n"); + + core::Extremes wordExtremes; getWordExtremes (word, &wordExtremes); - if (wordIndex == 0) { - wordExtremes.minWidth += line1OffsetEff; - wordExtremes.maxWidth += line1OffsetEff; - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, - // " (next plus %d)\n", page->line1_offset); - } - - if (nowrap) { - parMin += prevWordSpace + wordExtremes.minWidth; - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); - } else { - if (extremes->minWidth < wordExtremes.minWidth) - extremes->minWidth = wordExtremes.minWidth; + wordExtremes.minWidth += line1Offset; + wordExtremes.maxWidth += line1Offset; } - - _MSG("parMax = %d, wordMaxWidth=%d, prevWordSpace=%d\n", - parMax, wordExtremes.maxWidth, prevWordSpace); - if (word->content.type != core::Content::BREAK) - parMax += prevWordSpace; - parMax += wordExtremes.maxWidth; - prevWordSpace = word->origSpace; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 1, - // " word %s: maxWidth = %d\n", - // a_Dw_content_text (&word->content), - // word_extremes.maxWidth); + + // Minimum: between two *possible* breaks (or at the end). + if (word->badnessAndPenalty.lineCanBeBroken () || atLastWord) { + parMin += wordExtremes.minWidth + word->hyphenWidth; + extremes->minWidth = misc::max (extremes->minWidth, parMin); + parMin = 0; + } else + // Shrinkability could be considered, but really does not play a + // role. + parMin += wordExtremes.minWidth + word->origSpace; + + // Maximum: between two *neccessary* breaks (or at the end). + if (word->badnessAndPenalty.lineMustBeBroken () || atLastWord) { + parMax += wordExtremes.maxWidth + word->hyphenWidth; + extremes->maxWidth = misc::max (extremes->maxWidth, parMax); + parMax = 0; + } else + parMax += wordExtremes.maxWidth + word->origSpace; + + PRINTF (" => ... extremes = %d / %d, parMin = %d, parMax = %d\n", + extremes->minWidth, extremes->maxWidth, parMin, parMax); } - - if ((words->getRef(line->lastWord)->content.type - == core::Content::BREAK ) || - lineIndex == lines->size () - 1 ) { - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, - // " parMax = %d, after word %d (%s)\n", - // parMax, line->last_word - 1, - // a_Dw_content_text (&word->content)); - - if (extremes->maxWidth < parMax) - extremes->maxWidth = parMax; - - if (nowrap) { - //DBG_MSGF (widget, "extremes", 0, "parMin = %d", parMin); - if (extremes->minWidth < parMin) - extremes->minWidth = parMin; - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 2, - // " parMin = %d, after word %d (%s)\n", - // parMin, line->last_word - 1, - // a_Dw_content_text (&word->content)); - } - - prevWordSpace = 0; - parMin = 0; - parMax = 0; - } - - //DBG_MSG_END (widget); } - - //DEBUG_MSG (DEBUG_SIZE_LEVEL + 3, " Result: %d, %d\n", - // extremes->minWidth, extremes->maxWidth); } - //DBG_MSGF (widget, "extremes", 0, "width difference: %d + %d", - // page->inner_padding, p_Dw_style_box_diff_width (widget->style)); - int diff = innerPadding + getStyle()->boxDiffWidth (); extremes->minWidth += diff; extremes->maxWidth += diff; - //DBG_MSG_END (widget); + //printf ("[%p] GET_EXTREMES, on textblock that ", this); + //if (words->size() == 0) + // printf ("is empty\n"); + //else { + // printf ("starts with:\n "); + // printWord (words->getRef(0)); + // printf ("\n"); + //} + //printf ("=> %d / %d\n", extremes->minWidth, extremes->maxWidth); } void Textblock::sizeAllocateImpl (core::Allocation *allocation) { + PRINTF ("[%p] SIZE_ALLOCATE: %d, %d, %d x %d + %d\n", + this, allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); + + showMissingLines (); + int lineIndex, wordIndex; Line *line; Word *word; @@ -441,7 +438,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) xCursor += (word->size.width + word->effSpace); } } - + if(leftFloatSide) leftFloatSide->sizeAllocate(allocation); if(rightFloatSide) @@ -450,7 +447,7 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef(i); int y; - + if (anchor->wordIndex >= words->size()) { y = allocation->y + allocation->ascent + allocation->descent; } else { @@ -490,14 +487,17 @@ void Textblock::markExtremesChange (int ref) */ void Textblock::markChange (int ref) { - printf("markChange(%d)\n", ref); + PRINTF ("[%p] MARK_CHANGE (%d): %d => ...\n", this, ref, wrapRef); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything neccessary. */ int refEquiv = (ref == 0) ? 1 | (dw::core::style::FLOAT_NONE << 1) : ref; if (refEquiv != -1 && (refEquiv & 1)) { - //DBG_MSGF (page, "wrap", 0, "Dw_page_mark_size_change (ref = %d)", ref); - - // ABC switch((refEquiv >> 1) & 3) { case dw::core::style::FLOAT_NONE: @@ -518,11 +518,13 @@ void Textblock::markChange (int ref) break; } } + + PRINTF (" ... => %d\n", wrapRef); } void Textblock::notifySetAsTopLevel() { - containingBox = this; + containingBox = this; } void Textblock::notifySetParent() @@ -532,21 +534,25 @@ void Textblock::notifySetParent() containingBox = NULL; Textblock *topmostTextblock = this; - for(Widget *widget = getParent(); widget != NULL; widget = widget->getParent()) + for(Widget *widget = getParent(); widget != NULL; + widget = widget->getParent()) { if(widget->instanceOf(Textblock::CLASS_ID)) topmostTextblock = (Textblock*)widget; } - for(Widget *widget = getParent(); containingBox == NULL; widget = widget->getParent()) + for(Widget *widget = getParent(); containingBox == NULL; + widget = widget->getParent()) { if(widget->getParent() == NULL) // No other widget left. containingBox = topmostTextblock; else if(widget->instanceOf(Textblock::CLASS_ID)) { - if(widget->getParent()->instanceOf(Table::CLASS_ID) /* this widget is a table cell */ || - widget->getStyle()->vloat != dw::core::style::FLOAT_NONE /* this widget is a float */) + if(// this widget is a table cell + widget->getParent()->instanceOf(Table::CLASS_ID) || + // this widget is a float + widget->getStyle()->vloat != dw::core::style::FLOAT_NONE) containingBox = (Textblock*)widget; } } @@ -554,13 +560,13 @@ void Textblock::notifySetParent() void Textblock::setWidth (int width) { - /* If limitTextWidth is set to YES, a queue_resize may also be + /* If limitTextWidth is set to YES, a queueResize() may also be * necessary. */ if (availWidth != width || limitTextWidth) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_width: Calling p_Dw_widget_queue_resize, " + // "setWidth: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availWidth = width; //<<<<<<< textblock.cc @@ -577,9 +583,9 @@ void Textblock::setAscent (int ascent) { if (availAscent != ascent) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_ascent: Calling p_Dw_widget_queue_resize, " + // "setAscent: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availAscent = ascent; //<<<<<<< textblock.cc @@ -595,9 +601,9 @@ void Textblock::setDescent (int descent) { if (availDescent != descent) { //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Dw_page_set_descent: Calling p_Dw_widget_queue_resize, " + // "setDescent: Calling queueResize, " // "in page with %d word(s)\n", - // page->num_words); + // words->size()); availDescent = descent; //<<<<<<< textblock.cc @@ -694,102 +700,113 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, core::MousePositionEvent *event) { core::Iterator *it; - Line *line, *lastLine; - int nextWordStartX, wordStartX, wordX, nextWordX, yFirst, yLast; - int charPos = 0, link = -1, prevPos, wordIndex, lineIndex; - Word *word; - bool found, r; + int wordIndex; + int charPos = 0, link = -1; + bool r; if (words->size () == 0) { wordIndex = -1; } else { - lastLine = lines->getRef (lines->size () - 1); - yFirst = lineYOffsetCanvasI (0); - yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + - lastLine->boxDescent; + Line *lastLine = lines->getRef (lines->size () - 1); + int yFirst = lineYOffsetCanvasI (0); + int yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + + lastLine->boxDescent; if (event->yCanvas < yFirst) { // Above the first line: take the first word. wordIndex = 0; - charPos = 0; } else if (event->yCanvas >= yLast) { // Below the last line: take the last word. wordIndex = words->size () - 1; - word = words->getRef (wordIndex); - charPos = word->content.type == core::Content::TEXT ? - strlen (word->content.text) : 0; + charPos = core::SelectionState::END_OF_WORD; } else { - lineIndex = findLineIndex (event->yWidget); - line = lines->getRef (lineIndex); + Line *line = lines->getRef (findLineIndex (event->yWidget)); // Pointer within the break space? if (event->yWidget > (lineYOffsetWidget (line) + line->boxAscent + line->boxDescent)) { // Choose this break. wordIndex = line->lastWord; - charPos = 0; + charPos = core::SelectionState::END_OF_WORD; } else if (event->xWidget < lineXOffsetWidget (line)) { // Left of the first word in the line. wordIndex = line->firstWord; - charPos = 0; } else { - nextWordStartX = lineXOffsetWidget (line); - found = false; + int nextWordStartX = lineXOffsetWidget (line); + for (wordIndex = line->firstWord; - !found && wordIndex <= line->lastWord; + wordIndex <= line->lastWord; wordIndex++) { - word = words->getRef (wordIndex); - wordStartX = nextWordStartX; + Word *word = words->getRef (wordIndex); + int wordStartX = nextWordStartX; + nextWordStartX += word->size.width + word->effSpace; if (event->xWidget >= wordStartX && event->xWidget < nextWordStartX) { // We have found the word. - if (word->content.type == core::Content::TEXT) { - // Search the character the mouse pointer is in. - // nextWordX is the right side of this character. - charPos = 0; - while ((nextWordX = wordStartX + - layout->textWidth (word->style->font, - word->content.text, charPos)) - <= event->xWidget) - charPos = layout->nextGlyph (word->content.text, - charPos); - // The left side of this character. - prevPos = layout->prevGlyph (word->content.text, charPos); - wordX = wordStartX + layout->textWidth (word->style->font, - word->content.text, - prevPos); - - // If the mouse pointer is left from the middle, use the - // left position, otherwise, use the right one. - if (event->xWidget <= (wordX + nextWordX) / 2) - charPos = prevPos; + int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + + if (event->xWidget >= nextWordStartX - word->effSpace) { + charPos = core::SelectionState::END_OF_WORD; + if (wordIndex < line->lastWord && + (words->getRef(wordIndex + 1)->content.type != + core::Content::BREAK) && + (event->yWidget <= + yWidgetBase + word->spaceStyle->font->descent) && + (event->yWidget > + yWidgetBase - word->spaceStyle->font->ascent)) { + link = word->spaceStyle->x_link; + } } else { - // Depends on whether the pointer is within the left or - // right half of the (non-text) word. - if (event->xWidget >= - (wordStartX + nextWordStartX) / 2) - charPos = core::SelectionState::END_OF_WORD; - else - charPos = 0; + if (event->yWidget <= yWidgetBase + word->size.descent && + event->yWidget > yWidgetBase - word->size.ascent) { + link = word->style->x_link; + } + if (word->content.type == core::Content::TEXT) { + int glyphX = wordStartX; + + while (1) { + int nextCharPos = + layout->nextGlyph (word->content.text, charPos); + int glyphWidth = + textWidth (word->content.text, charPos, + nextCharPos - charPos, word->style); + if (event->xWidget > glyphX + glyphWidth) { + glyphX += glyphWidth; + charPos = nextCharPos; + continue; + } else if (event->xWidget >= glyphX + glyphWidth/2){ + // On the right half of a character; + // now just look for combining chars + charPos = nextCharPos; + while (word->content.text[charPos]) { + nextCharPos = + layout->nextGlyph (word->content.text, + charPos); + if (textWidth (word->content.text, charPos, + nextCharPos - charPos, + word->style)) + break; + charPos = nextCharPos; + } + } + break; + } + } else { + // Depends on whether the pointer is within the left or + // right half of the (non-text) word. + if (event->xWidget >= (wordStartX + nextWordStartX) /2) + charPos = core::SelectionState::END_OF_WORD; + } } - - found = true; - link = word->style ? word->style->x_link : -1; break; } } - - if (!found) { + if (wordIndex > line->lastWord) { // No word found in this line (i.e. we are on the right side), // take the last of this line. wordIndex = line->lastWord; - if (wordIndex >= words->size ()) - wordIndex--; - word = words->getRef (wordIndex); - charPos = word->content.type == core::Content::TEXT ? - strlen (word->content.text) : - (int)core::SelectionState::END_OF_WORD; + charPos = core::SelectionState::END_OF_WORD; } } } @@ -811,391 +828,6 @@ core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) return new TextblockIterator (this, mask, atEnd); } -/* - * ... - * - * availWidth is passed from wordWrap, to avoid calculating it twice. - */ -void Textblock::justifyLine (Line *line, int availWidth) -{ - /* To avoid rounding errors, the calculation is based on accumulated - * values (*_cum). */ - int i; - int origSpaceSum, origSpaceCum; - int effSpaceDiffCum, lastEffSpaceDiffCum; - int diff; - - diff = availWidth - lastLineWidth; - if (diff > 0) { - origSpaceSum = 0; - for (i = line->firstWord; i < line->lastWord; i++) - origSpaceSum += words->getRef(i)->origSpace; - - origSpaceCum = 0; - lastEffSpaceDiffCum = 0; - for (i = line->firstWord; i < line->lastWord; i++) { - origSpaceCum += words->getRef(i)->origSpace; - - if (origSpaceCum == 0) - effSpaceDiffCum = lastEffSpaceDiffCum; - else - effSpaceDiffCum = diff * origSpaceCum / origSpaceSum; - - words->getRef(i)->effSpace = words->getRef(i)->origSpace + - (effSpaceDiffCum - lastEffSpaceDiffCum); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", i, - // page->words[i].eff_space); - - lastEffSpaceDiffCum = effSpaceDiffCum; - } - } -} - - -Textblock::Line *Textblock::addLine (int wordIndex, bool newPar) -{ - Line *lastLine; - - //DBG_MSG (page, "wrap", 0, "Dw_page_add_line"); - //DBG_MSG_START (page); - - lines->increase (); - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); - - //DEBUG_MSG (DEBUG_REWRAP_LEVEL, "--- new line %d in %p, with word %d of %d" - // "\n", page->num_lines - 1, page, word_ind, page->num_words); - - lastLine = lines->getRef (lines->size () - 1); - - if (lines->size () == 1) { - lastLine->top = 0; - lastLine->maxLineWidth = line1OffsetEff; - lastLine->maxWordMin = 0; - lastLine->maxParMax = 0; - } else { - Line *prevLine = lines->getRef (lines->size () - 2); - - lastLine->top = prevLine->top + prevLine->boxAscent + - prevLine->boxDescent + prevLine->breakSpace; - lastLine->maxLineWidth = prevLine->maxLineWidth; - lastLine->maxWordMin = prevLine->maxWordMin; - lastLine->maxParMax = prevLine->maxParMax; - } - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.top", page->num_lines - 1, - // lastLine->top); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxLineWidth", page->num_lines - 1, - // lastLine->maxLineWidth); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxWordMin", page->num_lines - 1, - // lastLine->maxWordMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, - // lastLine->maxParMax); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, - // lastLine->parMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, - // lastLine->parMax); - - lastLine->firstWord = wordIndex; - lastLine->boxAscent = lastLine->contentAscent = 0; - lastLine->boxDescent = lastLine->contentDescent = 0; - lastLine->marginDescent = 0; - lastLine->breakSpace = 0; - lastLine->leftOffset = 0; - lastLine->boxLeft = 0; - lastLine->boxRight = 0; - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, - // lastLine->boxAscent); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, - // lastLine->boxDescent); - - /* update values in line */ - lastLine->maxLineWidth = misc::max (lastLine->maxLineWidth, lastLineWidth); - - if (lines->size () > 1) - lastLineWidth = 0; - else - lastLineWidth = line1OffsetEff; - - if (newPar) { - lastLine->maxParMax = misc::max (lastLine->maxParMax, lastLineParMax); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.maxParMax", page->num_lines - 1, - // lastLine->maxParMax); - - /* The following code looks questionable (especially since the values - * will be overwritten). In any case, line1OffsetEff is probably - * supposed to go into lastLinePar*, not lastLine->par*. - */ - if (lines->size () > 1) { - lastLine->parMin = 0; - lastLine->parMax = 0; - } else { - lastLine->parMin = line1OffsetEff; - lastLine->parMax = line1OffsetEff; - } - lastLineParMin = 0; - lastLineParMax = 0; - - //DBG_OBJ_SET_NUM(page, "lastLineParMin", page->lastLineParMin); - //DBG_OBJ_SET_NUM(page, "lastLineParMax", page->lastLineParMax); - } - - lastLine->parMin = lastLineParMin; - lastLine->parMax = lastLineParMax; - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMin", page->num_lines - 1, - // lastLine->parMin); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.parMax", page->num_lines - 1, - // lastLine->parMax); - - //DBG_MSG_END (page); - return lastLine; -} - -/* - * This method is called in two cases: (i) when a word is added - * (ii) when a page has to be (partially) rewrapped. It does word wrap, - * and adds new lines if necessary. - */ -void Textblock::wordWrap(int wordIndex) -{ - Line *lastLine; - Word *word; - int availWidth, lastSpace, leftOffset, len; - bool newLine = false, newPar = false; - core::Extremes wordExtremes; - - //DBG_MSGF (page, "wrap", 0, "Dw_page_real_word_wrap (%d): %s, width = %d", - // word_ind, a_Dw_content_html (&page->words[word_ind].content), - // page->words[word_ind].size.width); - //DBG_MSG_START (page); - - availWidth = this->availWidth - getStyle()->boxDiffWidth() - innerPadding; - if (limitTextWidth && - layout->getUsesViewport () && - availWidth > layout->getWidthViewport () - 10) - availWidth = layout->getWidthViewport () - 10; - - word = words->getRef (wordIndex); - word->effSpace = word->origSpace; - - /* Test whether line1Offset can be used. */ - if (wordIndex == 0) { - if (ignoreLine1OffsetSometimes && - line1Offset + word->size.width > availWidth) { - line1OffsetEff = 0; - } else { - int indent = 0; - - if (word->content.type == core::Content::WIDGET && - word->content.widget->blockLevel() == true) { - /* don't use text-indent when nesting blocks */ - } else { - if (core::style::isPerLength(getStyle()->textIndent)) { - indent = misc::roundInt(this->availWidth * - core::style::perLengthVal (getStyle()->textIndent)); - } else { - indent = core::style::absLengthVal (getStyle()->textIndent); - } - } - line1OffsetEff = line1Offset + indent; - } - } - - if(word->content.type == dw::core::Content::FLOAT_REF) - { - Line *line = lines->size() > 0 ? lines->getRef(lines->size() - 1) : NULL; - int y = - allocation.y - containingBox->allocation.y + getStyle()->boxOffsetY() + - (line ? line->top : 0); - int lineHeight = line ? line->boxAscent + line->boxDescent : 0; - - containingBox->handleFloatInContainer(word->content.widget, misc::max(lines->size() - 1, 0), y, lastLineWidth, lineHeight); - } - - - - if (lines->size () == 0) { - //DBG_MSG (page, "wrap", 0, "first line"); - newLine = true; - newPar = true; - lastLine = NULL; - } else { - Word *prevWord = words->getRef (wordIndex - 1); - - lastLine = lines->getRef (lines->size () - 1); - - if (prevWord->content.type == core::Content::BREAK) { - //DBG_MSG (page, "wrap", 0, "after a break"); - /* previous word is a break */ - newLine = true; - newPar = true; - } else if (word->style->whiteSpace == core::style::WHITE_SPACE_NOWRAP || - word->style->whiteSpace == core::style::WHITE_SPACE_PRE) { - //DBG_MSGF (page, "wrap", 0, "no wrap (white_space = %d)", - // word->style->white_space); - newLine = false; - newPar = false; - } else if (lastLine->firstWord != wordIndex) { - /* Does new word fit into the last line? */ - //DBG_MSGF (page, "wrap", 0, - // "word %d (%s) fits? (%d + %d + %d <= %d)...", - // word_ind, a_Dw_content_html (&word->content), - // page->lastLine_width, prevWord->orig_space, - // word->size.width, availWidth); - newLine = lastLineWidth + prevWord->origSpace + word->size.width > - availWidth - (lastLine->boxLeft + lastLine->boxRight); - //DBG_MSGF (page, "wrap", 0, "... %s.", - // newLine ? "No" : "Yes"); - } - } - - if (newLine) { - if (word->style->textAlign == core::style::TEXT_ALIGN_JUSTIFY && - lastLine != NULL && !newPar) { - justifyLine (lastLine, availWidth); - } - lastLine = addLine (wordIndex, newPar); - } - - lastLine->lastWord = wordIndex; - lastLine->boxAscent = misc::max (lastLine->boxAscent, word->size.ascent); - lastLine->boxDescent = misc::max (lastLine->boxDescent, word->size.descent); - - len = word->style->font->ascent; - if (word->style->valign == core::style::VALIGN_SUPER) - len += len / 2; - lastLine->contentAscent = misc::max (lastLine->contentAscent, len); - - len = word->style->font->descent; - if (word->style->valign == core::style::VALIGN_SUB) - len += word->style->font->ascent / 3; - lastLine->contentDescent = misc::max (lastLine->contentDescent, len); - - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.ascent", page->num_lines - 1, - // lastLine->boxAscent); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.descent", page->num_lines - 1, - // lastLine->boxDescent); - - if (word->content.type == core::Content::WIDGET) { - int collapseMarginTop = 0; - - lastLine->marginDescent = - misc::max (lastLine->marginDescent, - word->size.descent + - word->content.widget->getStyle()->margin.bottom); - - if (lines->size () == 1 && - word->content.widget->blockLevel () && - getStyle ()->borderWidth.top == 0 && - getStyle ()->padding.top == 0) { - // collapse top margin of parent element with top margin of first child - // see: http://www.w3.org/TR/CSS21/box.html#collapsing-margins - collapseMarginTop = getStyle ()->margin.top; - } - - lastLine->boxAscent = - misc::max (lastLine->boxAscent, - word->size.ascent, - word->size.ascent - + word->content.widget->getStyle()->margin.top - - collapseMarginTop); - - } else { - lastLine->marginDescent = - misc::max (lastLine->marginDescent, lastLine->boxDescent); - - if (word->content.type == core::Content::BREAK) - lastLine->breakSpace = - misc::max (word->content.breakSpace, - lastLine->marginDescent - lastLine->boxDescent, - lastLine->breakSpace); - } - - lastSpace = (wordIndex > 0) ? words->getRef(wordIndex - 1)->origSpace : 0; - - if (!newLine) - lastLineWidth += lastSpace; - if (!newPar) { - lastLineParMin += lastSpace; - lastLineParMax += lastSpace; - } - - lastLineWidth += word->size.width; - - getWordExtremes (word, &wordExtremes); - lastLineParMin += wordExtremes.maxWidth; /* Why maxWidth? */ - lastLineParMax += wordExtremes.maxWidth; - - if (word->style->whiteSpace == core::style::WHITE_SPACE_NOWRAP || - word->style->whiteSpace == core::style::WHITE_SPACE_PRE) { - lastLine->parMin += wordExtremes.minWidth + lastSpace; - /* This may also increase the accumulated minimum word width. */ - lastLine->maxWordMin = - misc::max (lastLine->maxWordMin, lastLine->parMin); - /* NOTE: Most code relies on that all values of nowrap are equal for all - * words within one line. */ - } else { - lastLine->maxWordMin = - misc::max (lastLine->maxWordMin, wordExtremes.minWidth); - } - - //DBG_OBJ_SET_NUM(page, "lastLine_par_min", page->lastLine_par_min); - //DBG_OBJ_SET_NUM(page, "lastLine_par_max", page->lastLine_par_max); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_min", page->num_lines - 1, - // lastLine->par_min); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.par_max", page->num_lines - 1, - // lastLine->par_max); - //DBG_OBJ_ARRSET_NUM (page, "lines.%d.max_word_min", page->num_lines - 1, - // lastLine->max_word_min); - - /* Align the line. - * \todo Use block's style instead once paragraphs become proper blocks. - */ - if (word->content.type != core::Content::BREAK) { - switch (word->style->textAlign) { - case core::style::TEXT_ALIGN_LEFT: - case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ - case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the - * future) */ - leftOffset = 0; - break; - case core::style::TEXT_ALIGN_RIGHT: - leftOffset = availWidth - lastLineWidth; - break; - case core::style::TEXT_ALIGN_CENTER: - leftOffset = (availWidth - lastLineWidth) / 2; - break; - default: - /* compiler happiness */ - leftOffset = 0; - } - - /* For large lines (images etc), which do not fit into the viewport: */ - if (leftOffset < 0) - leftOffset = 0; - - if (hasListitemValue && lastLine == lines->getRef (0)) { - /* List item markers are always on the left. */ - lastLine->leftOffset = 0; - words->getRef(0)->effSpace = words->getRef(0)->origSpace + leftOffset; - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", 0, - // page->words[0].eff_space); - } else { - lastLine->leftOffset = leftOffset; - } - - int y = - allocation.y - containingBox->allocation.y + - getStyle()->boxOffsetY () + lastLine->top; - lastLine->boxLeft = calcLeftFloatBorder(y, this); - lastLine->boxRight = calcRightFloatBorder(y, this); - } - mustQueueResize = true; - - //DBG_MSG_END (page); -} - /** * Calculate the size of a widget within the page. @@ -1254,92 +886,6 @@ void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) size->descent -= wstyle->margin.bottom; } -/** - * Rewrap the page from the line from which this is necessary. - * There are basically two times we'll want to do this: - * either when the viewport is resized, or when the size changes on one - * of the child widgets. - */ -void Textblock::rewrap () -{ - int i, wordIndex; - Word *word; - Line *lastLine; - - if (wrapRef == -1) - /* page does not have to be rewrapped */ - return; - - //DBG_MSGF (page, "wrap", 0, - // "Dw_page_rewrap: page->wrap_ref = %d, in page with %d word(s)", - // page->wrap_ref, page->num_words); - //DBG_MSG_START (page); - - /* All lines up from page->wrap_ref will be rebuild from the word list, - * the line list up from this position is rebuild. */ - lines->setSize (wrapRef); - lastLineWidth = 0; - //DBG_OBJ_SET_NUM(page, "num_lines", page->num_lines); - //DBG_OBJ_SET_NUM(page, "lastLine_width", page->lastLine_width); - - /* In the word list, start at the last word plus one in the line before. */ - if (wrapRef > 0) { - /* Note: In this case, Dw_page_real_word_wrap will immediately find - * the need to rewrap the line, since we start with the last one (plus - * one). This is also the reason, why page->lastLine_width is set - * to the length of the line. */ - lastLine = lines->getRef (lines->size () - 1); - - lastLineParMin = lastLine->parMin; - lastLineParMax = lastLine->parMax; - - wordIndex = lastLine->lastWord + 1; - for (i = lastLine->firstWord; i < lastLine->lastWord; i++) - lastLineWidth += (words->getRef(i)->size.width + - words->getRef(i)->origSpace); - lastLineWidth += words->getRef(lastLine->lastWord)->size.width; - } else { - lastLineParMin = 0; - lastLineParMax = 0; - - wordIndex = 0; - } - - for (; wordIndex < words->size (); wordIndex++) { - word = words->getRef (wordIndex); - - if (word->content.type == core::Content::WIDGET) - calcWidgetSize (word->content.widget, &word->size); - wordWrap (wordIndex); - - if (word->content.type == core::Content::WIDGET) { - // ABC - word->content.widget->parentRef = 1 | (dw::core::style::FLOAT_NONE << 1) | ((lines->size () - 1) << 3); - printf("parentRef = %d\n", word->content.widget->parentRef); - //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", - // word->content.widget->parent_ref); - } - - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "Assigning parent_ref = %d to rewrapped word %d, " - // "in page with %d word(s)\n", - // page->num_lines - 1, wordIndex, page->num_words); - - /* todo_refactoring: - if (word->content.type == DW_CONTENT_ANCHOR) - p_Dw_gtk_viewport_change_anchor - (widget, word->content.anchor, - Dw_page_line_total_y_offset (page, - &page->lines[page->num_lines - 1])); - */ - } - - /* Next time, the page will not have to be rewrapped. */ - wrapRef = -1; - - //DBG_MSG_END (page); -} - /* * Draw the decorations on a word. */ @@ -1366,14 +912,115 @@ void Textblock::decorateText(core::View *view, core::style::Style *style, } /* - * Draw a word of text. + * Draw a string of text */ -void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, - int xWidget, int yWidgetBase) +void Textblock::drawText(core::View *view, core::style::Style *style, + core::style::Color::Shading shading, int x, int y, + const char *text, int start, int len) +{ + if (len > 0) { + char *str = NULL; + + switch (style->textTransform) { + case core::style::TEXT_TRANSFORM_NONE: + default: + break; + case core::style::TEXT_TRANSFORM_UPPERCASE: + str = layout->textToUpper(text + start, len); + break; + case core::style::TEXT_TRANSFORM_LOWERCASE: + str = layout->textToLower(text + start, len); + break; + case core::style::TEXT_TRANSFORM_CAPITALIZE: + { + /* \bug No way to know about non-ASCII punctuation. */ + bool initial_seen = false; + + for (int i = 0; i < start; i++) + if (!ispunct(text[i])) + initial_seen = true; + if (initial_seen) + break; + + int after = 0; + text += start; + while (ispunct(text[after])) + after++; + if (text[after]) + after = layout->nextGlyph(text, after); + if (after > len) + after = len; + + char *initial = layout->textToUpper(text, after); + int newlen = strlen(initial) + len-after; + str = (char *)malloc(newlen + 1); + strcpy(str, initial); + strncpy(str + strlen(str), text+after, len-after); + str[newlen] = '\0'; + free(initial); + break; + } + } + view->drawText(style->font, style->color, shading, x, y, + str ? str : text + start, str ? strlen(str) : len); + if (str) + free(str); + } +} + +/* + * Draw a word of text. TODO New description; + */ +void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, + core::View *view, core::Rectangle *area, + int xWidget, int yWidgetBase) +{ + core::style::Style *style = words->getRef(wordIndex1)->style; + bool drawHyphen = wordIndex2 == line->lastWord + && words->getRef(wordIndex2)->hyphenWidth > 0; + + if (wordIndex1 == wordIndex2 && !drawHyphen) { + // Simple case, where copying in one buffer is not needed. + Word *word = words->getRef (wordIndex1); + drawWord0 (wordIndex1, wordIndex2, word->content.text, word->size.width, + style, view, area, xWidget, yWidgetBase); + } else { + // Concaternate all words in a new buffer. + int l = 0, totalWidth = 0; + for (int i = wordIndex1; i <= wordIndex2; i++) { + Word *w = words->getRef (i); + l += strlen (w->content.text); + totalWidth += w->size.width; + } + + char text[l + (drawHyphen ? 2 : 0) + 1]; + int p = 0; + for (int i = wordIndex1; i <= wordIndex2; i++) { + const char * t = words->getRef(i)->content.text; + strcpy (text + p, t); + p += strlen (t); + } + + if(drawHyphen) { + text[p++] = 0xc2; + text[p++] = 0xad; + text[p++] = 0; + } + + drawWord0 (wordIndex1, wordIndex2, text, totalWidth, + style, view, area, xWidget, yWidgetBase); + } +} + +/** + * TODO Comment + */ +void Textblock::drawWord0 (int wordIndex1, int wordIndex2, + const char *text, int totalWidth, + core::style::Style *style, core::View *view, + core::Rectangle *area, int xWidget, int yWidgetBase) { - Word *word = words->getRef(wordIndex); int xWorld = allocation.x + xWidget; - core::style::Style *style = word->style; int yWorldBase; /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */ @@ -1384,38 +1031,55 @@ void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, } yWorldBase = yWidgetBase + allocation.y; - view->drawText (style->font, style->color, - core::style::Color::SHADING_NORMAL, xWorld, yWorldBase, - word->content.text, strlen (word->content.text)); + drawText (view, style, core::style::Color::SHADING_NORMAL, xWorld, + yWorldBase, text, 0, strlen (text)); if (style->textDecoration) decorateText(view, style, core::style::Color::SHADING_NORMAL, xWorld, - yWorldBase, word->size.width); + yWorldBase, totalWidth); for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) { - if (hlStart[layer].index <= wordIndex && - hlEnd[layer].index >= wordIndex) { - const int wordLen = strlen (word->content.text); + if (wordIndex1 <= hlEnd[layer].index && + wordIndex2 >= hlStart[layer].index) { + const int wordLen = strlen (text); int xStart, width; - int firstCharIdx = 0; - int lastCharIdx = wordLen; - - if (wordIndex == hlStart[layer].index) - firstCharIdx = misc::min (hlStart[layer].nChar, wordLen); + int firstCharIdx; + int lastCharIdx; + + if (hlStart[layer].index < wordIndex1) + firstCharIdx = 0; + else { + firstCharIdx = + misc::min (hlStart[layer].nChar, + (int)strlen (words->getRef(hlStart[layer].index) + ->content.text)); + for (int i = wordIndex1; i < hlStart[layer].index; i++) + // It can be assumed that all words from wordIndex1 to + // wordIndex2 have content type TEXT. + firstCharIdx += strlen (words->getRef(i)->content.text); + } - if (wordIndex == hlEnd[layer].index) - lastCharIdx = misc::min (hlEnd[layer].nChar, wordLen); + if (hlEnd[layer].index > wordIndex2) + lastCharIdx = wordLen; + else { + lastCharIdx = + misc::min (hlEnd[layer].nChar, + (int)strlen (words->getRef(hlEnd[layer].index) + ->content.text)); + for (int i = wordIndex1; i < hlEnd[layer].index; i++) + // It can be assumed that all words from wordIndex1 to + // wordIndex2 have content type TEXT. + lastCharIdx += strlen (words->getRef(i)->content.text); + } xStart = xWorld; if (firstCharIdx) - xStart += layout->textWidth (style->font, word->content.text, - firstCharIdx); + xStart += textWidth (text, 0, firstCharIdx, style); if (firstCharIdx == 0 && lastCharIdx == wordLen) - width = word->size.width; + width = totalWidth; else - width = layout->textWidth (style->font, - word->content.text + firstCharIdx, - lastCharIdx - firstCharIdx); + width = textWidth (text, firstCharIdx, + lastCharIdx - firstCharIdx, style); if (width > 0) { /* Highlight text */ core::style::Color *wordBgColor; @@ -1430,10 +1094,9 @@ void Textblock::drawText(int wordIndex, core::View *view,core::Rectangle *area, style->font->ascent + style->font->descent); /* Highlight the text. */ - view->drawText (style->font, style->color, - core::style::Color::SHADING_INVERSE, xStart, - yWorldBase, word->content.text + firstCharIdx, - lastCharIdx - firstCharIdx); + drawText (view, style, core::style::Color::SHADING_INVERSE, xStart, + yWorldBase, text, firstCharIdx, + lastCharIdx - firstCharIdx); if (style->textDecoration) decorateText(view, style, core::style::Color::SHADING_INVERSE, @@ -1501,18 +1164,11 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) int xWidget = lineXOffsetWidget(line); int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; - /* Here's an idea on how to optimize this routine to minimize the number - * of drawing calls: - * - * Copy the text from the words into a buffer, adding a new word - * only if: the attributes match, and the spacing is either zero or - * equal to the width of ' '. In the latter case, copy a " " into - * the buffer. Then draw the buffer. */ - for (int wordIndex = line->firstWord; wordIndex <= line->lastWord && xWidget < area->x + area->width; wordIndex++) { Word *word = words->getRef(wordIndex); + int wordSize = word->size.width; if (xWidget + word->size.width + word->effSpace >= area->x) { if (word->content.type == core::Content::TEXT || @@ -1526,12 +1182,26 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (child->intersects (area, &childArea)) child->draw (view, &childArea); } else { + /* TODO: include in drawWord: if (word->style->hasBackground ()) { drawBox (view, word->style, area, xWidget, yWidgetBase - line->boxAscent, word->size.width, line->boxAscent + line->boxDescent, false); - } - drawText(wordIndex, view, area, xWidget, yWidgetBase); + }*/ + int wordIndex2 = wordIndex; + while (wordIndex2 < line->lastWord && + words->getRef(wordIndex2)->hyphenWidth > 0 && + word->style == words->getRef(wordIndex2 + 1)->style) + wordIndex2++; + + drawWord(line, wordIndex, wordIndex2, view, area, + xWidget, yWidgetBase); + wordSize = 0; + for (int i = wordIndex; i <= wordIndex2; i++) + wordSize += words->getRef(i)->size.width; + + wordIndex = wordIndex2; + word = words->getRef(wordIndex); } } if (word->effSpace > 0 && wordIndex < line->lastWord && @@ -1539,16 +1209,16 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) core::Content::BREAK) { if (word->spaceStyle->hasBackground ()) drawBox (view, word->spaceStyle, area, - xWidget + word->size.width, + xWidget + wordSize, yWidgetBase - line->boxAscent, word->effSpace, line->boxAscent + line->boxDescent, false); - drawSpace(wordIndex, view, area, xWidget + word->size.width, + drawSpace(wordIndex, view, area, xWidget + wordSize, yWidgetBase); } } } - xWidget += word->size.width + word->effSpace; + xWidget += wordSize + word->effSpace; } } @@ -1592,6 +1262,7 @@ int Textblock::findLineOfWord (int wordIndex) { int high = lines->size () - 1, index, low = 0; + // TODO regard also not-yet-existing lines? if (wordIndex < 0 || wordIndex >= words->size ()) return -1; @@ -1631,11 +1302,22 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) word = words->getRef (wordIndex); lastXCursor = xCursor; xCursor += word->size.width + word->effSpace; - if (lastXCursor <= x && xCursor > x && - y > yWidgetBase - word->size.ascent && - y <= yWidgetBase + word->size.descent) { - *inSpace = x >= xCursor - word->effSpace; - return word; + if (lastXCursor <= x && xCursor > x) { + if (x >= xCursor - word->effSpace) { + if (wordIndex < line->lastWord && + (words->getRef(wordIndex + 1)->content.type != + core::Content::BREAK) && + y > yWidgetBase - word->spaceStyle->font->ascent && + y <= yWidgetBase + word->spaceStyle->font->descent) { + *inSpace = true; + return word; + } + } else { + if (y > yWidgetBase - word->size.ascent && + y <= yWidgetBase + word->size.descent) + return word; + } + break; } } @@ -1644,6 +1326,9 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) void Textblock::draw (core::View *view, core::Rectangle *area) { + PRINTF ("DRAW: %d, %d, %d x %d\n", + area->x, area->y, area->width, area->height); + int lineIndex; Line *line; @@ -1669,39 +1354,89 @@ void Textblock::draw (core::View *view, core::Rectangle *area) * Add a new word (text, widget etc.) to a page. */ Textblock::Word *Textblock::addWord (int width, int ascent, int descent, + bool canBeHyphenated, core::style::Style *style) { - Word *word; - words->increase (); + Word *word = words->getLastRef (); + fillWord (word, width, ascent, descent, canBeHyphenated, style); + return word; +} - word = words->getRef (words->size() - 1); +void Textblock::fillWord (Word *word, int width, int ascent, int descent, + bool canBeHyphenated, core::style::Style *style) +{ word->size.width = width; word->size.ascent = ascent; word->size.descent = descent; - word->origSpace = 0; - word->effSpace = 0; + word->origSpace = word->effSpace = word->stretchability = + word->shrinkability = 0; + word->hyphenWidth = 0; + word->badnessAndPenalty.setPenaltyProhibitBreak (); word->content.space = false; - - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.width", page->num_words - 1, - // word->size.width); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.descent", page->num_words - 1, - // word->size.descent); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.size.ascent", page->num_words - 1, - // word->size.ascent); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", page->num_words - 1, - // word->orig_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", page->num_words - 1, - // word->eff_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", page->num_words - 1, - // word->content.space); + word->canBeHyphenated = canBeHyphenated; word->style = style; word->spaceStyle = style; style->ref (); style->ref (); +} - return word; +/* + * Get the width of a string of text. + */ +int Textblock::textWidth(const char *text, int start, int len, + core::style::Style *style) +{ + int ret = 0; + + if (len > 0) { + char *str = NULL; + + switch (style->textTransform) { + case core::style::TEXT_TRANSFORM_NONE: + default: + ret = layout->textWidth(style->font, text+start, len); + break; + case core::style::TEXT_TRANSFORM_UPPERCASE: + str = layout->textToUpper(text+start, len); + ret = layout->textWidth(style->font, str, strlen(str)); + break; + case core::style::TEXT_TRANSFORM_LOWERCASE: + str = layout->textToLower(text+start, len); + ret = layout->textWidth(style->font, str, strlen(str)); + break; + case core::style::TEXT_TRANSFORM_CAPITALIZE: + { + /* \bug No way to know about non-ASCII punctuation. */ + bool initial_seen = false; + + for (int i = 0; i < start; i++) + if (!ispunct(text[i])) + initial_seen = true; + if (initial_seen) { + ret = layout->textWidth(style->font, text+start, len); + } else { + int after = 0; + + text += start; + while (ispunct(text[after])) + after++; + if (text[after]) + after = layout->nextGlyph(text, after); + if (after > len) + after = len; + str = layout->textToUpper(text, after); + ret = layout->textWidth(style->font, str, strlen(str)) + + layout->textWidth(style->font, text+after, len-after); + } + break; + } + } + if (str) + free(str); + } + return ret; } /** @@ -1711,7 +1446,7 @@ void Textblock::calcTextSize (const char *text, size_t len, core::style::Style *style, core::Requisition *size) { - size->width = layout->textWidth (style->font, text, len); + size->width = textWidth (text, 0, len, style); size->ascent = style->font->ascent; size->descent = style->font->descent; @@ -1755,25 +1490,129 @@ void Textblock::calcTextSize (const char *text, size_t len, size->ascent += (style->font->ascent / 2); } - /** - * Add a word to the page structure. + * Add a word to the page structure. If it contains soft hyphens, it is + * divided. */ void Textblock::addText (const char *text, size_t len, core::style::Style *style) { - Word *word; - core::Requisition size; + PRINTF ("[%p] ADD_TEXT (%d characters)\n", this, (int)len); + + // Count hyphens. + int numHyphens = 0; + for (int i = 0; i < (int)len - 1; i++) + // (0xc2, 0xad) is the UTF-8 representation of a soft hyphen (Unicode + // 0xc2). + if((unsigned char)text[i] == 0xc2 && (unsigned char)text[i + 1] == 0xad) + numHyphens++; + + if (numHyphens == 0) { + // Simple (and often) case: no soft hyphens. May still be hyphenated + // automatically. + core::Requisition size; + calcTextSize (text, len, style, &size); + addText0 (text, len, true, style, &size); + } else { + PRINTF("HYPHENATION: '"); + for (size_t i = 0; i < len; i++) + PUTCHAR(text[i]); + PRINTF("', with %d hyphen(s)\n", numHyphens); + + // Store hyphen positions. + int n = 0, hyphenPos[numHyphens], breakPos[numHyphens]; + for (size_t i = 0; i < len - 1; i++) + if((unsigned char)text[i] == 0xc2 && + (unsigned char)text[i + 1] == 0xad) { + hyphenPos[n] = i; + breakPos[n] = i - 2 * n; + n++; + } + + // Get text without hyphens. (There are numHyphens + 1 parts in the word, + // and 2 * numHyphens bytes less, 2 for each hyphen, are needed.) + char textWithoutHyphens[len - 2 * numHyphens]; + int start = 0; // related to "text" + for (int i = 0; i < numHyphens + 1; i++) { + int end = (i == numHyphens) ? len : hyphenPos[i]; + memmove (textWithoutHyphens + start - 2 * i, text + start, + end - start); + start = end + 2; + } - calcTextSize (text, len, style, &size); - word = addWord (size.width, size.ascent, size.descent, style); + PRINTF("H... without hyphens: '"); + for (size_t i = 0; i < len - 2 * numHyphens; i++) + PUTCHAR(textWithoutHyphens[i]); + PRINTF("'\n"); + + core::Requisition wordSize[numHyphens + 1]; + calcTextSizes (textWithoutHyphens, len - 2 * numHyphens, style, + numHyphens, breakPos, wordSize); + + // Finished! + for (int i = 0; i < numHyphens + 1; i++) { + int start = (i == 0) ? 0 : hyphenPos[i - 1] + 2; + int end = (i == numHyphens) ? len : hyphenPos[i]; + // Do not anymore hyphen automatically. + addText0 (text + start, end - start, false, style, &wordSize[i]); + + PRINTF("H... [%d] '", i); + for (int j = start; j < end; j++) + PUTCHAR(text[j]); + PRINTF("' added\n"); + + if(i < numHyphens) { + addHyphen (); + PRINTF("H... yphen added\n"); + } + } + } +} + +void Textblock::calcTextSizes (const char *text, size_t textLen, + core::style::Style *style, + int numBreaks, int *breakPos, + core::Requisition *wordSize) +{ + // The size of the last part is calculated in a simple way. + int lastStart = breakPos[numBreaks - 1]; + calcTextSize (text + lastStart, textLen - lastStart, style, + &wordSize[numBreaks]); + + PRINTF("H... [%d] '", numBreaks); + for (size_t i = 0; i < textLen - lastStart; i++) + PUTCHAR(text[i + lastStart]); + PRINTF("' -> %d\n", wordSize[numBreaks].width); + + // The rest is more complicated. TODO Documentation. + for (int i = numBreaks - 1; i >= 0; i--) { + int start = (i == 0) ? 0 : breakPos[i - 1]; + calcTextSize (text + start, textLen - start, style, &wordSize[i]); + + PRINTF("H... [%d] '", i); + for (size_t j = 0; j < textLen - start; j++) + PUTCHAR(text[j + start]); + PRINTF("' -> %d\n", wordSize[i].width); + + for (int j = i + 1; j < numBreaks + 1; j++) { + wordSize[i].width -= wordSize[j].width; + PRINTF("H... - %d = %d\n", wordSize[j].width, wordSize[i].width); + } + } +} + +/** + * Add a word (without hyphens) to the page structure. + */ +void Textblock::addText0 (const char *text, size_t len, bool canBeHyphenated, + core::style::Style *style, core::Requisition *size) +{ + Word *word = addWord (size->width, size->ascent, size->descent, + canBeHyphenated, style); word->content.type = core::Content::TEXT; word->content.text = layout->textZone->strndup(text, len); - //DBG_OBJ_ARRSET_STR (page, "words.%d.content.text", page->num_words - 1, - // word->content.text); - - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /** @@ -1790,19 +1629,21 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) * end of this function, the correct value is assigned. */ widget->parentRef = -1; + PRINTF ("%p becomes child of %p\n", widget, this); + widget->setParent (this); widget->setStyle (style); calcWidgetSize (widget, &size); - word = addWord (size.width, size.ascent, size.descent, style); + word = addWord (size.width, size.ascent, size.descent, false, style); word->content.type = core::Content::WIDGET; word->content.widget = widget; - //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", page->num_words - 1, + //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1, // word->content.widget); - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); // ABC word->content.widget->parentRef = 1 | (dw::core::style::FLOAT_NONE << 1) | ((lines->size () - 1) << 3); printf("parentRef = %d\n", word->content.widget->parentRef); @@ -1812,10 +1653,9 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) //DEBUG_MSG(DEBUG_REWRAP_LEVEL, // "Assigning parent_ref = %d to added word %d, " // "in page with %d word(s)\n", - // page->num_lines - 1, page->num_words - 1, page->num_words); + // lines->size () - 1, words->size() - 1, words->size()); } - /** * Add an anchor to the page. "name" is copied, so no strdup is necessary for * the caller. @@ -1863,29 +1703,84 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) void Textblock::addSpace (core::style::Style *style) { int wordIndex = words->size () - 1; + if (wordIndex >= 0) { + fillSpace (words->getRef(wordIndex), style); + accumulateWordData (wordIndex); + } +} + +void Textblock::fillSpace (Word *word, core::style::Style *style) +{ + // Old comment: + // + // According to + // http://www.w3.org/TR/CSS2/text.html#white-space-model: "line + // breaking opportunities are determined based on the text + // prior to the white space collapsing steps". + // + // So we call addBreakOption () for each Textblock::addSpace () + // call. This is important e.g. to be able to break between + // foo and bar in: <span style="white-space:nowrap">foo </span> + // bar + // + // TODO: Re-evaluate again. + + // TODO: This line does not work: addBreakOption (word, style); + + if (!word->content.space) { + // Do not override a previously set break penalty. + if (!word->badnessAndPenalty.lineMustBeBroken()) { + switch (style->whiteSpace) { + case core::style::WHITE_SPACE_NORMAL: + case core::style::WHITE_SPACE_PRE_LINE: + word->badnessAndPenalty.setPenalty (0); + break; + + case core::style::WHITE_SPACE_PRE: + case core::style::WHITE_SPACE_NOWRAP: + case core::style::WHITE_SPACE_PRE_WRAP: + word->badnessAndPenalty.setPenaltyProhibitBreak (); + break; + } + } + + word->content.space = true; + word->effSpace = word->origSpace = style->font->spaceWidth + + style->wordSpacing; + word->stretchability = word->origSpace / 2; + if(style->textAlign == core::style::TEXT_ALIGN_JUSTIFY) + word->shrinkability = word->origSpace / 3; + else + word->shrinkability = 0; + + //DBG_OBJ_ARRSET_NUM (this, "words.%d.origSpace", wordIndex, + // word->origSpace); + //DBG_OBJ_ARRSET_NUM (this, "words.%d.effSpace", wordIndex, + // word->effSpace); + //DBG_OBJ_ARRSET_NUM (this, "words.%d.content.space", wordIndex, + // word->content.space); + + word->spaceStyle->unref (); + word->spaceStyle = style; + style->ref (); + } +} + +void Textblock::addHyphen () +{ + int wordIndex = words->size () - 1; if (wordIndex >= 0) { Word *word = words->getRef(wordIndex); + + word->badnessAndPenalty.setPenalty (HYPHEN_BREAK); + // TODO Optimize? Like spaces? + word->hyphenWidth = layout->textWidth (word->style->font, "\xc2\xad", 2); - if (!word->content.space) { - word->content.space = true; - word->effSpace = word->origSpace = style->font->spaceWidth + - style->wordSpacing; - - //DBG_OBJ_ARRSET_NUM (page, "words.%d.orig_space", nw, - // page->words[nw].orig_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.eff_space", nw, - // page->words[nw].eff_space); - //DBG_OBJ_ARRSET_NUM (page, "words.%d.content.space", nw, - // page->words[nw].content.space); - word->spaceStyle->unref (); - word->spaceStyle = style; - style->ref (); - } + accumulateWordData (wordIndex); } } - /** * Cause a paragraph break */ @@ -1959,10 +1854,11 @@ void Textblock::addParbreak (int space, core::style::Style *style) return; } - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::BREAK; + word->badnessAndPenalty.setPenaltyForceBreak (); word->content.breakSpace = space; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /* @@ -1976,14 +1872,16 @@ void Textblock::addLinebreak (core::style::Style *style) words->getRef(words->size () - 1)->content.type == core::Content::BREAK) // An <BR> in an empty line gets the height of the current font // (why would someone else place it here?), ... - word = addWord (0, style->font->ascent, style->font->descent, style); + word = + addWord (0, style->font->ascent, style->font->descent, false, style); else // ... otherwise, it has no size (and does not enlarge the line). - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::BREAK; + word->badnessAndPenalty.setPenaltyForceBreak (); word->content.breakSpace = 0; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } /** \todo This MUST be commented! */ @@ -1994,12 +1892,12 @@ void Textblock::addFloatIntoGenerator (core::Widget *widget, core::style::Style widget->setStyle (style); containingBox->addFloatIntoContainer(widget, this); - word = addWord (0, 0, 0, style); + word = addWord (0, 0, 0, false, style); word->content.type = core::Content::FLOAT_REF; word->content.breakSpace = 0; word->content.widget = widget; word->style = style; - wordWrap (words->size () - 1); + wordWrap (words->size () - 1, false); } @@ -2065,9 +1963,9 @@ void Textblock::handOverBreak (core::style::Style *style) } /* - * Any words added by a_Dw_page_add_... are not immediately (queued to + * Any words added by addWord() are not immediately (queued to * be) drawn, instead, this function must be called. This saves some - * calls to p_Dw_widget_queue_resize. + * calls to queueResize(). * */ void Textblock::flush () @@ -2077,9 +1975,11 @@ void Textblock::flush () // printf("queueResize(%s, -1, true)\n", asap ? "true" : "false"); // queueResize (asap, -1, true); //======= + PRINTF ("[%p] FLUSH => %s (parentRef = %d)\n", + this, mustQueueResize ? "true" : "false", parentRef); + if (mustQueueResize) { queueResize (-1, true); -//>>>>>>> 1.24 mustQueueResize = false; } } @@ -2522,9 +2422,8 @@ void Textblock::TextblockIterator::getAllocation (int start, int end, allocation->x += w->size.width + w->effSpace; } if (start > 0 && word->content.type == core::Content::TEXT) { - allocation->x += textblock->layout->textWidth (word->style->font, - word->content.text, - start); + allocation->x += textblock->textWidth (word->content.text, 0, start, + word->style); } allocation->y = textblock->lineYOffsetCanvas (line) + line->boxAscent - word->size.ascent; @@ -2536,9 +2435,8 @@ void Textblock::TextblockIterator::getAllocation (int start, int end, if (start > 0 || end < wordEnd) { end = misc::min(end, wordEnd); /* end could be INT_MAX */ allocation->width = - textblock->layout->textWidth (word->style->font, - word->content.text + start, - end - start); + textblock->textWidth (word->content.text, start, end - start, + word->style); } } allocation->ascent = word->size.ascent; |