summaryrefslogtreecommitdiffstats
path: root/lib/kotext/KoTextFormatter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kotext/KoTextFormatter.cpp')
-rw-r--r--lib/kotext/KoTextFormatter.cpp1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/lib/kotext/KoTextFormatter.cpp b/lib/kotext/KoTextFormatter.cpp
new file mode 100644
index 000000000..7670dff69
--- /dev/null
+++ b/lib/kotext/KoTextFormatter.cpp
@@ -0,0 +1,1069 @@
+/* This file is part of the KDE project
+ Copyright (C) 2001 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include "KoTextFormatter.h"
+#include "KoTextParag.h"
+#include "KoTextFormat.h"
+#include "KoTextDocument.h"
+#include "KoTextZoomHandler.h"
+#include "kohyphen/kohyphen.h"
+#include "KoParagCounter.h"
+
+#include <kdebug.h>
+#include <assert.h>
+
+//#define DEBUG_FORMATTER
+
+// Vertical info (height, baseline etc.)
+//#define DEBUG_FORMATTER_VERT
+
+// Line and paragraph width
+//#define DEBUG_FORMATTER_WIDTH
+
+// Hyphenation
+//#define DEBUG_HYPHENATION
+
+/////// keep in sync with kotextformat.cc !
+//#define REF_IS_LU
+
+KoTextFormatter::KoTextFormatter()
+{
+ try {
+ m_hyphenator = KoHyphenator::self();
+ } catch ( KoHyphenatorException& e )
+ {
+ m_hyphenator = 0L;
+ }
+}
+
+KoTextFormatter::~KoTextFormatter()
+{
+}
+
+// Hyphenation can break anywhere in the word, so
+// remember the temp data for every char.
+struct TemporaryWordData
+{
+ int baseLine;
+ int height;
+ int lineWidth; // value of wused
+};
+
+bool KoTextFormatter::format( KoTextDocument *doc, KoTextParag *parag,
+ int start, const QMap<int, KoTextParagLineStart*> &,
+ int& y, int& widthUsed )
+{
+ KoTextFormatterCore formatter( this, doc, parag, start );
+ bool worked = formatter.format();
+ y = formatter.resultY();
+ widthUsed = formatter.widthUsed();
+ return worked;
+}
+
+KoTextFormatterCore::KoTextFormatterCore( KoTextFormatter* _settings,
+ KoTextDocument *_doc, KoTextParag *_parag,
+ int _start )
+ : settings(_settings), doc(_doc), parag(_parag), start(_start)
+{
+}
+
+QPair<int, int> KoTextFormatterCore::determineCharWidth()
+{
+ int ww, pixelww;
+ KoTextZoomHandler *zh = doc->formattingZoomHandler();
+ if ( c->c != '\t' || c->isCustom() ) {
+ KoTextFormat *charFormat = c->format();
+ if ( c->isCustom() ) {
+ ww = c->customItem()->width;
+ Q_ASSERT( ww >= 0 );
+ ww = QMAX(0, ww);
+#ifndef REF_IS_LU
+ pixelww = zh->layoutUnitToPixelX( ww );
+#endif
+ } else {
+ ww = charFormat->charWidthLU( c, parag, i );
+#ifndef REF_IS_LU
+ // Pixel size - we want the metrics of the font that's going to be used.
+ pixelww = charFormat->charWidth( zh, true, c, parag, i );
+#endif
+ }
+ } else { // tab
+ int nx = parag->nextTab( i, x, availableWidth );
+ if ( nx < x )
+ ww = availableWidth - x;
+ else
+ ww = nx - x;
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "nextTab for x=" << x << " returned nx=" << nx << " (=> ww=" << ww << ")" << endl;
+#endif
+#ifndef REF_IS_LU
+ pixelww = zh->layoutUnitToPixelX( ww );
+#endif
+ }
+ Q_ASSERT( ww >= 0 );
+ c->width = ww;
+ return qMakePair(ww, pixelww);
+}
+
+
+int KoTextFormatterCore::leftMargin( bool firstLine, bool includeFirstLineMargin /* = true */ ) const
+{
+ int left = /*doc ?*/ parag->leftMargin() + doc->leftMargin() /*: 0*/;
+ if ( firstLine && !parag->string()->isRightToLeft() )
+ {
+ if ( includeFirstLineMargin )
+ left += parag->firstLineMargin();
+ // Add the width of the paragraph counter - first line of parag only.
+ if( parag->counter() &&
+ ( parag->counter()->alignment() == Qt::AlignLeft ||
+ parag->counter()->alignment() == Qt::AlignAuto ) )
+ left += parag->counterWidth(); // in LU pixels
+ }
+ return left;
+}
+
+int KoTextFormatterCore::rightMargin( bool firstLine ) const
+{
+ int right = parag->rightMargin(); // 'rm' in QRT
+ if ( /*doc &&*/ firstLine && parag->string()->isRightToLeft() )
+ right += parag->firstLineMargin();
+ return right;
+}
+
+bool KoTextFormatterCore::format()
+{
+ start = 0; // we don't do partial formatting yet
+ KoTextString *string = parag->string();
+ if ( start == 0 )
+ c = &string->at( start );
+ else
+ c = 0;
+
+ KoTextStringChar *firstChar = 0;
+ int left = doc ? leftMargin( true, false ) : 0;
+ int initialLMargin = leftMargin( true );
+
+ y = parag->breakableTopMargin();
+ // #57555, top margin doesn't apply if parag at top of page
+ // (but a portion of the margin can be needed, to complete the prev page)
+ // So we apply formatVertically() on the top margin, to find where to break it.
+ if ( !parag->prev() )
+ y = 0; // no top margin on very first parag
+ else if ( y )
+ {
+ int shift = doc->flow()->adjustFlow( parag->rect().y(),
+ 0 /*w, unused*/,
+ y );
+ if ( shift > 0 )
+ {
+ // The shift is in fact the amount of top-margin that should remain
+ // The remaining portion should be eaten away.
+ y = shift;
+ }
+
+ }
+ // Now add the rest of the top margin (e.g. the one for the border)
+ y += parag->topMargin() - parag->breakableTopMargin();
+ int len = parag->length();
+
+ int initialHeight = c->height(); // remember what adjustMargins was called with
+
+ int currentRightMargin = rightMargin( true );
+ int initialRMargin = currentRightMargin;
+ // Those three things must be done before calling determineCharWidth
+ i = start;
+ parag->tabCache().clear();
+ x = 0;
+
+ // We need the width of the first char for adjustMargins
+ // The result might not be 100% accurate when using a tab (it'll use x=0
+ // but with counters/margins this might be different). This is why
+ // we call determineCharWidth() again from within the loop.
+ QPair<int, int> widths = determineCharWidth();
+ int ww = widths.first; // width in layout units
+#ifndef REF_IS_LU
+ int pixelww = widths.second; // width in pixels
+#endif
+
+ // dw is the document width, i.e. the maximum available width, all included.
+ // We are in a variable-width design, so it is returned by each call to adjustMargins.
+ int dw = 0;
+ //if (doc) // always true in kotext
+ doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight, // input params
+ ww, initialLMargin, initialRMargin, dw, // output params
+ parag );
+ //else dw = parag->documentVisibleWidth();
+
+ x = initialLMargin; // as modified by adjustMargins
+
+ int maxY = doc ? doc->flow()->availableHeight() : -1;
+
+ availableWidth = dw - initialRMargin; // 'w' in QRT
+#if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH)
+ kdDebug(32500) << "KoTextFormatter::format formatting parag " << parag->paragId()
+ << " text:" << parag->string()->toString() << "\n"
+ << " left=" << left << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " availableWidth=" << availableWidth << " maxY=" << maxY << endl;
+#else
+ if ( availableWidth == 0 )
+ kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, availableWidth=0" << endl;
+ if ( maxY == 0 )
+ kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, maxY=0" << endl;
+#endif
+ bool fullWidth = TRUE;
+ //int marg = left + initialRMargin;
+
+ // minw is the really minimum width needed for this paragraph, i.e.
+ // the width of the longest set of non-breakable characters together.
+ // Currently unused.
+ //int minw = 0;
+
+ wused = 0;
+
+ QValueList<TemporaryWordData> tempWordData;
+
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Initial KoTextParagLineStart at y=" << y << endl;
+#endif
+ KoTextParagLineStart *lineStart = new KoTextParagLineStart( y, 0, 0 );
+ parag->insertLineStart( 0, lineStart );
+ int lastBreak = -1;
+ // tmph, tmpBaseLine and tminw are used after the last breakable char
+ // we don't know yet if we'll break there, or later.
+ int tmpBaseLine = 0, tmph = 0;
+ //int tminw = marg;
+ int tmpWused = 0;
+ bool lastWasNonInlineCustom = FALSE;
+ bool abort = false;
+
+ int align = parag->alignment();
+ if ( align == Qt::AlignAuto && doc && doc->alignment() != Qt::AlignAuto )
+ align = doc->alignment();
+
+ int col = 0;
+
+ maxAvailableWidth = qMakePair( 0, 0 );
+
+ KoTextZoomHandler *zh = doc->formattingZoomHandler();
+ int pixelx = zh->layoutUnitToPixelX( x );
+ int lastPixelx = 0;
+
+ KoTextStringChar* lastChr = 0;
+ for ( ; i < len; ++i, ++col ) {
+ if ( c )
+ lastChr = c;
+ c = &string->at( i );
+ if ( i > 0 && (x > initialLMargin || ww == 0) || lastWasNonInlineCustom ) {
+ c->lineStart = 0;
+ } else {
+ c->lineStart = 1;
+ firstChar = c;
+ tmph = c->height();
+ tmpBaseLine = c->ascent();
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << "New line, initializing tmpBaseLine=" << tmpBaseLine << " tmph=" << tmph << endl;
+#endif
+ }
+
+ if ( c->isCustom() && c->customItem()->placement() != KoTextCustomItem::PlaceInline )
+ lastWasNonInlineCustom = TRUE;
+ else
+ lastWasNonInlineCustom = FALSE;
+
+ QPair<int, int> widths = determineCharWidth();
+ ww = widths.first;
+ pixelww = widths.second;
+
+ // We're "aborting" the formatting. This still means we need to set the
+ // lineStart bools to false (trouble ahead, otherwise!), and while we're at
+ // it we also calculate the widths etc.
+ if ( abort ) {
+ x += ww;
+ c->x = x;
+ continue; // yeah, this seems a bit confusing :)
+ }
+
+ //code from qt-3.1beta2
+ if ( c->isCustom() && c->customItem()->ownLine() ) {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "i=" << i << "/" << len << " custom item with ownline" << endl;
+#endif
+ int rightMargin = currentRightMargin;
+ x = left;
+ if ( doc )
+ doc->flow()->adjustMargins( y + parag->rect().y(), parag->rect().height(), 15,
+ x, rightMargin, dw, parag );
+ int w = dw - rightMargin;
+ c->customItem()->resize( w - x );
+ y += lineStart->h;
+ lineStart = new KoTextParagLineStart( y, c->ascent(), c->height() );
+ // Added for kotext (to be tested)
+ lineStart->lineSpacing = doc ? parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, i, i ) : 0;
+ lineStart->h += lineStart->lineSpacing;
+ lineStart->w = dw;
+ parag->insertLineStart( i, lineStart );
+ tempWordData.clear();
+ c->lineStart = 1;
+ firstChar = c;
+ x = 0xffffff;
+ // Hmm, --i or setting lineStart on next char too?
+ continue;
+ }
+
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "c='" << QString(c->c) << "' i=" << i << "/" << len << " x=" << x << " ww=" << ww << " availableWidth=" << availableWidth << " (test is x+ww>aW) lastBreak=" << lastBreak << " isBreakable=" << settings->isBreakable(string, i) << endl;
+#endif
+ // Wrapping at end of line - one big if :)
+ if (
+ // Check if should break (i.e. we are after the max X for the end of the line)
+ ( ( /*wrapAtColumn() == -1 &&*/ x + ww > availableWidth &&
+ ( lastBreak != -1 || settings->allowBreakInWords() ) )
+
+ // Allow two breakable chars next to each other (e.g. ' ') but not more
+ && ( !settings->isBreakable( string, i ) ||
+ ( i > 1 && lastBreak == i-1 && settings->isBreakable( string, i-2 ) ) ||
+ lastBreak == -2 ) // ... used to be a special case...
+
+ // No point in breaking just for the trailing space (testcase: page numbers in TOC)
+ && ( i < len-1 )
+
+ // Ensure that there is at least one char per line, otherwise, on
+ // a very narrow document and huge chars, we could loop forever.
+ // checkVerticalBreak takes care of moving down the lines where no
+ // char should be, anyway.
+ // Hmm, it doesn't really do so. To be continued...
+ /////////// && ( firstChar != c )
+
+ )
+ // Or maybe we simply encountered a '\n'
+ || ( lastChr && lastChr->c == '\n' && parag->isNewLinesAllowed() && lastBreak > -1 ) )
+ {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "BREAKING" << endl;
+#endif
+ //if ( wrapAtColumn() != -1 )
+ // minw = QMAX( minw, x + ww );
+
+ bool hyphenated = false;
+ // Hyphenation: check if we can break somewhere between lastBreak and i
+ if ( settings->hyphenator() && !c->isCustom() )
+ {
+ int wordStart = QMAX(0, lastBreak+1);
+ // Breaking after i isn't possible, i is too far already
+ int maxlen = i - wordStart; // we can't accept to break after maxlen
+ QString word = string->mid( wordStart, maxlen );
+ int wordEnd = i;
+ // but we need to compose the entire word, to hyphenate it
+ while ( wordEnd < len && !settings->isBreakable( string, wordEnd ) ) {
+ word += string->at(wordEnd).c;
+ wordEnd++;
+ }
+ if ( word.length() > 1 ) // don't call the hyphenator for empty or one-letter words
+ {
+ QString lang = string->at(wordStart).format()->language();
+ char * hyphens = settings->hyphenator()->hyphens( word, lang );
+#if defined(DEBUG_HYPHENATION)
+ kdDebug(32500) << "Hyphenation: word=" << word << " lang=" << lang << " hyphens=" << hyphens << " maxlen=" << maxlen << endl;
+ kdDebug(32500) << "Parag indexes: wordStart=" << wordStart << " lastBreak=" << lastBreak << " i=" << i << endl;
+#endif
+ int hylen = strlen(hyphens);
+ Q_ASSERT( maxlen <= hylen );
+ // If this word was already hyphenated (at the previous line),
+ // don't break it there again. We can only break after firstChar.
+ int minPos = QMAX( 0, (firstChar - &string->at(0)) - wordStart );
+
+ // Check hyphenation positions from the end
+ for ( int hypos = maxlen-1 ; hypos >= minPos ; --hypos )
+ if ( ( hyphens[hypos] % 2 ) // odd number -> can break there...
+ && string->at(hypos + wordStart).format()->hyphenation() ) // ...if the user is ok with that
+ {
+ lineStart->hyphenated = true;
+ lastBreak = hypos + wordStart;
+ hyphenated = true;
+#if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH) || defined(DEBUG_HYPHENATION)
+ kdDebug(32500) << "Hyphenation: will break at " << lastBreak << " using tempworddata at position " << hypos << "/" << tempWordData.size() << endl;
+#endif
+ if ( hypos < (int)tempWordData.size() )
+ {
+ const TemporaryWordData& twd = tempWordData[ hypos ];
+ lineStart->baseLine = twd.baseLine;
+ lineStart->h = twd.height;
+ tmpWused = twd.lineWidth;
+ }
+ break;
+ }
+ delete[] hyphens;
+ }
+ }
+
+ // No breakable char found -> break at current char (i.e. before 'i')
+ if ( lastBreak < 0 ) {
+ // Remember if this is the start of a line; testing c->lineStart after breaking
+ // is always true...
+
+ // "Empty line" can happen when there is a very wide character (e.g. inline table),
+ // or a very narrow passage between frames.
+ // But in fact the second case is already handled by KWTextFrameSet's "no-space case",
+ // so we don't come here in that case.
+ const bool emptyLine = c->lineStart;
+ if ( !emptyLine && i > 0 )
+ {
+ // (combine lineStart->baseLine/lineStart->h and tmpBaseLine/tmph)
+ int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
+ lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
+ lineStart->h = lineStart->baseLine + belowBaseLine;
+ lineStart->w = dw;
+
+ KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c-1, align, availableWidth - x );
+ y += lineStart->h;
+ lineStart = lineStart2;
+#ifdef DEBUG_FORMATTER
+ int linenr = parag->lineStartList().count()-1;
+ kdDebug(32500) << "line " << linenr << " done (breaking at current char). y now " << y << endl;
+#endif
+ tmph = c->height();
+
+ initialRMargin = currentRightMargin;
+ x = left;
+ if ( doc )
+ doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
+ ww, // ## correct?
+ x, initialRMargin, dw, parag );
+
+ pixelx = zh->layoutUnitToPixelX( x );
+ initialHeight = tmph;
+ initialLMargin = x;
+ availableWidth = dw - initialRMargin;
+ if ( parag->isNewLinesAllowed() && c->c == '\t' ) {
+ int nx = parag->nextTab( i, x, availableWidth );
+ if ( nx < x )
+ ww = availableWidth - x;
+ else
+ ww = nx - x;
+ }
+ if ( x != left || availableWidth != dw )
+ fullWidth = FALSE;
+ lineStart->y = y;
+ parag->insertLineStart( i, lineStart );
+ tempWordData.clear();
+ lineStart->baseLine = c->ascent();
+ lineStart->h = c->height();
+ c->lineStart = 1;
+ firstChar = c;
+ tmpBaseLine = lineStart->baseLine;
+ lastBreak = -1;
+ col = 0;
+ //tminw = marg; // not in QRT?
+ tmpWused = 0;
+ }
+ // recalc everything for 'i', it might still not be ok where it is...
+ // (e.g. if there's no room at all on this line)
+ // But we don't want to do this forever, so we check against maxY (if known)
+ // [except if we come here after "final choice for empty line"!]
+ if ( !emptyLine && maxY > -1 )
+ {
+ if ( parag->rect().y() + y < maxY )
+ {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Re-checking formatting for character " << i << endl;
+#endif
+ --i; // so that the ++i in for() is a noop
+ continue;
+ }
+ else // we're after maxY, time to stop.
+ {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "We're after maxY, time to stop." << endl;
+#endif
+ // No solution for now. Hopefully KWord will create more pages...
+ abort = true;
+ }
+ }
+ // maxY not known (or "final choice for empty line") -> keep going ('i' remains where it is)
+ // (in case of maxY not known, this is the initial QRT behaviour)
+ } else {
+ // If breaking means we're after maxY, then we won't do it.
+ // Hopefully KWord will create more pages.
+ if ( maxY > -1 && parag->rect().y() + y + lineStart->h >= maxY ) {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "We're after maxY, time to stop." << endl;
+#endif
+ abort = true;
+ }
+ else
+ {
+ // Break the line at the last breakable character
+ i = lastBreak;
+ c = &string->at( i ); // The last char in the last line
+ int spaceAfterLine = availableWidth - c->x;
+ // ?? AFAICS we should always deduce the char's width from the available space....
+ //if ( string->isRightToLeft() && lastChr->c == '\n' )
+ spaceAfterLine -= c->width;
+
+ //else
+ if ( c->c.unicode() == 0xad || hyphenated ) // soft hyphen or hyphenation
+ {
+ // Recalculate its width, the hyphen will appear finally (important for the parag rect)
+ int width = KoTextZoomHandler::ptToLayoutUnitPt( c->format()->refFontMetrics().width( QChar(0xad) ) );
+ if ( c->c.unicode() == 0xad )
+ c->width = width;
+ spaceAfterLine -= width;
+ }
+ KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, spaceAfterLine );
+ lineStart->w = dw;
+ y += lineStart->h;
+ lineStart = lineStart2;
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Breaking at a breakable char (" << i << "). linenr=" << parag->lineStartList().count()-1 << " y=" << y << endl;
+#endif
+
+ c = &string->at( i + 1 ); // The first char in the new line
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Next line will start at i+1=" << i+1 << ", char=" << QString(c->c) << endl;
+#endif
+ tmph = c->height();
+
+ initialRMargin = currentRightMargin;
+ x = left;
+ if ( doc )
+ doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
+ c->width,
+ x, initialRMargin, dw, parag );
+
+ pixelx = zh->layoutUnitToPixelX( x );
+ initialHeight = tmph;
+ initialLMargin = x;
+ availableWidth = dw - initialRMargin;
+ if ( x != left || availableWidth != dw )
+ fullWidth = FALSE;
+ lineStart->y = y;
+ parag->insertLineStart( i + 1, lineStart );
+ tempWordData.clear();
+ lineStart->baseLine = c->ascent();
+ lineStart->h = c->height();
+ firstChar = c;
+ tmpBaseLine = lineStart->baseLine;
+ lastBreak = -1;
+ col = 0;
+ //tminw = marg;
+ tmpWused = 0;
+ c->lineStart = 1; // only do this if we will actually create a line for it
+ continue;
+ }
+ }
+ } else if ( lineStart && ( settings->isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) {
+ // Breakable character
+ if ( len <= 2 || i < len - 1 ) {
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "):"
+ << " combining " << tmpBaseLine << "/" << tmph
+ << " with " << c->ascent() << "/" << c->height() << endl;
+#endif
+ // (combine tmpBaseLine/tmph and this character)
+ int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
+ tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
+ tmph = tmpBaseLine + belowBaseLine;
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
+#endif
+ }
+ tempWordData.clear();
+ //minw = QMAX( minw, tminw );
+ //tminw = marg + ww;
+ wused = QMAX( wused, tmpWused );
+#ifdef DEBUG_FORMATTER_WIDTH
+ kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "): wused=" << wused << endl;
+#endif
+ tmpWused = 0;
+ // (combine lineStart and tmpBaseLine/tmph)
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << "Breakable character: combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
+#endif
+ int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
+ lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
+ lineStart->h = lineStart->baseLine + belowBaseLine;
+ lineStart->w = dw;
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " -> line baseLine/height : " << lineStart->baseLine << "/" << lineStart->h << endl;
+#endif
+ // if h > initialHeight, call adjustMargins, and if the result is != initial[LR]Margin,
+ // format this line again
+ if ( doc && lineStart->h > initialHeight )
+ {
+ bool firstLine = ( firstChar == &string->at( 0 ) );
+ int newLMargin = leftMargin( firstLine );
+ int newRMargin = rightMargin( firstLine );
+ int newPageWidth = dw;
+ initialHeight = lineStart->h;
+ doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight,
+ firstChar->width,
+ newLMargin, newRMargin, newPageWidth, parag );
+
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "new height: " << initialHeight << " => left=" << left << " first-char=" << (firstChar==&string->at(0)) << " newLMargin=" << newLMargin << " newRMargin=" << newRMargin << endl;
+#endif
+ if ( newLMargin != initialLMargin || newRMargin != initialRMargin || newPageWidth != dw )
+ {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "formatting again" << endl;
+#endif
+ i = (firstChar - &string->at(0));
+ x = newLMargin;
+ pixelx = zh->layoutUnitToPixelX( x );
+ availableWidth = dw - newRMargin;
+ initialLMargin = newLMargin;
+ initialRMargin = newRMargin;
+ dw = newPageWidth;
+ c = &string->at( i );
+ tmph = c->height();
+ tmpBaseLine = c->ascent();
+ lineStart->h = tmph;
+ lineStart->baseLine = tmpBaseLine;
+ lastBreak = -1;
+ col = 0;
+ //minw = x;
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Restarting with i=" << i << " x=" << x << " y=" << y << " tmph=" << tmph << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " y=" << y << endl;
+#endif
+ // ww and pixelww already calculated and stored, no need to duplicate
+ // code like QRT does.
+ ww = c->width;
+#ifndef REF_IS_LU
+ pixelww = c->pixelwidth;
+#endif
+ //tminw = x + ww;
+ tmpWused = 0;
+ }
+ }
+
+ //kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
+ if ( i < len - 2 || c->c != ' ' )
+ lastBreak = i;
+
+ } else if ( i < len - 1 ) { // ignore height of trailing space
+ // Non-breakable character
+ //tminw += ww;
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " Non-breakable character: combining " << tmpBaseLine << "/" << tmph << " with " << c->ascent() << "/" << c->height() << endl;
+#endif
+ // (combine tmpBaseLine/tmph and this character)
+ int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
+ tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
+ tmph = tmpBaseLine + belowBaseLine;
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
+#endif
+
+ TemporaryWordData twd;
+ twd.baseLine = tmpBaseLine;
+ twd.height = tmph;
+ twd.lineWidth = tmpWused;
+ tempWordData.append( twd );
+ }
+
+ c->x = x;
+ // pixelxadj is the adjustement to add to lu2pixel(x), to find pixelx
+ // (pixelx would be too expensive to store directly since it would require an int)
+ c->pixelxadj = pixelx - zh->layoutUnitToPixelX( x );
+ //c->pixelwidth = pixelww; // done as pixelx - lastPixelx below
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "LU: x=" << x << " [equiv. to pix=" << zh->layoutUnitToPixelX( x ) << "] ; PIX: x=" << pixelx << " --> adj=" << c->pixelxadj << endl;
+#endif
+
+ x += ww;
+
+ if ( i > 0 )
+ lastChr->pixelwidth = pixelx - lastPixelx;
+ if ( i < len - 1 )
+ tmpWused = QMAX( tmpWused, x );
+ else // trailing space
+ c->pixelwidth = zh->layoutUnitToPixelX( ww ); // was: pixelww;
+
+ lastPixelx = pixelx;
+#ifdef REF_IS_LU
+ pixelx = zh->layoutUnitToPixelX( x ); // no accumulating rounding errors anymore
+#else
+ pixelx += pixelww;
+#endif
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "LU: added " << ww << " -> now x=" << x << " ; PIX: added " << pixelww << " -> now pixelx=" << pixelx << endl;
+#endif
+ }
+
+ // ### hack. The last char in the paragraph is always invisible, and somehow sometimes has a wrong format. It changes between
+ // layouting and printing. This corrects some layouting errors in BiDi mode due to this.
+ if ( len > 1 /*&& !c->isAnchor()*/ ) {
+ c->format()->removeRef();
+ c->setFormat( string->at( len - 2 ).format() );
+ c->format()->addRef();
+ }
+
+ // Finish formatting the last line
+ if ( lineStart ) {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "Last Line.... linenr=" << (int)parag->lineStartList().count()-1 << endl;
+#endif
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << "Last Line... Combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
+#endif
+ // (combine lineStart and tmpBaseLine/tmph)
+ int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
+ lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
+ lineStart->h = lineStart->baseLine + belowBaseLine;
+ lineStart->w = dw;
+#ifdef DEBUG_FORMATTER_WIDTH
+ kdDebug(32500) << "Last line: w = dw = " << dw << endl;
+#endif
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
+#endif
+ // last line in a paragraph is not justified
+ if ( align == Qt::AlignJustify )
+ align = Qt::AlignAuto;
+ int space = availableWidth - x + c->width; // don't count the trailing space (it breaks e.g. centering)
+ KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, space );
+ delete lineStart2;
+ }
+
+ //minw = QMAX( minw, tminw );
+ wused = QMAX( wused, tmpWused );
+#ifdef DEBUG_FORMATTER_WIDTH
+ kdDebug(32500) << "Done, wused=" << wused << endl;
+#endif
+
+ int m = parag->bottomMargin();
+ // ##### Does OOo add margins or does it max them?
+ //if ( parag->next() && doc && !doc->addMargins() )
+ // m = QMAX( m, parag->next()->topMargin() );
+ parag->setFullWidth( fullWidth );
+ //if ( parag->next() && parag->next()->isLineBreak() )
+ // m = 0;
+#ifdef DEBUG_FORMATTER_VERT
+ kdDebug(32500) << "Adding height of last line(" << lineStart->h << ") and bottomMargin(" << m << ") to y(" << y << ") => " << y+lineStart->h+m << endl;
+#endif
+ y += lineStart->h + m;
+
+ tmpWused += currentRightMargin; // ### this can break with a variable right-margin
+ //if ( wrapAtColumn() != -1 )
+ // minw = QMAX(minw, wused);
+ //thisminw = minw;
+
+#ifdef DEBUG_FORMATTER
+ // Sanity checking
+ int numberOfLines = 0;
+ QString charPosList;
+ for ( int i = 0 ; i < len; ++i ) {
+ KoTextStringChar *chr = &string->at( i );
+ if ( i == 0 )
+ assert( chr->lineStart );
+ if ( chr->lineStart ) {
+ ++numberOfLines;
+ charPosList += QString::number(i) + " ";
+ }
+ }
+ kdDebug(32500) << parag->lineStartList().count() << " lines. " << numberOfLines << " chars with lineStart set: " << charPosList << endl;
+ assert( numberOfLines == (int)parag->lineStartList().count() );
+#endif
+ return !abort;
+}
+
+// Helper for koFormatLine and koBidiReorderLine
+void KoTextFormatterCore::moveChar( KoTextStringChar& chr, KoTextZoomHandler *zh,
+ int deltaX, int deltaPixelX )
+{
+#ifndef REF_IS_LU
+ int pixelx = chr.pixelxadj + zh->layoutUnitToPixelX( chr.x );
+#endif
+ chr.x += deltaX;
+#ifndef REF_IS_LU
+ chr.pixelxadj = pixelx + deltaPixelX - zh->layoutUnitToPixelX( chr.x );
+#endif
+}
+
+KoTextParagLineStart *KoTextFormatterCore::koFormatLine(
+ KoTextZoomHandler *zh,
+ KoTextParag *parag, KoTextString *string, KoTextParagLineStart *line,
+ KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
+{
+ KoTextParagLineStart* ret = 0;
+ if( string->isBidi() ) {
+ ret = koBidiReorderLine( zh, parag, string, line, startChar, lastChar, align, space );
+ } else {
+ int start = (startChar - &string->at(0));
+ int last = (lastChar - &string->at(0) );
+
+ if (space < 0)
+ space = 0;
+
+ // do alignment Auto == Left in this case
+ if ( align & Qt::AlignHCenter || align & Qt::AlignRight ) {
+ if ( align & Qt::AlignHCenter )
+ space /= 2;
+ int toAddPix = zh->layoutUnitToPixelX( space );
+ for ( int j = last; j >= start; --j ) {
+ KoTextStringChar &chr = string->at( j );
+ moveChar( chr, zh, space, toAddPix );
+ }
+ } else if ( align & Qt::AlignJustify ) {
+ int numSpaces = 0;
+ // End at "last-1", the last space ends up with a width of 0
+ for ( int j = last-1; j >= start; --j ) {
+ //// Start at last tab, if any. BR #40472 specifies that justifying should start after the last tab.
+ if ( string->at( j ).c == '\t' ) {
+ start = j+1;
+ break;
+ }
+ if( settings->isStretchable( string, j ) ) {
+ numSpaces++;
+ }
+ }
+ int toAdd = 0;
+ int toAddPix = 0;
+ for ( int k = start + 1; k <= last; ++k ) {
+ KoTextStringChar &chr = string->at( k );
+ if ( toAdd != 0 )
+ moveChar( chr, zh, toAdd, toAddPix );
+ if( settings->isStretchable( string, k ) && numSpaces ) {
+ int s = space / numSpaces;
+ toAdd += s;
+ toAddPix = zh->layoutUnitToPixelX( toAdd );
+ space -= s;
+ numSpaces--;
+ chr.width += s;
+#ifndef REF_IS_LU
+ chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
+#endif
+ }
+ }
+ }
+ int current=0;
+ int nc=0; // Not double, as we check it against 0 and to avoid gcc warnings
+ KoTextFormat refFormat( *string->at(0).format() ); // we need a ref format, doesn't matter where it comes from
+ for(int i=start;i<=last;++i)
+ {
+ KoTextFormat* format=string->at(i).format();
+ // End of underline
+ if ( (((!format->underline())&&
+ (!format->doubleUnderline())&&
+ (!format->waveUnderline())&&
+ (format->underlineType()!=KoTextFormat::U_SIMPLE_BOLD))
+ || i == last)
+ && nc )
+ {
+ double avg=static_cast<double>(current)/nc;
+ avg/=18.0;
+ // Apply underline width "avg" from i-nc to i
+ refFormat.setUnderLineWidth( avg );
+ parag->setFormat( i-nc, i, &refFormat, true, KoTextFormat::UnderLineWidth );
+ nc=0;
+ current=0;
+ }
+ // Inside underline
+ else if(format->underline()||
+ format->waveUnderline()||
+ format->doubleUnderline()||
+ (format->underlineType() == KoTextFormat::U_SIMPLE_BOLD))
+ {
+ ++nc;
+ current += format->pointSize(); //pointSize() is independent of {Sub,Super}Script in contrast to height()
+ }
+ }
+#if 0
+ if ( last >= 0 && last < string->length() ) {
+ KoTextStringChar &chr = string->at( last );
+ line->w = chr.x + chr.width; //string->width( last );
+ // Add width of hyphen (so that it appears)
+ if ( line->hyphenated )
+ line->w += KoTextZoomHandler::ptToLayoutUnitPt( chr.format()->refFontMetrics().width( QChar(0xad) ) );
+ } else
+ line->w = 0;
+#endif
+
+ ret = new KoTextParagLineStart();
+ }
+
+ // Now calculate and add linespacing
+ const int start = (startChar - &string->at(0));
+ const int last = (lastChar - &string->at(0) );
+ line->lineSpacing = parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, start, last );
+ line->h += line->lineSpacing;
+
+ return ret;
+}
+
+// collects one line of the paragraph and transforms it to visual order
+KoTextParagLineStart *KoTextFormatterCore::koBidiReorderLine(
+ KoTextZoomHandler *zh,
+ KoTextParag * /*parag*/, KoTextString *text, KoTextParagLineStart *line,
+ KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
+{
+ // This comes from Qt (3.3.x) but seems wrong: the last space is where we draw
+ // the "end of paragraph" sign, so it needs to be correctly positioned too.
+#if 0
+ // ignore white space at the end of the line.
+ int endSpaces = 0;
+ while ( lastChar > startChar && lastChar->whiteSpace ) {
+ space += lastChar->format()->width( ' ' );
+ --lastChar;
+ ++endSpaces;
+ }
+#endif
+
+ int start = (startChar - &text->at(0));
+ int last = (lastChar - &text->at(0) );
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "*KoTextFormatter::koBidiReorderLine from " << start << " to " << last << " space=" << space << " startChar->x=" << startChar->x << endl;
+#endif
+ KoBidiControl *control = new KoBidiControl( line->context(), line->status );
+ QString str;
+ str.setUnicode( 0, last - start + 1 );
+ // fill string with logically ordered chars.
+ KoTextStringChar *ch = startChar;
+ QChar *qch = (QChar *)str.unicode();
+ while ( ch <= lastChar ) {
+ *qch = ch->c;
+ qch++;
+ ch++;
+ }
+ int x = startChar->x;
+
+ QPtrList<KoTextRun> *runs;
+ runs = KoComplexText::bidiReorderLine(control, str, 0, last - start + 1,
+ (text->isRightToLeft() ? QChar::DirR : QChar::DirL) );
+
+ // now construct the reordered string out of the runs...
+
+ int numSpaces = 0;
+ // set the correct alignment. This is a bit messy....
+ if( align == Qt::AlignAuto ) {
+ // align according to directionality of the paragraph...
+ if ( text->isRightToLeft() )
+ align = Qt::AlignRight;
+ }
+
+ if ( align & Qt::AlignHCenter ) {
+ x += space/2;
+ } else if ( align & Qt::AlignRight ) {
+ x += space;
+ } else if ( align & Qt::AlignJustify ) {
+ for ( int j = last - 1; j >= start; --j ) {
+ //// Start at last tab, if any. BR #40472 specifies that justifying should start after the last tab.
+ if ( text->at( j ).c == '\t' ) {
+ start = j+1;
+ break;
+ }
+ if( settings->isStretchable( text, j ) ) {
+ numSpaces++;
+ }
+ }
+ }
+// TODO #ifndef REF_IS_LU or remove
+ int pixelx = zh->layoutUnitToPixelX( x );
+ int toAdd = 0;
+ int toAddPix = 0;
+ bool first = TRUE;
+ KoTextRun *r = runs->first();
+ int xmax = -0xffffff;
+ while ( r ) {
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "koBidiReorderLine level: " << r->level << endl;
+#endif
+ if(r->level %2) {
+ // odd level, need to reverse the string
+ int pos = r->stop + start;
+ while(pos >= r->start + start) {
+ KoTextStringChar &chr = text->at(pos);
+ if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
+ int s = space / numSpaces;
+ toAdd += s;
+ toAddPix = zh->layoutUnitToPixelX( toAdd );
+ space -= s;
+ numSpaces--;
+ chr.width += s;
+ chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
+ } else if ( first ) {
+ first = FALSE;
+ if ( chr.c == ' ' ) // trailing space
+ {
+ //x -= chr.format()->width( ' ' );
+ x -= chr.width;
+ pixelx -= chr.pixelwidth;
+ }
+ }
+ chr.x = x + toAdd;
+ chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << "koBidiReorderLine: pos=" << pos << " x(LU)=" << x << " toAdd(LU)=" << toAdd << " -> chr.x=" << chr.x << " pixelx=" << pixelx << "+" << zh->layoutUnitToPixelX( toAdd ) << ", pixelxadj=" << pixelx+zh->layoutUnitToPixelX( toAdd )-zh->layoutUnitToPixelX( chr.x ) << endl;
+#endif
+ chr.rightToLeft = TRUE;
+ chr.startOfRun = FALSE;
+ int ww = chr.width;
+ if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
+ x += ww;
+ pixelx += chr.pixelwidth;
+#ifdef DEBUG_FORMATTER
+ kdDebug(32500) << " ww=" << ww << " adding to x, now " << x << ". pixelwidth=" << chr.pixelwidth << " adding to pixelx, now " << pixelx << " xmax=" << xmax << endl;
+#endif
+ pos--;
+ }
+ } else {
+ int pos = r->start + start;
+ while(pos <= r->stop + start) {
+ KoTextStringChar& chr = text->at(pos);
+ if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
+ int s = space / numSpaces;
+ toAdd += s;
+ toAddPix = zh->layoutUnitToPixelX( toAdd );
+ space -= s;
+ numSpaces--;
+ } else if ( first ) {
+ first = FALSE;
+ if ( chr.c == ' ' ) // trailing space
+ {
+ //x -= chr.format()->width( ' ' );
+ x -= chr.width;
+ pixelx -= chr.pixelwidth;
+ }
+ }
+ chr.x = x + toAdd;
+ chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
+ chr.rightToLeft = FALSE;
+ chr.startOfRun = FALSE;
+ int ww = chr.width;
+ //kdDebug(32500) << "setting char " << pos << " at pos " << chr.x << endl;
+ if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
+ x += ww;
+ pixelx += chr.pixelwidth;
+ pos++;
+ }
+ }
+ text->at( r->start + start ).startOfRun = TRUE;
+ r = runs->next();
+ }
+
+ //line->w = xmax /*+ 10*/; // Why +10 ?
+ KoTextParagLineStart *ls = new KoTextParagLineStart( control->context, control->status );
+ delete control;
+ delete runs;
+ return ls;
+}
+
+void KoTextFormatter::postFormat( KoTextParag* parag )
+{
+ parag->fixParagWidth( viewFormattingChars() );
+}