2295 lines
85 KiB
C++
2295 lines
85 KiB
C++
/*
|
|
* 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) 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, "<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 = std::max (regardBorder ? newLineLeftBorder : 0,
|
|
boxOffsetX () + leftInnerPadding
|
|
+ (lineIndex == 0 ? line1OffsetEff : 0));
|
|
line->rightOffset = std::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 = std::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 = std::max (line->borderAscent, minHeight);
|
|
line->marginAscent = std::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 =
|
|
std::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 = std::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 = std::max (std::min (newLineLeftFloatHeight,
|
|
newLineRightFloatHeight),
|
|
1);
|
|
else if (newLineHasFloatLeft && !newLineHasFloatRight)
|
|
minHeight = std::max (newLineLeftFloatHeight, 1);
|
|
else if (!newLineHasFloatLeft && newLineHasFloatRight)
|
|
minHeight = std::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 = std::max (ascent, word->size.ascent);
|
|
descent = std::max (descent, word->size.descent);
|
|
}
|
|
|
|
return std::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 = std::max (lastPar->maxParMin, lastPar->parMin);
|
|
lastPar->maxParMinIntrinsic =
|
|
std::max (lastPar->maxParMinIntrinsic, lastPar->parMinIntrinsic);
|
|
lastPar->maxParAdjustmentWidth =
|
|
std::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 = std::max (lastPar->maxParMax, lastPar->parMax);
|
|
lastPar->maxParMaxIntrinsic =
|
|
std::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;
|
|
|
|
std::vector< core::Requisition > wordSize( numBreaks + 1 );
|
|
calcTextSizes (origWord.content.text, strlen (origWord.content.text),
|
|
origWord.style, numBreaks, breakPos, wordSize.data());
|
|
|
|
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.at (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);
|
|
} 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 = std::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 = std::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 = std::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 = std::max (line->borderAscent, borderAscent);
|
|
line->borderDescent = std::max (line->borderDescent, borderDescent);
|
|
line->marginAscent = std::max (line->marginAscent, marginAscent);
|
|
line->marginDescent = std::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 = std::max (prevWord->maxAscent, word->size.ascent);
|
|
word->maxDescent = std::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 = std::max (leftBorder, boxOffsetX());
|
|
rightBorder = std::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 = std::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 = std::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 = std::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 = std::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 = std::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 =
|
|
std::max (newLineLeftBorder,
|
|
oofm->getLeftBorder (y, height, this, effOofRef)
|
|
- getGeneratorX (i));
|
|
newLineLeftFloatHeight =
|
|
std::max (newLineLeftFloatHeight,
|
|
oofm->getLeftFloatHeight (y, height, this, effOofRef));
|
|
}
|
|
|
|
if (thisHasRight) {
|
|
newLineRightBorder =
|
|
std::max (newLineRightBorder,
|
|
oofm->getRightBorder (y, height, this, effOofRef)
|
|
- getGeneratorRest (i));
|
|
newLineRightFloatHeight =
|
|
std::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
|