lib

KoTextFormatter.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2001 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "KoTextFormatter.h"
00021 #include "KoTextParag.h"
00022 #include "KoTextFormat.h"
00023 #include "KoTextDocument.h"
00024 #include "KoTextZoomHandler.h"
00025 #include "kohyphen/kohyphen.h"
00026 #include "KoParagCounter.h"
00027 
00028 #include <kdebug.h>
00029 #include <assert.h>
00030 
00031 //#define DEBUG_FORMATTER
00032 
00033 // Vertical info (height, baseline etc.)
00034 //#define DEBUG_FORMATTER_VERT
00035 
00036 // Line and paragraph width
00037 //#define DEBUG_FORMATTER_WIDTH
00038 
00039 // Hyphenation
00040 //#define DEBUG_HYPHENATION
00041 
00043 //#define REF_IS_LU
00044 
00045 KoTextFormatter::KoTextFormatter()
00046 {
00047     try {
00048         m_hyphenator = KoHyphenator::self();
00049     } catch ( KoHyphenatorException& e )
00050     {
00051         m_hyphenator = 0L;
00052     }
00053 }
00054 
00055 KoTextFormatter::~KoTextFormatter()
00056 {
00057 }
00058 
00059 // Hyphenation can break anywhere in the word, so
00060 // remember the temp data for every char.
00061 struct TemporaryWordData
00062 {
00063     int baseLine;
00064     int height;
00065     int lineWidth; // value of wused
00066 };
00067 
00068 bool KoTextFormatter::format( KoTextDocument *doc, KoTextParag *parag,
00069                               int start, const QMap<int, KoTextParagLineStart*> &,
00070                               int& y, int& widthUsed )
00071 {
00072     KoTextFormatterCore formatter( this, doc, parag, start );
00073     bool worked = formatter.format();
00074     y = formatter.resultY();
00075     widthUsed = formatter.widthUsed();
00076     return worked;
00077 }
00078 
00079 KoTextFormatterCore::KoTextFormatterCore( KoTextFormatter* _settings,
00080                                           KoTextDocument *_doc, KoTextParag *_parag,
00081                                           int _start )
00082     : settings(_settings), doc(_doc), parag(_parag), start(_start)
00083 {
00084 }
00085 
00086 QPair<int, int> KoTextFormatterCore::determineCharWidth()
00087 {
00088     int ww, pixelww;
00089     KoTextZoomHandler *zh = doc->formattingZoomHandler();
00090     if ( c->c != '\t' || c->isCustom() ) {
00091         KoTextFormat *charFormat = c->format();
00092         if ( c->isCustom() ) {
00093             ww = c->customItem()->width;
00094             Q_ASSERT( ww >= 0 );
00095             ww = QMAX(0, ww);
00096 #ifndef REF_IS_LU
00097             pixelww = zh->layoutUnitToPixelX( ww );
00098 #endif
00099         } else {
00100             ww = charFormat->charWidthLU( c, parag, i );
00101 #ifndef REF_IS_LU
00102             // Pixel size - we want the metrics of the font that's going to be used.
00103             pixelww = charFormat->charWidth( zh, true, c, parag, i );
00104 #endif
00105         }
00106     } else { // tab
00107         int nx = parag->nextTab( i, x, availableWidth );
00108         if ( nx < x )
00109             ww = availableWidth - x;
00110         else
00111             ww = nx - x;
00112 #ifdef DEBUG_FORMATTER
00113         kdDebug(32500) << "nextTab for x=" << x << " returned nx=" << nx << "  (=> ww=" << ww << ")" << endl;
00114 #endif
00115 #ifndef REF_IS_LU
00116         pixelww = zh->layoutUnitToPixelX( ww );
00117 #endif
00118     }
00119     Q_ASSERT( ww >= 0 );
00120     c->width = ww;
00121     return qMakePair(ww, pixelww);
00122 }
00123 
00124 
00125 int KoTextFormatterCore::leftMargin( bool firstLine, bool includeFirstLineMargin /* = true */ ) const
00126 {
00127     int left = /*doc ?*/ parag->leftMargin() + doc->leftMargin() /*: 0*/;
00128     if ( firstLine && !parag->string()->isRightToLeft() )
00129     {
00130         if ( includeFirstLineMargin )
00131             left += parag->firstLineMargin();
00132         // Add the width of the paragraph counter - first line of parag only.
00133         if( parag->counter() &&
00134             ( parag->counter()->alignment() == Qt::AlignLeft ||
00135               parag->counter()->alignment() == Qt::AlignAuto ) )
00136             left += parag->counterWidth(); // in LU pixels
00137     }
00138     return left;
00139 }
00140 
00141 int KoTextFormatterCore::rightMargin( bool firstLine ) const
00142 {
00143     int right = parag->rightMargin(); // 'rm' in QRT
00144     if ( /*doc &&*/ firstLine && parag->string()->isRightToLeft() )
00145         right += parag->firstLineMargin();
00146     return right;
00147 }
00148 
00149 bool KoTextFormatterCore::format()
00150 {
00151     start = 0; // we don't do partial formatting yet
00152     KoTextString *string = parag->string();
00153     if ( start == 0 )
00154         c = &string->at( start );
00155     else
00156         c = 0;
00157 
00158     KoTextStringChar *firstChar = 0;
00159     int left = doc ? leftMargin( true, false ) : 0;
00160     int initialLMargin = leftMargin( true );
00161 
00162     y = parag->breakableTopMargin();
00163     // #57555, top margin doesn't apply if parag at top of page
00164     // (but a portion of the margin can be needed, to complete the prev page)
00165     // So we apply formatVertically() on the top margin, to find where to break it.
00166     if ( !parag->prev() )
00167         y = 0; // no top margin on very first parag
00168     else if ( y )
00169     {
00170         int shift = doc->flow()->adjustFlow( parag->rect().y(),
00171                                              0 /*w, unused*/,
00172                                              y );
00173         if ( shift > 0 )
00174         {
00175             // The shift is in fact the amount of top-margin that should remain
00176             // The remaining portion should be eaten away.
00177             y = shift;
00178         }
00179 
00180     }
00181     // Now add the rest of the top margin (e.g. the one for the border)
00182     y += parag->topMargin() - parag->breakableTopMargin();
00183     int len = parag->length();
00184 
00185     int initialHeight = c->height(); // remember what adjustMargins was called with
00186 
00187     int currentRightMargin = rightMargin( true );
00188     int initialRMargin = currentRightMargin;
00189     // Those three things must be done before calling determineCharWidth
00190     i = start;
00191     parag->tabCache().clear();
00192     x = 0;
00193 
00194     // We need the width of the first char for adjustMargins
00195     // The result might not be 100% accurate when using a tab (it'll use x=0
00196     // but with counters/margins this might be different). This is why
00197     // we call determineCharWidth() again from within the loop.
00198     QPair<int, int> widths = determineCharWidth();
00199     int ww = widths.first; // width in layout units
00200 #ifndef REF_IS_LU
00201     int pixelww = widths.second; // width in pixels
00202 #endif
00203 
00204     // dw is the document width, i.e. the maximum available width, all included.
00205     // We are in a variable-width design, so it is returned by each call to adjustMargins.
00206     int dw = 0;
00207     //if (doc) // always true in kotext
00208     doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight, // input params
00209                                 ww, initialLMargin, initialRMargin, dw,  // output params
00210                                 parag );
00211     //else dw = parag->documentVisibleWidth();
00212 
00213     x = initialLMargin; // as modified by adjustMargins
00214 
00215     int maxY = doc ? doc->flow()->availableHeight() : -1;
00216 
00217     availableWidth = dw - initialRMargin; // 'w' in QRT
00218 #if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH)
00219     kdDebug(32500) << "KoTextFormatter::format formatting parag " << parag->paragId()
00220                    << " text:" << parag->string()->toString() << "\n"
00221                    << " left=" << left << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " availableWidth=" << availableWidth << " maxY=" << maxY << endl;
00222 #else
00223     if ( availableWidth == 0 )
00224         kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, availableWidth=0" << endl;
00225     if ( maxY == 0 )
00226         kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, maxY=0" << endl;
00227 #endif
00228     bool fullWidth = TRUE;
00229     //int marg = left + initialRMargin;
00230 
00231     // minw is the really minimum width needed for this paragraph, i.e.
00232     // the width of the longest set of non-breakable characters together.
00233     // Currently unused.
00234     //int minw = 0;
00235 
00236     wused = 0;
00237 
00238     QValueList<TemporaryWordData> tempWordData;
00239 
00240 #ifdef DEBUG_FORMATTER
00241     kdDebug(32500) << "Initial KoTextParagLineStart at y=" << y << endl;
00242 #endif
00243     KoTextParagLineStart *lineStart = new KoTextParagLineStart( y, 0, 0 );
00244     parag->insertLineStart( 0, lineStart );
00245     int lastBreak = -1;
00246     // tmph, tmpBaseLine and tminw are used after the last breakable char
00247     // we don't know yet if we'll break there, or later.
00248     int tmpBaseLine = 0, tmph = 0;
00249     //int tminw = marg;
00250     int tmpWused = 0;
00251     bool lastWasNonInlineCustom = FALSE;
00252     bool abort = false;
00253 
00254     int align = parag->alignment();
00255     if ( align == Qt::AlignAuto && doc && doc->alignment() != Qt::AlignAuto )
00256         align = doc->alignment();
00257 
00258     int col = 0;
00259 
00260     maxAvailableWidth = qMakePair( 0, 0 );
00261 
00262     KoTextZoomHandler *zh = doc->formattingZoomHandler();
00263     int pixelx = zh->layoutUnitToPixelX( x );
00264     int lastPixelx = 0;
00265 
00266     KoTextStringChar* lastChr = 0;
00267     for ( ; i < len; ++i, ++col ) {
00268         if ( c )
00269             lastChr = c;
00270         c = &string->at( i );
00271         if ( i > 0 && (x > initialLMargin || ww == 0) || lastWasNonInlineCustom ) {
00272             c->lineStart = 0;
00273         } else {
00274             c->lineStart = 1;
00275             firstChar = c;
00276             tmph = c->height();
00277             tmpBaseLine = c->ascent();
00278 #ifdef DEBUG_FORMATTER_VERT
00279             kdDebug(32500) << "New line, initializing tmpBaseLine=" << tmpBaseLine << " tmph=" << tmph << endl;
00280 #endif
00281         }
00282 
00283         if ( c->isCustom() && c->customItem()->placement() != KoTextCustomItem::PlaceInline )
00284             lastWasNonInlineCustom = TRUE;
00285         else
00286             lastWasNonInlineCustom = FALSE;
00287 
00288         QPair<int, int> widths = determineCharWidth();
00289         ww = widths.first;
00290         pixelww = widths.second;
00291 
00292         // We're "aborting" the formatting. This still means we need to set the
00293         // lineStart bools to false (trouble ahead, otherwise!), and while we're at
00294         // it we also calculate the widths etc.
00295         if ( abort ) {
00296             x += ww;
00297             c->x = x;
00298             continue; // yeah, this seems a bit confusing :)
00299         }
00300 
00301         //code from qt-3.1beta2
00302         if ( c->isCustom() && c->customItem()->ownLine() ) {
00303 #ifdef DEBUG_FORMATTER
00304             kdDebug(32500) << "i=" << i << "/" << len << " custom item with ownline" << endl;
00305 #endif
00306             int rightMargin = currentRightMargin;
00307             x = left;
00308             if ( doc )
00309                 doc->flow()->adjustMargins( y + parag->rect().y(), parag->rect().height(), 15,
00310                                             x, rightMargin, dw, parag );
00311             int w = dw - rightMargin;
00312             c->customItem()->resize( w - x );
00313             y += lineStart->h;
00314             lineStart = new KoTextParagLineStart( y, c->ascent(), c->height() );
00315             // Added for kotext (to be tested)
00316             lineStart->lineSpacing = doc ? parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, i, i ) : 0;
00317             lineStart->h += lineStart->lineSpacing;
00318             lineStart->w = dw;
00319             parag->insertLineStart( i, lineStart );
00320             tempWordData.clear();
00321             c->lineStart = 1;
00322             firstChar = c;
00323             x = 0xffffff;
00324             // Hmm, --i or setting lineStart on next char too?
00325             continue;
00326         }
00327 
00328 #ifdef DEBUG_FORMATTER
00329         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;
00330 #endif
00331         // Wrapping at end of line - one big if :)
00332         if (
00333              // Check if should break (i.e. we are after the max X for the end of the line)
00334              ( ( /*wrapAtColumn() == -1 &&*/ x + ww > availableWidth &&
00335                ( lastBreak != -1 || settings->allowBreakInWords() ) )
00336 
00337              // Allow two breakable chars next to each other (e.g. '  ') but not more
00338              && ( !settings->isBreakable( string, i ) ||
00339                   ( i > 1 && lastBreak == i-1 && settings->isBreakable( string, i-2 ) ) ||
00340                   lastBreak == -2 ) // ... used to be a special case...
00341 
00342              // No point in breaking just for the trailing space (testcase: page numbers in TOC)
00343              && ( i < len-1 )
00344 
00345              // Ensure that there is at least one char per line, otherwise, on
00346              // a very narrow document and huge chars, we could loop forever.
00347              // checkVerticalBreak takes care of moving down the lines where no
00348              // char should be, anyway.
00349              // Hmm, it doesn't really do so. To be continued...
00351 
00352              )
00353              // Or maybe we simply encountered a '\n'
00354              || ( lastChr && lastChr->c == '\n' && parag->isNewLinesAllowed() && lastBreak > -1 ) )
00355         {
00356 #ifdef DEBUG_FORMATTER
00357             kdDebug(32500) << "BREAKING" << endl;
00358 #endif
00359             //if ( wrapAtColumn() != -1 )
00360             //    minw = QMAX( minw, x + ww );
00361 
00362             bool hyphenated = false;
00363             // Hyphenation: check if we can break somewhere between lastBreak and i
00364             if ( settings->hyphenator() && !c->isCustom() )
00365             {
00366                 int wordStart = QMAX(0, lastBreak+1);
00367                 // Breaking after i isn't possible, i is too far already
00368                 int maxlen = i - wordStart; // we can't accept to break after maxlen
00369                 QString word = string->mid( wordStart, maxlen );
00370                 int wordEnd = i;
00371                 // but we need to compose the entire word, to hyphenate it
00372                 while ( wordEnd < len && !settings->isBreakable( string, wordEnd ) ) {
00373                     word += string->at(wordEnd).c;
00374                     wordEnd++;
00375                 }
00376                 if ( word.length() > 1 ) // don't call the hyphenator for empty or one-letter words
00377                 {
00378                     QString lang = string->at(wordStart).format()->language();
00379                     char * hyphens = settings->hyphenator()->hyphens( word, lang );
00380 #if defined(DEBUG_HYPHENATION)
00381                     kdDebug(32500) << "Hyphenation: word=" << word << " lang=" << lang << " hyphens=" << hyphens << " maxlen=" << maxlen << endl;
00382                     kdDebug(32500) << "Parag indexes: wordStart=" << wordStart << " lastBreak=" << lastBreak << " i=" << i << endl;
00383 #endif
00384                     int hylen = strlen(hyphens);
00385                     Q_ASSERT( maxlen <= hylen );
00386                     // If this word was already hyphenated (at the previous line),
00387                     // don't break it there again. We can only break after firstChar.
00388                     int minPos = QMAX( 0, (firstChar - &string->at(0)) - wordStart );
00389 
00390                     // Check hyphenation positions from the end
00391                     for ( int hypos = maxlen-1 ; hypos >= minPos ; --hypos )
00392                         if ( ( hyphens[hypos] % 2 ) // odd number -> can break there...
00393                                && string->at(hypos + wordStart).format()->hyphenation() ) // ...if the user is ok with that
00394                         {
00395                             lineStart->hyphenated = true;
00396                             lastBreak = hypos + wordStart;
00397                             hyphenated = true;
00398 #if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH) || defined(DEBUG_HYPHENATION)
00399                             kdDebug(32500) << "Hyphenation: will break at " << lastBreak << " using tempworddata at position " << hypos << "/" << tempWordData.size() << endl;
00400 #endif
00401                             if ( hypos < (int)tempWordData.size() )
00402                             {
00403                                 const TemporaryWordData& twd = tempWordData[ hypos ];
00404                                 lineStart->baseLine = twd.baseLine;
00405                                 lineStart->h = twd.height;
00406                                 tmpWused = twd.lineWidth;
00407                             }
00408                             break;
00409                         }
00410                     delete[] hyphens;
00411                 }
00412             }
00413 
00414             // No breakable char found -> break at current char (i.e. before 'i')
00415             if ( lastBreak < 0 ) {
00416                 // Remember if this is the start of a line; testing c->lineStart after breaking
00417                 // is always true...
00418 
00419                 // "Empty line" can happen when there is a very wide character (e.g. inline table),
00420                 // or a very narrow passage between frames.
00421                 // But in fact the second case is already handled by KWTextFrameSet's "no-space case",
00422                 // so we don't come here in that case.
00423                 const bool emptyLine = c->lineStart;
00424                 if ( !emptyLine && i > 0 )
00425                 {
00426                     // (combine lineStart->baseLine/lineStart->h and tmpBaseLine/tmph)
00427                     int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
00428                     lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
00429                     lineStart->h = lineStart->baseLine + belowBaseLine;
00430                     lineStart->w = dw;
00431 
00432                     KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c-1, align, availableWidth - x );
00433                     y += lineStart->h;
00434                     lineStart = lineStart2;
00435 #ifdef DEBUG_FORMATTER
00436                     int linenr = parag->lineStartList().count()-1;
00437                     kdDebug(32500) << "line " << linenr << " done (breaking at current char). y now " << y << endl;
00438 #endif
00439                     tmph = c->height();
00440 
00441                     initialRMargin = currentRightMargin;
00442                     x = left;
00443                     if ( doc )
00444                         doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
00445                                                     ww, // ## correct?
00446                                                     x, initialRMargin, dw, parag );
00447 
00448                     pixelx = zh->layoutUnitToPixelX( x );
00449                     initialHeight = tmph;
00450                     initialLMargin = x;
00451                     availableWidth = dw - initialRMargin;
00452                     if ( parag->isNewLinesAllowed() && c->c == '\t' ) {
00453                         int nx = parag->nextTab( i, x, availableWidth );
00454                         if ( nx < x )
00455                             ww = availableWidth - x;
00456                         else
00457                             ww = nx - x;
00458                     }
00459                     if ( x != left || availableWidth != dw )
00460                         fullWidth = FALSE;
00461                     lineStart->y = y;
00462                     parag->insertLineStart( i, lineStart );
00463                     tempWordData.clear();
00464                     lineStart->baseLine = c->ascent();
00465                     lineStart->h = c->height();
00466                     c->lineStart = 1;
00467                     firstChar = c;
00468                     tmpBaseLine = lineStart->baseLine;
00469                     lastBreak = -1;
00470                     col = 0;
00471                     //tminw = marg; // not in QRT?
00472                     tmpWused = 0;
00473                 }
00474                 // recalc everything for 'i', it might still not be ok where it is...
00475                 // (e.g. if there's no room at all on this line)
00476                 // But we don't want to do this forever, so we check against maxY (if known)
00477                 // [except if we come here after "final choice for empty line"!]
00478                 if ( !emptyLine && maxY > -1 )
00479                 {
00480                     if ( parag->rect().y() + y < maxY )
00481                     {
00482 #ifdef DEBUG_FORMATTER
00483                         kdDebug(32500) << "Re-checking formatting for character " << i << endl;
00484 #endif
00485                         --i; // so that the ++i in for() is a noop
00486                         continue;
00487                     }
00488                     else // we're after maxY, time to stop.
00489                     {
00490 #ifdef DEBUG_FORMATTER
00491                         kdDebug(32500) << "We're after maxY, time to stop." << endl;
00492 #endif
00493                         // No solution for now. Hopefully KWord will create more pages...
00494                         abort = true;
00495                     }
00496                 }
00497                 // maxY not known (or "final choice for empty line") -> keep going ('i' remains where it is)
00498                 // (in case of maxY not known, this is the initial QRT behaviour)
00499             } else {
00500                 // If breaking means we're after maxY, then we won't do it.
00501                 // Hopefully KWord will create more pages.
00502                 if ( maxY > -1 && parag->rect().y() + y + lineStart->h >= maxY ) {
00503 #ifdef DEBUG_FORMATTER
00504                     kdDebug(32500) << "We're after maxY, time to stop." << endl;
00505 #endif
00506                     abort = true;
00507                 }
00508                 else
00509                 {
00510                     // Break the line at the last breakable character
00511                     i = lastBreak;
00512                     c = &string->at( i ); // The last char in the last line
00513                     int spaceAfterLine = availableWidth - c->x;
00514                     // ?? AFAICS we should always deduce the char's width from the available space....
00515                     //if ( string->isRightToLeft() && lastChr->c == '\n' )
00516                     spaceAfterLine -= c->width;
00517 
00518                     //else
00519                     if ( c->c.unicode() == 0xad || hyphenated ) // soft hyphen or hyphenation
00520                     {
00521                         // Recalculate its width, the hyphen will appear finally (important for the parag rect)
00522                         int width = KoTextZoomHandler::ptToLayoutUnitPt( c->format()->refFontMetrics().width( QChar(0xad) ) );
00523                         if ( c->c.unicode() == 0xad )
00524                             c->width = width;
00525                         spaceAfterLine -= width;
00526                     }
00527                     KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, spaceAfterLine );
00528                     lineStart->w = dw;
00529                     y += lineStart->h;
00530                     lineStart = lineStart2;
00531 #ifdef DEBUG_FORMATTER
00532                     kdDebug(32500) << "Breaking at a breakable char (" << i << "). linenr=" << parag->lineStartList().count()-1 << " y=" << y << endl;
00533 #endif
00534 
00535                     c = &string->at( i + 1 ); // The first char in the new line
00536 #ifdef DEBUG_FORMATTER
00537                     kdDebug(32500) << "Next line will start at i+1=" << i+1 << ", char=" << QString(c->c) << endl;
00538 #endif
00539                     tmph = c->height();
00540 
00541                     initialRMargin = currentRightMargin;
00542                     x = left;
00543                     if ( doc )
00544                         doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
00545                                                     c->width,
00546                                                     x, initialRMargin, dw, parag );
00547 
00548                     pixelx = zh->layoutUnitToPixelX( x );
00549                     initialHeight = tmph;
00550                     initialLMargin = x;
00551                     availableWidth = dw - initialRMargin;
00552                     if ( x != left || availableWidth != dw )
00553                         fullWidth = FALSE;
00554                     lineStart->y = y;
00555                     parag->insertLineStart( i + 1, lineStart );
00556                     tempWordData.clear();
00557                     lineStart->baseLine = c->ascent();
00558                     lineStart->h = c->height();
00559                     firstChar = c;
00560                     tmpBaseLine = lineStart->baseLine;
00561                     lastBreak = -1;
00562                     col = 0;
00563                     //tminw = marg;
00564                     tmpWused = 0;
00565                     c->lineStart = 1; // only do this if we will actually create a line for it
00566                     continue;
00567                 }
00568             }
00569         } else if ( lineStart && ( settings->isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) {
00570             // Breakable character
00571             if ( len <= 2 || i < len - 1 ) {
00572 #ifdef DEBUG_FORMATTER_VERT
00573                 kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "):"
00574                                << " combining " << tmpBaseLine << "/" << tmph
00575                                << " with " << c->ascent() << "/" << c->height() << endl;
00576 #endif
00577                 // (combine tmpBaseLine/tmph and this character)
00578                 int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
00579                 tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
00580                 tmph = tmpBaseLine + belowBaseLine;
00581 #ifdef DEBUG_FORMATTER_VERT
00582                 kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
00583 #endif
00584             }
00585             tempWordData.clear();
00586             //minw = QMAX( minw, tminw );
00587             //tminw = marg + ww;
00588             wused = QMAX( wused, tmpWused );
00589 #ifdef DEBUG_FORMATTER_WIDTH
00590             kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "): wused=" << wused << endl;
00591 #endif
00592             tmpWused = 0;
00593             // (combine lineStart and tmpBaseLine/tmph)
00594 #ifdef DEBUG_FORMATTER_VERT
00595             kdDebug(32500) << "Breakable character: combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
00596 #endif
00597             int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
00598             lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
00599             lineStart->h = lineStart->baseLine + belowBaseLine;
00600             lineStart->w = dw;
00601 #ifdef DEBUG_FORMATTER_VERT
00602             kdDebug(32500) << " -> line baseLine/height : " << lineStart->baseLine << "/" << lineStart->h << endl;
00603 #endif
00604             // if h > initialHeight,  call adjustMargins, and if the result is != initial[LR]Margin,
00605             // format this line again
00606             if ( doc && lineStart->h > initialHeight )
00607             {
00608                 bool firstLine = ( firstChar == &string->at( 0 ) );
00609                 int newLMargin = leftMargin( firstLine );
00610                 int newRMargin = rightMargin( firstLine );
00611                 int newPageWidth = dw;
00612                 initialHeight = lineStart->h;
00613                 doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight,
00614                                             firstChar->width,
00615                                             newLMargin, newRMargin, newPageWidth, parag );
00616 
00617 #ifdef DEBUG_FORMATTER
00618                 kdDebug(32500) << "new height: " << initialHeight << " => left=" << left << " first-char=" << (firstChar==&string->at(0)) << " newLMargin=" << newLMargin << " newRMargin=" << newRMargin << endl;
00619 #endif
00620                 if ( newLMargin != initialLMargin || newRMargin != initialRMargin || newPageWidth != dw )
00621                 {
00622 #ifdef DEBUG_FORMATTER
00623                     kdDebug(32500) << "formatting again" << endl;
00624 #endif
00625                     i = (firstChar - &string->at(0));
00626                     x = newLMargin;
00627                     pixelx = zh->layoutUnitToPixelX( x );
00628                     availableWidth = dw - newRMargin;
00629                     initialLMargin = newLMargin;
00630                     initialRMargin = newRMargin;
00631                     dw = newPageWidth;
00632                     c = &string->at( i );
00633                     tmph = c->height();
00634                     tmpBaseLine = c->ascent();
00635                     lineStart->h = tmph;
00636                     lineStart->baseLine = tmpBaseLine;
00637                     lastBreak = -1;
00638                     col = 0;
00639                     //minw = x;
00640 #ifdef DEBUG_FORMATTER
00641                     kdDebug(32500) << "Restarting with i=" << i << " x=" << x << " y=" << y << " tmph=" << tmph << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " y=" << y << endl;
00642 #endif
00643                     // ww and pixelww already calculated and stored, no need to duplicate
00644                     // code like QRT does.
00645                     ww = c->width;
00646 #ifndef REF_IS_LU
00647                     pixelww = c->pixelwidth;
00648 #endif
00649                     //tminw = x + ww;
00650                     tmpWused = 0;
00651                 }
00652             }
00653 
00654             //kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
00655             if ( i < len - 2 || c->c != ' ' )
00656                 lastBreak = i;
00657 
00658         } else if ( i < len - 1 ) { // ignore height of trailing space
00659             // Non-breakable character
00660             //tminw += ww;
00661 #ifdef DEBUG_FORMATTER_VERT
00662             kdDebug(32500) << " Non-breakable character: combining " << tmpBaseLine << "/" << tmph << " with " << c->ascent() << "/" << c->height() << endl;
00663 #endif
00664             // (combine tmpBaseLine/tmph and this character)
00665             int belowBaseLine = QMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
00666             tmpBaseLine = QMAX( tmpBaseLine, c->ascent() );
00667             tmph = tmpBaseLine + belowBaseLine;
00668 #ifdef DEBUG_FORMATTER_VERT
00669             kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
00670 #endif
00671 
00672             TemporaryWordData twd;
00673             twd.baseLine = tmpBaseLine;
00674             twd.height = tmph;
00675             twd.lineWidth = tmpWused;
00676             tempWordData.append( twd );
00677         }
00678 
00679         c->x = x;
00680         // pixelxadj is the adjustement to add to lu2pixel(x), to find pixelx
00681         // (pixelx would be too expensive to store directly since it would require an int)
00682         c->pixelxadj = pixelx - zh->layoutUnitToPixelX( x );
00683         //c->pixelwidth = pixelww; // done as pixelx - lastPixelx below
00684 #ifdef DEBUG_FORMATTER
00685         kdDebug(32500) << "LU: x=" << x << " [equiv. to pix=" << zh->layoutUnitToPixelX( x ) << "] ; PIX: x=" << pixelx << "  --> adj=" << c->pixelxadj << endl;
00686 #endif
00687 
00688         x += ww;
00689 
00690         if ( i > 0 )
00691             lastChr->pixelwidth = pixelx - lastPixelx;
00692         if ( i < len - 1 )
00693             tmpWused = QMAX( tmpWused, x );
00694         else // trailing space
00695             c->pixelwidth = zh->layoutUnitToPixelX( ww ); // was: pixelww;
00696 
00697         lastPixelx = pixelx;
00698 #ifdef REF_IS_LU
00699         pixelx = zh->layoutUnitToPixelX( x ); // no accumulating rounding errors anymore
00700 #else
00701         pixelx += pixelww;
00702 #endif
00703 #ifdef DEBUG_FORMATTER
00704         kdDebug(32500) << "LU: added " << ww << " -> now x=" << x << " ; PIX: added " << pixelww << " -> now pixelx=" << pixelx << endl;
00705 #endif
00706     }
00707 
00708     // ### hack. The last char in the paragraph is always invisible, and somehow sometimes has a wrong format. It changes between
00709     // layouting and printing. This corrects some layouting errors in BiDi mode due to this.
00710     if ( len > 1 /*&& !c->isAnchor()*/ ) {
00711         c->format()->removeRef();
00712         c->setFormat( string->at( len - 2 ).format() );
00713         c->format()->addRef();
00714     }
00715 
00716     // Finish formatting the last line
00717     if ( lineStart ) {
00718 #ifdef DEBUG_FORMATTER
00719         kdDebug(32500) << "Last Line.... linenr=" << (int)parag->lineStartList().count()-1 << endl;
00720 #endif
00721 #ifdef DEBUG_FORMATTER_VERT
00722         kdDebug(32500) << "Last Line... Combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
00723 #endif
00724         // (combine lineStart and tmpBaseLine/tmph)
00725         int belowBaseLine = QMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
00726         lineStart->baseLine = QMAX( lineStart->baseLine, tmpBaseLine );
00727         lineStart->h = lineStart->baseLine + belowBaseLine;
00728         lineStart->w = dw;
00729 #ifdef DEBUG_FORMATTER_WIDTH
00730         kdDebug(32500) << "Last line: w = dw = " << dw << endl;
00731 #endif
00732 #ifdef DEBUG_FORMATTER_VERT
00733         kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
00734 #endif
00735         // last line in a paragraph is not justified
00736         if ( align == Qt::AlignJustify )
00737             align = Qt::AlignAuto;
00738         int space = availableWidth - x + c->width; // don't count the trailing space (it breaks e.g. centering)
00739         KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, space );
00740         delete lineStart2;
00741     }
00742 
00743     //minw = QMAX( minw, tminw );
00744     wused = QMAX( wused, tmpWused );
00745 #ifdef DEBUG_FORMATTER_WIDTH
00746     kdDebug(32500) << "Done, wused=" << wused << endl;
00747 #endif
00748 
00749     int m = parag->bottomMargin();
00750     // ##### Does OOo add margins or does it max them?
00751     //if ( parag->next() && doc && !doc->addMargins() )
00752     //  m = QMAX( m, parag->next()->topMargin() );
00753     parag->setFullWidth( fullWidth );
00754     //if ( parag->next() && parag->next()->isLineBreak() )
00755     //    m = 0;
00756 #ifdef DEBUG_FORMATTER_VERT
00757     kdDebug(32500) << "Adding height of last line(" << lineStart->h << ") and bottomMargin(" << m << ") to y(" << y << ") => " << y+lineStart->h+m << endl;
00758 #endif
00759     y += lineStart->h + m;
00760 
00761     tmpWused += currentRightMargin; // ### this can break with a variable right-margin
00762     //if ( wrapAtColumn() != -1  )
00763     //    minw = QMAX(minw, wused);
00764     //thisminw = minw;
00765 
00766 #ifdef DEBUG_FORMATTER
00767     // Sanity checking
00768     int numberOfLines = 0;
00769     QString charPosList;
00770     for ( int i = 0 ; i < len; ++i ) {
00771         KoTextStringChar *chr = &string->at( i );
00772         if ( i == 0 )
00773             assert( chr->lineStart );
00774         if ( chr->lineStart ) {
00775             ++numberOfLines;
00776             charPosList += QString::number(i) + " ";
00777         }
00778     }
00779     kdDebug(32500) << parag->lineStartList().count() << " lines. " << numberOfLines << " chars with lineStart set: " << charPosList << endl;
00780     assert( numberOfLines == (int)parag->lineStartList().count() );
00781 #endif
00782     return !abort;
00783 }
00784 
00785 // Helper for koFormatLine and koBidiReorderLine
00786 void KoTextFormatterCore::moveChar( KoTextStringChar& chr, KoTextZoomHandler *zh,
00787                                     int deltaX, int deltaPixelX )
00788 {
00789 #ifndef REF_IS_LU
00790     int pixelx = chr.pixelxadj + zh->layoutUnitToPixelX( chr.x );
00791 #endif
00792     chr.x += deltaX;
00793 #ifndef REF_IS_LU
00794     chr.pixelxadj = pixelx + deltaPixelX - zh->layoutUnitToPixelX( chr.x );
00795 #endif
00796 }
00797 
00798 KoTextParagLineStart *KoTextFormatterCore::koFormatLine(
00799     KoTextZoomHandler *zh,
00800     KoTextParag *parag, KoTextString *string, KoTextParagLineStart *line,
00801     KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
00802 {
00803     KoTextParagLineStart* ret = 0;
00804     if( string->isBidi() ) {
00805         ret = koBidiReorderLine( zh, parag, string, line, startChar, lastChar, align, space );
00806     } else {
00807         int start = (startChar - &string->at(0));
00808         int last = (lastChar - &string->at(0) );
00809 
00810         if (space < 0)
00811             space = 0;
00812 
00813         // do alignment Auto == Left in this case
00814         if ( align & Qt::AlignHCenter || align & Qt::AlignRight ) {
00815             if ( align & Qt::AlignHCenter )
00816                 space /= 2;
00817             int toAddPix = zh->layoutUnitToPixelX( space );
00818             for ( int j = last; j >= start; --j ) {
00819                 KoTextStringChar &chr = string->at( j );
00820                 moveChar( chr, zh, space, toAddPix );
00821             }
00822         } else if ( align & Qt::AlignJustify ) {
00823             int numSpaces = 0;
00824             // End at "last-1", the last space ends up with a width of 0
00825             for ( int j = last-1; j >= start; --j ) {
00827                 if ( string->at( j ).c == '\t' ) {
00828                     start = j+1;
00829                     break;
00830                 }
00831                 if( settings->isStretchable( string, j ) ) {
00832                     numSpaces++;
00833                 }
00834             }
00835             int toAdd = 0;
00836             int toAddPix = 0;
00837             for ( int k = start + 1; k <= last; ++k ) {
00838                 KoTextStringChar &chr = string->at( k );
00839                 if ( toAdd != 0 )
00840                     moveChar( chr, zh, toAdd, toAddPix );
00841                 if( settings->isStretchable( string, k ) && numSpaces ) {
00842                     int s = space / numSpaces;
00843                     toAdd += s;
00844                     toAddPix = zh->layoutUnitToPixelX( toAdd );
00845                     space -= s;
00846                     numSpaces--;
00847                     chr.width += s;
00848 #ifndef REF_IS_LU
00849                     chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
00850 #endif
00851                 }
00852             }
00853         }
00854         int current=0;
00855         int nc=0; // Not double, as we check it against 0 and to avoid gcc warnings
00856         KoTextFormat refFormat( *string->at(0).format() ); // we need a ref format, doesn't matter where it comes from
00857         for(int i=start;i<=last;++i)
00858         {
00859             KoTextFormat* format=string->at(i).format();
00860             // End of underline
00861             if ( (((!format->underline())&&
00862                    (!format->doubleUnderline())&&
00863                    (!format->waveUnderline())&&
00864                    (format->underlineType()!=KoTextFormat::U_SIMPLE_BOLD))
00865                   || i == last)
00866                  && nc )
00867             {
00868                 double avg=static_cast<double>(current)/nc;
00869                 avg/=18.0;
00870                 // Apply underline width "avg" from i-nc to i
00871                 refFormat.setUnderLineWidth( avg );
00872                 parag->setFormat( i-nc, i, &refFormat, true, KoTextFormat::UnderLineWidth );
00873                 nc=0;
00874                 current=0;
00875             }
00876             // Inside underline
00877             else if(format->underline()||
00878                     format->waveUnderline()||
00879                     format->doubleUnderline()||
00880                     (format->underlineType() == KoTextFormat::U_SIMPLE_BOLD))
00881             {
00882                 ++nc;
00883                 current += format->pointSize(); //pointSize() is independent of {Sub,Super}Script in contrast to height()
00884             }
00885         }
00886 #if 0
00887         if ( last >= 0 && last < string->length() ) {
00888             KoTextStringChar &chr = string->at( last );
00889             line->w = chr.x + chr.width; //string->width( last );
00890             // Add width of hyphen (so that it appears)
00891             if ( line->hyphenated )
00892                 line->w += KoTextZoomHandler::ptToLayoutUnitPt( chr.format()->refFontMetrics().width( QChar(0xad) ) );
00893         } else
00894             line->w = 0;
00895 #endif
00896 
00897         ret = new KoTextParagLineStart();
00898     }
00899 
00900     // Now calculate and add linespacing
00901     const int start = (startChar - &string->at(0));
00902     const int last = (lastChar - &string->at(0) );
00903     line->lineSpacing = parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, start, last );
00904     line->h += line->lineSpacing;
00905 
00906     return ret;
00907 }
00908 
00909 // collects one line of the paragraph and transforms it to visual order
00910 KoTextParagLineStart *KoTextFormatterCore::koBidiReorderLine(
00911     KoTextZoomHandler *zh,
00912     KoTextParag * /*parag*/, KoTextString *text, KoTextParagLineStart *line,
00913     KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
00914 {
00915     // This comes from Qt (3.3.x) but seems wrong: the last space is where we draw
00916     // the "end of paragraph" sign, so it needs to be correctly positioned too.
00917 #if 0
00918     // ignore white space at the end of the line.
00919     int endSpaces = 0;
00920     while ( lastChar > startChar && lastChar->whiteSpace ) {
00921         space += lastChar->format()->width( ' ' );
00922         --lastChar;
00923         ++endSpaces;
00924     }
00925 #endif
00926 
00927     int start = (startChar - &text->at(0));
00928     int last = (lastChar - &text->at(0) );
00929 #ifdef DEBUG_FORMATTER
00930     kdDebug(32500) << "*KoTextFormatter::koBidiReorderLine from " << start << " to " << last << " space=" << space << " startChar->x=" << startChar->x << endl;
00931 #endif
00932     KoBidiControl *control = new KoBidiControl( line->context(), line->status );
00933     QString str;
00934     str.setUnicode( 0, last - start + 1 );
00935     // fill string with logically ordered chars.
00936     KoTextStringChar *ch = startChar;
00937     QChar *qch = (QChar *)str.unicode();
00938     while ( ch <= lastChar ) {
00939         *qch = ch->c;
00940         qch++;
00941         ch++;
00942     }
00943     int x = startChar->x;
00944 
00945     QPtrList<KoTextRun> *runs;
00946     runs = KoComplexText::bidiReorderLine(control, str, 0, last - start + 1,
00947                                          (text->isRightToLeft() ? QChar::DirR : QChar::DirL) );
00948 
00949     // now construct the reordered string out of the runs...
00950 
00951     int numSpaces = 0;
00952     // set the correct alignment. This is a bit messy....
00953     if( align == Qt::AlignAuto ) {
00954         // align according to directionality of the paragraph...
00955         if ( text->isRightToLeft() )
00956             align = Qt::AlignRight;
00957     }
00958 
00959     if ( align & Qt::AlignHCenter ) {
00960         x += space/2;
00961     } else if ( align & Qt::AlignRight ) {
00962         x += space;
00963     } else if ( align & Qt::AlignJustify ) {
00964         for ( int j = last - 1; j >= start; --j ) {
00966             if ( text->at( j ).c == '\t' ) {
00967                 start = j+1;
00968                 break;
00969             }
00970             if( settings->isStretchable( text, j ) ) {
00971                 numSpaces++;
00972             }
00973         }
00974     }
00975 // TODO #ifndef REF_IS_LU or remove
00976     int pixelx = zh->layoutUnitToPixelX( x );
00977     int toAdd = 0;
00978     int toAddPix = 0;
00979     bool first = TRUE;
00980     KoTextRun *r = runs->first();
00981     int xmax = -0xffffff;
00982     while ( r ) {
00983 #ifdef DEBUG_FORMATTER
00984         kdDebug(32500) << "koBidiReorderLine level: " << r->level << endl;
00985 #endif
00986         if(r->level %2) {
00987             // odd level, need to reverse the string
00988             int pos = r->stop + start;
00989             while(pos >= r->start + start) {
00990                 KoTextStringChar &chr = text->at(pos);
00991                 if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
00992                     int s = space / numSpaces;
00993                     toAdd += s;
00994                     toAddPix = zh->layoutUnitToPixelX( toAdd );
00995                     space -= s;
00996                     numSpaces--;
00997                     chr.width += s;
00998                     chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
00999                 } else if ( first ) {
01000                     first = FALSE;
01001                     if ( chr.c == ' ' ) // trailing space
01002                     {
01003                         //x -= chr.format()->width( ' ' );
01004                         x -= chr.width;
01005                         pixelx -= chr.pixelwidth;
01006                     }
01007                 }
01008                 chr.x = x + toAdd;
01009                 chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
01010 #ifdef DEBUG_FORMATTER
01011                 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;
01012 #endif
01013                 chr.rightToLeft = TRUE;
01014                 chr.startOfRun = FALSE;
01015                 int ww = chr.width;
01016                 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
01017                 x += ww;
01018                 pixelx += chr.pixelwidth;
01019 #ifdef DEBUG_FORMATTER
01020                 kdDebug(32500) << "              ww=" << ww << " adding to x, now " << x << ". pixelwidth=" << chr.pixelwidth << " adding to pixelx, now " << pixelx << " xmax=" << xmax << endl;
01021 #endif
01022                 pos--;
01023             }
01024         } else {
01025             int pos = r->start + start;
01026             while(pos <= r->stop + start) {
01027                 KoTextStringChar& chr = text->at(pos);
01028                 if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
01029                     int s = space / numSpaces;
01030                     toAdd += s;
01031                     toAddPix = zh->layoutUnitToPixelX( toAdd );
01032                     space -= s;
01033                     numSpaces--;
01034                 } else if ( first ) {
01035                     first = FALSE;
01036                     if ( chr.c == ' ' ) // trailing space
01037                     {
01038                         //x -= chr.format()->width( ' ' );
01039                         x -= chr.width;
01040                         pixelx -= chr.pixelwidth;
01041                     }
01042                 }
01043                 chr.x = x + toAdd;
01044                 chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
01045                 chr.rightToLeft = FALSE;
01046                 chr.startOfRun = FALSE;
01047                 int ww = chr.width;
01048                 //kdDebug(32500) << "setting char " << pos << " at pos " << chr.x << endl;
01049                 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
01050                 x += ww;
01051                 pixelx += chr.pixelwidth;
01052                 pos++;
01053             }
01054         }
01055         text->at( r->start + start ).startOfRun = TRUE;
01056         r = runs->next();
01057     }
01058 
01059     //line->w = xmax /*+ 10*/; // Why +10 ?
01060     KoTextParagLineStart *ls = new KoTextParagLineStart( control->context, control->status );
01061     delete control;
01062     delete runs;
01063     return ls;
01064 }
01065 
01066 void KoTextFormatter::postFormat( KoTextParag* parag )
01067 {
01068     parag->fixParagWidth( viewFormattingChars() );
01069 }
KDE Home | KDE Accessibility Home | Description of Access Keys