]> git.lyx.org Git - features.git/blob - src/TextMetrics.cpp
metrics fix: reset some Row values to default in top of computeRowMetrics as the...
[features.git] / src / TextMetrics.cpp
1 /**
2  * \file src/TextMetrics.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Jean-Marc Lasgouttes
9  * \author John Levon
10  * \author André Pönitz
11  * \author Dekel Tsur
12  * \author Jürgen Vigna
13  * \author Abdelrazak Younes
14  *
15  * Full author contact details are available in file CREDITS.
16  */
17
18 #include <config.h>
19
20 #include "TextMetrics.h"
21
22 #include "Buffer.h"
23 #include "BufferParams.h"
24 #include "BufferView.h"
25 #include "bufferview_funcs.h"
26 #include "Color.h"
27 #include "CoordCache.h"
28 #include "debug.h"
29 #include "FontIterator.h"
30 #include "FuncRequest.h"
31 #include "Length.h"
32 #include "LyXRC.h"
33 #include "MetricsInfo.h"
34 #include "paragraph_funcs.h"
35 #include "ParagraphParameters.h"
36 #include "ParIterator.h"
37 #include "rowpainter.h"
38 #include "Text.h"
39 #include "VSpace.h"
40
41 #include "frontends/FontMetrics.h"
42 #include "frontends/Painter.h"
43
44 #include <boost/current_function.hpp>
45
46 using std::max;
47 using std::min;
48 using std::endl;
49
50 namespace lyx {
51
52 using frontend::FontMetrics;
53
54 namespace {
55
56 int numberOfSeparators(Paragraph const & par, Row const & row)
57 {
58         pos_type const first = max(row.pos(), par.beginOfBody());
59         pos_type const last = row.endpos() - 1;
60         int n = 0;
61         for (pos_type p = first; p < last; ++p) {
62                 if (par.isSeparator(p))
63                         ++n;
64         }
65         return n;
66 }
67
68
69 int numberOfLabelHfills(Paragraph const & par, Row const & row)
70 {
71         pos_type last = row.endpos() - 1;
72         pos_type first = row.pos();
73
74         // hfill *DO* count at the beginning of paragraphs!
75         if (first) {
76                 while (first < last && par.isHfill(first))
77                         ++first;
78         }
79
80         last = min(last, par.beginOfBody());
81         int n = 0;
82         for (pos_type p = first; p < last; ++p) {
83                 if (par.isHfill(p))
84                         ++n;
85         }
86         return n;
87 }
88
89
90 int numberOfHfills(Paragraph const & par, Row const & row)
91 {
92         pos_type const last = row.endpos();
93         pos_type first = row.pos();
94
95         // hfill *DO* count at the beginning of paragraphs!
96         if (first) {
97                 while (first < last && par.isHfill(first))
98                         ++first;
99         }
100
101         first = max(first, par.beginOfBody());
102
103         int n = 0;
104         for (pos_type p = first; p < last; ++p) {
105                 if (par.isHfill(p))
106                         ++n;
107         }
108         return n;
109 }
110
111 } // namespace anon
112
113 TextMetrics::TextMetrics(BufferView * bv, Text * text)
114         : bv_(bv), text_(text)
115 {
116         BOOST_ASSERT(bv_);
117         max_width_ = bv_->workWidth();
118         dim_.wid = max_width_;
119         dim_.asc = 10;
120         dim_.des = 10;
121
122         //text_->updateLabels(bv->buffer());
123 }
124
125
126 ParagraphMetrics const & TextMetrics::parMetrics(pit_type pit) const
127 {
128         return const_cast<TextMetrics *>(this)->parMetrics(pit, true);
129 }
130
131
132 ParagraphMetrics & TextMetrics::parMetrics(pit_type pit,
133                 bool redo)
134 {
135         ParMetricsCache::iterator pmc_it = par_metrics_.find(pit);
136         if (pmc_it == par_metrics_.end()) {
137                 pmc_it = par_metrics_.insert(
138                         std::make_pair(pit, ParagraphMetrics(text_->getPar(pit)))).first;
139         }
140         if (pmc_it->second.rows().empty() && redo) {
141                 redoParagraph(pit);
142         }
143         return pmc_it->second;
144 }
145
146
147 bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim)
148 {
149         BOOST_ASSERT(mi.base.textwidth);
150         max_width_ = mi.base.textwidth;
151         // backup old dimension.
152         Dimension const old_dim = dim_;
153         // reset dimension.
154         dim_ = Dimension();
155         size_t npar = text_->paragraphs().size();
156         if (npar > 1)
157                 // If there is more than one row, expand the text to 
158                 // the full allowable width.
159                 dim_.wid = max_width_;
160
161         //lyxerr << "Text::metrics: width: " << mi.base.textwidth
162         //      << " maxWidth: " << max_width_ << "\nfont: " << mi.base.font << endl;
163
164         bool changed = false;
165         unsigned int h = 0;
166         for (pit_type pit = 0; pit != npar; ++pit) {
167                 changed |= redoParagraph(pit);
168                 ParagraphMetrics const & pm = par_metrics_[pit];
169                 h += pm.height();
170                 if (dim_.wid < pm.width())
171                         dim_.wid = pm.width();
172         }
173
174         dim_.asc = par_metrics_[0].ascent();
175         dim_.des = h - dim_.asc;
176         //lyxerr << "dim_.wid " << dim_.wid << endl;
177         //lyxerr << "dim_.asc " << dim_.asc << endl;
178         //lyxerr << "dim_.des " << dim_.des << endl;
179
180         changed |= dim_ != old_dim;
181         dim = dim_;
182         return changed;
183 }
184
185
186 int TextMetrics::rightMargin(ParagraphMetrics const & pm) const
187 {
188         return main_text_? pm.rightMargin(bv_->buffer()) : 0;
189 }
190
191
192 int TextMetrics::rightMargin(pit_type const pit) const
193 {
194         return main_text_? par_metrics_[pit].rightMargin(bv_->buffer()) : 0;
195 }
196
197
198 bool TextMetrics::redoParagraph(pit_type const pit)
199 {
200         Paragraph & par = text_->getPar(pit);
201         // IMPORTANT NOTE: We pass 'false' explicitely in order to not call
202         // redoParagraph() recursively inside parMetrics.
203         Dimension old_dim = parMetrics(pit, false).dim();
204         ParagraphMetrics & pm = par_metrics_[pit];
205         pm.reset(par);
206
207         Buffer & buffer = bv_->buffer();
208         BufferParams const & bparams = buffer.params();
209         main_text_ = (text_ == &buffer.text());
210         bool changed = false;
211
212         // FIXME This check ought to be done somewhere else. It is the reason
213         // why text_ is not     const. But then, where else to do it?
214         // Well, how can you end up with either (a) a biblio environment that
215         // has no InsetBibitem or (b) a biblio environment with more than one
216         // InsetBibitem? I think the answer is: when paragraphs are merged;
217         // when layout is set; when material is pasted.
218         int const moveCursor = par.checkBiblio(buffer.params().trackChanges);
219         if (moveCursor > 0)
220                 const_cast<Cursor &>(bv_->cursor()).posRight();
221         else if (moveCursor < 0) {
222                 Cursor & cursor = const_cast<Cursor &>(bv_->cursor());
223                 if (cursor.pos() >= -moveCursor)
224                         cursor.posLeft();
225         }
226
227         // Optimisation: this is used in the next two loops
228         // so better to calculate that once here.
229         int const right_margin = rightMargin(pm);
230
231         // redo insets
232         // FIXME: We should always use getFont(), see documentation of
233         // noFontChange() in Inset.h.
234         Font const bufferfont = buffer.params().getFont();
235         InsetList::const_iterator ii = par.insetlist.begin();
236         InsetList::const_iterator iend = par.insetlist.end();
237         for (; ii != iend; ++ii) {
238                 Dimension old_dim = ii->inset->dimension();
239                 Dimension dim;
240                 int const w = max_width_ - text_->leftMargin(buffer, max_width_, pit, ii->pos)
241                         - right_margin;
242                 Font const & font = ii->inset->noFontChange() ?
243                         bufferfont : text_->getFont(buffer, par, ii->pos);
244                 MetricsInfo mi(bv_, font, w);
245                 changed |= ii->inset->metrics(mi, dim);
246                 changed |= (old_dim != dim);
247         }
248
249         par.setBeginOfBody();
250         pos_type first = 0;
251         size_t row_index = 0;
252         // maximum pixel width of a row
253         int width = max_width_ - right_margin; // - leftMargin(buffer, max_width_, pit, row);
254         do {
255                 Dimension dim;
256                 pos_type end = rowBreakPoint(width, pit, first);
257                 if (row_index || end < par.size())
258                         // If there is more than one row, expand the text to 
259                         // the full allowable width. This setting here is needed
260                         // for the computeRowMetrics below().
261                         dim_.wid = max_width_;
262
263                 dim.wid = rowWidth(right_margin, pit, first, end);
264                 boost::tie(dim.asc, dim.des) = rowHeight(pit, first, end);
265                 if (row_index == pm.rows().size())
266                         pm.rows().push_back(Row());
267                 Row & row = pm.rows()[row_index];
268                 row.setChanged(false);
269                 row.pos(first);
270                 row.endpos(end);
271                 row.setDimension(dim);
272                 computeRowMetrics(pit, row);
273                 pm.computeRowSignature(row, bparams);
274                 first = end;
275                 ++row_index;
276
277                 pm.dim().wid = std::max(pm.dim().wid, dim.wid);
278                 pm.dim().des += dim.height();
279         } while (first < par.size());
280
281         if (row_index < pm.rows().size())
282                 pm.rows().resize(row_index);
283
284         // Make sure that if a par ends in newline, there is one more row
285         // under it
286         if (first > 0 && par.isNewline(first - 1)) {
287                 Dimension dim;
288                 dim.wid = rowWidth(right_margin, pit, first, first);
289                 boost::tie(dim.asc, dim.des) = rowHeight(pit, first, first);
290                 if (row_index == pm.rows().size())
291                         pm.rows().push_back(Row());
292                 Row & row = pm.rows()[row_index];
293                 row.setChanged(false);
294                 row.pos(first);
295                 row.endpos(first);
296                 row.setDimension(dim);
297                 computeRowMetrics(pit, row);
298                 pm.computeRowSignature(row, bparams);
299                 pm.dim().des += dim.height();
300         }
301
302         pm.dim().asc += pm.rows()[0].ascent();
303         pm.dim().des -= pm.rows()[0].ascent();
304
305         changed |= old_dim.height() != pm.dim().height();
306
307         return changed;
308 }
309
310
311 void TextMetrics::computeRowMetrics(pit_type const pit,
312                 Row & row) const
313 {
314
315         row.label_hfill = 0;
316         row.hfill = 0;
317         row.separator = 0;
318
319         Buffer & buffer = bv_->buffer();
320         Paragraph const & par = text_->getPar(pit);
321
322         double w = dim_.wid - row.width();
323         // FIXME: put back this assertion when the crash on new doc is solved.
324         //BOOST_ASSERT(w >= 0);
325
326         //lyxerr << "\ndim_.wid " << dim_.wid << endl;
327         //lyxerr << "row.width() " << row.width() << endl;
328         //lyxerr << "w " << w << endl;
329
330         bool const is_rtl = text_->isRTL(buffer, par);
331         if (is_rtl)
332                 row.x = rightMargin(pit);
333         else
334                 row.x = text_->leftMargin(buffer, max_width_, pit, row.pos());
335
336         // is there a manual margin with a manual label
337         LayoutPtr const & layout = par.layout();
338
339         if (layout->margintype == MARGIN_MANUAL
340             && layout->labeltype == LABEL_MANUAL) {
341                 /// We might have real hfills in the label part
342                 int nlh = numberOfLabelHfills(par, row);
343
344                 // A manual label par (e.g. List) has an auto-hfill
345                 // between the label text and the body of the
346                 // paragraph too.
347                 // But we don't want to do this auto hfill if the par
348                 // is empty.
349                 if (!par.empty())
350                         ++nlh;
351
352                 if (nlh && !par.getLabelWidthString().empty())
353                         row.label_hfill = labelFill(pit, row) / double(nlh);
354         }
355
356         // are there any hfills in the row?
357         int const nh = numberOfHfills(par, row);
358
359         if (nh) {
360                 if (w > 0)
361                         row.hfill = w / nh;
362         // we don't have to look at the alignment if it is ALIGN_LEFT and
363         // if the row is already larger then the permitted width as then
364         // we force the LEFT_ALIGN'edness!
365         } else if (int(row.width()) < max_width_) {
366                 // is it block, flushleft or flushright?
367                 // set x how you need it
368                 int align;
369                 if (par.params().align() == LYX_ALIGN_LAYOUT)
370                         align = layout->align;
371                 else
372                         align = par.params().align();
373
374                 // Display-style insets should always be on a centred row
375                 // The test on par.size() is to catch zero-size pars, which
376                 // would trigger the assert in Paragraph::getInset().
377                 //inset = par.size() ? par.getInset(row.pos()) : 0;
378                 if (row.pos() < par.size()
379                     && par.isInset(row.pos()))
380                 {
381                     switch(par.getInset(row.pos())->display()) {
382                         case Inset::AlignLeft:
383                                 align = LYX_ALIGN_BLOCK;
384                                 break;
385                         case Inset::AlignCenter:
386                                 align = LYX_ALIGN_CENTER;
387                                 break;
388                         case Inset::Inline:
389                         case Inset::AlignRight:
390                                 // unchanged (use align)
391                                 break;
392                     }
393                 }
394
395                 switch (align) {
396                 case LYX_ALIGN_BLOCK: {
397                         int const ns = numberOfSeparators(par, row);
398                         bool disp_inset = false;
399                         if (row.endpos() < par.size()) {
400                                 Inset const * in = par.getInset(row.endpos());
401                                 if (in)
402                                         disp_inset = in->display();
403                         }
404                         // If we have separators, this is not the last row of a
405                         // par, does not end in newline, and is not row above a
406                         // display inset... then stretch it
407                         if (ns
408                             && row.endpos() < par.size()
409                             && !par.isNewline(row.endpos() - 1)
410                             && !disp_inset
411                                 ) {
412                                 row.separator = w / ns;
413                                 //lyxerr << "row.separator " << row.separator << endl;
414                                 //lyxerr << "ns " << ns << endl;
415                         } else if (is_rtl) {
416                                 row.x += w;
417                         }
418                         break;
419                 }
420                 case LYX_ALIGN_RIGHT:
421                         row.x += w;
422                         break;
423                 case LYX_ALIGN_CENTER:
424                         row.x += w / 2;
425                         break;
426                 }
427         }
428
429         if (is_rtl) {
430                 pos_type body_pos = par.beginOfBody();
431                 pos_type end = row.endpos();
432
433                 if (body_pos > 0
434                     && (body_pos > end || !par.isLineSeparator(body_pos - 1)))
435                 {
436                         row.x += theFontMetrics(text_->getLabelFont(buffer, par)).
437                                 width(layout->labelsep);
438                         if (body_pos <= end)
439                                 row.x += row.label_hfill;
440                 }
441         }
442 }
443
444
445 int TextMetrics::labelFill(pit_type const pit, Row const & row) const
446 {
447         Buffer & buffer = bv_->buffer();
448         Paragraph const & par = text_->getPar(pit);
449
450         pos_type last = par.beginOfBody();
451         BOOST_ASSERT(last > 0);
452
453         // -1 because a label ends with a space that is in the label
454         --last;
455
456         // a separator at this end does not count
457         if (par.isLineSeparator(last))
458                 --last;
459
460         int w = 0;
461         for (pos_type i = row.pos(); i <= last; ++i)
462                 w += singleWidth(pit, i);
463
464         docstring const & label = par.params().labelWidthString();
465         if (label.empty())
466                 return 0;
467
468         FontMetrics const & fm
469                 = theFontMetrics(text_->getLabelFont(buffer, par));
470
471         return max(0, fm.width(label) - w);
472 }
473
474
475 namespace {
476
477 // this needs special handling - only newlines count as a break point
478 pos_type addressBreakPoint(pos_type i, Paragraph const & par)
479 {
480         pos_type const end = par.size();
481
482         for (; i < end; ++i)
483                 if (par.isNewline(i))
484                         return i + 1;
485
486         return end;
487 }
488
489 };
490
491
492 int TextMetrics::labelEnd(pit_type const pit) const
493 {
494         // labelEnd is only needed if the layout fills a flushleft label.
495         if (text_->getPar(pit).layout()->margintype != MARGIN_MANUAL)
496                 return 0;
497         // return the beginning of the body
498         return text_->leftMargin(bv_->buffer(), max_width_, pit);
499 }
500
501
502 pit_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
503                 pit_type pos) const
504 {
505         Buffer & buffer = bv_->buffer();
506         ParagraphMetrics const & pm = par_metrics_[pit];
507         Paragraph const & par = text_->getPar(pit);
508         pos_type const end = par.size();
509         if (pos == end || width < 0)
510                 return end;
511
512         LayoutPtr const & layout = par.layout();
513
514         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX)
515                 return addressBreakPoint(pos, par);
516
517         pos_type const body_pos = par.beginOfBody();
518
519
520         // Now we iterate through until we reach the right margin
521         // or the end of the par, then choose the possible break
522         // nearest that.
523
524         int label_end = labelEnd(pit);
525         int const left = text_->leftMargin(buffer, max_width_, pit, pos);
526         int x = left;
527
528         // pixel width since last breakpoint
529         int chunkwidth = 0;
530
531         FontIterator fi = FontIterator(buffer, *text_, par, pos);
532         pos_type point = end;
533         pos_type i = pos;
534         for ( ; i < end; ++i, ++fi) {
535                 int thiswidth = pm.singleWidth(i, *fi);
536
537                 // add the auto-hfill from label end to the body
538                 if (body_pos && i == body_pos) {
539                         FontMetrics const & fm = theFontMetrics(
540                                 text_->getLabelFont(buffer, par));
541                         int add = fm.width(layout->labelsep);
542                         if (par.isLineSeparator(i - 1))
543                                 add -= singleWidth(pit, i - 1);
544
545                         add = std::max(add, label_end - x);
546                         thiswidth += add;
547                 }
548
549                 x += thiswidth;
550                 chunkwidth += thiswidth;
551
552                 // break before a character that will fall off
553                 // the right of the row
554                 if (x >= width) {
555                         // if no break before, break here
556                         if (point == end || chunkwidth >= width - left) {
557                                 if (i > pos)
558                                         point = i;
559                                 else
560                                         point = i + 1;
561
562                         }
563                         // exit on last registered breakpoint:
564                         break;
565                 }
566
567                 if (par.isNewline(i)) {
568                         point = i + 1;
569                         break;
570                 }
571                 // Break before...
572                 if (i + 1 < end) {
573                         if (par.isInset(i + 1) && par.getInset(i + 1)->display()) {
574                                 point = i + 1;
575                                 break;
576                         }
577                         // ...and after.
578                         if (par.isInset(i) && par.getInset(i)->display()) {
579                                 point = i + 1;
580                                 break;
581                         }
582                 }
583
584                 if (!par.isInset(i) || par.getInset(i)->isChar()) {
585                         // some insets are line separators too
586                         if (par.isLineSeparator(i)) {
587                                 // register breakpoint:
588                                 point = i + 1;
589                                 chunkwidth = 0;
590                         }
591                 }
592         }
593
594         // maybe found one, but the par is short enough.
595         if (i == end && x < width)
596                 point = end;
597
598         // manual labels cannot be broken in LaTeX. But we
599         // want to make our on-screen rendering of footnotes
600         // etc. still break
601         if (body_pos && point < body_pos)
602                 point = body_pos;
603
604         return point;
605 }
606
607
608 int TextMetrics::rowWidth(int right_margin, pit_type const pit,
609                 pos_type const first, pos_type const end) const
610 {
611         Buffer & buffer = bv_->buffer();
612         // get the pure distance
613         ParagraphMetrics const & pm = par_metrics_[pit];
614         Paragraph const & par = text_->getPar(pit);
615         int w = text_->leftMargin(buffer, max_width_, pit, first);
616         int label_end = labelEnd(pit);
617
618         pos_type const body_pos = par.beginOfBody();
619         pos_type i = first;
620
621         if (i < end) {
622                 FontIterator fi = FontIterator(buffer, *text_, par, i);
623                 for ( ; i < end; ++i, ++fi) {
624                         if (body_pos > 0 && i == body_pos) {
625                                 FontMetrics const & fm = theFontMetrics(
626                                         text_->getLabelFont(buffer, par));
627                                 w += fm.width(par.layout()->labelsep);
628                                 if (par.isLineSeparator(i - 1))
629                                         w -= singleWidth(pit, i - 1);
630                                 w = max(w, label_end);
631                         }
632                         w += pm.singleWidth(i, *fi);
633                 }
634         }
635
636         if (body_pos > 0 && body_pos >= end) {
637                 FontMetrics const & fm = theFontMetrics(
638                         text_->getLabelFont(buffer, par));
639                 w += fm.width(par.layout()->labelsep);
640                 if (end > 0 && par.isLineSeparator(end - 1))
641                         w -= singleWidth(pit, end - 1);
642                 w = max(w, label_end);
643         }
644
645         return w + right_margin;
646 }
647
648
649 boost::tuple<int, int> TextMetrics::rowHeight(pit_type const pit, pos_type const first,
650                 pos_type const end) const
651 {
652         Paragraph const & par = text_->getPar(pit);
653         // get the maximum ascent and the maximum descent
654         double layoutasc = 0;
655         double layoutdesc = 0;
656         double const dh = defaultRowHeight();
657
658         // ok, let us initialize the maxasc and maxdesc value.
659         // Only the fontsize count. The other properties
660         // are taken from the layoutfont. Nicer on the screen :)
661         LayoutPtr const & layout = par.layout();
662
663         // as max get the first character of this row then it can
664         // increase but not decrease the height. Just some point to
665         // start with so we don't have to do the assignment below too
666         // often.
667         Buffer const & buffer = bv_->buffer();
668         Font font = text_->getFont(buffer, par, first);
669         Font::FONT_SIZE const tmpsize = font.size();
670         font = text_->getLayoutFont(buffer, pit);
671         Font::FONT_SIZE const size = font.size();
672         font.setSize(tmpsize);
673
674         Font labelfont = text_->getLabelFont(buffer, par);
675
676         FontMetrics const & labelfont_metrics = theFontMetrics(labelfont);
677         FontMetrics const & fontmetrics = theFontMetrics(font);
678
679         // these are minimum values
680         double const spacing_val = layout->spacing.getValue()
681                 * text_->spacing(buffer, par);
682         //lyxerr << "spacing_val = " << spacing_val << endl;
683         int maxasc  = int(fontmetrics.maxAscent()  * spacing_val);
684         int maxdesc = int(fontmetrics.maxDescent() * spacing_val);
685
686         // insets may be taller
687         InsetList::const_iterator ii = par.insetlist.begin();
688         InsetList::const_iterator iend = par.insetlist.end();
689         for ( ; ii != iend; ++ii) {
690                 if (ii->pos >= first && ii->pos < end) {
691                         maxasc  = max(maxasc,  ii->inset->ascent());
692                         maxdesc = max(maxdesc, ii->inset->descent());
693                 }
694         }
695
696         // Check if any custom fonts are larger (Asger)
697         // This is not completely correct, but we can live with the small,
698         // cosmetic error for now.
699         int labeladdon = 0;
700
701         Font::FONT_SIZE maxsize =
702                 par.highestFontInRange(first, end, size);
703         if (maxsize > font.size()) {
704                 // use standard paragraph font with the maximal size
705                 Font maxfont = font;
706                 maxfont.setSize(maxsize);
707                 FontMetrics const & maxfontmetrics = theFontMetrics(maxfont);
708                 maxasc  = max(maxasc,  maxfontmetrics.maxAscent());
709                 maxdesc = max(maxdesc, maxfontmetrics.maxDescent());
710         }
711
712         // This is nicer with box insets:
713         ++maxasc;
714         ++maxdesc;
715
716         ParagraphList const & pars = text_->paragraphs();
717
718         // is it a top line?
719         if (first == 0) {
720                 BufferParams const & bufparams = buffer.params();
721                 // some parskips VERY EASY IMPLEMENTATION
722                 if (bufparams.paragraph_separation
723                     == BufferParams::PARSEP_SKIP
724                         && par.ownerCode() != Inset::ERT_CODE
725                         && par.ownerCode() != Inset::LISTINGS_CODE
726                         && pit > 0
727                         && ((layout->isParagraph() && par.getDepth() == 0)
728                             || (pars[pit - 1].layout()->isParagraph()
729                                 && pars[pit - 1].getDepth() == 0)))
730                 {
731                                 maxasc += bufparams.getDefSkip().inPixels(*bv_);
732                 }
733
734                 if (par.params().startOfAppendix())
735                         maxasc += int(3 * dh);
736
737                 // This is special code for the chapter, since the label of this
738                 // layout is printed in an extra row
739                 if (layout->counter == "chapter"
740                     && !par.params().labelString().empty()) {
741                         labeladdon = int(labelfont_metrics.maxHeight()
742                                      * layout->spacing.getValue()
743                                      * text_->spacing(buffer, par));
744                 }
745
746                 // special code for the top label
747                 if ((layout->labeltype == LABEL_TOP_ENVIRONMENT
748                      || layout->labeltype == LABEL_BIBLIO
749                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)
750                     && isFirstInSequence(pit, pars)
751                     && !par.getLabelstring().empty())
752                 {
753                         labeladdon = int(
754                                   labelfont_metrics.maxHeight()
755                                         * layout->spacing.getValue()
756                                         * text_->spacing(buffer, par)
757                                 + (layout->topsep + layout->labelbottomsep) * dh);
758                 }
759
760                 // Add the layout spaces, for example before and after
761                 // a section, or between the items of a itemize or enumerate
762                 // environment.
763
764                 pit_type prev = depthHook(pit, pars, par.getDepth());
765                 Paragraph const & prevpar = pars[prev];
766                 if (prev != pit
767                     && prevpar.layout() == layout
768                     && prevpar.getDepth() == par.getDepth()
769                     && prevpar.getLabelWidthString()
770                                         == par.getLabelWidthString()) {
771                         layoutasc = layout->itemsep * dh;
772                 } else if (pit != 0 || first != 0) {
773                         if (layout->topsep > 0)
774                                 layoutasc = layout->topsep * dh;
775                 }
776
777                 prev = outerHook(pit, pars);
778                 if (prev != pit_type(pars.size())) {
779                         maxasc += int(pars[prev].layout()->parsep * dh);
780                 } else if (pit != 0) {
781                         Paragraph const & prevpar = pars[pit - 1];
782                         if (prevpar.getDepth() != 0 ||
783                                         prevpar.layout() == layout) {
784                                 maxasc += int(layout->parsep * dh);
785                         }
786                 }
787         }
788
789         // is it a bottom line?
790         if (end >= par.size()) {
791                 // add the layout spaces, for example before and after
792                 // a section, or between the items of a itemize or enumerate
793                 // environment
794                 pit_type nextpit = pit + 1;
795                 if (nextpit != pit_type(pars.size())) {
796                         pit_type cpit = pit;
797                         double usual = 0;
798                         double unusual = 0;
799
800                         if (pars[cpit].getDepth() > pars[nextpit].getDepth()) {
801                                 usual = pars[cpit].layout()->bottomsep * dh;
802                                 cpit = depthHook(cpit, pars, pars[nextpit].getDepth());
803                                 if (pars[cpit].layout() != pars[nextpit].layout()
804                                         || pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
805                                 {
806                                         unusual = pars[cpit].layout()->bottomsep * dh;
807                                 }
808                                 layoutdesc = max(unusual, usual);
809                         } else if (pars[cpit].getDepth() == pars[nextpit].getDepth()) {
810                                 if (pars[cpit].layout() != pars[nextpit].layout()
811                                         || pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
812                                         layoutdesc = int(pars[cpit].layout()->bottomsep * dh);
813                         }
814                 }
815         }
816
817         // incalculate the layout spaces
818         maxasc  += int(layoutasc  * 2 / (2 + pars[pit].getDepth()));
819         maxdesc += int(layoutdesc * 2 / (2 + pars[pit].getDepth()));
820
821         // FIXME: the correct way is to do the following is to move the
822         // following code in another method specially tailored for the
823         // main Text. The following test is thus bogus.
824         // Top and bottom margin of the document (only at top-level)
825         if (main_text_) {
826                 if (pit == 0 && first == 0)
827                         maxasc += 20;
828                 if (pit + 1 == pit_type(pars.size()) &&
829                     end == par.size() &&
830                                 !(end > 0 && par.isNewline(end - 1)))
831                         maxdesc += 20;
832         }
833
834         return boost::make_tuple(maxasc + labeladdon, maxdesc);
835 }
836
837
838 // x is an absolute screen coord
839 // returns the column near the specified x-coordinate of the row
840 // x is set to the real beginning of this column
841 pos_type TextMetrics::getColumnNearX(pit_type const pit,
842                 Row const & row, int & x, bool & boundary) const
843 {
844         Buffer const & buffer = bv_->buffer();
845
846         /// For the main Text, it is possible that this pit is not
847         /// yet in the CoordCache when moving cursor up.
848         /// x Paragraph coordinate is always 0 for main text anyway.
849         int const xo = main_text_? 0 : bv_->coordCache().get(text_, pit).x_;
850         x -= xo;
851         Paragraph const & par = text_->getPar(pit);
852         ParagraphMetrics const & pm = par_metrics_[pit];
853         Bidi bidi;
854         bidi.computeTables(par, buffer, row);
855
856         pos_type vc = row.pos();
857         pos_type end = row.endpos();
858         pos_type c = 0;
859         LayoutPtr const & layout = par.layout();
860
861         bool left_side = false;
862
863         pos_type body_pos = par.beginOfBody();
864
865         double tmpx = row.x;
866         double last_tmpx = tmpx;
867
868         if (body_pos > 0 &&
869             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
870                 body_pos = 0;
871
872         // check for empty row
873         if (vc == end) {
874                 x = int(tmpx) + xo;
875                 return 0;
876         }
877
878         while (vc < end && tmpx <= x) {
879                 c = bidi.vis2log(vc);
880                 last_tmpx = tmpx;
881                 if (body_pos > 0 && c == body_pos - 1) {
882                         FontMetrics const & fm = theFontMetrics(
883                                 text_->getLabelFont(buffer, par));
884                         tmpx += row.label_hfill + fm.width(layout->labelsep);
885                         if (par.isLineSeparator(body_pos - 1))
886                                 tmpx -= singleWidth(pit, body_pos - 1);
887                 }
888
889                 if (pm.hfillExpansion(row, c)) {
890                         tmpx += singleWidth(pit, c);
891                         if (c >= body_pos)
892                                 tmpx += row.hfill;
893                         else
894                                 tmpx += row.label_hfill;
895                 } else if (par.isSeparator(c)) {
896                         tmpx += singleWidth(pit, c);
897                         if (c >= body_pos)
898                                 tmpx += row.separator;
899                 } else {
900                         tmpx += singleWidth(pit, c);
901                 }
902                 ++vc;
903         }
904
905         if ((tmpx + last_tmpx) / 2 > x) {
906                 tmpx = last_tmpx;
907                 left_side = true;
908         }
909
910         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
911
912         boundary = false;
913         // This (rtl_support test) is not needed, but gives
914         // some speedup if rtl_support == false
915         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
916
917         // If lastrow is false, we don't need to compute
918         // the value of rtl.
919         bool const rtl = lastrow ? text_->isRTL(buffer, par) : false;
920         if (lastrow &&
921             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
922              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
923                 c = end;
924         else if (vc == row.pos()) {
925                 c = bidi.vis2log(vc);
926                 if (bidi.level(c) % 2 == 1)
927                         ++c;
928         } else {
929                 c = bidi.vis2log(vc - 1);
930                 bool const rtl = (bidi.level(c) % 2 == 1);
931                 if (left_side == rtl) {
932                         ++c;
933                         boundary = text_->isRTLBoundary(buffer, par, c);
934                 }
935         }
936
937 // I believe this code is not needed anymore (Jug 20050717)
938 #if 0
939         // The following code is necessary because the cursor position past
940         // the last char in a row is logically equivalent to that before
941         // the first char in the next row. That's why insets causing row
942         // divisions -- Newline and display-style insets -- must be treated
943         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
944         // Newline inset, air gap below:
945         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
946                 if (bidi.level(end -1) % 2 == 0)
947                         tmpx -= singleWidth(pit, end - 1);
948                 else
949                         tmpx += singleWidth(pit, end - 1);
950                 c = end - 1;
951         }
952
953         // Air gap above display inset:
954         if (row.pos() < end && c >= end && end < par.size()
955             && par.isInset(end) && par.getInset(end)->display()) {
956                 c = end - 1;
957         }
958         // Air gap below display inset:
959         if (row.pos() < end && c >= end && par.isInset(end - 1)
960             && par.getInset(end - 1)->display()) {
961                 c = end - 1;
962         }
963 #endif
964
965         x = int(tmpx) + xo;
966         pos_type const col = c - row.pos();
967
968         if (!c || end == par.size())
969                 return col;
970
971         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
972                 boundary = true;
973                 return col;
974         }
975
976         return min(col, end - 1 - row.pos());
977 }
978
979
980 pos_type TextMetrics::x2pos(pit_type pit, int row, int x) const
981 {
982         ParagraphMetrics const & pm = par_metrics_[pit];
983         BOOST_ASSERT(!pm.rows().empty());
984         BOOST_ASSERT(row < int(pm.rows().size()));
985         bool bound = false;
986         Row const & r = pm.rows()[row];
987         return r.pos() + getColumnNearX(pit, r, x, bound);
988 }
989
990
991 int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
992 {
993         Buffer const & buffer = bv_->buffer();
994         ParagraphMetrics const & pm = par_metrics_[pit];
995
996         return pm.singleWidth(pos, text_->getFont(buffer, text_->getPar(pit), pos));
997 }
998
999
1000 // only used for inset right now. should also be used for main text
1001 void TextMetrics::draw(PainterInfo & pi, int x, int y) const
1002 {
1003         if (par_metrics_.empty())
1004                 return;
1005
1006         ParMetricsCache::const_iterator it = par_metrics_.begin();
1007         ParMetricsCache::const_iterator const end = par_metrics_.end();
1008         y -= it->second.ascent();
1009         for (; it != end; ++it) {
1010                 ParagraphMetrics const & pmi = it->second;
1011                 y += pmi.ascent();
1012                 drawParagraph(pi, it->first, x, y);
1013                 y += pmi.descent();
1014         }
1015 }
1016
1017
1018 void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const
1019 {
1020 //      lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
1021         int const ww = bv_->workHeight();
1022
1023         bv_->coordCache().parPos()[text_][pit] = Point(x, y);
1024
1025         ParagraphMetrics const & pm = par_metrics_[pit];
1026         if (pm.rows().empty())
1027                 return;
1028
1029         RowList::const_iterator const rb = pm.rows().begin();
1030         RowList::const_iterator const re = pm.rows().end();
1031
1032         Bidi bidi;
1033
1034         y -= rb->ascent();
1035         for (RowList::const_iterator rit = rb; rit != re; ++rit) {
1036                 y += rit->ascent();
1037
1038                 bool const inside = (y + rit->descent() >= 0
1039                         && y - rit->ascent() < ww);
1040                 // it is not needed to draw on screen if we are not inside.
1041                 pi.pain.setDrawingEnabled(inside);
1042                 RowPainter rp(pi, *text_, pit, *rit, bidi, x, y);
1043
1044                 // Row signature; has row changed since last paint?
1045                 bool row_has_changed = rit->changed();
1046                 
1047                 if (!pi.full_repaint && !row_has_changed) {
1048                         // Paint the only the insets if the text itself is
1049                         // unchanged.
1050                         rp.paintOnlyInsets();
1051                         y += rit->descent();
1052                         continue;
1053                 }
1054
1055                 // Paint the row if a full repaint has been requested or it has
1056                 // changed.
1057                 // Clear background of this row
1058                 // (if paragraph background was not cleared)
1059                 if (!pi.full_repaint && row_has_changed) {
1060                         pi.pain.fillRectangle(x, y - rit->ascent(),
1061                         width(), rit->height(),
1062                         text_->backgroundColor());
1063                 }
1064
1065                 // Instrumentation for testing row cache (see also
1066                 // 12 lines lower):
1067                 if (lyxerr.debugging(Debug::PAINTING)) {
1068                         if (text_->isMainText(bv_->buffer()))
1069                                 LYXERR(Debug::PAINTING) << "\n{" <<
1070                                 pi.full_repaint << row_has_changed << "}";
1071                         else
1072                                 LYXERR(Debug::PAINTING) << "[" <<
1073                                 pi.full_repaint << row_has_changed << "]";
1074                 }
1075
1076                 // Backup full_repaint status and force full repaint
1077                 // for inner insets as the Row has been cleared out.
1078                 bool tmp = pi.full_repaint;
1079                 pi.full_repaint = true;
1080                 rp.paintAppendix();
1081                 rp.paintDepthBar();
1082                 rp.paintChangeBar();
1083                 if (rit == rb)
1084                         rp.paintFirst();
1085                 rp.paintText();
1086                 if (rit + 1 == re)
1087                         rp.paintLast();
1088                 y += rit->descent();
1089                 // Restore full_repaint status.
1090                 pi.full_repaint = tmp;
1091         }
1092         // Re-enable screen drawing for future use of the painter.
1093         pi.pain.setDrawingEnabled(true);
1094
1095         //LYXERR(Debug::PAINTING) << "." << endl;
1096 }
1097
1098
1099 // only used for inset right now. should also be used for main text
1100 void TextMetrics::drawSelection(PainterInfo & pi, int x, int) const
1101 {
1102         Cursor & cur = bv_->cursor();
1103         if (!cur.selection())
1104                 return;
1105         if (!ptr_cmp(cur.text(), text_))
1106                 return;
1107
1108         LYXERR(Debug::DEBUG)
1109                 << BOOST_CURRENT_FUNCTION
1110                 << "draw selection at " << x
1111                 << endl;
1112
1113         DocIterator beg = cur.selectionBegin();
1114         DocIterator end = cur.selectionEnd();
1115
1116         // the selection doesn't touch the visible screen?
1117         if (bv_funcs::status(bv_, beg) == bv_funcs::CUR_BELOW
1118             || bv_funcs::status(bv_, end) == bv_funcs::CUR_ABOVE)
1119                 return;
1120
1121         ParagraphMetrics const & pm1 = par_metrics_[beg.pit()];
1122         ParagraphMetrics const & pm2 = par_metrics_[end.pit()];
1123         Row const & row1 = pm1.getRow(beg.pos(), beg.boundary());
1124         Row const & row2 = pm2.getRow(end.pos(), end.boundary());
1125
1126         // clip above
1127         int middleTop;
1128         bool const clipAbove = 
1129                 (bv_funcs::status(bv_, beg) == bv_funcs::CUR_ABOVE);
1130         if (clipAbove)
1131                 middleTop = 0;
1132         else
1133                 middleTop = bv_funcs::getPos(*bv_, beg, beg.boundary()).y_ + row1.descent();
1134         
1135         // clip below
1136         int middleBottom;
1137         bool const clipBelow = 
1138                 (bv_funcs::status(bv_, end) == bv_funcs::CUR_BELOW);
1139         if (clipBelow)
1140                 middleBottom = bv_->workHeight();
1141         else
1142                 middleBottom = bv_funcs::getPos(*bv_, end, end.boundary()).y_ - row2.ascent();
1143
1144         // start and end in the same line?
1145         if (!(clipAbove || clipBelow) && &row1 == &row2)
1146                 // then only draw this row's selection
1147                 drawRowSelection(pi, x, row1, beg, end, false, false);
1148         else {
1149                 if (!clipAbove) {
1150                         // get row end
1151                         DocIterator begRowEnd = beg;
1152                         begRowEnd.pos() = row1.endpos();
1153                         begRowEnd.boundary(true);
1154                         
1155                         // draw upper rectangle
1156                         drawRowSelection(pi, x, row1, beg, begRowEnd, false, true);
1157                 }
1158                         
1159                 if (middleTop < middleBottom) {
1160                         // draw middle rectangle
1161                         pi.pain.fillRectangle(x, middleTop, width(), middleBottom - middleTop,
1162                                 Color::selection);
1163                 }
1164
1165                 if (!clipBelow) {
1166                         // get row begin
1167                         DocIterator endRowBeg = end;
1168                         endRowBeg.pos() = row2.pos();
1169                         endRowBeg.boundary(false);
1170                         
1171                         // draw low rectangle
1172                         drawRowSelection(pi, x, row2, endRowBeg, end, true, false);
1173                 }
1174         }
1175 }
1176
1177
1178 void TextMetrics::drawRowSelection(PainterInfo & pi, int x, Row const & row,
1179                 DocIterator const & beg, DocIterator const & end,
1180                 bool drawOnBegMargin, bool drawOnEndMargin) const
1181 {
1182         Buffer & buffer = bv_->buffer();
1183         DocIterator cur = beg;
1184         int x1 = text_->cursorX(*bv_, beg.top(), beg.boundary());
1185         int x2 = text_->cursorX(*bv_, end.top(), end.boundary());
1186         int y1 = bv_funcs::getPos(*bv_, cur, cur.boundary()).y_ - row.ascent();
1187         int y2 = y1 + row.height();
1188         
1189         // draw the margins
1190         if (drawOnBegMargin) {
1191                 if (text_->isRTL(buffer, beg.paragraph()))
1192                         pi.pain.fillRectangle(x + x1, y1, width() - x1, y2 - y1, Color::selection);
1193                 else
1194                         pi.pain.fillRectangle(x, y1, x1, y2 - y1, Color::selection);
1195         }
1196         
1197         if (drawOnEndMargin) {
1198                 if (text_->isRTL(buffer, beg.paragraph()))
1199                         pi.pain.fillRectangle(x, y1, x2, y2 - y1, Color::selection);
1200                 else
1201                         pi.pain.fillRectangle(x + x2, y1, width() - x2, y2 - y1, Color::selection);
1202         }
1203         
1204         // if we are on a boundary from the beginning, it's probably
1205         // a RTL boundary and we jump to the other side directly as this
1206         // segement is 0-size and confuses the logic below
1207         if (cur.boundary())
1208                 cur.boundary(false);
1209         
1210         // go through row and draw from RTL boundary to RTL boundary
1211         while (cur < end) {
1212                 bool drawNow = false;
1213                 
1214                 // simplified cursorRight code below which does not
1215                 // descend into insets and which does not go into the
1216                 // next line. Compare the logic with the original cursorRight
1217                 
1218                 // if left of boundary -> just jump to right side
1219                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
1220                 if (cur.boundary()) {
1221                         cur.boundary(false);
1222                 }       else if (text_->isRTLBoundary(buffer, cur.paragraph(), cur.pos() + 1)) {
1223                         // in front of RTL boundary -> Stay on this side of the boundary because:
1224                         //   ab|cDDEEFFghi -> abc|DDEEFFghi
1225                         ++cur.pos();
1226                         cur.boundary(true);
1227                         drawNow = true;
1228                 } else {
1229                         // move right
1230                         ++cur.pos();
1231                         
1232                         // line end?
1233                         if (cur.pos() == row.endpos())
1234                                 cur.boundary(true);
1235                 }
1236                         
1237                 if (x1 == -1) {
1238                         // the previous segment was just drawn, now the next starts
1239                         x1 = text_->cursorX(*bv_, cur.top(), cur.boundary());
1240                 }
1241                 
1242                 if (!(cur < end) || drawNow) {
1243                         x2 = text_->cursorX(*bv_, cur.top(), cur.boundary());
1244                         pi.pain.fillRectangle(x + min(x1,x2), y1, abs(x2 - x1), y2 - y1,
1245                                 Color::selection);
1246                         
1247                         // reset x1, so it is set again next round (which will be on the 
1248                         // right side of a boundary or at the selection end)
1249                         x1 = -1;
1250                 }
1251         }
1252 }
1253
1254 //int Text::pos2x(pit_type pit, pos_type pos) const
1255 //{
1256 //      ParagraphMetrics const & pm = par_metrics_[pit];
1257 //      Row const & r = pm.rows()[row];
1258 //      int x = 0;
1259 //      pos -= r.pos();
1260 //}
1261
1262
1263 int defaultRowHeight()
1264 {
1265         return int(theFontMetrics(Font(Font::ALL_SANE)).maxHeight() *  1.2);
1266 }
1267
1268 } // namespace lyx