|
|
|
/* This file is part of the KDE project
|
|
|
|
Copyright (C) 2001 David Faure <faure@kde.org>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Library General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Library General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "KoTextFormatter.h"
|
|
|
|
#include "KoTextParag.h"
|
|
|
|
#include "KoTextFormat.h"
|
|
|
|
#include "KoTextDocument.h"
|
|
|
|
#include "KoTextZoomHandler.h"
|
|
|
|
#include "kohyphen/kohyphen.h"
|
|
|
|
#include "KoParagCounter.h"
|
|
|
|
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
//#define DEBUG_FORMATTER
|
|
|
|
|
|
|
|
// Vertical info (height, baseline etc.)
|
|
|
|
//#define DEBUG_FORMATTER_VERT
|
|
|
|
|
|
|
|
// Line and paragraph width
|
|
|
|
//#define DEBUG_FORMATTER_WIDTH
|
|
|
|
|
|
|
|
// Hyphenation
|
|
|
|
//#define DEBUG_HYPHENATION
|
|
|
|
|
|
|
|
/////// keep in sync with kotextformat.cpp !
|
|
|
|
//#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, KoTextParagLineStart*> &,
|
|
|
|
int& y, int& widthUsed )
|
|
|
|
{
|
|
|
|
KoTextFormatterCore formatter( this, doc, parag, start );
|
|
|
|
bool worked = formatter.format();
|
|
|
|
y = formatter.resultY();
|
|
|
|
widthUsed = formatter.widthUsed();
|
|
|
|
return worked;
|
|
|
|
}
|
|
|
|
|
|
|
|
KoTextFormatterCore::KoTextFormatterCore( KoTextFormatter* _settings,
|
|
|
|
KoTextDocument *_doc, KoTextParag *_parag,
|
|
|
|
int _start )
|
|
|
|
: settings(_settings), doc(_doc), parag(_parag), start(_start)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
TQPair<int, int> KoTextFormatterCore::determineCharWidth()
|
|
|
|
{
|
|
|
|
int ww, pixelww;
|
|
|
|
KoTextZoomHandler *zh = doc->formattingZoomHandler();
|
|
|
|
if ( c->c != '\t' || c->isCustom() ) {
|
|
|
|
KoTextFormat *charFormat = c->format();
|
|
|
|
if ( c->isCustom() ) {
|
|
|
|
ww = c->customItem()->width;
|
|
|
|
Q_ASSERT( ww >= 0 );
|
|
|
|
ww = 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 qMakePair(ww, pixelww);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int KoTextFormatterCore::leftMargin( bool firstLine, bool includeFirstLineMargin /* = true */ ) const
|
|
|
|
{
|
|
|
|
int left = /*doc ?*/ parag->leftMargin() + doc->leftMargin() /*: 0*/;
|
|
|
|
if ( firstLine && !parag->string()->isRightToLeft() )
|
|
|
|
{
|
|
|
|
if ( includeFirstLineMargin )
|
|
|
|
left += parag->firstLineMargin();
|
|
|
|
// Add the width of the paragraph counter - first line of parag only.
|
|
|
|
if( parag->counter() &&
|
|
|
|
( parag->counter()->alignment() == 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<int, int> widths = determineCharWidth();
|
|
|
|
int ww = widths.first; // width in layout units
|
|
|
|
#ifndef REF_IS_LU
|
|
|
|
int pixelww = widths.second; // width in pixels
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// dw is the document width, i.e. the maximum available width, all included.
|
|
|
|
// We are in a variable-width design, so it is returned by each call to adjustMargins.
|
|
|
|
int dw = 0;
|
|
|
|
//if (doc) // always true in kotext
|
|
|
|
doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight, // input params
|
|
|
|
ww, initialLMargin, initialRMargin, dw, // output params
|
|
|
|
parag );
|
|
|
|
//else dw = parag->documentVisibleWidth();
|
|
|
|
|
|
|
|
x = initialLMargin; // as modified by adjustMargins
|
|
|
|
|
|
|
|
int maxY = doc ? doc->flow()->availableHeight() : -1;
|
|
|
|
|
|
|
|
availableWidth = dw - initialRMargin; // 'w' in 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<TemporaryWordData> tempWordData;
|
|
|
|
|
|
|
|
#ifdef DEBUG_FORMATTER
|
|
|
|
kdDebug(32500) << "Initial KoTextParagLineStart at y=" << y << endl;
|
|
|
|
#endif
|
|
|
|
KoTextParagLineStart *lineStart = new KoTextParagLineStart( y, 0, 0 );
|
|
|
|
parag->insertLineStart( 0, lineStart );
|
|
|
|
int lastBreak = -1;
|
|
|
|
// tmph, tmpBaseLine and tminw are used after the last breakable char
|
|
|
|
// we don't know yet if we'll break there, or later.
|
|
|
|
int tmpBaseLine = 0, tmph = 0;
|
|
|
|
//int tminw = marg;
|
|
|
|
int tmpWused = 0;
|
|
|
|
bool lastWasNonInlineCustom = FALSE;
|
|
|
|
bool abort = false;
|
|
|
|
|
|
|
|
int align = parag->alignment();
|
|
|
|
if ( align == TQt::AlignAuto && doc && doc->alignment() != TQt::AlignAuto )
|
|
|
|
align = doc->alignment();
|
|
|
|
|
|
|
|
int col = 0;
|
|
|
|
|
|
|
|
maxAvailableWidth = qMakePair( 0, 0 );
|
|
|
|
|
|
|
|
KoTextZoomHandler *zh = doc->formattingZoomHandler();
|
|
|
|
int pixelx = zh->layoutUnitToPixelX( x );
|
|
|
|
int lastPixelx = 0;
|
|
|
|
|
|
|
|
KoTextStringChar* lastChr = 0;
|
|
|
|
for ( ; i < len; ++i, ++col ) {
|
|
|
|
if ( c )
|
|
|
|
lastChr = c;
|
|
|
|
c = &string->at( i );
|
|
|
|
if ( i > 0 && (x > initialLMargin || ww == 0) || lastWasNonInlineCustom ) {
|
|
|
|
c->lineStart = 0;
|
|
|
|
} else {
|
|
|
|
c->lineStart = 1;
|
|
|
|
firstChar = c;
|
|
|
|
tmph = c->height();
|
|
|
|
tmpBaseLine = c->ascent();
|
|
|
|
#ifdef DEBUG_FORMATTER_VERT
|
|
|
|
kdDebug(32500) << "New line, initializing tmpBaseLine=" << tmpBaseLine << " tmph=" << tmph << endl;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( c->isCustom() && c->customItem()->placement() != KoTextCustomItem::PlaceInline )
|
|
|
|
lastWasNonInlineCustom = TRUE;
|
|
|
|
else
|
|
|
|
lastWasNonInlineCustom = FALSE;
|
|
|
|
|
|
|
|
TQPair<int, int> widths = determineCharWidth();
|
|
|
|
ww = widths.first;
|
|
|
|
pixelww = widths.second;
|
|
|
|
|
|
|
|
// We're "aborting" the formatting. This still means we need to set the
|
|
|
|
// lineStart bools to false (trouble ahead, otherwise!), and while we're at
|
|
|
|
// it we also calculate the widths etc.
|
|
|
|
if ( abort ) {
|
|
|
|
x += ww;
|
|
|
|
c->x = x;
|
|
|
|
continue; // yeah, this seems a bit confusing :)
|
|
|
|
}
|
|
|
|
|
|
|
|
//code from qt-3.1beta2
|
|
|
|
if ( c->isCustom() && c->customItem()->ownLine() ) {
|
|
|
|
#ifdef DEBUG_FORMATTER
|
|
|
|
kdDebug(32500) << "i=" << i << "/" << len << " custom item with ownline" << endl;
|
|
|
|
#endif
|
|
|
|
int rightMargin = currentRightMargin;
|
|
|
|
x = left;
|
|
|
|
if ( doc )
|
|
|
|
doc->flow()->adjustMargins( y + parag->rect().y(), parag->rect().height(), 15,
|
|
|
|
x, rightMargin, dw, parag );
|
|
|
|
int w = dw - rightMargin;
|
|
|
|
c->customItem()->resize( w - x );
|
|
|
|
y += lineStart->h;
|
|
|
|
lineStart = new KoTextParagLineStart( y, c->ascent(), c->height() );
|
|
|
|
// Added for kotext (to be tested)
|
|
|
|
lineStart->lineSpacing = doc ? parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, i, i ) : 0;
|
|
|
|
lineStart->h += lineStart->lineSpacing;
|
|
|
|
lineStart->w = dw;
|
|
|
|
parag->insertLineStart( i, lineStart );
|
|
|
|
tempWordData.clear();
|
|
|
|
c->lineStart = 1;
|
|
|
|
firstChar = c;
|
|
|
|
x = 0xffffff;
|
|
|
|
// Hmm, --i or setting lineStart on next char too?
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG_FORMATTER
|
|
|
|
kdDebug(32500) << "c='" << 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<double>(current)/nc;
|
|
|
|
avg/=18.0;
|
|
|
|
// Apply underline width "avg" from i-nc to i
|
|
|
|
refFormat.setUnderLineWidth( avg );
|
|
|
|
parag->setFormat( i-nc, i, &refFormat, true, KoTextFormat::UnderLineWidth );
|
|
|
|
nc=0;
|
|
|
|
current=0;
|
|
|
|
}
|
|
|
|
// Inside underline
|
|
|
|
else if(format->underline()||
|
|
|
|
format->waveUnderline()||
|
|
|
|
format->doubleUnderline()||
|
|
|
|
(format->underlineType() == KoTextFormat::U_SIMPLE_BOLD))
|
|
|
|
{
|
|
|
|
++nc;
|
|
|
|
current += format->pointSize(); //pointSize() is independent of {Sub,Super}Script in contrast to height()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
if ( last >= 0 && last < string->length() ) {
|
|
|
|
KoTextStringChar &chr = string->at( last );
|
|
|
|
line->w = chr.x + chr.width; //string->width( last );
|
|
|
|
// Add width of hyphen (so that it appears)
|
|
|
|
if ( line->hyphenated )
|
|
|
|
line->w += KoTextZoomHandler::ptToLayoutUnitPt( chr.format()->refFontMetrics().width( 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<KoTextRun> *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() );
|
|
|
|
}
|