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