/*
 * Dillo Widget
 *
 * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org>
 *
 * (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 <http://www.gnu.org/licenses/>.
 */


#include "textblock.hh"
#include "hyphenator.hh"
#include "../lout/msg.h"
#include "../lout/debug.hh"
#include "../lout/misc.hh"

#include <stdio.h>
#include <math.h>

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)&nbsp;A perfectly
 * fitting line has a badness of 0. (ii)&nbsp;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, "<i>first word:</i> ", firstWord, "");
      DBG_MSG_WORD ("construct.line", 1, "<i>last word:</i> ", 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;
   DBG_OBJ_SET_BOOL ("mustQueueResize", mustQueueResize);

   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, "<i>processed word:</i>", 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 happened. 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, "<i>processed word now:</i>",
                    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, "<i>wrapped word:</i> ", 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, "<b>new line:</b> 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, "<b>new line:</b> 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,
                         "<b>new line:</b> line too tight");
         } else {
            DBG_OBJ_MSG ("construct.word", 1, "no <b>new line</b>");
            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;
         DBG_OBJ_SET_BOOL ("mustQueueResize", mustQueueResize);
      }

      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, "<i>floatHandled loop cycle</i>");
            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, "<i>first word:</i> ", firstIndex, "");

   int result;
   bool lineAdded;

   do {
      DBG_OBJ_MSG ("construct.word", 1, "<i>searchBreakPos loop cycle</i>");
      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, "<i>break at word:</i> ",
                       breakPos, "");
         DBG_OBJ_MSGF ("construct.word", 1, "hyphenatedWord = %d",
                       hyphenatedWord);
         if (hyphenatedWord != -1)
            DBG_MSG_WORD ("construct.word", 1,
                          "<i>hyphenate at word:</i> ",
                          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>i. e.:</i> ", 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,
                 "<i>handled word:</i> ", 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 = new core::Requisition[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 widgets 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);
      delete[] wordSize;
   } 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;
   }

   // Additional 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, "<i>word:</i> ", 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, "<i>word:</i> ", 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 necessarily 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 <i>of %d</i>", 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);

         core::style::TextAlignType alignStyle = core::style::TEXT_ALIGN_LEFT;

         /**
          * Only inline elements should be affected by the text-align property.
          * While alignStyle will still apply to other elements, the default
          * value of TEXT_ALIGN_LEFT will have no effect on the alignment.
          */
         if (firstWord->style->display == core::style::DISPLAY_INLINE ||
             firstWord->style->display == core::style::DISPLAY_INLINE_BLOCK) {
            /**
             * Elements that *are* affected by text-align are aligned based on
             * the text-align value of their containing element.
             */
            alignStyle = getStyle()->textAlign;
         }

         switch (alignStyle) {
         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:
            //
            // <div>
            //   Some text A ...
            //   <p> Some text B ... <img style="float:right" ...> </p>
            //   Some more text C ...
            // </div>
            //
            // 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 ... |         |
            //               |  <img>  |
            //    Some more  |         | <---- Consider this line!
            //    text C ... '---------'
            //
            // Since this float is generated in the <p> element, not in the-
            // <div> 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 <div> 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;
                       
            // TODO "max" is not really correct for the heights. (Does
            // not matter, since only one, the float manager, returns
            // meaningful values.)
            if (thisHasLeft) {
               newLineLeftBorder =
                  max (newLineLeftBorder,
                       oofm->getLeftBorder (y, height, this, effOofRef)
                       - getGeneratorX (i));
               newLineLeftFloatHeight =
                  max (newLineLeftFloatHeight,
                       oofm->getLeftFloatHeight (y, height, this, effOofRef));
            }

            if (thisHasRight) {
               newLineRightBorder =
                  max (newLineRightBorder,
                       oofm->getRightBorder (y, height, this, effOofRef)
                       - getGeneratorRest (i));
               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, "<i>last word:</i> ",
                 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
