/* 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., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include "kotextformatter.h"
#include "kotextparag.h"
#include "kotextformat.h"
#include "kotextdocument.h"
#include "kozoomhandler.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;
    KoZoomHandler *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 );
        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 ) const
{
    int left = /*doc ?*/ parag->leftMargin() + doc->leftMargin() /*: 0*/;
    if ( firstLine && !parag->string()->isRightToLeft() )
    {
        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 ? parag->leftMargin() + doc->leftMargin() : 0;
    int initialLMargin = leftMargin( true );

    y = doc && doc->addMargins() ? parag->topMargin() : 0;
    // #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 ( parag->breakableTopMargin() )
    {
        int shift = doc->flow()->adjustFlow( parag->rect().y(),
                                             0 /*w, unused*/,
                                             parag->breakableTopMargin() );
        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 );

    KoZoomHandler *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->lineSpacing( (int)parag->lineStartList().count()-1 ) : 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->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() )
            {
                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_FORMATTER) || 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 ) {
                bool formatLine = true;
                if ( c->lineStart ) // ouch, empty line
                {
                    // Remember where the biggest availableWidth was, so that if we really
                    // find nowhere for this big character, we'll come back here.
                    if ( availableWidth > maxAvailableWidth.second )
                    {
                        maxAvailableWidth.first = y;
                        maxAvailableWidth.second = availableWidth;
                    }
                    // Check if we're at the bottom of the doc, we won't find better then
                    // (and the check further down would abort)
                    // Instead we go back to where there was most width for it.
                    if ( maxY > -1 && parag->rect().y() + y >= maxY )
                    {
                        kdDebug(32500) << parag->rect().y() + y << " over maxY=" << maxY
                                       << " -> final choice for the line: y=" << maxAvailableWidth.first << endl;
                        y = maxAvailableWidth.first;
                        if ( availableWidth )
                            Q_ASSERT( maxAvailableWidth.second != 0 );
                        lineStart->y = y;
                        // In this case we want to actually format the line,
                        // so we don't set emptyLine
                    }
                    else
                    {
                        // We don't know yet what to do with this line that needs to go down
                        // Ideally KWTextFrameSet would tell us how much we need to move
                        // ("validHeight" idea). For now we keep the old behavior:
                        y += tmph;
                        kdDebug(32500) << "KoTextFormatter: moving down empty line by h=" << tmph << ": y=" << y << endl;
                        formatLine = false; // line is not ready yet
                    }
                }
                if ( formatLine && 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 );
                    lineStart->lineSpacing = parag->lineSpacing( (int)parag->lineStartList().count()-1 );
                    lineStart->h += lineStart->lineSpacing;
                    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 );
                        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)
                if ( formatLine && maxY > -1 )
                {
                    if ( parag->rect().y() + y < maxY )
                    {
                        --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 -> keep going ('i' remains where it is)
                // (that's 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->lineSpacing = doc ? parag->lineSpacing( (int)parag->lineStartList().count()-1 ) : 0;
                    lineStart->w = dw;
                    lineStart->h += lineStart->lineSpacing;
                    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 {
            // 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 );
        lineStart->lineSpacing = doc ? parag->lineSpacing( (int)parag->lineStartList().count()-1 ) : 0;
        lineStart->h += lineStart->lineSpacing;
        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, KoZoomHandler *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(
    KoZoomHandler *zh,
    KoTextParag *parag, KoTextString *string, KoTextParagLineStart *line,
    KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
{
    if( string->isBidi() )
        return koBidiReorderLine( zh, parag, string, line, startChar, lastChar, align, space );
    int start = (startChar - &string->at(0));
    int last = (lastChar - &string->at(0) );

#if 0 // strange Qt code, breaks right-alignment
    KoTextStringChar *ch = lastChar;
    while ( ch > startChar && ch->whiteSpace ) {
        space += ch->format()->width( ' ' ); // wrong unit, in any case
        --ch;
    }
#endif

    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 );
            //// Start at last tab, if any - BR #40472.
            if ( chr.c == '\t' ) {
                break;
            }
            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

    return new KoTextParagLineStart();
}

// collects one line of the paragraph and transforms it to visual order
KoTextParagLineStart *KoTextFormatterCore::koBidiReorderLine(
    KoZoomHandler *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() );
}
