diff options
Diffstat (limited to 'dw/textblock_linebreaking.cc')
-rw-r--r-- | dw/textblock_linebreaking.cc | 427 |
1 files changed, 293 insertions, 134 deletions
diff --git a/dw/textblock_linebreaking.cc b/dw/textblock_linebreaking.cc index 2375c988..9cbe6814 100644 --- a/dw/textblock_linebreaking.cc +++ b/dw/textblock_linebreaking.cc @@ -210,21 +210,7 @@ void Textblock::BadnessAndPenalty::print () void Textblock::printWordShort (Word *word) { - switch(word->content.type) { - case core::Content::TEXT: - printf ("\"%s\"", word->content.text); - break; - case core::Content::WIDGET: - printf ("<widget: %p (%s)>", - word->content.widget, word->content.widget->getClassName()); - break; - case core::Content::BREAK: - printf ("<break>"); - break; - default: - printf ("<?>"); - break; - } + core::Content::print (&(word->content)); } void Textblock::printWordFlags (short flags) @@ -317,6 +303,12 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, PRINTF ("[%p] ADD_LINE (%d, %d) => %d\n", this, firstWord, lastWord, lines->size ()); + //for (int i = firstWord; i <= lastWord; i++) { + // printf (" word %d: ", i); + // printWord (words->getRef (i)); + // printf ("\n"); + //} + Word *lastWordOfLine = words->getRef(lastWord); // Word::totalWidth includes the hyphen (which is what we want here). int lineWidth = lastWordOfLine->totalWidth; @@ -376,6 +368,26 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, for(int i = line->firstWord; i <= line->lastWord; i++) accumulateWordForLine (lineIndex, i); + // Especially empty lines (possible when there are floats) have + // zero height, which may cause endless loops. For this reasons, + // the height should be positive. + line->boxAscent = misc::max (line->boxAscent, 1); + + // Calculate offsetCompleteWidget, which includes also floats. + int leftBorder; + if (containingBlock->outOfFlowMgr && mustBorderBeRegarded (line)) { + int y = line->top + getStyle()->boxOffsetY(); + int h = line->boxAscent + line->boxDescent; + leftBorder = containingBlock->outOfFlowMgr->getLeftBorder (this, y, h); + } else + leftBorder = 0; + + line->offsetCompleteWidget = + misc::max (leftBorder, + getStyle()->boxOffsetX() + innerPadding + + (lineIndex == 0 ? line1OffsetEff : 0)) + + line->leftOffset; + PRINTF (" line[%d].top = %d\n", lines->size () - 1, line->top); PRINTF (" line[%d].boxAscent = %d\n", lines->size () - 1, line->boxAscent); PRINTF (" line[%d].boxDescent = %d\n", @@ -384,9 +396,10 @@ Textblock::Line *Textblock::addLine (int firstWord, int lastWord, line->contentAscent); PRINTF (" line[%d].contentDescent = %d\n", lines->size () - 1, line->contentDescent); - PRINTF (" line[%d].maxLineWidth = %d\n", lines->size () - 1, line->maxLineWidth); + PRINTF (" line[%d].offsetCompleteWidget = %d\n", + lines->size () - 1, line->offsetCompleteWidget); mustQueueResize = true; @@ -454,14 +467,12 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) PRINTF ("[%p] WORD_WRAP (%d, %s)\n", this, wordIndex, wrapAll ? "true" : "false"); - Word *word; - if (!wrapAll) removeTemporaryLines (); initLine1Offset (wordIndex); - word = words->getRef (wordIndex); + Word *word = words->getRef (wordIndex); word->effSpace = word->origSpace; accumulateWordData (wordIndex); @@ -470,16 +481,56 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) //printWord (word); //printf ("\n"); + if (word->content.type == core::Content::WIDGET_OOF_REF) + wrapWidgetOofRef (wordIndex); + int penaltyIndex = calcPenaltyIndexForNewLine (); bool newLine; do { + // This variable is set to true, if, due to floats, this line is + // smaller than following lines will be (and, at the end, there + // will be surely lines without floats). If this is the case, lines + // may, in an extreme case, be left empty. + + // (In other cases, lines are never left empty, even if this means + // that the contents is wider than the available witdh. Leaving + // lines empty does not make sense without floats, since there will + // be no possibility with more space anymore.) + + bool thereWillBeMoreSpace; + if (containingBlock->outOfFlowMgr == NULL || + !mustBorderBeRegarded (lines->size ())) { + thereWillBeMoreSpace = false; + PRINTF (" thereWillBeMoreSpace = false (no OOFM or ...)\n"); + } else { + int y = + topOfPossiblyMissingLine (lines->size ()); + int h = heightOfPossiblyMissingLine (lines->size ()); + + // A previous version checked only the borders, not directly, + // whether there are floats. The distinction is rather + // disputable. (More on this later.) + + thereWillBeMoreSpace = + containingBlock->outOfFlowMgr->hasFloatLeft (this, y, h) || + containingBlock->outOfFlowMgr->hasFloatRight (this, y, h); + + PRINTF (" thereWillBeMoreSpace = %s (y = %d, h = %d)\n", + thereWillBeMoreSpace ? "true" : "false", y, h); + } + bool tempNewLine = false; int firstIndex = lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; int searchUntil; - if (wrapAll && wordIndex >= firstIndex && wordIndex == words->size() -1) { + if (wordIndex < firstIndex) + // Current word is already part of a line (ending with + // firstIndex - 1), so no new line has to be added. + newLine = false; + else if (wrapAll && wordIndex >= firstIndex && + wordIndex == words->size() -1) { newLine = true; searchUntil = wordIndex; tempNewLine = true; @@ -495,13 +546,21 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) // Break the line when too tight, but only when there is a // possible break point so far. (TODO: I've forgotten the // original bug which is fixed by this.) + + // Exception of the latter rule: thereWillBeMoreSpace; see + // above, where it is defined. + bool possibleLineBreak = false; - for (int i = firstIndex; !possibleLineBreak && i <= wordIndex - 1; i++) + for (int i = firstIndex; + !(thereWillBeMoreSpace || possibleLineBreak) + && i <= wordIndex - 1; + i++) if (words->getRef(i)->badnessAndPenalty .lineCanBeBroken (penaltyIndex)) possibleLineBreak = true; - if (possibleLineBreak && word->badnessAndPenalty.lineTooTight ()) { + if ((thereWillBeMoreSpace || possibleLineBreak) + && word->badnessAndPenalty.lineTooTight ()) { newLine = true; searchUntil = wordIndex - 1; PRINTF (" NEW LINE: line too tight\n"); @@ -518,118 +577,68 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) // newLine is calculated as "true". mustQueueResize = true; + PRINTF ("[%p] special case? newLine = %s, wrapAll = %s => " + "mustQueueResize = %s\n", this, newLine ? "true" : "false", + wrapAll ? "true" : "false", mustQueueResize ? "true" : "false"); + if(newLine) { accumulateWordData (wordIndex); int wordIndexEnd = wordIndex; bool lineAdded; do { - PRINTF (" searching from %d to %d\n", firstIndex, searchUntil); - - int breakPos = -1; - for (int i = firstIndex; i <= searchUntil; i++) { - Word *w = words->getRef(i); - - //printf (" %d (of %d): ", i, words->size ()); - //printWord (w); - //printf ("\n"); - - if (breakPos == -1 || - w->badnessAndPenalty.compareTo - (penaltyIndex, - &words->getRef(breakPos)->badnessAndPenalty) <= 0) - // "<=" instead of "<" in the next lines tends to result in - // more words per line -- theoretically. Practically, the - // case "==" will never occur. - breakPos = i; - } - - PRINTF (" breakPos = %d\n", breakPos); - - if (wrapAll && searchUntil == words->size () - 1) { - // Since no break and no space is added, the last word - // will have a penalty of inf. Actually, it should be - // less, since it is the last word. However, since more - // words may follow, the penalty is not changesd, but - // here, the search is corrected (maybe only - // temporary). - - // (Notice that it was once (temporally) set to -inf, - // not 0, but this will make e.g. test/table-1.html not - // work.) - Word *lastWord = words->getRef (searchUntil); - BadnessAndPenalty correctedBap = lastWord->badnessAndPenalty; - correctedBap.setPenalty (0); - if (correctedBap.compareTo - (penaltyIndex, - &words->getRef(breakPos)->badnessAndPenalty) <= 0) { - breakPos = searchUntil; - PRINTF (" corrected: breakPos = %d\n", breakPos); - } - } - - int hyphenatedWord = -1; - Word *word1 = words->getRef(breakPos); - PRINTF ("[%p] line (broken at word %d): ", this, breakPos); - //word1->badnessAndPenalty.print (); - PRINTF ("\n"); - - if (word1->badnessAndPenalty.lineTight () && - (word1->flags & Word::CAN_BE_HYPHENATED) && - word1->style->x_lang[0] && - word1->content.type == core::Content::TEXT && - Hyphenator::isHyphenationCandidate (word1->content.text)) - hyphenatedWord = breakPos; - - if (word1->badnessAndPenalty.lineLoose () && - breakPos + 1 < words->size ()) { - Word *word2 = words->getRef(breakPos + 1); - if ((word2->flags & Word::CAN_BE_HYPHENATED) && - word2->style->x_lang[0] && - word2->content.type == core::Content::TEXT && - Hyphenator::isHyphenationCandidate (word2->content.text)) - hyphenatedWord = breakPos + 1; - } - - PRINTF ("[%p] breakPos = %d, hyphenatedWord = %d\n", - this, breakPos, hyphenatedWord); - - if(hyphenatedWord == -1) { - addLine (firstIndex, breakPos, tempNewLine); - PRINTF ("[%p] new line %d (%s), from %d to %d\n", - this, lines->size() - 1, - tempNewLine ? "temporally" : "permanently", - firstIndex, breakPos); + if (firstIndex > searchUntil) { + // empty line + assert (searchUntil == firstIndex - 1); + addLine (firstIndex, firstIndex - 1, tempNewLine); lineAdded = true; penaltyIndex = calcPenaltyIndexForNewLine (); } else { - // TODO hyphenateWord() should return whether something has - // changed at all. So that a second run, with - // !word->canBeHyphenated, is unnecessary. - // TODO Update: for this, searchUntil == 0 should be checked. - PRINTF ("[%p] old searchUntil = %d ...\n", this, searchUntil); - int n = hyphenateWord (hyphenatedWord); - searchUntil += n; - if (hyphenatedWord <= wordIndex) - wordIndexEnd += n; - PRINTF ("[%p] -> new searchUntil = %d ...\n", this, searchUntil); - lineAdded = false; + int breakPos = + searchMinBap (firstIndex, searchUntil, penaltyIndex, wrapAll); + int hyphenatedWord = considerHyphenation (breakPos); + + PRINTF ("[%p] breakPos = %d, hyphenatedWord = %d\n", + this, breakPos, hyphenatedWord); - // update word pointer as hyphenateWord() can trigger a - // reorganization of the words structure - word = words->getRef (wordIndex); + if(hyphenatedWord == -1) { + addLine (firstIndex, breakPos, tempNewLine); + PRINTF ("[%p] new line %d (%s), from %d to %d\n", + this, lines->size() - 1, + tempNewLine ? "temporally" : "permanently", + firstIndex, breakPos); + lineAdded = true; + penaltyIndex = calcPenaltyIndexForNewLine (); + } else { + // TODO hyphenateWord() should return whether + // something has changed at all. So that a second + // run, with !word->canBeHyphenated, is unnecessary. + // TODO Update: for this, searchUntil == 0 should be + // checked. + PRINTF ("[%p] old searchUntil = %d ...\n", this, searchUntil); + int n = hyphenateWord (hyphenatedWord); + searchUntil += n; + if (hyphenatedWord <= wordIndex) + wordIndexEnd += n; + PRINTF ("[%p] -> new searchUntil = %d ...\n", + this, searchUntil); + lineAdded = false; + + // update word pointer as hyphenateWord() can trigger a + // reorganization of the words structure + word = words->getRef (wordIndex); + } + + PRINTF ("[%p] accumulating again from %d to %d\n", + this, breakPos + 1, wordIndexEnd); + for(int i = breakPos + 1; i <= wordIndexEnd; i++) + accumulateWordData (i); } - - PRINTF ("[%p] accumulating again from %d to %d\n", - this, breakPos + 1, wordIndexEnd); - for(int i = breakPos + 1; i <= wordIndexEnd; i++) - accumulateWordData (i); - } while(!lineAdded); } } while (newLine); - if(word->content.type == core::Content::WIDGET) { + if(word->content.type == core::Content::WIDGET_IN_FLOW) { // Set parentRef for the child, when necessary. // // parentRef is set for the child already, when a line is @@ -646,7 +655,8 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) firstWordWithoutLine = lines->getLastRef()->lastWord + 1; if (wordIndex >= firstWordWithoutLine) { - word->content.widget->parentRef = lines->size (); + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lines->size ()); PRINTF ("The %s %p is assigned parentRef = %d.\n", word->content.widget->getClassName(), word->content.widget, word->content.widget->parentRef); @@ -654,6 +664,118 @@ void Textblock::wordWrap (int wordIndex, bool wrapAll) } } +void Textblock::wrapWidgetOofRef (int wordIndex) +{ + int top; + if (lines->size() == 0) + top = 0; + else { + Line *prevLine = lines->getLastRef (); + top = prevLine->top + prevLine->boxAscent + + prevLine->boxDescent + prevLine->breakSpace; + } + + containingBlock->outOfFlowMgr->tellPosition + (words->getRef(wordIndex)->content.widget, + top + getStyle()->boxOffsetY()); + + // TODO: compare old/new values of calcAvailWidth(...) (?) + int firstIndex = + lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; + assert (firstIndex <= wordIndex); + for (int i = firstIndex; i <= wordIndex; i++) + accumulateWordData (i); +} + +int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, + bool correctAtEnd) +{ + PRINTF (" searching from %d to %d\n", firstWord, lastWord); + + int pos = -1; + + for (int i = firstWord; i <= lastWord; i++) { + Word *w = words->getRef(i); + + //printf (" %d (of %d): ", i, words->size ()); + //printWord (w); + //printf ("\n"); + + if (pos == -1 || + w->badnessAndPenalty.compareTo (penaltyIndex, + &words->getRef(pos) + ->badnessAndPenalty) <= 0) + // "<=" instead of "<" in the next lines tends to result in + // more words per line -- theoretically. Practically, the + // case "==" will never occur. + pos = i; + } + + PRINTF (" found at %d\n", pos); + + if (correctAtEnd && lastWord == words->size () - 1) { + // Since no break and no space is added, the last word will have + // a penalty of inf. Actually, it should be less, since it is + // the last word. However, since more words may follow, the + // penalty is not changed, but here, the search is corrected + // (maybe only temporary). + + // (Notice that it was once (temporally) set to -inf, not 0, but + // this will make e.g. test/table-1.html not work.) + Word *w = words->getRef (lastWord); + BadnessAndPenalty correctedBap = w->badnessAndPenalty; + correctedBap.setPenalty (0); + if (correctedBap.compareTo(penaltyIndex, + &words->getRef(pos)->badnessAndPenalty) <= 0) { + pos = lastWord; + PRINTF (" corrected => %d\n", pos); + } + } + + return pos; +} + + +/** + * Suggest a word to hyphenate, when breaking at breakPos is + * planned. Return a word index or -1, when hyphenation makes no + * sense. + */ +int Textblock::considerHyphenation (int breakPos) +{ + int hyphenatedWord = -1; + + Word *word1 = words->getRef(breakPos); + PRINTF ("[%p] line (broken at word %d): ", this, breakPos); + //word1->badnessAndPenalty.print (); + PRINTF ("\n"); + + // A tight line: maybe, after hyphenation, some parts of the last + // word of this line can be put into the next line. + if (word1->badnessAndPenalty.lineTight () && + isHyphenationCandidate (word1)) + hyphenatedWord = breakPos; + + // A loose line: maybe, after hyphenation, some parts of the first + // word of the next line can be put into this line. + if (word1->badnessAndPenalty.lineLoose () && + breakPos + 1 < words->size ()) { + Word *word2 = words->getRef(breakPos + 1); + if (isHyphenationCandidate (word2)) + hyphenatedWord = breakPos + 1; + } + + return hyphenatedWord; +} + +bool Textblock::isHyphenationCandidate (Word *word) +{ + return (word->flags & Word::CAN_BE_HYPHENATED) && + word->style->x_lang[0] && + word->content.type == core::Content::TEXT && + Hyphenator::isHyphenationCandidate (word->content.text); +} + /** * Counter part to wordWrap(), but for extremes, not size calculation. */ @@ -830,13 +952,20 @@ int Textblock::hyphenateWord (int wordIndex) } } - accumulateWordData (wordIndex + i); - //printf ("[%p] %d: hyphenated word part: ", this, wordIndex + i); //printWordWithFlags (w); //printf ("\n"); } + // AccumulateWordData() will calculate the width, which depends + // on the borders (possibly limited by floats), which depends on + // the widgeds so far. For this reason, it is important to first + // make all words consistent before calling + // accumulateWordData(); therefore the second loop. + + for (int i = 0; i < numBreaks + 1; i++) + accumulateWordData (wordIndex + i); + PRINTF (" finished\n"); //delete origword->content.text; TODO: Via textZone? @@ -855,8 +984,12 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) Line *line = lines->getRef (lineIndex); Word *word = words->getRef (wordIndex); - PRINTF (" %d + %d / %d + %d\n", line->boxAscent, line->boxDescent, + PRINTF ("[%p] ACCUMULATE_WORD_FOR_LINE (%d, %d): %d + %d / %d + %d\n", + this, lineIndex, wordIndex, line->boxAscent, line->boxDescent, word->size.ascent, word->size.descent); + //printf (" "); + //printWord (word); + //printf ("\n"); line->boxAscent = misc::max (line->boxAscent, word->size.ascent); line->boxDescent = misc::max (line->boxDescent, word->size.descent); @@ -871,7 +1004,7 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) len += word->style->font->ascent / 3; line->contentDescent = misc::max (line->contentDescent, len); - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { int collapseMarginTop = 0; line->marginDescent = @@ -895,7 +1028,8 @@ void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) + word->content.widget->getStyle()->margin.top - collapseMarginTop); - word->content.widget->parentRef = lineIndex; + word->content.widget->parentRef = + OutOfFlowMgr::createRefNormalFlow (lineIndex); } else { line->marginDescent = misc::max (line->marginDescent, line->boxDescent); @@ -961,18 +1095,37 @@ void Textblock::accumulateWordData (int wordIndex) int Textblock::calcAvailWidth (int lineIndex) { - int availWidth = - this->availWidth - getStyle()->boxDiffWidth() - innerPadding; + int availWidth = this->availWidth - innerPadding; if (limitTextWidth && layout->getUsesViewport () && - availWidth > layout->getWidthViewport () - 10) + // margin/border/padding will be subtracted later, via OOFM. + availWidth - getStyle()->boxDiffWidth() + > layout->getWidthViewport () - 10) availWidth = layout->getWidthViewport () - 10; if (lineIndex == 0) availWidth -= line1OffsetEff; - //PRINTF("[%p] CALC_AVAIL_WIDTH => %d - %d - %d = %d\n", - // this, this->availWidth, getStyle()->boxDiffWidth(), innerPadding, - // availWidth); + // TODO if the result is too small, but only in some cases + // (e. g. because of floats), the caller should skip a + // line. General distinction? + int leftBorder, rightBorder; + if (containingBlock->outOfFlowMgr && mustBorderBeRegarded (lineIndex)) { + int y = topOfPossiblyMissingLine (lineIndex); + int h = heightOfPossiblyMissingLine (lineIndex); + leftBorder = containingBlock->outOfFlowMgr->getLeftBorder (this, y, h); + rightBorder = containingBlock->outOfFlowMgr->getRightBorder (this, y, h); + } else + leftBorder = rightBorder = 0; + + leftBorder = misc::max (leftBorder, getStyle()->boxOffsetX()); + rightBorder = misc::max (rightBorder, getStyle()->boxRestWidth()); + + availWidth -= (leftBorder + rightBorder); + + PRINTF ("[%p] CALC_AVAIL_WIDTH (%d of %d) => %d - %d - (%d + %d)" + " = %d\n", this, lineIndex, lines->size(), this->availWidth, + innerPadding, leftBorder, rightBorder, + availWidth); return availWidth; } @@ -989,7 +1142,7 @@ void Textblock::initLine1Offset (int wordIndex) } else { int indent = 0; - if (word->content.type == core::Content::WIDGET && + if (word->content.type == core::Content::WIDGET_IN_FLOW && word->content.widget->blockLevel() == true) { /* don't use text-indent when nesting blocks */ } else { @@ -1080,8 +1233,14 @@ void Textblock::rewrap () for (int i = firstWord; i < words->size (); i++) { Word *word = words->getRef (i); - - if (word->content.type == core::Content::WIDGET) + if (word->content.type == core::Content::WIDGET_OOF_REF) + containingBlock->outOfFlowMgr->tellNoPosition (word->content.widget); + } + + for (int i = firstWord; i < words->size (); i++) { + Word *word = words->getRef (i); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) calcWidgetSize (word->content.widget, &word->size); wordWrap (i, false); |