lib Library API Documentation

kotextformatter.cc

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