/* * Dillo Widget * * Copyright 2005-2007, 2012-2014 Sebastian Geerken * * (Parts of this file were originally part of textblock.cc.) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "textblock.hh" #include "hyphenator.hh" #include "../lout/msg.h" #include "../lout/debug.hh" #include "../lout/misc.hh" #include #include using namespace lout; using namespace lout::misc; namespace dw { int Textblock::BadnessAndPenalty::badnessValue (int infLevel) { switch (badnessState) { case NOT_STRETCHABLE: return infLevel == INF_NOT_STRETCHABLE ? 1 : 0; case QUITE_LOOSE: return infLevel == INF_LARGE ? 1 : 0; case BADNESS_VALUE: return infLevel == INF_VALUE ? badness : 0; case TOO_TIGHT: return infLevel == INF_TOO_TIGHT ? 1 : 0; } // compiler happiness assertNotReached (); return 0; } int Textblock::BadnessAndPenalty::penaltyValue (int index, int infLevel) { if (penalty[index] == INT_MIN) return infLevel == INF_PENALTIES ? -1 : 0; else if (penalty[index] == INT_MAX) return infLevel == INF_PENALTIES ? 1 : 0; else return infLevel == INF_VALUE ? penalty[index] : 0; } void Textblock::BadnessAndPenalty::calcBadness (int totalWidth, int idealWidth, int totalStretchability, int totalShrinkability) { #ifdef DEBUG this->totalWidth = totalWidth; this->idealWidth = idealWidth; this->totalStretchability = totalStretchability; this->totalShrinkability = totalShrinkability; #endif ratio = 0; if (totalWidth == idealWidth) { badnessState = BADNESS_VALUE; badness = 0; } else if (totalWidth < idealWidth) { if (totalStretchability == 0) badnessState = NOT_STRETCHABLE; else { ratio = 100 * (idealWidth - totalWidth) / totalStretchability; if (ratio > 1024) badnessState = QUITE_LOOSE; else { badnessState = BADNESS_VALUE; badness = ratio * ratio * ratio; } } } else { // if (totalWidth > idealWidth) if (totalShrinkability == 0) badnessState = TOO_TIGHT; else { // ratio is negative here ratio = 100 * (idealWidth - totalWidth) / totalShrinkability; if (ratio <= - 100) badnessState = TOO_TIGHT; else { badnessState = BADNESS_VALUE; badness = - ratio * ratio * ratio; } } } } /** * Sets the penalty, multiplied by 100. Multiplication is necessary * to deal with fractional numbers, without having to use floating * point numbers. So, to set a penalty to 0.5, pass 50. * * INT_MAX and INT_MIN (representing inf and -inf, respectively) are * also allowed. * * The definition of penalties depends on the definition of badness, * which adheres to the description in \ref dw-line-breaking, section * "Criteria for Line-Breaking". The exact calculation may vary, but * this definition of should be rather stable: (i) A perfectly * fitting line has a badness of 0. (ii) A line, where all spaces * are extended by exactly the stretchability, as well as a line, where * all spaces are reduced by the shrinkability, have a badness of 1. * * (TODO plural: penalties, not penalty. Correct above comment) */ void Textblock::BadnessAndPenalty::setPenalties (int penalty1, int penalty2) { // TODO Check here some cases, e.g. both or no penalty INT_MIN. setSinglePenalty(0, penalty1); setSinglePenalty(1, penalty2); } void Textblock::BadnessAndPenalty::setSinglePenalty (int index, int penalty) { if (penalty == INT_MAX || penalty == INT_MIN) this->penalty[index] = penalty; else // This factor consists of: (i) 100^3, since in calcBadness(), the // ratio is multiplied with 100 (again, to use integer numbers for // fractional numbers), and the badness (which has to be compared // to the penalty!) is the third power or it; (ii) the denominator // 100, of course, since 100 times the penalty is passed to this // method. this->penalty[index] = penalty * (100 * 100 * 100 / 100); } bool Textblock::BadnessAndPenalty::lineLoose () { return badnessState == NOT_STRETCHABLE || badnessState == QUITE_LOOSE || (badnessState == BADNESS_VALUE && ratio > 0); } bool Textblock::BadnessAndPenalty::lineTight () { return badnessState == TOO_TIGHT || (badnessState == BADNESS_VALUE && ratio < 0); } bool Textblock::BadnessAndPenalty::lineTooTight () { return badnessState == TOO_TIGHT; } bool Textblock::BadnessAndPenalty::lineMustBeBroken (int penaltyIndex) { return penalty[penaltyIndex] == PENALTY_FORCE_BREAK; } bool Textblock::BadnessAndPenalty::lineCanBeBroken (int penaltyIndex) { return penalty[penaltyIndex] != PENALTY_PROHIBIT_BREAK; } int Textblock::BadnessAndPenalty::compareTo (int penaltyIndex, BadnessAndPenalty *other) { for (int l = INF_MAX; l >= 0; l--) { int thisValue = badnessValue (l) + penaltyValue (penaltyIndex, l); int otherValue = other->badnessValue (l) + other->penaltyValue (penaltyIndex, l); if (thisValue != otherValue) return thisValue - otherValue; } return 0; } void Textblock::BadnessAndPenalty::intoStringBuffer(StringBuffer *sb) { switch (badnessState) { case NOT_STRETCHABLE: sb->append ("not stretchable"); break; case TOO_TIGHT: sb->append ("too tight"); break; case QUITE_LOOSE: sb->append ("quite loose (ratio = "); sb->appendInt (ratio); sb->append (")"); break; case BADNESS_VALUE: sb->appendInt (badness); break; } #ifdef DEBUG sb->append (" ["); sb->appendInt (totalWidth); sb->append (" + "); sb->appendInt (totalStretchability); sb->append (" - "); sb->appendInt (totalShrinkability); sb->append (" vs. "); sb->appendInt (idealWidth); sb->append ("]"); #endif sb->append (" + ("); for (int i = 0; i < 2; i++) { if (penalty[i] == INT_MIN) sb->append ("-inf"); else if (penalty[i] == INT_MAX) sb->append ("inf"); else sb->appendInt (penalty[i]); if (i == 0) sb->append (", "); } sb->append (")"); } /* * ... * * diff ... */ void Textblock::justifyLine (Line *line, int diff) { DBG_OBJ_ENTER ("construct.line", 0, "justifyLine", "..., %d", diff); // To avoid rounding errors, the calculation is based on accumulated // values. See doc/rounding-errors.doc. if (diff > 0) { int spaceStretchabilitySum = 0; for (int i = line->firstWord; i < line->lastWord; i++) spaceStretchabilitySum += getSpaceStretchability(words->getRef(i)); if (spaceStretchabilitySum > 0) { int spaceStretchabilityCum = 0; int spaceDiffCum = 0; for (int i = line->firstWord; i < line->lastWord; i++) { Word *word = words->getRef (i); spaceStretchabilityCum += getSpaceStretchability(word); int spaceDiff = spaceStretchabilityCum * diff / spaceStretchabilitySum - spaceDiffCum; spaceDiffCum += spaceDiff; DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", i, words->size (), spaceDiff); word->effSpace = word->origSpace + spaceDiff; } } } else if (diff < 0) { int spaceShrinkabilitySum = 0; for (int i = line->firstWord; i < line->lastWord; i++) spaceShrinkabilitySum += getSpaceShrinkability(words->getRef(i)); if (spaceShrinkabilitySum > 0) { int spaceShrinkabilityCum = 0; int spaceDiffCum = 0; for (int i = line->firstWord; i < line->lastWord; i++) { Word *word = words->getRef (i); spaceShrinkabilityCum += getSpaceShrinkability(word); int spaceDiff = spaceShrinkabilityCum * diff / spaceShrinkabilitySum - spaceDiffCum; spaceDiffCum += spaceDiff; DBG_OBJ_MSGF ("construct.line", 1, "%d (of %d): diff = %d", i, words->size (), spaceDiff); word->effSpace = word->origSpace + spaceDiff; } } } DBG_OBJ_LEAVE (); } Textblock::Line *Textblock::addLine (int firstWord, int lastWord, int newLastOofPos, bool temporary, int minHeight) { DBG_OBJ_ENTER ("construct.line", 0, "addLine", "%d, %d, %d, %s, %d", firstWord, lastWord, newLastOofPos, temporary ? "true" : "false", minHeight); DBG_OBJ_MSGF ("construct.line", 0, "=> %d", lines->size ()); int lineWidth; if (lastWord >= firstWord) { DBG_MSG_WORD ("construct.line", 1, "first word: ", firstWord, ""); DBG_MSG_WORD ("construct.line", 1, "last word: ", lastWord, ""); Word *lastWordOfLine = words->getRef(lastWord); // Word::totalWidth includes the hyphen (which is what we want here). lineWidth = lastWordOfLine->totalWidth; DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (from last word): %d", lineWidth); } else { // empty line lineWidth = 0; DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (empty line): %d", lineWidth); } // "lineWidth" is relative to leftOffset, so we may have to add // "line1OffsetEff" (remember: this is, for list items, negative). if (lines->size () == 0) { lineWidth += line1OffsetEff; DBG_OBJ_MSGF ("construct.line", 1, "lineWidth (line1OffsetEff): %d", lineWidth); } lines->increase (); DBG_OBJ_SET_NUM ("lines.size", lines->size ()); if(!temporary) { // If the last line was temporary, this will be temporary, too, even // if not requested. if (lines->size () == 1 || nonTemporaryLines == lines->size () - 1) nonTemporaryLines = lines->size (); } DBG_OBJ_MSGF ("construct.line", 1, "nonTemporaryLines = %d", nonTemporaryLines); int lineIndex = lines->size () - 1; Line *line = lines->getRef (lineIndex); line->firstWord = firstWord; line->lastWord = lastWord; DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "firstWord", line->firstWord); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "lastWord", line->lastWord); line->borderAscent = line->contentAscent = 0; line->borderDescent = line->contentDescent = 0; line->marginAscent = 0; line->marginDescent = 0; line->breakSpace = 0; bool regardBorder = mustBorderBeRegarded (line); line->leftOffset = max (regardBorder ? newLineLeftBorder : 0, boxOffsetX () + leftInnerPadding + (lineIndex == 0 ? line1OffsetEff : 0)); line->rightOffset = max (regardBorder ? newLineRightBorder : 0, boxRestWidth ()); DBG_OBJ_MSGF ("construct.line", 1, "regardBorder = %s, newLineLeftBorder = %d, " "newLineRightBorder = %d", regardBorder ? "true" : "false", newLineLeftBorder, newLineRightBorder); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "leftOffset", line->leftOffset); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "rightOffset", line->rightOffset); alignLine (lineIndex); calcTextOffset (lineIndex, lineBreakWidth); for (int i = line->firstWord; i < line->lastWord; i++) { Word *word = words->getRef (i); lineWidth += (word->effSpace - word->origSpace); DBG_OBJ_MSGF ("construct.line", 1, "lineWidth [corrected space (%d - %d) after word %d]: %d", word->effSpace, word->origSpace, i, lineWidth); } // Until here, lineWidth refers does not include floats on the left // side. To include left floats, so that maxLineWidth, and // eventually the requisition, is correct, line->leftOffset (minus // margin+border+padding) has to be added, which was calculated // just before. The correction in sizeAllocateImpl() is irrelevant // in this regard. Also, right floats are not regarded here, but in // OutOfFlowMgr::getSize(), lineWidth += (line->leftOffset - getStyle()->boxOffsetX ()); if (lines->size () == 1) { // first line line->maxLineWidth = lineWidth; line->lastOofRefPositionedBeforeThisLine = -1; } else { Line *prevLine = lines->getRef (lines->size () - 2); line->maxLineWidth = max (lineWidth, prevLine->maxLineWidth); line->lastOofRefPositionedBeforeThisLine = prevLine->lastOofRefPositionedBeforeThisLine; } DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "maxLineWidth", line->maxLineWidth); for(int i = line->firstWord; i <= line->lastWord; i++) accumulateWordForLine (lineIndex, i); if (lines->size () == 1) line->top = 0; else { // See comment in Line::totalHeight for collapsing of the // margins of adjacent lines. Line *prevLine = lines->getRef (lines->size () - 2); line->top = prevLine->top + prevLine->totalHeight (line->marginAscent - line->borderAscent); } DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "top", line->top); // Especially empty lines (possible when there are floats) have // zero height, which may cause endless loops. For this reasons, // the height should be positive (assuming the caller passed // minHeight > 0). line->borderAscent = max (line->borderAscent, minHeight); line->marginAscent = max (line->marginAscent, minHeight); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "borderAscent", line->borderAscent); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "borderDescent", line->borderDescent); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "marginAscent", line->marginAscent); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "marginDescent", line->marginDescent); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "contentAscent", line->contentAscent); DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "contentDescent", line->contentDescent); mustQueueResize = true; int xWidget = line->textOffset; int yLine = yOffsetOfLineCreated (line); for (int i = firstWord; i <= lastWord; i++) { Word *word = words->getRef (i); if (word->wordImgRenderer) word->wordImgRenderer->setData (xWidget, lines->size () - 1); if (word->spaceImgRenderer) word->spaceImgRenderer->setData (xWidget, lines->size () - 1); if (word->content.type == core::Content::WIDGET_OOF_REF) { Widget *widget = word->content.widgetReference->widget; int oofmIndex = getWidgetOOFIndex (widget); oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex); // See also Textblock::sizeAllocate, and notes there about // vertical alignment. Calculating the vertical position // should probably be centralized. if (oofm) { int xRel = xWidget; int yRel = yLine + (line->borderAscent - word->size.ascent); int xRef, yRef; if (findSizeRequestReference (oofmIndex, &xRef, &yRef)) oofm->tellPosition2 (widget, xRef + xRel, yRef + yRel); else oofm->tellIncompletePosition2 (widget, this, xRel, yRel); } } xWidget += word->size.width + word->effSpace; } line->lastOofRefPositionedBeforeThisLine = max (line->lastOofRefPositionedBeforeThisLine, newLastOofPos); DBG_OBJ_SET_NUM ("lastLine.lastOofRefPositionedBeforeThisLine", line->lastOofRefPositionedBeforeThisLine); initNewLine (); DBG_OBJ_LEAVE (); return line; } void Textblock::processWord (int wordIndex) { DBG_OBJ_ENTER ("construct.all", 0, "processWord", "%d", wordIndex); DBG_MSG_WORD ("construct.all", 1, "processed word:", wordIndex, ""); int diffWords = wordWrap (wordIndex, false); if (diffWords == 0) handleWordExtremes (wordIndex); else { // If wordWrap has called hyphenateWord here, this has an effect // on the call of handleWordExtremes. To avoid adding values // more than one time (original un-hyphenated word, plus all // parts of the hyphenated word, except the first one), the // whole paragraph is recalculated again. // // (Note: the hyphenated word is often *before* wordIndex, and // it may be even more than one word, which makes it nearly // impossible to reconstruct what has happend. Therefore, there // is no simpler approach to handle this.) DBG_OBJ_MSGF ("construct.paragraph", 1, "word list has become longer by %d", diffWords); DBG_MSG_WORD ("construct.all", 1, "processed word now:", wordIndex, ""); int firstWord; if (paragraphs->size() > 0) { firstWord = paragraphs->getLastRef()->firstWord; paragraphs->setSize (paragraphs->size() - 1); DBG_OBJ_SET_NUM ("paragraphs.size", paragraphs->size ()); DBG_OBJ_MSG ("construct.paragraph", 1, "removing last paragraph"); } else firstWord = 0; int lastIndex = wordIndex + diffWords; DBG_OBJ_MSGF ("construct.paragraph", 1, "processing words again from %d to %d", firstWord, lastIndex); // Furthermore, some more words have to be processed, so we // iterate until wordIndex + diffWords, not only // wordIndex. DBG_OBJ_MSG_START (); for (int i = firstWord; i <= lastIndex; i++) handleWordExtremes (i); DBG_OBJ_MSG_END (); } DBG_OBJ_LEAVE (); } /* * 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. * * Returns whether the words list has changed at, or before, the word * index. */ int Textblock::wordWrap (int wordIndex, bool wrapAll) { DBG_OBJ_ENTER ("construct.word", 0, "wordWrap", "%d, %s", wordIndex, wrapAll ? "true" : "false"); DBG_MSG_WORD ("construct.word", 1, "wrapped word: ", wordIndex, ""); if (!wrapAll) removeTemporaryLines (); initLine1Offset (wordIndex); Word *word = words->getRef (wordIndex); word->effSpace = word->origSpace; accumulateWordData (wordIndex); int n; if (word->content.type == core::Content::WIDGET_OOF_REF) n = wrapWordOofRef (wordIndex, wrapAll); else n = wrapWordInFlow (wordIndex, wrapAll); DBG_OBJ_MSGF ("construct.word", 1, "=> %d", n); DBG_OBJ_LEAVE (); return n; } int Textblock::wrapWordInFlow (int wordIndex, bool wrapAll) { DBG_OBJ_ENTER ("construct.word", 0, "wrapWordInFlow", "%d, %s", wordIndex, wrapAll ? "true" : "false"); Word *word = words->getRef (wordIndex); int diffWords = 0; int penaltyIndex = calcPenaltyIndexForNewLine (); bool newLine; do { // This variable, thereWillBeMoreSpace, 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 line break width. Leaving // lines empty does not make sense without floats, since there will // be no possibility with more space anymore.) bool regardBorder = mustBorderBeRegarded (lines->size ()); bool thereWillBeMoreSpace = regardBorder ? newLineHasFloatLeft || newLineHasFloatRight : false; DBG_OBJ_MSGF ("construct.word", 1, "thereWillBeMoreSpace = %s ? %s || %s : false = %s", regardBorder ? "true" : "false", newLineHasFloatLeft ? "true" : "false", newLineHasFloatRight ? "true" : "false", thereWillBeMoreSpace ? "true" : "false"); bool tempNewLine = false; int firstIndex = lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1; int searchUntil; 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; DBG_OBJ_MSG ("construct.word", 1, "new line: last word"); } else if (wordIndex >= firstIndex && // TODO: lineMustBeBroken should be independent of // the penalty index? word->badnessAndPenalty.lineMustBeBroken (penaltyIndex)) { newLine = true; searchUntil = wordIndex; DBG_OBJ_MSG ("construct.word", 1, "new line: forced break"); } else { // 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. DBG_OBJ_MSGF ("construct.word", 1, "possible line break between %d and %d?", firstIndex, wordIndex - 1); DBG_OBJ_MSG_START (); bool possibleLineBreak = false; for (int i = firstIndex; !(thereWillBeMoreSpace || possibleLineBreak) && i <= wordIndex - 1; i++) { DBG_OBJ_MSGF ("construct.word", 2, "examining word %d", i); if (words->getRef(i)->badnessAndPenalty .lineCanBeBroken (penaltyIndex)) { DBG_MSG_WORD ("construct.word", 2, "break possible for word:", i, ""); possibleLineBreak = true; } } DBG_OBJ_MSG_END (); DBG_OBJ_MSGF ("construct.word", 1, "=> %s", possibleLineBreak ? "true" : "false"); DBG_OBJ_MSGF ("construct.word", 1, "word->... too tight: %s", word->badnessAndPenalty.lineTooTight () ? "true" : "false"); if ((thereWillBeMoreSpace || possibleLineBreak) && word->badnessAndPenalty.lineTooTight ()) { newLine = true; searchUntil = wordIndex - 1; DBG_OBJ_MSG ("construct.word", 1, "new line: line too tight"); } else { DBG_OBJ_MSG ("construct.word", 1, "no new line"); newLine = false; } } if(!newLine && !wrapAll) // No new line is added. "mustQueueResize" must, // nevertheless, be set, so that flush() will call // queueResize(), and later sizeRequestImpl() is called, // which then calls showMissingLines(), which eventually // calls this method again, with wrapAll == true, so that // 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; int height = 1; // assumed by calcBorders before (see there) int breakPos; int lastFloatPos = lines->size() > 0 ? lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1; DBG_OBJ_MSGF ("construct.word", 2, "lastFloatPos = %d", lastFloatPos); balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, tempNewLine, penaltyIndex, true, &thereWillBeMoreSpace, wrapAll, &diffWords, &wordIndexEnd, &lastFloatPos, regardBorder, &height, &breakPos); bool floatHandled; int yNewLine = yOffsetOfLineToBeCreated (); do { DBG_OBJ_MSG ("construct.word", 1, "floatHandled loop cycle"); DBG_OBJ_MSG_START (); DBG_OBJ_MSGF ("construct.word", 2, "breakPos = %d, height = %d, lastFloatPos = %d", breakPos, height, lastFloatPos); int startSearch = max (firstIndex, lastFloatPos + 1); int newFloatPos = -1; // Step 1: search for the next float. DBG_OBJ_MSGF ("construct.word", 2, "searching from %d to %d", startSearch, breakPos); for (int i = startSearch; newFloatPos == -1 && i <= breakPos; i++) { core::Content *content = &(words->getRef(i)->content); if (content->type == core::Content::WIDGET_OOF_REF) { for (int j = 0; newFloatPos == -1 && j < NUM_OOFM; j++) { Widget *widget = content->widgetReference->widget; if ((searchOutOfFlowMgr(j)->affectsLeftBorder(widget) || searchOutOfFlowMgr(j)->affectsRightBorder (widget))) newFloatPos = i; } } } DBG_OBJ_MSGF ("construct.word", 2, "newFloatPos = %d", newFloatPos); if (newFloatPos == -1) floatHandled = false; else { floatHandled = true; // Step 2: position the float and re-calculate the line. // TODO "x" is not quite correct, but this does not matter // (currently?). lastFloatPos = newFloatPos; Widget *widget = words->getRef(lastFloatPos)->content.widgetReference->widget; int oofmIndex = getWidgetOOFIndex (widget); oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex); if (oofm && oofm->mayAffectBordersAtAll ()) { int xRel = boxOffsetX (), yRel = yNewLine, xRef, yRef; if (findSizeRequestReference (oofmIndex, &xRef, &yRef)) oofm->tellPosition1 (widget, xRef + xRel, yRef + yRel); else oofm->tellIncompletePosition1 (widget, this, xRel, yRel); } balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil, tempNewLine, penaltyIndex, false, &thereWillBeMoreSpace, wrapAll, &diffWords, &wordIndexEnd, &lastFloatPos, regardBorder, &height, &breakPos); } DBG_OBJ_MSG_END (); } while (floatHandled); int minHeight; if (firstIndex <= breakPos) { // Not an empty line: calculate line height from contents. minHeight = 1; DBG_OBJ_MSGF ("construct.word", 1, "%d <= %d => minHeight = %d", firstIndex, breakPos, minHeight); } else { // Empty line. Too avoid too many lines one pixel high, we // use the float heights. if (newLineHasFloatLeft && newLineHasFloatRight) minHeight = max (min (newLineLeftFloatHeight, newLineRightFloatHeight), 1); else if (newLineHasFloatLeft && !newLineHasFloatRight) minHeight = max (newLineLeftFloatHeight, 1); else if (!newLineHasFloatLeft && newLineHasFloatRight) minHeight = max (newLineRightFloatHeight, 1); else // May this happen? minHeight = 1; DBG_OBJ_MSGF ("construct.word", 1, "%d < %d => minHeight = %d (l: %s (%d), r: %s (%d))", firstIndex, breakPos, minHeight, boolToStr (newLineHasFloatLeft), newLineHasFloatLeft ? newLineLeftFloatHeight : 0, boolToStr (newLineHasFloatRight), newLineHasFloatRight ? newLineRightFloatHeight : 0); } addLine (firstIndex, breakPos, lastFloatPos, tempNewLine, minHeight); DBG_OBJ_MSGF ("construct.word", 1, "accumulating again from %d to %d", breakPos + 1, wordIndexEnd); for(int i = breakPos + 1; i <= wordIndexEnd; i++) accumulateWordData (i); // update word pointer as hyphenateWord() can trigger a // reorganization of the words structure word = words->getRef (wordIndex); penaltyIndex = calcPenaltyIndexForNewLine (); } } while (newLine); 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 // added. There are a couple of different situations to // consider, e.g. when called from showMissingLines(), this word // may already have been added in a previous call. To make // things simple, we just check whether this word is contained // within any line, or still "missing". int firstWordWithoutLine; if (lines->size() == 0) firstWordWithoutLine = 0; else firstWordWithoutLine = lines->getLastRef()->lastWord + 1; if (wordIndex >= firstWordWithoutLine) { word->content.widget->parentRef = makeParentRefInFlow (lines->size ()); DBG_OBJ_SET_NUM_O (word->content.widget, "parentRef", word->content.widget->parentRef); } } DBG_OBJ_LEAVE (); return diffWords; } int Textblock::wrapWordOofRef (int wordIndex, bool wrapAll) { DBG_OBJ_ENTER ("construct.word", 0, "wrapWordOofRef", "%d, %s", wordIndex, wrapAll ? "true" : "false"); Word *word = words->getRef (wordIndex); Widget *widget = word->content.widgetReference->widget; int yNewLine = yOffsetOfLineToBeCreated (); // Floats, which affect either border, are handled in wrapWordInFlow; this // is rather for positioned elements (but only for completeness: // tellPosition1 is not implemented for positioned elements). int oofmIndex = getWidgetOOFIndex (widget); oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex); DBG_OBJ_MSGF ("construct.word", 1, "parentRef = %d, oofm = %p", widget->parentRef, oofm); if (oofm && !oofm->mayAffectBordersAtAll ()) { // TODO Again, "x" is not correct (see above). int xRel = boxOffsetX (), yRel = yNewLine, xRef, yRef; if (findSizeRequestReference (oofmIndex, &xRef, &yRef)) oofm->tellPosition1 (widget, xRef + xRel, yRef + yRel); else oofm->tellIncompletePosition1 (widget, this, xRel, yRel); } if(word->content.type == core::Content::WIDGET_OOF_REF) { // Set parentRef for the referred widget. Cf. wrapWordInFlow. int firstWordWithoutLine; if (lines->size() == 0) firstWordWithoutLine = 0; else firstWordWithoutLine = lines->getLastRef()->lastWord + 1; if (wordIndex >= firstWordWithoutLine) { word->content.widgetReference->parentRef = makeParentRefInFlow (lines->size ()); DBG_SET_WORD (wordIndex); } } DBG_OBJ_LEAVE (); return 0; // Words list not changed. } // *height must be initialized, but not *breakPos. // *wordIndexEnd must be initialized (initially to wordIndex) void Textblock::balanceBreakPosAndHeight (int wordIndex, int firstIndex, int *searchUntil, bool tempNewLine, int penaltyIndex, bool borderIsCalculated, bool *thereWillBeMoreSpace, bool wrapAll, int *diffWords, int *wordIndexEnd, int *lastFloatPos, bool regardBorder, int *height, int *breakPos) { DBG_OBJ_ENTER ("construct.word", 0, "balanceBreakPosAndHeight", "%d, %d. %d, %s, %d, %s, ..., %s, ..., %d, %s, %d, ...", wordIndex, firstIndex, *searchUntil, tempNewLine ? "true" : "false", penaltyIndex, borderIsCalculated ? "true" : "false", wrapAll ? "true" : "false", *lastFloatPos, regardBorder ? "true" : "false", *height); // The height of this part of the line (until the new break // position) may change with the break position, but the break // position may depend on the height. We try to let these values // converge. // // The height, as a function of the break position, is // monotonically (but not strictly) increasing, since more words // may make the line higher (but not flatter). The break position, // as a function of the height, is, however, monotonically (but not // strictly) *de*creasing, since flatter lines may fit easier // between floats (although this is a rare case). So a convergence // is not necessary. // // For this reason, we iterate only as long as the height does not // increase again, and stop if it remains the same. As the minimum // is 1, this approach will force the iteration to stop. // // (As a side effect, this will lead to a larger break position, // and so place as much words as possible in the line.) int runNo = 1; while (true) { if (!(borderIsCalculated && runNo == 1)) { // borderIsCalculated is, of course, only valid in the first run calcBorders (*lastFloatPos, *height); *thereWillBeMoreSpace = regardBorder ? newLineHasFloatLeft || newLineHasFloatRight : false; for(int i = firstIndex; i <= *wordIndexEnd; i++) accumulateWordData (i); } DBG_OBJ_MSGF ("construct.word", 1, "thereWillBeMoreSpace = %s", *thereWillBeMoreSpace ? "true" : "false"); int newBreakPos = searchBreakPos (wordIndex, firstIndex, searchUntil, tempNewLine, penaltyIndex, *thereWillBeMoreSpace, wrapAll, diffWords, wordIndexEnd, lastFloatPos); int newHeight = calcLinePartHeight (firstIndex, newBreakPos); DBG_OBJ_MSGF ("construct.word", 1, "runNo = %d, newBreakPos = %d, newHeight = %d", runNo, newBreakPos, newHeight); if (runNo == 1) DBG_OBJ_MSGF ("construct.word", 1, "old: height = %d, breakPos undefined", *height); else DBG_OBJ_MSGF ("construct.word", 1, "old: height = %d, breakPos = %d", *height, *breakPos); if (runNo != 1 /* Since *some* value are needed, the results from the first run are never discarded. */ && newHeight >= *height) { if (newHeight == *height) { // newHeight == height: convergence, stop here. The new break // position is, nevertheless, adopted. DBG_OBJ_MSG ("construct.word", 1, "stopping, adopting new values"); *breakPos = newBreakPos; } else // newHeight > height: do not proceed, discard new values, // which are less desirable than the old ones (see above). DBG_OBJ_MSG ("construct.word", 1, "stopping, discarding new values"); break; } else { DBG_OBJ_MSG ("construct.word", 1, "adopting new values, continuing"); *height = newHeight; *breakPos = newBreakPos; } runNo++; } DBG_OBJ_LEAVE_VAL ("%d, %d, %d, %d", *searchUntil, *lastFloatPos, *height, *breakPos); } // *wordIndexEnd must be initialized (initially to wordIndex) int Textblock::searchBreakPos (int wordIndex, int firstIndex, int *searchUntil, bool tempNewLine, int penaltyIndex, bool thereWillBeMoreSpace, bool wrapAll, int *diffWords, int *wordIndexEnd, int *addIndex1) { DBG_OBJ_ENTER ("construct.word", 0, "searchBreakPos", "%d, %d. %d, %s, %d, %s, %s, ...", wordIndex, firstIndex, *searchUntil, tempNewLine ? "true" : "false", penaltyIndex, thereWillBeMoreSpace ? "true" : "false", wrapAll ? "true" : "false"); DBG_MSG_WORD ("construct.word", 1, "first word: ", firstIndex, ""); int result; bool lineAdded; do { DBG_OBJ_MSG ("construct.word", 1, "searchBreakPos loop cycle"); DBG_OBJ_MSG_START (); if (firstIndex > *searchUntil) { // empty line DBG_OBJ_MSG ("construct.word", 1, "empty line"); assert (*searchUntil == firstIndex - 1); result = firstIndex - 1; lineAdded = true; } else if (thereWillBeMoreSpace && words->getRef(firstIndex)->badnessAndPenalty.lineTooTight ()) { int hyphenatedWord = considerHyphenation (firstIndex, firstIndex); DBG_IF_RTFL { StringBuffer sb; words->getRef(firstIndex)->badnessAndPenalty.intoStringBuffer (&sb); DBG_OBJ_MSGF ("construct.word", 1, "too tight: %s ... hyphenatedWord = %d", sb.getChars (), hyphenatedWord); } if (hyphenatedWord == -1) { DBG_OBJ_MSG ("construct.word", 1, "... => empty line"); result = firstIndex - 1; lineAdded = true; } else { DBG_OBJ_MSG ("construct.word", 1, "... => hyphenate word and try again"); int n = hyphenateWord (hyphenatedWord, addIndex1); *searchUntil += n; if (hyphenatedWord <= wordIndex) *wordIndexEnd += n; DBG_OBJ_MSGF ("construct.word", 1, "new searchUntil = %d", *searchUntil); lineAdded = false; } } else { DBG_OBJ_MSG ("construct.word", 1, "non-empty line"); int breakPos = searchMinBap (firstIndex, *searchUntil, penaltyIndex, thereWillBeMoreSpace, wrapAll); int hyphenatedWord = considerHyphenation (firstIndex, breakPos); DBG_OBJ_MSGF ("construct.word", 1, "breakPos = %d", breakPos); DBG_MSG_WORD ("construct.word", 1, "break at word: ", breakPos, ""); DBG_OBJ_MSGF ("construct.word", 1, "hyphenatedWord = %d", hyphenatedWord); if (hyphenatedWord != -1) DBG_MSG_WORD ("construct.word", 1, "hyphenate at word: ", hyphenatedWord, ""); if(hyphenatedWord == -1) { result = breakPos; lineAdded = true; } else { // TODO hyphenateWord() should return whether something // has changed at all. So that a second run, with // !word->canBeHyphenated, is unnecessary. // TODO Update: The return value of hyphenateWord() should // be checked. DBG_OBJ_MSGF ("construct.word", 1, "old searchUntil = %d", *searchUntil); int n = hyphenateWord (hyphenatedWord, addIndex1); *searchUntil += n; if (hyphenatedWord <= wordIndex) *wordIndexEnd += n; DBG_OBJ_MSGF ("construct.word", 1, "new searchUntil = %d", *searchUntil); lineAdded = false; if (hyphenatedWord <= wordIndex) *diffWords += n; DBG_OBJ_MSGF ("construct.word", 1, "accumulating again from %d to %d", breakPos + 1, *wordIndexEnd); for(int i = breakPos + 1; i <= *wordIndexEnd; i++) accumulateWordData (i); } } DBG_OBJ_MSG_END (); } while(!lineAdded); DBG_OBJ_LEAVE_VAL ("%d", result); return result; } int Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex, bool thereWillBeMoreSpace, bool correctAtEnd) { DBG_OBJ_ENTER ("construct.word", 0, "searchMinBap", "%d, %d, %d, %s, %s", firstWord, lastWord, penaltyIndex, thereWillBeMoreSpace ? "true" : "false", correctAtEnd ? "true" : "false"); int pos = -1; DBG_OBJ_MSG_START (); for (int i = firstWord; i <= lastWord; i++) { Word *w = words->getRef(i); DBG_IF_RTFL { StringBuffer sb; w->badnessAndPenalty.intoStringBuffer (&sb); DBG_OBJ_MSGF ("construct.word", 2, "%d (of %d): b+p: %s", i, words->size (), sb.getChars ()); DBG_MSG_WORD ("construct.word", 2, "(i. e.: ", i, ")"); } // "<=" instead of "<" in the next lines (see also // "correctedBap.compareTo ...) tends to result in more words // per line -- theoretically. Practically, the case "==" will // never occur. if (pos == -1 || w->badnessAndPenalty.compareTo (penaltyIndex, &words->getRef(pos) ->badnessAndPenalty) <= 0) pos = i; } DBG_OBJ_MSG_END (); DBG_OBJ_MSGF ("construct.word", 1, "found at %d", 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); DBG_IF_RTFL { StringBuffer sb; correctedBap.intoStringBuffer (&sb); DBG_OBJ_MSGF ("construct.word", 1, "corrected b+p: %s", sb.getChars ()); } if (correctedBap.compareTo(penaltyIndex, &words->getRef(pos)->badnessAndPenalty) <= 0) { pos = lastWord; DBG_OBJ_MSGF ("construct.word", 1, "corrected: %d", pos); } } DBG_OBJ_LEAVE (); 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 firstIndex, int breakPos) { int hyphenatedWord = -1; Word *wordBreak = words->getRef(breakPos); //printf ("[%p] line (broken at word %d): ", this, breakPos); //printWord (wordBreak); //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 (wordBreak->badnessAndPenalty.lineTight ()) { // Sometimes, it is not the last word, which must be hyphenated, // but some word before. Here, we search for the first word // which can be hyphenated, *and* makes the line too tight. for (int i = breakPos; i >= firstIndex; i--) { Word *word1 = words->getRef (i); if (word1->badnessAndPenalty.lineTight () && isHyphenationCandidate (word1)) hyphenatedWord = i; } } // A loose line: maybe, after hyphenation, some parts of the first // word of the next line can be put into this line. if (wordBreak->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] && isBreakAllowedInWord (word) && word->content.type == core::Content::TEXT && Hyphenator::isHyphenationCandidate (word->content.text); } int Textblock::calcLinePartHeight (int firstWord, int lastWord) { int ascent = 0, descent = 0; for (int i = firstWord; i <= lastWord; i++) { Word *word = words->getRef (i); ascent = max (ascent, word->size.ascent); descent = max (descent, word->size.descent); } return max (ascent + descent, 1); } /** * Counter part to wordWrap(), but for extremes, not size calculation. */ void Textblock::handleWordExtremes (int wordIndex) { // TODO Overall, clarify penalty index. DBG_OBJ_ENTER ("construct.paragraph", 0, "handleWordExtremes", "%d", wordIndex); initLine1Offset (wordIndex); Word *word = words->getRef (wordIndex); DBG_MSG_WORD ("construct.paragraph", 1, "handled word: ", wordIndex, ""); core::Extremes wordExtremes; getWordExtremes (word, &wordExtremes); DBG_OBJ_MSGF ("construct.paragraph", 1, "extremes: %d (%d) / %d (%d)", wordExtremes.minWidth, wordExtremes.minWidthIntrinsic, wordExtremes.maxWidth, wordExtremes.maxWidthIntrinsic); if (wordIndex == 0) { wordExtremes.minWidth += line1OffsetEff; wordExtremes.minWidthIntrinsic += line1OffsetEff; wordExtremes.maxWidth += line1OffsetEff; wordExtremes.maxWidthIntrinsic += line1OffsetEff; } if (paragraphs->size() == 0 || words->getRef(paragraphs->getLastRef()->lastWord) ->badnessAndPenalty.lineMustBeBroken (1)) { // Add a new paragraph. paragraphs->increase (); DBG_OBJ_SET_NUM ("paragraphs.size", paragraphs->size ()); Paragraph *prevPar = paragraphs->size() == 1 ? NULL : paragraphs->getRef(paragraphs->size() - 2); Paragraph *par = paragraphs->getLastRef(); par->firstWord = par->lastWord = wordIndex; par->parMin = par->parMinIntrinsic = par->parMax = par->parMaxIntrinsic = par->parAdjustmentWidth = 0; if (prevPar) { par->maxParMin = prevPar->maxParMin; par->maxParMinIntrinsic = prevPar->maxParMinIntrinsic; par->maxParMax = prevPar->maxParMax; par->maxParMaxIntrinsic = prevPar->maxParMaxIntrinsic; par->maxParAdjustmentWidth = prevPar->maxParAdjustmentWidth; } else par->maxParMin = par->maxParMinIntrinsic = par->maxParMax = par->maxParMaxIntrinsic = par->maxParAdjustmentWidth = 0; DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMin", par->maxParMin); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMinIntrinsic", par->maxParMinIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMax", par->maxParMax); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMaxIntrinsic", par->maxParMaxIntrinsic); } Paragraph *lastPar = paragraphs->getLastRef(); int corrDiffMin, corrDiffMax; if (wordIndex - 1 >= lastPar->firstWord) { Word *lastWord = words->getRef (wordIndex - 1); if (lastWord->badnessAndPenalty.lineCanBeBroken (1) && (lastWord->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) corrDiffMin = 0; else corrDiffMin = lastWord->origSpace - lastWord->hyphenWidth; corrDiffMax = lastWord->origSpace - lastWord->hyphenWidth; } else corrDiffMin = corrDiffMax = 0; // Minimum: between two *possible* breaks. // Shrinkability could be considered, but really does not play a role. lastPar->parMin += wordExtremes.minWidth + word->hyphenWidth + corrDiffMin; lastPar->parMinIntrinsic += wordExtremes.minWidthIntrinsic + word->hyphenWidth + corrDiffMin; lastPar->parAdjustmentWidth += wordExtremes.adjustmentWidth + word->hyphenWidth + corrDiffMin; lastPar->maxParMin = max (lastPar->maxParMin, lastPar->parMin); lastPar->maxParMinIntrinsic = max (lastPar->maxParMinIntrinsic, lastPar->parMinIntrinsic); lastPar->maxParAdjustmentWidth = max (lastPar->maxParAdjustmentWidth, lastPar->parAdjustmentWidth); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMin", lastPar->parMin); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMinIntrinsic", lastPar->parMinIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMin", lastPar->maxParMin); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMinIntrinsic", lastPar->maxParMinIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parAdjustmentWidth", lastPar->parAdjustmentWidth); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParAdjustmentWidth", lastPar->maxParAdjustmentWidth); if (word->badnessAndPenalty.lineCanBeBroken (1) && (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) { lastPar->parMin = lastPar->parMinIntrinsic = lastPar->parAdjustmentWidth = 0; DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMin", lastPar->parMin); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMinIntrinsic", lastPar->parMinIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parAdjustmentWidth", lastPar->parAdjustmentWidth); } // Maximum: between two *necessary* breaks. lastPar->parMax += wordExtremes.maxWidth + word->hyphenWidth + corrDiffMax; lastPar->parMaxIntrinsic += wordExtremes.maxWidthIntrinsic + word->hyphenWidth + corrDiffMax; lastPar->maxParMax = max (lastPar->maxParMax, lastPar->parMax); lastPar->maxParMaxIntrinsic = max (lastPar->maxParMaxIntrinsic, lastPar->parMaxIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMax", lastPar->parMax); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "parMaxIntrinsic", lastPar->parMaxIntrinsic); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMax", lastPar->maxParMax); DBG_OBJ_ARRATTRSET_NUM ("paragraphs", paragraphs->size() - 1, "maxParMaxIntrinsic", lastPar->maxParMaxIntrinsic); lastPar->lastWord = wordIndex; DBG_OBJ_LEAVE (); } /** * Called when something changed for the last word (space, hyphens etc.). */ void Textblock::correctLastWordExtremes () { if (paragraphs->size() > 0) { Word *word = words->getLastRef (); if (word->badnessAndPenalty.lineCanBeBroken (1) && (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) { Paragraph *lastPar = paragraphs->getLastRef(); lastPar->parMin = lastPar->parMinIntrinsic = lastPar->parAdjustmentWidth = 0; PRINTF (" => corrected; parMin = %d\n", paragraphs->getLastRef()->parMin); } } } int Textblock::hyphenateWord (int wordIndex, int *addIndex1) { Word *hyphenatedWord = words->getRef(wordIndex); char lang[3] = { hyphenatedWord->style->x_lang[0], hyphenatedWord->style->x_lang[1], 0 }; Hyphenator *hyphenator = Hyphenator::getHyphenator (lang); PRINTF ("[%p] considering to hyphenate word %d, '%s', in language '%s'\n", this, wordIndex, words->getRef(wordIndex)->content.text, lang); int numBreaks; int *breakPos = hyphenator->hyphenateWord (layout->getPlatform (), hyphenatedWord->content.text, &numBreaks); if (numBreaks > 0) { Word origWord = *hyphenatedWord; core::Requisition wordSize[numBreaks + 1]; calcTextSizes (origWord.content.text, strlen (origWord.content.text), origWord.style, numBreaks, breakPos, wordSize); PRINTF ("[%p] %d words ...\n", this, words->size ()); words->insert (wordIndex, numBreaks); DBG_IF_RTFL { for (int i = wordIndex + numBreaks; i < words->size (); i++) DBG_SET_WORD (i); } for (int i = 0; i < numBreaks; i++) initWord (wordIndex + i); PRINTF ("[%p] ... => %d words\n", this, words->size ()); moveWordIndices (wordIndex, numBreaks, addIndex1); // Adjust anchor indexes. for (int i = 0; i < anchors->size (); i++) { Anchor *anchor = anchors->getRef (i); if (anchor->wordIndex > wordIndex) anchor->wordIndex += numBreaks; } for (int i = 0; i < numBreaks + 1; i++) { Word *w = words->getRef (wordIndex + i); fillWord (wordIndex + i, wordSize[i].width, wordSize[i].ascent, wordSize[i].descent, false, origWord.style); // TODO There should be a method fillText0. w->content.type = core::Content::TEXT; int start = (i == 0 ? 0 : breakPos[i - 1]); int end = (i == numBreaks ? strlen (origWord.content.text) : breakPos[i]); w->content.text = layout->textZone->strndup (origWord.content.text + start, end - start); // Note: there are numBreaks + 1 word parts. if (i == 0) w->flags |= Word::WORD_START; else w->flags &= ~Word::WORD_START; if (i == numBreaks) w->flags |= Word::WORD_END; else w->flags &= ~Word::WORD_END; if (i < numBreaks) { // TODO There should be a method fillHyphen. w->badnessAndPenalty.setPenalties (penalties[PENALTY_HYPHEN][0], penalties[PENALTY_HYPHEN][1]); // "\xe2\x80\x90" is an unconditional hyphen. w->hyphenWidth = layout->textWidth (w->style->font, hyphenDrawChar, strlen (hyphenDrawChar)); w->flags |= (Word::DRAW_AS_ONE_TEXT | Word::DIV_CHAR_AT_EOL | Word::UNBREAKABLE_FOR_MIN_WIDTH); } else { if (origWord.content.space) fillSpace (wordIndex + i, origWord.spaceStyle); } DBG_SET_WORD (wordIndex + i); } // 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? origWord.style->unref (); origWord.spaceStyle->unref (); free (breakPos); } else words->getRef(wordIndex)->flags &= ~Word::CAN_BE_HYPHENATED; return numBreaks; } void Textblock::moveWordIndices (int wordIndex, int num, int *addIndex1) { DBG_OBJ_ENTER ("construct.word", 0, "moveWordIndices", "%d, %d", wordIndex, num); for (int i = 0; i < NUM_OOFM; i++) if (searchOutOfFlowMgr(i)) searchOutOfFlowMgr(i)->moveExternalIndices (this, wordIndex, num); for (int i = lines->size () - 1; i >= 0; i--) { Line *line = lines->getRef (i); if (line->lastOofRefPositionedBeforeThisLine < wordIndex) { // Since lastOofRefPositionedBeforeThisLine are ascending, // the search can be stopped here. DBG_OBJ_MSGF ("construct.word", 1, "lines[%d]->lastOofRef = %d < %d => stop", i, line->lastOofRefPositionedBeforeThisLine, wordIndex); break; } else { DBG_OBJ_MSGF ("construct.word", 1, "adding %d to lines[%d]->lastOofRef...: %d -> %d", num, i, line->lastOofRefPositionedBeforeThisLine, line->lastOofRefPositionedBeforeThisLine + num); line->lastOofRefPositionedBeforeThisLine += num; } } // Unlike the last line, the last paragraph is already constructed. (To // make sure we cover all cases, we iterate over the last paragraphs.) Paragraph *par; for (int parNo = paragraphs->size () - 1; parNo >= 0 && (par = paragraphs->getRef(parNo)) && par->lastWord > wordIndex; parNo--) { par->lastWord += num; if (par->firstWord > wordIndex) par->firstWord += num; } // Addiditional indices. When needed, the number can be extended. if (addIndex1 && *addIndex1 >= wordIndex) *addIndex1 += num; DBG_OBJ_LEAVE (); } void Textblock::accumulateWordForLine (int lineIndex, int wordIndex) { DBG_OBJ_ENTER ("construct.line", 1, "accumulateWordForLine", "%d, %d", lineIndex, wordIndex); DBG_MSG_WORD ("construct.line", 2, "word: ", wordIndex, ""); Line *line = lines->getRef (lineIndex); Word *word = words->getRef (wordIndex); int len = word->style->font->ascent; if (word->style->valign == core::style::VALIGN_SUPER) len += len / 2; line->contentAscent = max (line->contentAscent, len); len = word->style->font->descent; if (word->style->valign == core::style::VALIGN_SUB) len += word->style->font->ascent / 3; line->contentDescent = max (line->contentDescent, len); int borderAscent, borderDescent, marginAscent, marginDescent; DBG_OBJ_MSGF ("construct.line", 2, "size.ascent = %d, size.descent = %d", word->size.ascent, word->size.descent); if (word->content.type == core::Content::WIDGET_IN_FLOW) { // TODO Consider extraSpace? marginAscent = word->size.ascent; marginDescent = word->size.descent; borderAscent = marginAscent - word->content.widget->getStyle()->margin.top; borderDescent = marginDescent - word->content.widget->getStyle()->margin.bottom; word->content.widget->parentRef = makeParentRefInFlow (lineIndex); DBG_OBJ_SET_NUM_O (word->content.widget, "parentRef", word->content.widget->parentRef); } else { borderAscent = marginAscent = word->size.ascent; borderDescent = marginDescent = word->size.descent; if (word->content.type == core::Content::BREAK) line->breakSpace = max (word->content.breakSpace, line->breakSpace); else if (word->content.type == core::Content::WIDGET_OOF_REF) { word->content.widgetReference->parentRef = makeParentRefInFlow (lineIndex); DBG_SET_WORD (wordIndex); } } DBG_OBJ_MSGF ("construct.line", 2, "borderAscent = %d, borderDescent = %d, marginAscent = %d, " "marginDescent = %d", borderAscent, borderDescent, marginAscent, marginDescent); line->borderAscent = max (line->borderAscent, borderAscent); line->borderDescent = max (line->borderDescent, borderDescent); line->marginAscent = max (line->marginAscent, marginAscent); line->marginDescent = max (line->marginDescent, marginDescent); DBG_OBJ_LEAVE (); } void Textblock::accumulateWordData (int wordIndex) { DBG_OBJ_ENTER ("construct.word.accum", 1, "accumulateWordData", "%d", wordIndex); DBG_MSG_WORD ("construct.word.accum", 1, "word: ", wordIndex, ""); // Typically, the word in question is in the last line; in any case // quite at the end of the text, so that linear search is actually // the fastest option. int lineIndex = lines->size (); while (lineIndex > 0 && wordIndex <= lines->getRef(lineIndex - 1)->lastWord) lineIndex--; int firstWordOfLine; if (lineIndex == 0) firstWordOfLine = 0; else firstWordOfLine = lines->getRef(lineIndex - 1)->lastWord + 1; Word *word = words->getRef (wordIndex); DBG_OBJ_MSGF ("construct.word.accum", 2, "lineIndex = %d", lineIndex); int lineBreakWidth = calcLineBreakWidth (lineIndex); DBG_OBJ_MSGF ("construct.word.accum", 2, "(%s existing line %d starts with word %d; " "lineBreakWidth = %d)", lineIndex < lines->size () ? "already" : "not yet", lineIndex, firstWordOfLine, lineBreakWidth); if (wordIndex == firstWordOfLine) { // first word of the (not neccessarily yet existing) line word->totalWidth = word->size.width + word->hyphenWidth; word->maxAscent = word->size.ascent; word->maxDescent = word->size.descent; word->totalSpaceStretchability = 0; word->totalSpaceShrinkability = 0; DBG_OBJ_MSGF ("construct.word.accum", 1, "first word of line: words[%d].totalWidth = %d + %d = %d; " "maxAscent = %d, maxDescent = %d", wordIndex, word->size.width, word->hyphenWidth, word->totalWidth, word->maxAscent, word->maxDescent); } else { Word *prevWord = words->getRef (wordIndex - 1); word->totalWidth = prevWord->totalWidth + prevWord->origSpace - prevWord->hyphenWidth + word->size.width + word->hyphenWidth; word->maxAscent = max (prevWord->maxAscent, word->size.ascent); word->maxDescent = max (prevWord->maxDescent, word->size.descent); word->totalSpaceStretchability = prevWord->totalSpaceStretchability + getSpaceStretchability(prevWord); word->totalSpaceShrinkability = prevWord->totalSpaceShrinkability + getSpaceShrinkability(prevWord); DBG_OBJ_MSGF ("construct.word.accum", 1, "not first word of line: words[%d].totalWidth = %d + %d - " "%d + %d + %d = %d; maxAscent = max (%d, %d) = %d, " "maxDescent = max (%d, %d) = %d", wordIndex, prevWord->totalWidth, prevWord->origSpace, prevWord->hyphenWidth, word->size.width, word->hyphenWidth, word->totalWidth, prevWord->maxAscent, word->size.ascent, word->maxAscent, prevWord->maxDescent, word->size.descent, word->maxDescent); } int totalStretchability = word->totalSpaceStretchability + getLineStretchability (wordIndex); int totalShrinkability = word->totalSpaceShrinkability + getLineShrinkability (wordIndex); DBG_OBJ_MSGF ("construct.word.accum", 1, "totalStretchability = %d + ... = %d", word->totalSpaceStretchability, totalStretchability); DBG_OBJ_MSGF ("construct.word.accum", 1, "totalShrinkability = %d + ... = %d", word->totalSpaceShrinkability, totalShrinkability); word->badnessAndPenalty.calcBadness (word->totalWidth, lineBreakWidth, totalStretchability, totalShrinkability); DBG_IF_RTFL { StringBuffer sb; word->badnessAndPenalty.intoStringBuffer (&sb); DBG_OBJ_ARRATTRSET_SYM ("words", wordIndex, "badnessAndPenalty", sb.getChars ()); } DBG_OBJ_LEAVE (); } int Textblock::calcLineBreakWidth (int lineIndex) { DBG_OBJ_ENTER ("construct.word.width", 1, "calcLineBreakWidth", "%d of %d", lineIndex, lines->size()); int lineBreakWidth = this->lineBreakWidth - leftInnerPadding; if (limitTextWidth && layout->getUsesViewport () && // margin/border/padding will be subtracted later, via OOFM. lineBreakWidth - boxDiffWidth() > layout->getWidthViewport () - 10) lineBreakWidth = layout->getWidthViewport () - 10; if (lineIndex == 0) lineBreakWidth -= line1OffsetEff; int leftBorder, rightBorder; if (mustBorderBeRegarded (lineIndex)) { leftBorder = newLineLeftBorder; rightBorder = newLineRightBorder; } else leftBorder = rightBorder = 0; leftBorder = max (leftBorder, boxOffsetX()); rightBorder = max (rightBorder, boxRestWidth()); lineBreakWidth -= (leftBorder + rightBorder); DBG_OBJ_LEAVE_VAL ("%d - %d - (%d + %d) = %d", this->lineBreakWidth, leftInnerPadding, leftBorder, rightBorder, lineBreakWidth); return lineBreakWidth; } void Textblock::initLine1Offset (int wordIndex) { Word *word = words->getRef (wordIndex); /* Test whether line1Offset can be used. */ if (wordIndex == 0) { if (ignoreLine1OffsetSometimes && line1Offset + word->size.width > lineBreakWidth) { line1OffsetEff = 0; } else { int indent = 0; if (word->content.type == core::Content::WIDGET_IN_FLOW && word->content.widget->isBlockLevel()) { /* don't use text-indent when nesting blocks */ } else { if (core::style::isPerLength(getStyle()->textIndent)) { indent = core::style::multiplyWithPerLengthRounded (lineBreakWidth, getStyle()->textIndent); } else { indent = core::style::absLengthVal (getStyle()->textIndent); } } line1OffsetEff = line1Offset + indent; } } } /** * Align the line. * * \todo Use block's style instead once paragraphs become proper blocks. */ void Textblock::alignLine (int lineIndex) { DBG_OBJ_ENTER ("construct.line", 0, "alignLine", "%d", lineIndex); Line *line = lines->getRef (lineIndex); for (int i = line->firstWord; i <= line->lastWord; i++) words->getRef(i)->effSpace = words->getRef(i)->origSpace; // We are not interested in the alignment of floats etc. int firstWordNotOofRef = line->firstWord; while (firstWordNotOofRef <= line->lastWord && words->getRef(firstWordNotOofRef)->content.type == core::Content::WIDGET_OOF_REF) firstWordNotOofRef++; if (firstWordNotOofRef <= line->lastWord) { Word *firstWord = words->getRef (firstWordNotOofRef); if (firstWord->content.type != core::Content::BREAK) { Word *lastWord = words->getRef (line->lastWord); int lineBreakWidth = this->lineBreakWidth - (line->leftOffset + line->rightOffset); switch (firstWord->style->textAlign) { case core::style::TEXT_ALIGN_LEFT: DBG_OBJ_MSG ("construct.line", 1, "first word has 'text-align: left'"); line->alignment = Line::LEFT; break; case core::style::TEXT_ALIGN_STRING: /* handled elsewhere (in the * future)? */ DBG_OBJ_MSG ("construct.line", 1, "first word has 'text-align: string'"); line->alignment = Line::LEFT; break; case core::style::TEXT_ALIGN_JUSTIFY: /* see some lines above */ DBG_OBJ_MSG ("construct.line", 1, "first word has 'text-align: justify'"); line->alignment = Line::LEFT; // Do not justify the last line of a paragraph (which ends on a // BREAK or with the last word of the page). if(!(lastWord->content.type == core::Content::BREAK || line->lastWord == words->size () - 1) || // In some cases, however, an unjustified line would be too wide: // when the line would be shrunken otherwise. (This solution is // far from perfect, but a better solution would make changes in // the line breaking algorithm necessary.) lineBreakWidth < lastWord->totalWidth) justifyLine (line, lineBreakWidth - lastWord->totalWidth); break; case core::style::TEXT_ALIGN_RIGHT: DBG_OBJ_MSG ("construct.line", 1, "first word has 'text-align: right'"); line->alignment = Line::RIGHT; break; case core::style::TEXT_ALIGN_CENTER: DBG_OBJ_MSG ("construct.line", 1, "first word has 'text-align: center'"); line->alignment = Line::CENTER; break; default: // compiler happiness line->alignment = Line::LEFT; } } else // empty line (only line break); line->alignment = Line::LEFT; } else // empty line (or only OOF references). line->alignment = Line::LEFT; DBG_OBJ_LEAVE (); } void Textblock::calcTextOffset (int lineIndex, int totalWidth) { DBG_OBJ_ENTER ("construct.line", 0, "calcTextOffset", "%d, %d", lineIndex, totalWidth); Line *line = lines->getRef (lineIndex); int lineWidth = line->firstWord <= line->lastWord ? words->getRef(line->lastWord)->totalWidth : 0; switch (line->alignment) { case Line::LEFT: line->textOffset = line->leftOffset; DBG_OBJ_MSGF ("construct.line", 1, "left: textOffset = %d", line->textOffset); break; case Line::RIGHT: line->textOffset = totalWidth - line->rightOffset - lineWidth; DBG_OBJ_MSGF ("construct.line", 1, "right: textOffset = %d - %d - %d = %d", totalWidth, line->rightOffset, lineWidth, line->textOffset); break; case Line::CENTER: line->textOffset = (line->leftOffset + totalWidth - line->rightOffset - lineWidth) / 2; DBG_OBJ_MSGF ("construct.line", 1, "center: textOffset = (%d + %d - %d - %d) /2 = %d", line->leftOffset, totalWidth, line->rightOffset, lineWidth, line->textOffset); break; default: assertNotReached (); break; } // For large lines (images etc), which do not fit into the viewport: if (line->textOffset < line->leftOffset) line->textOffset = line->leftOffset; DBG_OBJ_ARRATTRSET_NUM ("lines", lineIndex, "textOffset", line->textOffset); DBG_OBJ_LEAVE (); } /** * 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 () { DBG_OBJ_ENTER0 ("construct.line", 0, "rewrap"); if (wrapRefLines == -1) DBG_OBJ_MSG ("construct.line", 0, "does not have to be rewrapped"); else { // All lines up from wrapRef will be rebuild from the word list, // the line list up from this position is rebuild. lines->setSize (wrapRefLines); DBG_OBJ_SET_NUM ("lines.size", lines->size ()); nonTemporaryLines = min (nonTemporaryLines, wrapRefLines); initNewLine (); int firstWord; if (lines->size () > 0) { Line *lastLine = lines->getLastRef(); firstWord = lastLine->lastWord + 1; } else firstWord = 0; DBG_OBJ_MSGF ("construct.line", 0, "starting with word %d", firstWord); lastWordDrawn = min (lastWordDrawn, firstWord - 1); DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); for (int i = firstWord; i < words->size (); i++) { Word *word = words->getRef (i); switch (word->content.type) { case core::Content::WIDGET_IN_FLOW: calcSizeOfWidgetInFlow (i, word->content.widget, &word->size); DBG_SET_WORD_SIZE (i); break; case core::Content::WIDGET_OOF_REF: { int oofmIndex = getOOFMIndex (word->content.widgetReference->widget); oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex); oofm->calcWidgetRefSize (word->content.widgetReference->widget, &(word->size)); DBG_SET_WORD_SIZE (i); } break; default: break; } wordWrap (i, false); // Somewhat historical, but still important, note: // // For the case that something else is done with this word, it // is important that wordWrap() may insert some new words; since // NotSoSimpleVector is used for the words list, the internal // structure may have changed, so getRef() must be called again. // // So this is necessary: word = words->getRef (i); } // Next time, the page will not have to be rewrapped. wrapRefLines = -1; DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); } DBG_OBJ_LEAVE (); } /** * Counter part to rewrap(), but for extremes, not size calculation. */ void Textblock::fillParagraphs () { DBG_OBJ_ENTER0 ("resize", 0, "fillParagraphs"); DBG_OBJ_MSGF ("resize", 1, "wrapRefParagraphs = %d", wrapRefParagraphs); if (wrapRefParagraphs != -1) { // Notice that wrapRefParagraphs refers to the lines, not to the // paragraphs. int firstWordOfLine; if (lines->size () > 0 && wrapRefParagraphs > 0) { // Sometimes, wrapRefParagraphs is larger than lines->size(), due to // floats? (Has to be clarified.) int lineNo = min (wrapRefParagraphs, lines->size ()) - 1; firstWordOfLine = lines->getRef(lineNo)->lastWord + 1; } else firstWordOfLine = 0; int parNo; if (paragraphs->size() > 0 && firstWordOfLine > paragraphs->getLastRef()->firstWord) // A special case: the paragraphs list has been partly built, but // not yet the paragraph containing the word in question. In // this case, only the rest of the paragraphs list must be // constructed. (Without this check, findParagraphOfWord would // return -1 in this case, so that all paragraphs would be // rebuilt.) parNo = paragraphs->size (); else // If there are no paragraphs yet, findParagraphOfWord will return // -1: use 0 then instead. parNo = max (0, findParagraphOfWord (firstWordOfLine)); paragraphs->setSize (parNo); DBG_OBJ_SET_NUM ("paragraphs.size", paragraphs->size ()); int firstWord; if (paragraphs->size () > 0) firstWord = paragraphs->getLastRef()->lastWord + 1; else firstWord = 0; DBG_OBJ_MSGF ("resize", 1, "firstWord = %d, words->size() = %d [before]", firstWord, words->size ()); for (int i = firstWord; i < words->size (); i++) handleWordExtremes (i); DBG_OBJ_MSGF ("resize", 1, "words->size() = %d [after]", words->size ()); wrapRefParagraphs = -1; DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); } DBG_OBJ_LEAVE (); } void Textblock::initNewLine () { DBG_OBJ_ENTER0 ("construct.line", 0, "initNewLine"); calcBorders (lines->size() > 0 ? lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1, 1); newLineAscent = newLineDescent = 0; DBG_OBJ_SET_NUM ("newLineAscent", newLineAscent); DBG_OBJ_SET_NUM ("newLineDescent", newLineDescent); DBG_OBJ_LEAVE (); } void Textblock::calcBorders (int lastOofRef, int height) { DBG_OBJ_ENTER ("construct.line", 0, "calcBorders", "%d, %d", lastOofRef, height); newLineHasFloatLeft = newLineHasFloatRight = false; newLineLeftBorder = newLineRightBorder = 0; newLineLeftFloatHeight = newLineRightFloatHeight = 0; bool oofmDefined = false; for (int i = 0; i < NUM_OOFM && !oofmDefined; i++) if (findSizeRequestReference (i)) oofmDefined = true; if (oofmDefined) { int firstWordOfLine = lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0; int effOofRef = max (lastOofRef, firstWordOfLine - 1); int yRel = yOffsetOfLineToBeCreated (), yRef; for (int i = 0; i < NUM_OOFM; i++) { oof::OutOfFlowMgr *oofm; if ((oofm = searchOutOfFlowMgr (i)) && findSizeRequestReference (i, NULL, &yRef)) { // Consider the example: // //
// Some text A ... //

Some text B ...

// Some more text C ... //
// // If the image is large enough, it should float around the last // paragraph, "Some more text C ...": // // Some more text A ... // // Some more ,---------. // text B ... | | // | | // Some more | | <---- Consider this line! // text C ... '---------' // // Since this float is generated in the

element, not in the- //

element, and since they are represented by different // instances of dw::Textblock, lastOofRefPositionedBeforeThisLine, // and so lastOofRef, is -1 for the line marked with an arrow; // this would result in ignoring the float, because -1 is // equivalent to the very beginning of the
element ("Some // more text A ..."), which is not affected by the float. // // On the other hand, the only relevant values of // Line::lastOofRefPositionedBeforeThisLine are those greater // than the first word of the new line, so a solution is to use // the maximum of both. int y = yRef + yRel; bool thisHasLeft, thisHasRight; thisHasLeft = oofm->hasFloatLeft (y, height, this, effOofRef); newLineHasFloatLeft = newLineHasFloatLeft || thisHasLeft; thisHasRight = oofm->hasFloatRight (y, height, this, effOofRef); newLineHasFloatRight = newLineHasFloatRight || thisHasRight; newLineLeftBorder = max (newLineLeftBorder, oofm->getLeftBorder (y, height, this, effOofRef) - getGeneratorX (i)); newLineRightBorder = max (newLineRightBorder, oofm->getRightBorder (y, height, this, effOofRef) - getGeneratorRest (i)); // TODO "max" is not really correct for the heights. (Does // not matter, since only one, the float manager, returns // meaningful values.) if (thisHasLeft) newLineLeftFloatHeight = max (newLineLeftFloatHeight, oofm->getLeftFloatHeight (y, height, this, effOofRef)); if (thisHasRight) newLineRightFloatHeight = max (newLineRightFloatHeight, oofm->getRightFloatHeight (y, height, this, effOofRef)); DBG_OBJ_MSGF ("construct.line", 1, "OOFM #%d: %d * %d (%s) / %d * %d (%s), at %d (%d), " "until %d = max (%d, %d - 1)", i, newLineLeftBorder, newLineLeftFloatHeight, newLineHasFloatLeft ? "true" : "false", newLineRightBorder, newLineRightFloatHeight, newLineHasFloatRight ? "true" : "false", y, height, effOofRef, lastOofRef, firstWordOfLine); } } } DBG_OBJ_SET_BOOL ("newLineHasFloatLeft", newLineHasFloatLeft); DBG_OBJ_SET_BOOL ("newLineHasFloatRight", newLineHasFloatRight); DBG_OBJ_SET_NUM ("newLineLeftBorder", newLineLeftBorder); DBG_OBJ_SET_NUM ("newLineRightBorder", newLineRightBorder); DBG_OBJ_SET_NUM ("newLineLeftFloatHeight", newLineLeftFloatHeight); DBG_OBJ_SET_NUM ("newLineRightFloatHeight", newLineRightFloatHeight); DBG_OBJ_LEAVE (); } void Textblock::showMissingLines () { DBG_OBJ_ENTER0 ("construct.line", 0, "showMissingLines"); // "Temporary word": when the last word is an OOF reference, it is // not processed, and not part of any line. For this reason, we // introduce a "temporary word", which is in flow, after this last // OOF reference, and later removed again. bool tempWord = words->size () > 0 && words->getLastRef()->content.type == core::Content::WIDGET_OOF_REF; int firstWordToWrap = lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0; DBG_OBJ_MSGF ("construct.line", 1, "words->size() = %d, firstWordToWrap = %d, tempWord = %s", words->size (), firstWordToWrap, tempWord ? "true" : "false"); if (tempWord) { core::Requisition size = { 0, 0, 0 }; addText0 ("", 0, Word::WORD_START | Word::WORD_END, getStyle (), &size); } for (int i = firstWordToWrap; i < words->size (); i++) wordWrap (i, true); // Remove temporary word again. The only reference should be the line. if (tempWord) { cleanupWord (words->size () - 1); words->setSize (words->size () - 1); if (lines->getLastRef()->lastWord > words->size () - 1) lines->getLastRef()->lastWord = words->size () - 1; } // The following old code should not be necessary anymore, after // the introduction of the "virtual word". Instead, test the // condition. assert (lines->size () == 0 || lines->getLastRef()->lastWord == words->size () - 1); /* // In some cases, there are some words of type WIDGET_OOF_REF left, which // are not added to line, since addLine() is only called within // wrapWordInFlow(), but not within wrapWordOofRef(). The missing line // is created here, so it is ensured that the last line ends with the last // word. int firstWordNotInLine = lines->size () > 0 ? lines->getLastRef()->lastWord + 1: 0; DBG_OBJ_MSGF ("construct.line", 1, "firstWordNotInLine = %d (of %d)", firstWordNotInLine, words->size ()); if (firstWordNotInLine < words->size ()) addLine (firstWordNotInLine, words->size () - 1, -1, true); */ DBG_OBJ_LEAVE (); } void Textblock::removeTemporaryLines () { DBG_OBJ_ENTER0 ("construct.line", 0, "removeTemporaryLines"); if (nonTemporaryLines < lines->size ()) { lines->setSize (nonTemporaryLines); DBG_OBJ_SET_NUM ("lines.size", lines->size ()); // For words which will be added, the values calculated before in // accumulateWordData() are wrong, so it is called again. (Actually, the // words from the first temporary line are correct, but for simplicity, // we re-calculate all.) int firstWord = lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0; for (int i = firstWord; i < words->size (); i++) accumulateWordData (i); } DBG_OBJ_LEAVE (); } int Textblock::getSpaceShrinkability(struct Word *word) { if (word->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) return word->origSpace / 3; else return 0; } int Textblock::getSpaceStretchability(struct Word *word) { if (word->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) return word->origSpace / 2; else return 0; // Alternative: return word->origSpace / 2; } int Textblock::getLineShrinkability(int lastWordIndex) { return 0; } int Textblock::getLineStretchability(int lastWordIndex) { DBG_OBJ_ENTER ("construct.word.accum", 0, "getLineStretchability", "%d", lastWordIndex); DBG_MSG_WORD ("construct.word.accum", 1, "last word: ", lastWordIndex, ""); Word *lastWord = words->getRef (lastWordIndex); int str; if (lastWord->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) { str = 0; DBG_OBJ_MSG ("construct.word.accum", 1, "justified => 0"); } else { str = stretchabilityFactor * (lastWord->maxAscent + lastWord->maxDescent) / 100; DBG_OBJ_MSGF ("construct.word.accum", 1, "not justified => %d * (%d + %d) / 100 = %d", stretchabilityFactor, lastWord->maxAscent, lastWord->maxDescent, str); } DBG_OBJ_LEAVE (); return str; // Alternative: return 0; } } // namespace dw