lib

KoTextFormatter.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2001 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include "KoTextFormatter.h"
21#include "KoTextParag.h"
22#include "KoTextFormat.h"
23#include "KoTextDocument.h"
24#include "KoTextZoomHandler.h"
25#include "kohyphen/kohyphen.h"
26#include "KoParagCounter.h"
27
28#include <kdebug.h>
29#include <assert.h>
30
31//#define DEBUG_FORMATTER
32
33// Vertical info (height, baseline etc.)
34//#define DEBUG_FORMATTER_VERT
35
36// Line and paragraph width
37//#define DEBUG_FORMATTER_WIDTH
38
39// Hyphenation
40//#define DEBUG_HYPHENATION
41
43//#define REF_IS_LU
44
45KoTextFormatter::KoTextFormatter()
46{
47 try {
48 m_hyphenator = KoHyphenator::self();
49 } catch ( KoHyphenatorException& e )
50 {
51 m_hyphenator = 0L;
52 }
53}
54
55KoTextFormatter::~KoTextFormatter()
56{
57}
58
59// Hyphenation can break anywhere in the word, so
60// remember the temp data for every char.
61struct TemporaryWordData
62{
63 int baseLine;
64 int height;
65 int lineWidth; // value of wused
66};
67
68bool KoTextFormatter::format( KoTextDocument *doc, KoTextParag *parag,
69 int start, const TQMap<int, KoTextParagLineStart*> &,
70 int& y, int& widthUsed )
71{
72 KoTextFormatterCore formatter( this, doc, parag, start );
73 bool worked = formatter.format();
74 y = formatter.resultY();
75 widthUsed = formatter.widthUsed();
76 return worked;
77}
78
79KoTextFormatterCore::KoTextFormatterCore( KoTextFormatter* _settings,
80 KoTextDocument *_doc, KoTextParag *_parag,
81 int _start )
82 : settings(_settings), doc(_doc), parag(_parag), start(_start)
83{
84}
85
86TQPair<int, int> KoTextFormatterCore::determineCharWidth()
87{
88 int ww, pixelww;
89 KoTextZoomHandler *zh = doc->formattingZoomHandler();
90 if ( c->c != '\t' || c->isCustom() ) {
91 KoTextFormat *charFormat = c->format();
92 if ( c->isCustom() ) {
93 ww = c->customItem()->width;
94 Q_ASSERT( ww >= 0 );
95 ww = TQMAX(0, ww);
96#ifndef REF_IS_LU
97 pixelww = zh->layoutUnitToPixelX( ww );
98#endif
99 } else {
100 ww = charFormat->charWidthLU( c, parag, i );
101#ifndef REF_IS_LU
102 // Pixel size - we want the metrics of the font that's going to be used.
103 pixelww = charFormat->charWidth( zh, true, c, parag, i );
104#endif
105 }
106 } else { // tab
107 int nx = parag->nextTab( i, x, availableWidth );
108 if ( nx < x )
109 ww = availableWidth - x;
110 else
111 ww = nx - x;
112#ifdef DEBUG_FORMATTER
113 kdDebug(32500) << "nextTab for x=" << x << " returned nx=" << nx << " (=> ww=" << ww << ")" << endl;
114#endif
115#ifndef REF_IS_LU
116 pixelww = zh->layoutUnitToPixelX( ww );
117#endif
118 }
119 Q_ASSERT( ww >= 0 );
120 c->width = ww;
121 return qMakePair(ww, pixelww);
122}
123
124
125int KoTextFormatterCore::leftMargin( bool firstLine, bool includeFirstLineMargin /* = true */ ) const
126{
127 int left = /*doc ?*/ parag->leftMargin() + doc->leftMargin() /*: 0*/;
128 if ( firstLine && !parag->string()->isRightToLeft() )
129 {
130 if ( includeFirstLineMargin )
131 left += parag->firstLineMargin();
132 // Add the width of the paragraph counter - first line of parag only.
133 if( parag->counter() &&
134 ( parag->counter()->alignment() == TQt::AlignLeft ||
135 parag->counter()->alignment() == TQt::AlignAuto ) )
136 left += parag->counterWidth(); // in LU pixels
137 }
138 return left;
139}
140
141int KoTextFormatterCore::rightMargin( bool firstLine ) const
142{
143 int right = parag->rightMargin(); // 'rm' in TQRT
144 if ( /*doc &&*/ firstLine && parag->string()->isRightToLeft() )
145 right += parag->firstLineMargin();
146 return right;
147}
148
149bool KoTextFormatterCore::format()
150{
151 start = 0; // we don't do partial formatting yet
152 KoTextString *string = parag->string();
153 if ( start == 0 )
154 c = &string->at( start );
155 else
156 c = 0;
157
158 KoTextStringChar *firstChar = 0;
159 int left = doc ? leftMargin( true, false ) : 0;
160 int initialLMargin = leftMargin( true );
161
162 y = parag->breakableTopMargin();
163 // #57555, top margin doesn't apply if parag at top of page
164 // (but a portion of the margin can be needed, to complete the prev page)
165 // So we apply formatVertically() on the top margin, to find where to break it.
166 if ( !parag->prev() )
167 y = 0; // no top margin on very first parag
168 else if ( y )
169 {
170 int shift = doc->flow()->adjustFlow( parag->rect().y(),
171 0 /*w, unused*/,
172 y );
173 if ( shift > 0 )
174 {
175 // The shift is in fact the amount of top-margin that should remain
176 // The remaining portion should be eaten away.
177 y = shift;
178 }
179
180 }
181 // Now add the rest of the top margin (e.g. the one for the border)
182 y += parag->topMargin() - parag->breakableTopMargin();
183 int len = parag->length();
184
185 int initialHeight = c->height(); // remember what adjustMargins was called with
186
187 int currentRightMargin = rightMargin( true );
188 int initialRMargin = currentRightMargin;
189 // Those three things must be done before calling determineCharWidth
190 i = start;
191 parag->tabCache().clear();
192 x = 0;
193
194 // We need the width of the first char for adjustMargins
195 // The result might not be 100% accurate when using a tab (it'll use x=0
196 // but with counters/margins this might be different). This is why
197 // we call determineCharWidth() again from within the loop.
198 TQPair<int, int> widths = determineCharWidth();
199 int ww = widths.first; // width in layout units
200#ifndef REF_IS_LU
201 int pixelww = widths.second; // width in pixels
202#endif
203
204 // dw is the document width, i.e. the maximum available width, all included.
205 // We are in a variable-width design, so it is returned by each call to adjustMargins.
206 int dw = 0;
207 //if (doc) // always true in kotext
208 doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight, // input params
209 ww, initialLMargin, initialRMargin, dw, // output params
210 parag );
211 //else dw = parag->documentVisibleWidth();
212
213 x = initialLMargin; // as modified by adjustMargins
214
215 int maxY = doc ? doc->flow()->availableHeight() : -1;
216
217 availableWidth = dw - initialRMargin; // 'w' in TQRT
218#if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH)
219 kdDebug(32500) << "KoTextFormatter::format formatting parag " << parag->paragId()
220 << " text:" << parag->string()->toString() << "\n"
221 << " left=" << left << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " availableWidth=" << availableWidth << " maxY=" << maxY << endl;
222#else
223 if ( availableWidth == 0 )
224 kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, availableWidth=0" << endl;
225 if ( maxY == 0 )
226 kdDebug(32500) << "KoTextFormatter::format " << parag->paragId() << " warning, maxY=0" << endl;
227#endif
228 bool fullWidth = true;
229 //int marg = left + initialRMargin;
230
231 // minw is the really minimum width needed for this paragraph, i.e.
232 // the width of the longest set of non-breakable characters together.
233 // Currently unused.
234 //int minw = 0;
235
236 wused = 0;
237
238 TQValueList<TemporaryWordData> tempWordData;
239
240#ifdef DEBUG_FORMATTER
241 kdDebug(32500) << "Initial KoTextParagLineStart at y=" << y << endl;
242#endif
243 KoTextParagLineStart *lineStart = new KoTextParagLineStart( y, 0, 0 );
244 parag->insertLineStart( 0, lineStart );
245 int lastBreak = -1;
246 // tmph, tmpBaseLine and tminw are used after the last breakable char
247 // we don't know yet if we'll break there, or later.
248 int tmpBaseLine = 0, tmph = 0;
249 //int tminw = marg;
250 int tmpWused = 0;
251 bool lastWasNonInlineCustom = false;
252 bool abort = false;
253
254 int align = parag->alignment();
255 if ( align == TQt::AlignAuto && doc && doc->alignment() != TQt::AlignAuto )
256 align = doc->alignment();
257
258 int col = 0;
259
260 maxAvailableWidth = qMakePair( 0, 0 );
261
262 KoTextZoomHandler *zh = doc->formattingZoomHandler();
263 int pixelx = zh->layoutUnitToPixelX( x );
264 int lastPixelx = 0;
265
266 KoTextStringChar* lastChr = 0;
267 for ( ; i < len; ++i, ++col ) {
268 if ( c )
269 lastChr = c;
270 c = &string->at( i );
271 if ( i > 0 && (x > initialLMargin || ww == 0) || lastWasNonInlineCustom ) {
272 c->lineStart = 0;
273 } else {
274 c->lineStart = 1;
275 firstChar = c;
276 tmph = c->height();
277 tmpBaseLine = c->ascent();
278#ifdef DEBUG_FORMATTER_VERT
279 kdDebug(32500) << "New line, initializing tmpBaseLine=" << tmpBaseLine << " tmph=" << tmph << endl;
280#endif
281 }
282
283 if ( c->isCustom() && c->customItem()->placement() != KoTextCustomItem::PlaceInline )
284 lastWasNonInlineCustom = true;
285 else
286 lastWasNonInlineCustom = false;
287
288 TQPair<int, int> widths = determineCharWidth();
289 ww = widths.first;
290 pixelww = widths.second;
291
292 // We're "aborting" the formatting. This still means we need to set the
293 // lineStart bools to false (trouble ahead, otherwise!), and while we're at
294 // it we also calculate the widths etc.
295 if ( abort ) {
296 x += ww;
297 c->x = x;
298 continue; // yeah, this seems a bit confusing :)
299 }
300
301 //code from qt-3.1beta2
302 if ( c->isCustom() && c->customItem()->ownLine() ) {
303#ifdef DEBUG_FORMATTER
304 kdDebug(32500) << "i=" << i << "/" << len << " custom item with ownline" << endl;
305#endif
306 int rightMargin = currentRightMargin;
307 x = left;
308 if ( doc )
309 doc->flow()->adjustMargins( y + parag->rect().y(), parag->rect().height(), 15,
310 x, rightMargin, dw, parag );
311 int w = dw - rightMargin;
312 c->customItem()->resize( w - x );
313 y += lineStart->h;
314 lineStart = new KoTextParagLineStart( y, c->ascent(), c->height() );
315 // Added for kotext (to be tested)
316 lineStart->lineSpacing = doc ? parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, i, i ) : 0;
317 lineStart->h += lineStart->lineSpacing;
318 lineStart->w = dw;
319 parag->insertLineStart( i, lineStart );
320 tempWordData.clear();
321 c->lineStart = 1;
322 firstChar = c;
323 x = 0xffffff;
324 // Hmm, --i or setting lineStart on next char too?
325 continue;
326 }
327
328#ifdef DEBUG_FORMATTER
329 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;
330#endif
331 // Wrapping at end of line - one big if :)
332 if (
333 // Check if should break (i.e. we are after the max X for the end of the line)
334 ( ( /*wrapAtColumn() == -1 &&*/ x + ww > availableWidth &&
335 ( lastBreak != -1 || settings->allowBreakInWords() ) )
336
337 // Allow two breakable chars next to each other (e.g. ' ') but not more
338 && ( !settings->isBreakable( string, i ) ||
339 ( i > 1 && lastBreak == i-1 && settings->isBreakable( string, i-2 ) ) ||
340 lastBreak == -2 ) // ... used to be a special case...
341
342 // No point in breaking just for the trailing space (testcase: page numbers in TOC)
343 && ( i < len-1 )
344
345 // Ensure that there is at least one char per line, otherwise, on
346 // a very narrow document and huge chars, we could loop forever.
347 // checkVerticalBreak takes care of moving down the lines where no
348 // char should be, anyway.
349 // Hmm, it doesn't really do so. To be continued...
351
352 )
353 // Or maybe we simply encountered a '\n'
354 || ( lastChr && lastChr->c == '\n' && parag->isNewLinesAllowed() && lastBreak > -1 ) )
355 {
356#ifdef DEBUG_FORMATTER
357 kdDebug(32500) << "BREAKING" << endl;
358#endif
359 //if ( wrapAtColumn() != -1 )
360 // minw = TQMAX( minw, x + ww );
361
362 bool hyphenated = false;
363 // Hyphenation: check if we can break somewhere between lastBreak and i
364 if ( settings->hyphenator() && !c->isCustom() )
365 {
366 int wordStart = TQMAX(0, lastBreak+1);
367 // Breaking after i isn't possible, i is too far already
368 int maxlen = i - wordStart; // we can't accept to break after maxlen
369 TQString word = string->mid( wordStart, maxlen );
370 int wordEnd = i;
371 // but we need to compose the entire word, to hyphenate it
372 while ( wordEnd < len && !settings->isBreakable( string, wordEnd ) ) {
373 word += string->at(wordEnd).c;
374 wordEnd++;
375 }
376 if ( word.length() > 1 ) // don't call the hyphenator for empty or one-letter words
377 {
378 TQString lang = string->at(wordStart).format()->language();
379 char * hyphens = settings->hyphenator()->hyphens( word, lang );
380#if defined(DEBUG_HYPHENATION)
381 kdDebug(32500) << "Hyphenation: word=" << word << " lang=" << lang << " hyphens=" << hyphens << " maxlen=" << maxlen << endl;
382 kdDebug(32500) << "Parag indexes: wordStart=" << wordStart << " lastBreak=" << lastBreak << " i=" << i << endl;
383#endif
384 int hylen = strlen(hyphens);
385 Q_ASSERT( maxlen <= hylen );
386 // If this word was already hyphenated (at the previous line),
387 // don't break it there again. We can only break after firstChar.
388 int minPos = TQMAX( 0, (firstChar - &string->at(0)) - wordStart );
389
390 // Check hyphenation positions from the end
391 for ( int hypos = maxlen-1 ; hypos >= minPos ; --hypos )
392 if ( ( hyphens[hypos] % 2 ) // odd number -> can break there...
393 && string->at(hypos + wordStart).format()->hyphenation() ) // ...if the user is ok with that
394 {
395 lineStart->hyphenated = true;
396 lastBreak = hypos + wordStart;
397 hyphenated = true;
398#if defined(DEBUG_FORMATTER) || defined(DEBUG_FORMATTER_WIDTH) || defined(DEBUG_HYPHENATION)
399 kdDebug(32500) << "Hyphenation: will break at " << lastBreak << " using tempworddata at position " << hypos << "/" << tempWordData.size() << endl;
400#endif
401 if ( hypos < (int)tempWordData.size() )
402 {
403 const TemporaryWordData& twd = tempWordData[ hypos ];
404 lineStart->baseLine = twd.baseLine;
405 lineStart->h = twd.height;
406 tmpWused = twd.lineWidth;
407 }
408 break;
409 }
410 delete[] hyphens;
411 }
412 }
413
414 // No breakable char found -> break at current char (i.e. before 'i')
415 if ( lastBreak < 0 ) {
416 // Remember if this is the start of a line; testing c->lineStart after breaking
417 // is always true...
418
419 // "Empty line" can happen when there is a very wide character (e.g. inline table),
420 // or a very narrow passage between frames.
421 // But in fact the second case is already handled by KWTextFrameSet's "no-space case",
422 // so we don't come here in that case.
423 const bool emptyLine = c->lineStart;
424 if ( !emptyLine && i > 0 )
425 {
426 // (combine lineStart->baseLine/lineStart->h and tmpBaseLine/tmph)
427 int belowBaseLine = TQMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
428 lineStart->baseLine = TQMAX( lineStart->baseLine, tmpBaseLine );
429 lineStart->h = lineStart->baseLine + belowBaseLine;
430 lineStart->w = dw;
431
432 KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c-1, align, availableWidth - x );
433 y += lineStart->h;
434 lineStart = lineStart2;
435#ifdef DEBUG_FORMATTER
436 int linenr = parag->lineStartList().count()-1;
437 kdDebug(32500) << "line " << linenr << " done (breaking at current char). y now " << y << endl;
438#endif
439 tmph = c->height();
440
441 initialRMargin = currentRightMargin;
442 x = left;
443 if ( doc )
444 doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
445 ww, // ## correct?
446 x, initialRMargin, dw, parag );
447
448 pixelx = zh->layoutUnitToPixelX( x );
449 initialHeight = tmph;
450 initialLMargin = x;
451 availableWidth = dw - initialRMargin;
452 if ( parag->isNewLinesAllowed() && c->c == '\t' ) {
453 int nx = parag->nextTab( i, x, availableWidth );
454 if ( nx < x )
455 ww = availableWidth - x;
456 else
457 ww = nx - x;
458 }
459 if ( x != left || availableWidth != dw )
460 fullWidth = false;
461 lineStart->y = y;
462 parag->insertLineStart( i, lineStart );
463 tempWordData.clear();
464 lineStart->baseLine = c->ascent();
465 lineStart->h = c->height();
466 c->lineStart = 1;
467 firstChar = c;
468 tmpBaseLine = lineStart->baseLine;
469 lastBreak = -1;
470 col = 0;
471 //tminw = marg; // not in TQRT?
472 tmpWused = 0;
473 }
474 // recalc everything for 'i', it might still not be ok where it is...
475 // (e.g. if there's no room at all on this line)
476 // But we don't want to do this forever, so we check against maxY (if known)
477 // [except if we come here after "final choice for empty line"!]
478 if ( !emptyLine && maxY > -1 )
479 {
480 if ( parag->rect().y() + y < maxY )
481 {
482#ifdef DEBUG_FORMATTER
483 kdDebug(32500) << "Re-checking formatting for character " << i << endl;
484#endif
485 --i; // so that the ++i in for() is a noop
486 continue;
487 }
488 else // we're after maxY, time to stop.
489 {
490#ifdef DEBUG_FORMATTER
491 kdDebug(32500) << "We're after maxY, time to stop." << endl;
492#endif
493 // No solution for now. Hopefully KWord will create more pages...
494 abort = true;
495 }
496 }
497 // maxY not known (or "final choice for empty line") -> keep going ('i' remains where it is)
498 // (in case of maxY not known, this is the initial TQRT behaviour)
499 } else {
500 // If breaking means we're after maxY, then we won't do it.
501 // Hopefully KWord will create more pages.
502 if ( maxY > -1 && parag->rect().y() + y + lineStart->h >= maxY ) {
503#ifdef DEBUG_FORMATTER
504 kdDebug(32500) << "We're after maxY, time to stop." << endl;
505#endif
506 abort = true;
507 }
508 else
509 {
510 // Break the line at the last breakable character
511 i = lastBreak;
512 c = &string->at( i ); // The last char in the last line
513 int spaceAfterLine = availableWidth - c->x;
514 // ?? AFAICS we should always deduce the char's width from the available space....
515 //if ( string->isRightToLeft() && lastChr->c == '\n' )
516 spaceAfterLine -= c->width;
517
518 //else
519 if ( c->c.unicode() == 0xad || hyphenated ) // soft hyphen or hyphenation
520 {
521 // Recalculate its width, the hyphen will appear finally (important for the parag rect)
522 int width = KoTextZoomHandler::ptToLayoutUnitPt( c->format()->refFontMetrics().width( TQChar(0xad) ) );
523 if ( c->c.unicode() == 0xad )
524 c->width = width;
525 spaceAfterLine -= width;
526 }
527 KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, spaceAfterLine );
528 lineStart->w = dw;
529 y += lineStart->h;
530 lineStart = lineStart2;
531#ifdef DEBUG_FORMATTER
532 kdDebug(32500) << "Breaking at a breakable char (" << i << "). linenr=" << parag->lineStartList().count()-1 << " y=" << y << endl;
533#endif
534
535 c = &string->at( i + 1 ); // The first char in the new line
536#ifdef DEBUG_FORMATTER
537 kdDebug(32500) << "Next line will start at i+1=" << i+1 << ", char=" << TQString(c->c) << endl;
538#endif
539 tmph = c->height();
540
541 initialRMargin = currentRightMargin;
542 x = left;
543 if ( doc )
544 doc->flow()->adjustMargins( y + parag->rect().y(), tmph,
545 c->width,
546 x, initialRMargin, dw, parag );
547
548 pixelx = zh->layoutUnitToPixelX( x );
549 initialHeight = tmph;
550 initialLMargin = x;
551 availableWidth = dw - initialRMargin;
552 if ( x != left || availableWidth != dw )
553 fullWidth = false;
554 lineStart->y = y;
555 parag->insertLineStart( i + 1, lineStart );
556 tempWordData.clear();
557 lineStart->baseLine = c->ascent();
558 lineStart->h = c->height();
559 firstChar = c;
560 tmpBaseLine = lineStart->baseLine;
561 lastBreak = -1;
562 col = 0;
563 //tminw = marg;
564 tmpWused = 0;
565 c->lineStart = 1; // only do this if we will actually create a line for it
566 continue;
567 }
568 }
569 } else if ( lineStart && ( settings->isBreakable( string, i ) || parag->isNewLinesAllowed() && c->c == '\n' ) ) {
570 // Breakable character
571 if ( len <= 2 || i < len - 1 ) {
572#ifdef DEBUG_FORMATTER_VERT
573 kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "):"
574 << " combining " << tmpBaseLine << "/" << tmph
575 << " with " << c->ascent() << "/" << c->height() << endl;
576#endif
577 // (combine tmpBaseLine/tmph and this character)
578 int belowBaseLine = TQMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
579 tmpBaseLine = TQMAX( tmpBaseLine, c->ascent() );
580 tmph = tmpBaseLine + belowBaseLine;
581#ifdef DEBUG_FORMATTER_VERT
582 kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
583#endif
584 }
585 tempWordData.clear();
586 //minw = TQMAX( minw, tminw );
587 //tminw = marg + ww;
588 wused = TQMAX( wused, tmpWused );
589#ifdef DEBUG_FORMATTER_WIDTH
590 kdDebug(32500) << " Breakable character (i=" << i << " len=" << len << "): wused=" << wused << endl;
591#endif
592 tmpWused = 0;
593 // (combine lineStart and tmpBaseLine/tmph)
594#ifdef DEBUG_FORMATTER_VERT
595 kdDebug(32500) << "Breakable character: combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
596#endif
597 int belowBaseLine = TQMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
598 lineStart->baseLine = TQMAX( lineStart->baseLine, tmpBaseLine );
599 lineStart->h = lineStart->baseLine + belowBaseLine;
600 lineStart->w = dw;
601#ifdef DEBUG_FORMATTER_VERT
602 kdDebug(32500) << " -> line baseLine/height : " << lineStart->baseLine << "/" << lineStart->h << endl;
603#endif
604 // if h > initialHeight, call adjustMargins, and if the result is != initial[LR]Margin,
605 // format this line again
606 if ( doc && lineStart->h > initialHeight )
607 {
608 bool firstLine = ( firstChar == &string->at( 0 ) );
609 int newLMargin = leftMargin( firstLine );
610 int newRMargin = rightMargin( firstLine );
611 int newPageWidth = dw;
612 initialHeight = lineStart->h;
613 doc->flow()->adjustMargins( y + parag->rect().y(), initialHeight,
614 firstChar->width,
615 newLMargin, newRMargin, newPageWidth, parag );
616
617#ifdef DEBUG_FORMATTER
618 kdDebug(32500) << "new height: " << initialHeight << " => left=" << left << " first-char=" << (firstChar==&string->at(0)) << " newLMargin=" << newLMargin << " newRMargin=" << newRMargin << endl;
619#endif
620 if ( newLMargin != initialLMargin || newRMargin != initialRMargin || newPageWidth != dw )
621 {
622#ifdef DEBUG_FORMATTER
623 kdDebug(32500) << "formatting again" << endl;
624#endif
625 i = (firstChar - &string->at(0));
626 x = newLMargin;
627 pixelx = zh->layoutUnitToPixelX( x );
628 availableWidth = dw - newRMargin;
629 initialLMargin = newLMargin;
630 initialRMargin = newRMargin;
631 dw = newPageWidth;
632 c = &string->at( i );
633 tmph = c->height();
634 tmpBaseLine = c->ascent();
635 lineStart->h = tmph;
636 lineStart->baseLine = tmpBaseLine;
637 lastBreak = -1;
638 col = 0;
639 //minw = x;
640#ifdef DEBUG_FORMATTER
641 kdDebug(32500) << "Restarting with i=" << i << " x=" << x << " y=" << y << " tmph=" << tmph << " initialHeight=" << initialHeight << " initialLMargin=" << initialLMargin << " initialRMargin=" << initialRMargin << " y=" << y << endl;
642#endif
643 // ww and pixelww already calculated and stored, no need to duplicate
644 // code like TQRT does.
645 ww = c->width;
646#ifndef REF_IS_LU
647 pixelww = c->pixelwidth;
648#endif
649 //tminw = x + ww;
650 tmpWused = 0;
651 }
652 }
653
654 //kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
655 if ( i < len - 2 || c->c != ' ' )
656 lastBreak = i;
657
658 } else if ( i < len - 1 ) { // ignore height of trailing space
659 // Non-breakable character
660 //tminw += ww;
661#ifdef DEBUG_FORMATTER_VERT
662 kdDebug(32500) << " Non-breakable character: combining " << tmpBaseLine << "/" << tmph << " with " << c->ascent() << "/" << c->height() << endl;
663#endif
664 // (combine tmpBaseLine/tmph and this character)
665 int belowBaseLine = TQMAX( tmph - tmpBaseLine, c->height() - c->ascent() );
666 tmpBaseLine = TQMAX( tmpBaseLine, c->ascent() );
667 tmph = tmpBaseLine + belowBaseLine;
668#ifdef DEBUG_FORMATTER_VERT
669 kdDebug(32500) << " -> tmpBaseLine/tmph : " << tmpBaseLine << "/" << tmph << endl;
670#endif
671
672 TemporaryWordData twd;
673 twd.baseLine = tmpBaseLine;
674 twd.height = tmph;
675 twd.lineWidth = tmpWused;
676 tempWordData.append( twd );
677 }
678
679 c->x = x;
680 // pixelxadj is the adjustement to add to lu2pixel(x), to find pixelx
681 // (pixelx would be too expensive to store directly since it would require an int)
682 c->pixelxadj = pixelx - zh->layoutUnitToPixelX( x );
683 //c->pixelwidth = pixelww; // done as pixelx - lastPixelx below
684#ifdef DEBUG_FORMATTER
685 kdDebug(32500) << "LU: x=" << x << " [equiv. to pix=" << zh->layoutUnitToPixelX( x ) << "] ; PIX: x=" << pixelx << " --> adj=" << c->pixelxadj << endl;
686#endif
687
688 x += ww;
689
690 if ( i > 0 )
691 lastChr->pixelwidth = pixelx - lastPixelx;
692 if ( i < len - 1 )
693 tmpWused = TQMAX( tmpWused, x );
694 else // trailing space
695 c->pixelwidth = zh->layoutUnitToPixelX( ww ); // was: pixelww;
696
697 lastPixelx = pixelx;
698#ifdef REF_IS_LU
699 pixelx = zh->layoutUnitToPixelX( x ); // no accumulating rounding errors anymore
700#else
701 pixelx += pixelww;
702#endif
703#ifdef DEBUG_FORMATTER
704 kdDebug(32500) << "LU: added " << ww << " -> now x=" << x << " ; PIX: added " << pixelww << " -> now pixelx=" << pixelx << endl;
705#endif
706 }
707
708 // ### hack. The last char in the paragraph is always invisible, and somehow sometimes has a wrong format. It changes between
709 // layouting and printing. This corrects some layouting errors in BiDi mode due to this.
710 if ( len > 1 /*&& !c->isAnchor()*/ ) {
711 c->format()->removeRef();
712 c->setFormat( string->at( len - 2 ).format() );
713 c->format()->addRef();
714 }
715
716 // Finish formatting the last line
717 if ( lineStart ) {
718#ifdef DEBUG_FORMATTER
719 kdDebug(32500) << "Last Line.... linenr=" << (int)parag->lineStartList().count()-1 << endl;
720#endif
721#ifdef DEBUG_FORMATTER_VERT
722 kdDebug(32500) << "Last Line... Combining " << lineStart->baseLine << "/" << lineStart->h << " with " << tmpBaseLine << "/" << tmph << endl;
723#endif
724 // (combine lineStart and tmpBaseLine/tmph)
725 int belowBaseLine = TQMAX( lineStart->h - lineStart->baseLine, tmph - tmpBaseLine );
726 lineStart->baseLine = TQMAX( lineStart->baseLine, tmpBaseLine );
727 lineStart->h = lineStart->baseLine + belowBaseLine;
728 lineStart->w = dw;
729#ifdef DEBUG_FORMATTER_WIDTH
730 kdDebug(32500) << "Last line: w = dw = " << dw << endl;
731#endif
732#ifdef DEBUG_FORMATTER_VERT
733 kdDebug(32500) << " -> lineStart->baseLine/lineStart->h : " << lineStart->baseLine << "/" << lineStart->h << endl;
734#endif
735 // last line in a paragraph is not justified
736 if ( align == TQt::AlignJustify )
737 align = TQt::AlignAuto;
738 int space = availableWidth - x + c->width; // don't count the trailing space (it breaks e.g. centering)
739 KoTextParagLineStart *lineStart2 = koFormatLine( zh, parag, string, lineStart, firstChar, c, align, space );
740 delete lineStart2;
741 }
742
743 //minw = TQMAX( minw, tminw );
744 wused = TQMAX( wused, tmpWused );
745#ifdef DEBUG_FORMATTER_WIDTH
746 kdDebug(32500) << "Done, wused=" << wused << endl;
747#endif
748
749 int m = parag->bottomMargin();
750 // ##### Does OOo add margins or does it max them?
751 //if ( parag->next() && doc && !doc->addMargins() )
752 // m = TQMAX( m, parag->next()->topMargin() );
753 parag->setFullWidth( fullWidth );
754 //if ( parag->next() && parag->next()->isLineBreak() )
755 // m = 0;
756#ifdef DEBUG_FORMATTER_VERT
757 kdDebug(32500) << "Adding height of last line(" << lineStart->h << ") and bottomMargin(" << m << ") to y(" << y << ") => " << y+lineStart->h+m << endl;
758#endif
759 y += lineStart->h + m;
760
761 tmpWused += currentRightMargin; // ### this can break with a variable right-margin
762 //if ( wrapAtColumn() != -1 )
763 // minw = TQMAX(minw, wused);
764 //thisminw = minw;
765
766#ifdef DEBUG_FORMATTER
767 // Sanity checking
768 int numberOfLines = 0;
769 TQString charPosList;
770 for ( int i = 0 ; i < len; ++i ) {
771 KoTextStringChar *chr = &string->at( i );
772 if ( i == 0 )
773 assert( chr->lineStart );
774 if ( chr->lineStart ) {
775 ++numberOfLines;
776 charPosList += TQString::number(i) + " ";
777 }
778 }
779 kdDebug(32500) << parag->lineStartList().count() << " lines. " << numberOfLines << " chars with lineStart set: " << charPosList << endl;
780 assert( numberOfLines == (int)parag->lineStartList().count() );
781#endif
782 return !abort;
783}
784
785// Helper for koFormatLine and koBidiReorderLine
786void KoTextFormatterCore::moveChar( KoTextStringChar& chr, KoTextZoomHandler *zh,
787 int deltaX, int deltaPixelX )
788{
789#ifndef REF_IS_LU
790 int pixelx = chr.pixelxadj + zh->layoutUnitToPixelX( chr.x );
791#endif
792 chr.x += deltaX;
793#ifndef REF_IS_LU
794 chr.pixelxadj = pixelx + deltaPixelX - zh->layoutUnitToPixelX( chr.x );
795#endif
796}
797
798KoTextParagLineStart *KoTextFormatterCore::koFormatLine(
800 KoTextParag *parag, KoTextString *string, KoTextParagLineStart *line,
801 KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
802{
803 KoTextParagLineStart* ret = 0;
804 if( string->isBidi() ) {
805 ret = koBidiReorderLine( zh, parag, string, line, startChar, lastChar, align, space );
806 } else {
807 int start = (startChar - &string->at(0));
808 int last = (lastChar - &string->at(0) );
809
810 if (space < 0)
811 space = 0;
812
813 // do alignment Auto == Left in this case
814 if ( align & TQt::AlignHCenter || align & TQt::AlignRight ) {
815 if ( align & TQt::AlignHCenter )
816 space /= 2;
817 int toAddPix = zh->layoutUnitToPixelX( space );
818 for ( int j = last; j >= start; --j ) {
819 KoTextStringChar &chr = string->at( j );
820 moveChar( chr, zh, space, toAddPix );
821 }
822 } else if ( align & TQt::AlignJustify ) {
823 int numSpaces = 0;
824 // End at "last-1", the last space ends up with a width of 0
825 for ( int j = last-1; j >= start; --j ) {
827 if ( string->at( j ).c == '\t' ) {
828 start = j+1;
829 break;
830 }
831 if( settings->isStretchable( string, j ) ) {
832 numSpaces++;
833 }
834 }
835 int toAdd = 0;
836 int toAddPix = 0;
837 for ( int k = start + 1; k <= last; ++k ) {
838 KoTextStringChar &chr = string->at( k );
839 if ( toAdd != 0 )
840 moveChar( chr, zh, toAdd, toAddPix );
841 if( settings->isStretchable( string, k ) && numSpaces ) {
842 int s = space / numSpaces;
843 toAdd += s;
844 toAddPix = zh->layoutUnitToPixelX( toAdd );
845 space -= s;
846 numSpaces--;
847 chr.width += s;
848#ifndef REF_IS_LU
849 chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
850#endif
851 }
852 }
853 }
854 int current=0;
855 int nc=0; // Not double, as we check it against 0 and to avoid gcc warnings
856 KoTextFormat refFormat( *string->at(0).format() ); // we need a ref format, doesn't matter where it comes from
857 for(int i=start;i<=last;++i)
858 {
859 KoTextFormat* format=string->at(i).format();
860 // End of underline
861 if ( (((!format->underline())&&
862 (!format->doubleUnderline())&&
863 (!format->waveUnderline())&&
864 (format->underlineType()!=KoTextFormat::U_SIMPLE_BOLD))
865 || i == last)
866 && nc )
867 {
868 double avg=static_cast<double>(current)/nc;
869 avg/=18.0;
870 // Apply underline width "avg" from i-nc to i
871 refFormat.setUnderLineWidth( avg );
872 parag->setFormat( i-nc, i, &refFormat, true, KoTextFormat::UnderLineWidth );
873 nc=0;
874 current=0;
875 }
876 // Inside underline
877 else if(format->underline()||
878 format->waveUnderline()||
879 format->doubleUnderline()||
880 (format->underlineType() == KoTextFormat::U_SIMPLE_BOLD))
881 {
882 ++nc;
883 current += format->pointSize(); //pointSize() is independent of {Sub,Super}Script in contrast to height()
884 }
885 }
886#if 0
887 if ( last >= 0 && last < string->length() ) {
888 KoTextStringChar &chr = string->at( last );
889 line->w = chr.x + chr.width; //string->width( last );
890 // Add width of hyphen (so that it appears)
891 if ( line->hyphenated )
892 line->w += KoTextZoomHandler::ptToLayoutUnitPt( chr.format()->refFontMetrics().width( TQChar(0xad) ) );
893 } else
894 line->w = 0;
895#endif
896
897 ret = new KoTextParagLineStart();
898 }
899
900 // Now calculate and add linespacing
901 const int start = (startChar - &string->at(0));
902 const int last = (lastChar - &string->at(0) );
903 line->lineSpacing = parag->calculateLineSpacing( (int)parag->lineStartList().count()-1, start, last );
904 line->h += line->lineSpacing;
905
906 return ret;
907}
908
909// collects one line of the paragraph and transforms it to visual order
910KoTextParagLineStart *KoTextFormatterCore::koBidiReorderLine(
912 KoTextParag * /*parag*/, KoTextString *text, KoTextParagLineStart *line,
913 KoTextStringChar *startChar, KoTextStringChar *lastChar, int align, int space )
914{
915 // This comes from TQt (3.3.x) but seems wrong: the last space is where we draw
916 // the "end of paragraph" sign, so it needs to be correctly positioned too.
917#if 0
918 // ignore white space at the end of the line.
919 int endSpaces = 0;
920 while ( lastChar > startChar && lastChar->whiteSpace ) {
921 space += lastChar->format()->width( ' ' );
922 --lastChar;
923 ++endSpaces;
924 }
925#endif
926
927 int start = (startChar - &text->at(0));
928 int last = (lastChar - &text->at(0) );
929#ifdef DEBUG_FORMATTER
930 kdDebug(32500) << "*KoTextFormatter::koBidiReorderLine from " << start << " to " << last << " space=" << space << " startChar->x=" << startChar->x << endl;
931#endif
932 KoBidiControl *control = new KoBidiControl( line->context(), line->status );
933 TQString str;
934 str.setUnicode( 0, last - start + 1 );
935 // fill string with logically ordered chars.
936 KoTextStringChar *ch = startChar;
937 TQChar *qch = (TQChar *)str.unicode();
938 while ( ch <= lastChar ) {
939 *qch = ch->c;
940 qch++;
941 ch++;
942 }
943 int x = startChar->x;
944
945 TQPtrList<KoTextRun> *runs;
946 runs = KoComplexText::bidiReorderLine(control, str, 0, last - start + 1,
947 (text->isRightToLeft() ? TQChar::DirR : TQChar::DirL) );
948
949 // now construct the reordered string out of the runs...
950
951 int numSpaces = 0;
952 // set the correct alignment. This is a bit messy....
953 if( align == TQt::AlignAuto ) {
954 // align according to directionality of the paragraph...
955 if ( text->isRightToLeft() )
956 align = TQt::AlignRight;
957 }
958
959 if ( align & TQt::AlignHCenter ) {
960 x += space/2;
961 } else if ( align & TQt::AlignRight ) {
962 x += space;
963 } else if ( align & TQt::AlignJustify ) {
964 for ( int j = last - 1; j >= start; --j ) {
966 if ( text->at( j ).c == '\t' ) {
967 start = j+1;
968 break;
969 }
970 if( settings->isStretchable( text, j ) ) {
971 numSpaces++;
972 }
973 }
974 }
975// TODO #ifndef REF_IS_LU or remove
976 int pixelx = zh->layoutUnitToPixelX( x );
977 int toAdd = 0;
978 int toAddPix = 0;
979 bool first = true;
980 KoTextRun *r = runs->first();
981 int xmax = -0xffffff;
982 while ( r ) {
983#ifdef DEBUG_FORMATTER
984 kdDebug(32500) << "koBidiReorderLine level: " << r->level << endl;
985#endif
986 if(r->level %2) {
987 // odd level, need to reverse the string
988 int pos = r->stop + start;
989 while(pos >= r->start + start) {
990 KoTextStringChar &chr = text->at(pos);
991 if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
992 int s = space / numSpaces;
993 toAdd += s;
994 toAddPix = zh->layoutUnitToPixelX( toAdd );
995 space -= s;
996 numSpaces--;
997 chr.width += s;
998 chr.pixelwidth += zh->layoutUnitToPixelX( s ); // ### rounding problem, recalculate
999 } else if ( first ) {
1000 first = false;
1001 if ( chr.c == ' ' ) // trailing space
1002 {
1003 //x -= chr.format()->width( ' ' );
1004 x -= chr.width;
1005 pixelx -= chr.pixelwidth;
1006 }
1007 }
1008 chr.x = x + toAdd;
1009 chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
1010#ifdef DEBUG_FORMATTER
1011 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;
1012#endif
1013 chr.rightToLeft = true;
1014 chr.startOfRun = false;
1015 int ww = chr.width;
1016 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
1017 x += ww;
1018 pixelx += chr.pixelwidth;
1019#ifdef DEBUG_FORMATTER
1020 kdDebug(32500) << " ww=" << ww << " adding to x, now " << x << ". pixelwidth=" << chr.pixelwidth << " adding to pixelx, now " << pixelx << " xmax=" << xmax << endl;
1021#endif
1022 pos--;
1023 }
1024 } else {
1025 int pos = r->start + start;
1026 while(pos <= r->stop + start) {
1027 KoTextStringChar& chr = text->at(pos);
1028 if( numSpaces && !first && settings->isBreakable( text, pos ) ) {
1029 int s = space / numSpaces;
1030 toAdd += s;
1031 toAddPix = zh->layoutUnitToPixelX( toAdd );
1032 space -= s;
1033 numSpaces--;
1034 } else if ( first ) {
1035 first = false;
1036 if ( chr.c == ' ' ) // trailing space
1037 {
1038 //x -= chr.format()->width( ' ' );
1039 x -= chr.width;
1040 pixelx -= chr.pixelwidth;
1041 }
1042 }
1043 chr.x = x + toAdd;
1044 chr.pixelxadj = pixelx + toAddPix - zh->layoutUnitToPixelX( chr.x );
1045 chr.rightToLeft = false;
1046 chr.startOfRun = false;
1047 int ww = chr.width;
1048 //kdDebug(32500) << "setting char " << pos << " at pos " << chr.x << endl;
1049 if ( xmax < x + toAdd + ww ) xmax = x + toAdd + ww;
1050 x += ww;
1051 pixelx += chr.pixelwidth;
1052 pos++;
1053 }
1054 }
1055 text->at( r->start + start ).startOfRun = true;
1056 r = runs->next();
1057 }
1058
1059 //line->w = xmax /*+ 10*/; // Why +10 ?
1060 KoTextParagLineStart *ls = new KoTextParagLineStart( control->context, control->status );
1061 delete control;
1062 delete runs;
1063 return ls;
1064}
1065
1066void KoTextFormatter::postFormat( KoTextParag* parag )
1067{
1068 parag->fixParagWidth( viewFormattingChars() );
1069}
static KoHyphenator * self()
Returns the single KoHyphenator instance (singleton pattern) Beware that this might throw an exceptio...
Definition kohyphen.cpp:41
int charWidth(const KoTextZoomHandler *zh, bool applyZoom, const KoTextStringChar *c, const KoTextParag *parag, int i) const
Return the width of one char in one paragraph.
const TQFontMetrics & refFontMetrics() const
int width(const TQChar &c) const
Return the width of one char (from a string, not necessarily from a paragraph) in LU pixels.
int charWidthLU(const KoTextStringChar *c, const KoTextParag *parag, int i) const
Return the width of one char in LU pixels.
We implement our own text formatter to implement WYSIWYG: It is heavily based on KoTextFormatterBaseB...
This class extends KoZoomHandler to add support for WYSIWYG text layouting.
static double ptToLayoutUnitPt(double pt)
Not zoom dependent.
int layoutUnitToPixelX(int lupix) const
The "layout unit pixel" -> "[zoomed] view pixel" conversions.