]> git.lyx.org Git - features.git/blob - src/TextMetrics.cpp
2d820c4caeec34994711d032801ce7f4edecca34
[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 "buffer_funcs.h"
24 #include "BufferParams.h"
25 #include "BufferView.h"
26 #include "bufferview_funcs.h"
27 #include "Color.h"
28 #include "CoordCache.h"
29 #include "CutAndPaste.h"
30 #include "debug.h"
31 #include "FontIterator.h"
32 #include "FuncRequest.h"
33 #include "Length.h"
34 #include "LyXRC.h"
35 #include "MetricsInfo.h"
36 #include "paragraph_funcs.h"
37 #include "ParagraphParameters.h"
38 #include "ParIterator.h"
39 #include "rowpainter.h"
40 #include "Text.h"
41 #include "Undo.h"
42 #include "VSpace.h"
43
44 #include "frontends/FontMetrics.h"
45 #include "frontends/Painter.h"
46
47 #include <boost/current_function.hpp>
48
49 using std::max;
50 using std::min;
51 using std::endl;
52
53 namespace lyx {
54
55 using frontend::FontMetrics;
56
57 namespace {
58
59 int numberOfSeparators(Paragraph const & par, Row const & row)
60 {
61         pos_type const first = max(row.pos(), par.beginOfBody());
62         pos_type const last = row.endpos() - 1;
63         int n = 0;
64         for (pos_type p = first; p < last; ++p) {
65                 if (par.isSeparator(p))
66                         ++n;
67         }
68         return n;
69 }
70
71
72 int numberOfLabelHfills(Paragraph const & par, Row const & row)
73 {
74         pos_type last = row.endpos() - 1;
75         pos_type first = row.pos();
76
77         // hfill *DO* count at the beginning of paragraphs!
78         if (first) {
79                 while (first < last && par.isHfill(first))
80                         ++first;
81         }
82
83         last = min(last, par.beginOfBody());
84         int n = 0;
85         for (pos_type p = first; p < last; ++p) {
86                 if (par.isHfill(p))
87                         ++n;
88         }
89         return n;
90 }
91
92
93 int numberOfHfills(Paragraph const & par, Row const & row)
94 {
95         pos_type const last = row.endpos();
96         pos_type first = row.pos();
97
98         // hfill *DO* count at the beginning of paragraphs!
99         if (first) {
100                 while (first < last && par.isHfill(first))
101                         ++first;
102         }
103
104         first = max(first, par.beginOfBody());
105
106         int n = 0;
107         for (pos_type p = first; p < last; ++p) {
108                 if (par.isHfill(p))
109                         ++n;
110         }
111         return n;
112 }
113
114 } // namespace anon
115
116 TextMetrics::TextMetrics(BufferView * bv, Text * text)
117         : bv_(bv), text_(text)
118 {
119         BOOST_ASSERT(bv_);
120         max_width_ = bv_->workWidth();
121         dim_.wid = max_width_;
122         dim_.asc = 10;
123         dim_.des = 10;
124
125         //text_->updateLabels(bv->buffer());
126 }
127
128
129 ParagraphMetrics const & TextMetrics::parMetrics(pit_type pit) const
130 {
131         return const_cast<TextMetrics *>(this)->parMetrics(pit, true);
132 }
133
134
135 ParagraphMetrics & TextMetrics::parMetrics(pit_type pit,
136                 bool redo)
137 {
138         ParMetricsCache::iterator pmc_it = par_metrics_.find(pit);
139         if (pmc_it == par_metrics_.end()) {
140                 pmc_it = par_metrics_.insert(
141                         std::make_pair(pit, ParagraphMetrics(text_->getPar(pit)))).first;
142         }
143         if (pmc_it->second.rows().empty() && redo) {
144                 redoParagraph(pit);
145         }
146         return pmc_it->second;
147 }
148
149
150 bool TextMetrics::metrics(MetricsInfo & mi, Dimension & dim)
151 {
152         BOOST_ASSERT(mi.base.textwidth);
153         max_width_ = mi.base.textwidth;
154         // backup old dimension.
155         Dimension const old_dim = dim_;
156         // reset dimension.
157         dim_ = Dimension();
158         pit_type const npar = text_->paragraphs().size();
159         if (npar > 1)
160                 // If there is more than one row, expand the text to 
161                 // the full allowable width.
162                 dim_.wid = max_width_;
163
164         //lyxerr << "TextMetrics::metrics: width: " << mi.base.textwidth
165         //      << " maxWidth: " << max_width_ << "\nfont: " << mi.base.font << endl;
166
167         bool changed = false;
168         unsigned int h = 0;
169         for (pit_type pit = 0; pit != npar; ++pit) {
170                 changed |= redoParagraph(pit);
171                 ParagraphMetrics const & pm = par_metrics_[pit];
172                 h += pm.height();
173                 if (dim_.wid < pm.width())
174                         dim_.wid = pm.width();
175         }
176
177         dim_.asc = par_metrics_[0].ascent();
178         dim_.des = h - dim_.asc;
179         //lyxerr << "dim_.wid " << dim_.wid << endl;
180         //lyxerr << "dim_.asc " << dim_.asc << endl;
181         //lyxerr << "dim_.des " << dim_.des << endl;
182
183         changed |= dim_ != old_dim;
184         dim = dim_;
185         return changed;
186 }
187
188
189 int TextMetrics::rightMargin(ParagraphMetrics const & pm) const
190 {
191         return main_text_? pm.rightMargin(bv_->buffer()) : 0;
192 }
193
194
195 int TextMetrics::rightMargin(pit_type const pit) const
196 {
197         return main_text_? par_metrics_[pit].rightMargin(bv_->buffer()) : 0;
198 }
199
200
201 void TextMetrics::applyOuterFont(Font & font) const
202 {
203         Font lf(font_);
204         lf.reduce(bv_->buffer().params().getFont());
205         lf.realize(font);
206         lf.setLanguage(font.language());
207         font = lf;
208 }
209
210
211 Font TextMetrics::getDisplayFont(Paragraph const & par,
212                 pos_type const pos) const
213 {
214         BOOST_ASSERT(pos >= 0);
215
216         LayoutPtr const & layout = par.layout();
217         Buffer const & buffer = bv_->buffer();
218         // FIXME: broken?
219         BufferParams const & params = buffer.params();
220         pos_type const body_pos = par.beginOfBody();
221
222         // We specialize the 95% common case:
223         if (!par.getDepth()) {
224                 Font f = par.getFontSettings(params, pos);
225                 if (!text_->isMainText(buffer))
226                         applyOuterFont(f);
227                 Font lf;
228                 Font rlf;
229                 if (layout->labeltype == LABEL_MANUAL && pos < body_pos) {
230                         lf = layout->labelfont;
231                         rlf = layout->reslabelfont;
232                 } else {
233                         lf = layout->font;
234                         rlf = layout->resfont;
235                 }
236                 // In case the default family has been customized
237                 if (lf.family() == Font::INHERIT_FAMILY)
238                         rlf.setFamily(params.getFont().family());
239                 return f.realize(rlf);
240         }
241
242         // The uncommon case need not be optimized as much
243         Font layoutfont;
244         if (pos < body_pos)
245                 layoutfont = layout->labelfont;
246         else
247                 layoutfont = layout->font;
248
249         Font font = par.getFontSettings(params, pos);
250         font.realize(layoutfont);
251
252         if (!text_->isMainText(buffer))
253                 applyOuterFont(font);
254
255         // Find the pit value belonging to paragraph. This will not break
256         // even if pars_ would not be a vector anymore.
257         // Performance appears acceptable.
258
259         ParagraphList const & pars = text_->paragraphs();
260
261         pit_type pit = pars.size();
262         for (pit_type it = 0; it < pit; ++it)
263                 if (&pars[it] == &par) {
264                         pit = it;
265                         break;
266                 }
267         // Realize against environment font information
268         // NOTE: the cast to pit_type should be removed when pit_type
269         // changes to a unsigned integer.
270         if (pit < pit_type(pars.size()))
271                 font.realize(outerFont(pit, pars));
272
273         // Realize with the fonts of lesser depth.
274         font.realize(params.getFont());
275
276         return font;
277 }
278
279
280 bool TextMetrics::isRTL(CursorSlice const & sl, bool boundary) const
281 {
282         if (!lyxrc.rtl_support && !sl.text())
283                 return false;
284
285         int correction = 0;
286         if (boundary && sl.pos() > 0)
287                 correction = -1;
288                 
289         Paragraph const & par = text_->getPar(sl.pit());
290         return getDisplayFont(par, sl.pos() + correction).isVisibleRightToLeft();
291 }
292
293
294 bool TextMetrics::isRTLBoundary(Paragraph const & par,
295                          pos_type pos) const
296 {
297         if (!lyxrc.rtl_support)
298                 return false;
299
300         // no RTL boundary at line start
301         if (pos == 0)
302                 return false;
303
304         bool left = getDisplayFont(par, pos - 1).isVisibleRightToLeft();
305         bool right;
306         if (pos == par.size())
307                 right = par.isRightToLeftPar(bv_->buffer().params());
308         else
309                 right = getDisplayFont(par, pos).isVisibleRightToLeft();
310         return left != right;
311 }
312
313
314 bool TextMetrics::isRTLBoundary(Paragraph const & par,
315                          pos_type pos, Font const & font) const
316 {
317         if (!lyxrc.rtl_support)
318                 return false;
319
320         bool left = font.isVisibleRightToLeft();
321         bool right;
322         if (pos == par.size())
323                 right = par.isRightToLeftPar(bv_->buffer().params());
324         else
325                 right = getDisplayFont(par, pos).isVisibleRightToLeft();
326         return left != right;
327 }
328
329
330 bool TextMetrics::redoParagraph(pit_type const pit)
331 {
332         Paragraph & par = text_->getPar(pit);
333         // IMPORTANT NOTE: We pass 'false' explicitely in order to not call
334         // redoParagraph() recursively inside parMetrics.
335         Dimension old_dim = parMetrics(pit, false).dim();
336         ParagraphMetrics & pm = par_metrics_[pit];
337         pm.reset(par);
338
339         Buffer & buffer = bv_->buffer();
340         BufferParams const & bparams = buffer.params();
341         main_text_ = (text_ == &buffer.text());
342         bool changed = false;
343
344         // FIXME This check ought to be done somewhere else. It is the reason
345         // why text_ is not     const. But then, where else to do it?
346         // Well, how can you end up with either (a) a biblio environment that
347         // has no InsetBibitem or (b) a biblio environment with more than one
348         // InsetBibitem? I think the answer is: when paragraphs are merged;
349         // when layout is set; when material is pasted.
350         int const moveCursor = par.checkBiblio(buffer.params().trackChanges);
351         if (moveCursor > 0)
352                 const_cast<Cursor &>(bv_->cursor()).posRight();
353         else if (moveCursor < 0) {
354                 Cursor & cursor = const_cast<Cursor &>(bv_->cursor());
355                 if (cursor.pos() >= -moveCursor)
356                         cursor.posLeft();
357         }
358
359         // Optimisation: this is used in the next two loops
360         // so better to calculate that once here.
361         int const right_margin = rightMargin(pm);
362
363         // redo insets
364         // FIXME: We should always use getFont(), see documentation of
365         // noFontChange() in Inset.h.
366         Font const bufferfont = buffer.params().getFont();
367         InsetList::const_iterator ii = par.insetlist.begin();
368         InsetList::const_iterator iend = par.insetlist.end();
369         for (; ii != iend; ++ii) {
370                 Dimension old_dim = ii->inset->dimension();
371                 Dimension dim;
372                 int const w = max_width_ - leftMargin(max_width_, pit, ii->pos)
373                         - right_margin;
374                 Font const & font = ii->inset->noFontChange() ?
375                         bufferfont : getDisplayFont(par, ii->pos);
376                 MetricsInfo mi(bv_, font, w);
377                 changed |= ii->inset->metrics(mi, dim);
378                 changed |= (old_dim != dim);
379         }
380
381         par.setBeginOfBody();
382         pos_type first = 0;
383         size_t row_index = 0;
384         // maximum pixel width of a row
385         int width = max_width_ - right_margin; // - leftMargin(max_width_, pit, row);
386         do {
387                 Dimension dim;
388                 pos_type end = rowBreakPoint(width, pit, first);
389                 if (row_index || end < par.size())
390                         // If there is more than one row, expand the text to 
391                         // the full allowable width. This setting here is needed
392                         // for the computeRowMetrics below().
393                         dim_.wid = max_width_;
394
395                 dim.wid = rowWidth(right_margin, pit, first, end);
396                 boost::tie(dim.asc, dim.des) = rowHeight(pit, first, end);
397                 if (row_index == pm.rows().size())
398                         pm.rows().push_back(Row());
399                 Row & row = pm.rows()[row_index];
400                 row.setChanged(false);
401                 row.pos(first);
402                 row.endpos(end);
403                 row.setDimension(dim);
404                 computeRowMetrics(pit, row);
405                 pm.computeRowSignature(row, bparams);
406                 first = end;
407                 ++row_index;
408
409                 pm.dim().wid = std::max(pm.dim().wid, dim.wid);
410                 pm.dim().des += dim.height();
411         } while (first < par.size());
412
413         if (row_index < pm.rows().size())
414                 pm.rows().resize(row_index);
415
416         // Make sure that if a par ends in newline, there is one more row
417         // under it
418         if (first > 0 && par.isNewline(first - 1)) {
419                 Dimension dim;
420                 dim.wid = rowWidth(right_margin, pit, first, first);
421                 boost::tie(dim.asc, dim.des) = rowHeight(pit, first, first);
422                 if (row_index == pm.rows().size())
423                         pm.rows().push_back(Row());
424                 Row & row = pm.rows()[row_index];
425                 row.setChanged(false);
426                 row.pos(first);
427                 row.endpos(first);
428                 row.setDimension(dim);
429                 computeRowMetrics(pit, row);
430                 pm.computeRowSignature(row, bparams);
431                 pm.dim().des += dim.height();
432         }
433
434         pm.dim().asc += pm.rows()[0].ascent();
435         pm.dim().des -= pm.rows()[0].ascent();
436
437         changed |= old_dim.height() != pm.dim().height();
438
439         return changed;
440 }
441
442
443 void TextMetrics::computeRowMetrics(pit_type const pit,
444                 Row & row) const
445 {
446
447         row.label_hfill = 0;
448         row.hfill = 0;
449         row.separator = 0;
450
451         Buffer & buffer = bv_->buffer();
452         Paragraph const & par = text_->getPar(pit);
453
454         double w = dim_.wid - row.width();
455         // FIXME: put back this assertion when the crash on new doc is solved.
456         //BOOST_ASSERT(w >= 0);
457
458         //lyxerr << "\ndim_.wid " << dim_.wid << endl;
459         //lyxerr << "row.width() " << row.width() << endl;
460         //lyxerr << "w " << w << endl;
461
462         bool const is_rtl = text_->isRTL(buffer, par);
463         if (is_rtl)
464                 row.x = rightMargin(pit);
465         else
466                 row.x = leftMargin(max_width_, pit, row.pos());
467
468         // is there a manual margin with a manual label
469         LayoutPtr const & layout = par.layout();
470
471         if (layout->margintype == MARGIN_MANUAL
472             && layout->labeltype == LABEL_MANUAL) {
473                 /// We might have real hfills in the label part
474                 int nlh = numberOfLabelHfills(par, row);
475
476                 // A manual label par (e.g. List) has an auto-hfill
477                 // between the label text and the body of the
478                 // paragraph too.
479                 // But we don't want to do this auto hfill if the par
480                 // is empty.
481                 if (!par.empty())
482                         ++nlh;
483
484                 if (nlh && !par.getLabelWidthString().empty())
485                         row.label_hfill = labelFill(pit, row) / double(nlh);
486         }
487
488         // are there any hfills in the row?
489         int const nh = numberOfHfills(par, row);
490
491         if (nh) {
492                 if (w > 0)
493                         row.hfill = w / nh;
494         // we don't have to look at the alignment if it is ALIGN_LEFT and
495         // if the row is already larger then the permitted width as then
496         // we force the LEFT_ALIGN'edness!
497         } else if (int(row.width()) < max_width_) {
498                 // is it block, flushleft or flushright?
499                 // set x how you need it
500                 int align;
501                 if (par.params().align() == LYX_ALIGN_LAYOUT)
502                         align = layout->align;
503                 else
504                         align = par.params().align();
505
506                 // Display-style insets should always be on a centred row
507                 // The test on par.size() is to catch zero-size pars, which
508                 // would trigger the assert in Paragraph::getInset().
509                 //inset = par.size() ? par.getInset(row.pos()) : 0;
510                 if (row.pos() < par.size()
511                     && par.isInset(row.pos()))
512                 {
513                     switch(par.getInset(row.pos())->display()) {
514                         case Inset::AlignLeft:
515                                 align = LYX_ALIGN_BLOCK;
516                                 break;
517                         case Inset::AlignCenter:
518                                 align = LYX_ALIGN_CENTER;
519                                 break;
520                         case Inset::Inline:
521                         case Inset::AlignRight:
522                                 // unchanged (use align)
523                                 break;
524                     }
525                 }
526
527                 switch (align) {
528                 case LYX_ALIGN_BLOCK: {
529                         int const ns = numberOfSeparators(par, row);
530                         bool disp_inset = false;
531                         if (row.endpos() < par.size()) {
532                                 Inset const * in = par.getInset(row.endpos());
533                                 if (in)
534                                         disp_inset = in->display();
535                         }
536                         // If we have separators, this is not the last row of a
537                         // par, does not end in newline, and is not row above a
538                         // display inset... then stretch it
539                         if (ns
540                             && row.endpos() < par.size()
541                             && !par.isNewline(row.endpos() - 1)
542                             && !disp_inset
543                                 ) {
544                                 row.separator = w / ns;
545                                 //lyxerr << "row.separator " << row.separator << endl;
546                                 //lyxerr << "ns " << ns << endl;
547                         } else if (is_rtl) {
548                                 row.x += w;
549                         }
550                         break;
551                 }
552                 case LYX_ALIGN_RIGHT:
553                         row.x += w;
554                         break;
555                 case LYX_ALIGN_CENTER:
556                         row.x += w / 2;
557                         break;
558                 }
559         }
560
561         if (is_rtl) {
562                 pos_type body_pos = par.beginOfBody();
563                 pos_type end = row.endpos();
564
565                 if (body_pos > 0
566                     && (body_pos > end || !par.isLineSeparator(body_pos - 1)))
567                 {
568                         row.x += theFontMetrics(text_->getLabelFont(buffer, par)).
569                                 width(layout->labelsep);
570                         if (body_pos <= end)
571                                 row.x += row.label_hfill;
572                 }
573         }
574 }
575
576
577 int TextMetrics::labelFill(pit_type const pit, Row const & row) const
578 {
579         Buffer & buffer = bv_->buffer();
580         Paragraph const & par = text_->getPar(pit);
581
582         pos_type last = par.beginOfBody();
583         BOOST_ASSERT(last > 0);
584
585         // -1 because a label ends with a space that is in the label
586         --last;
587
588         // a separator at this end does not count
589         if (par.isLineSeparator(last))
590                 --last;
591
592         int w = 0;
593         for (pos_type i = row.pos(); i <= last; ++i)
594                 w += singleWidth(pit, i);
595
596         docstring const & label = par.params().labelWidthString();
597         if (label.empty())
598                 return 0;
599
600         FontMetrics const & fm
601                 = theFontMetrics(text_->getLabelFont(buffer, par));
602
603         return max(0, fm.width(label) - w);
604 }
605
606
607 namespace {
608
609 // this needs special handling - only newlines count as a break point
610 pos_type addressBreakPoint(pos_type i, Paragraph const & par)
611 {
612         pos_type const end = par.size();
613
614         for (; i < end; ++i)
615                 if (par.isNewline(i))
616                         return i + 1;
617
618         return end;
619 }
620
621 };
622
623
624 int TextMetrics::labelEnd(pit_type const pit) const
625 {
626         // labelEnd is only needed if the layout fills a flushleft label.
627         if (text_->getPar(pit).layout()->margintype != MARGIN_MANUAL)
628                 return 0;
629         // return the beginning of the body
630         return leftMargin(max_width_, pit);
631 }
632
633
634 pit_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
635                 pit_type pos) const
636 {
637         Buffer & buffer = bv_->buffer();
638         ParagraphMetrics const & pm = par_metrics_[pit];
639         Paragraph const & par = text_->getPar(pit);
640         pos_type const end = par.size();
641         if (pos == end || width < 0)
642                 return end;
643
644         LayoutPtr const & layout = par.layout();
645
646         if (layout->margintype == MARGIN_RIGHT_ADDRESS_BOX)
647                 return addressBreakPoint(pos, par);
648
649         pos_type const body_pos = par.beginOfBody();
650
651
652         // Now we iterate through until we reach the right margin
653         // or the end of the par, then choose the possible break
654         // nearest that.
655
656         int label_end = labelEnd(pit);
657         int const left = leftMargin(max_width_, pit, pos);
658         int x = left;
659
660         // pixel width since last breakpoint
661         int chunkwidth = 0;
662
663         FontIterator fi = FontIterator(*this, par, pos);
664         pos_type point = end;
665         pos_type i = pos;
666         for ( ; i < end; ++i, ++fi) {
667                 int thiswidth = pm.singleWidth(i, *fi);
668
669                 // add the auto-hfill from label end to the body
670                 if (body_pos && i == body_pos) {
671                         FontMetrics const & fm = theFontMetrics(
672                                 text_->getLabelFont(buffer, par));
673                         int add = fm.width(layout->labelsep);
674                         if (par.isLineSeparator(i - 1))
675                                 add -= singleWidth(pit, i - 1);
676
677                         add = std::max(add, label_end - x);
678                         thiswidth += add;
679                 }
680
681                 x += thiswidth;
682                 chunkwidth += thiswidth;
683
684                 // break before a character that will fall off
685                 // the right of the row
686                 if (x >= width) {
687                         // if no break before, break here
688                         if (point == end || chunkwidth >= width - left) {
689                                 if (i > pos)
690                                         point = i;
691                                 else
692                                         point = i + 1;
693
694                         }
695                         // exit on last registered breakpoint:
696                         break;
697                 }
698
699                 if (par.isNewline(i)) {
700                         point = i + 1;
701                         break;
702                 }
703                 // Break before...
704                 if (i + 1 < end) {
705                         if (par.isInset(i + 1) && par.getInset(i + 1)->display()) {
706                                 point = i + 1;
707                                 break;
708                         }
709                         // ...and after.
710                         if (par.isInset(i) && par.getInset(i)->display()) {
711                                 point = i + 1;
712                                 break;
713                         }
714                 }
715
716                 if (!par.isInset(i) || par.getInset(i)->isChar()) {
717                         // some insets are line separators too
718                         if (par.isLineSeparator(i)) {
719                                 // register breakpoint:
720                                 point = i + 1;
721                                 chunkwidth = 0;
722                         }
723                 }
724         }
725
726         // maybe found one, but the par is short enough.
727         if (i == end && x < width)
728                 point = end;
729
730         // manual labels cannot be broken in LaTeX. But we
731         // want to make our on-screen rendering of footnotes
732         // etc. still break
733         if (body_pos && point < body_pos)
734                 point = body_pos;
735
736         return point;
737 }
738
739
740 int TextMetrics::rowWidth(int right_margin, pit_type const pit,
741                 pos_type const first, pos_type const end) const
742 {
743         Buffer & buffer = bv_->buffer();
744         // get the pure distance
745         ParagraphMetrics const & pm = par_metrics_[pit];
746         Paragraph const & par = text_->getPar(pit);
747         int w = leftMargin(max_width_, pit, first);
748         int label_end = labelEnd(pit);
749
750         pos_type const body_pos = par.beginOfBody();
751         pos_type i = first;
752
753         if (i < end) {
754                 FontIterator fi = FontIterator(*this, par, i);
755                 for ( ; i < end; ++i, ++fi) {
756                         if (body_pos > 0 && i == body_pos) {
757                                 FontMetrics const & fm = theFontMetrics(
758                                         text_->getLabelFont(buffer, par));
759                                 w += fm.width(par.layout()->labelsep);
760                                 if (par.isLineSeparator(i - 1))
761                                         w -= singleWidth(pit, i - 1);
762                                 w = max(w, label_end);
763                         }
764                         w += pm.singleWidth(i, *fi);
765                 }
766         }
767
768         if (body_pos > 0 && body_pos >= end) {
769                 FontMetrics const & fm = theFontMetrics(
770                         text_->getLabelFont(buffer, par));
771                 w += fm.width(par.layout()->labelsep);
772                 if (end > 0 && par.isLineSeparator(end - 1))
773                         w -= singleWidth(pit, end - 1);
774                 w = max(w, label_end);
775         }
776
777         return w + right_margin;
778 }
779
780
781 boost::tuple<int, int> TextMetrics::rowHeight(pit_type const pit, pos_type const first,
782                 pos_type const end) const
783 {
784         Paragraph const & par = text_->getPar(pit);
785         // get the maximum ascent and the maximum descent
786         double layoutasc = 0;
787         double layoutdesc = 0;
788         double const dh = defaultRowHeight();
789
790         // ok, let us initialize the maxasc and maxdesc value.
791         // Only the fontsize count. The other properties
792         // are taken from the layoutfont. Nicer on the screen :)
793         LayoutPtr const & layout = par.layout();
794
795         // as max get the first character of this row then it can
796         // increase but not decrease the height. Just some point to
797         // start with so we don't have to do the assignment below too
798         // often.
799         Buffer const & buffer = bv_->buffer();
800         Font font = getDisplayFont(par, first);
801         Font::FONT_SIZE const tmpsize = font.size();
802         font = text_->getLayoutFont(buffer, pit);
803         Font::FONT_SIZE const size = font.size();
804         font.setSize(tmpsize);
805
806         Font labelfont = text_->getLabelFont(buffer, par);
807
808         FontMetrics const & labelfont_metrics = theFontMetrics(labelfont);
809         FontMetrics const & fontmetrics = theFontMetrics(font);
810
811         // these are minimum values
812         double const spacing_val = layout->spacing.getValue()
813                 * text_->spacing(buffer, par);
814         //lyxerr << "spacing_val = " << spacing_val << endl;
815         int maxasc  = int(fontmetrics.maxAscent()  * spacing_val);
816         int maxdesc = int(fontmetrics.maxDescent() * spacing_val);
817
818         // insets may be taller
819         InsetList::const_iterator ii = par.insetlist.begin();
820         InsetList::const_iterator iend = par.insetlist.end();
821         for ( ; ii != iend; ++ii) {
822                 if (ii->pos >= first && ii->pos < end) {
823                         maxasc  = max(maxasc,  ii->inset->ascent());
824                         maxdesc = max(maxdesc, ii->inset->descent());
825                 }
826         }
827
828         // Check if any custom fonts are larger (Asger)
829         // This is not completely correct, but we can live with the small,
830         // cosmetic error for now.
831         int labeladdon = 0;
832
833         Font::FONT_SIZE maxsize =
834                 par.highestFontInRange(first, end, size);
835         if (maxsize > font.size()) {
836                 // use standard paragraph font with the maximal size
837                 Font maxfont = font;
838                 maxfont.setSize(maxsize);
839                 FontMetrics const & maxfontmetrics = theFontMetrics(maxfont);
840                 maxasc  = max(maxasc,  maxfontmetrics.maxAscent());
841                 maxdesc = max(maxdesc, maxfontmetrics.maxDescent());
842         }
843
844         // This is nicer with box insets:
845         ++maxasc;
846         ++maxdesc;
847
848         ParagraphList const & pars = text_->paragraphs();
849
850         // is it a top line?
851         if (first == 0) {
852                 BufferParams const & bufparams = buffer.params();
853                 // some parskips VERY EASY IMPLEMENTATION
854                 if (bufparams.paragraph_separation
855                     == BufferParams::PARSEP_SKIP
856                         && par.ownerCode() != Inset::ERT_CODE
857                         && par.ownerCode() != Inset::LISTINGS_CODE
858                         && pit > 0
859                         && ((layout->isParagraph() && par.getDepth() == 0)
860                             || (pars[pit - 1].layout()->isParagraph()
861                                 && pars[pit - 1].getDepth() == 0)))
862                 {
863                                 maxasc += bufparams.getDefSkip().inPixels(*bv_);
864                 }
865
866                 if (par.params().startOfAppendix())
867                         maxasc += int(3 * dh);
868
869                 // This is special code for the chapter, since the label of this
870                 // layout is printed in an extra row
871                 if (layout->counter == "chapter"
872                     && !par.params().labelString().empty()) {
873                         labeladdon = int(labelfont_metrics.maxHeight()
874                                      * layout->spacing.getValue()
875                                      * text_->spacing(buffer, par));
876                 }
877
878                 // special code for the top label
879                 if ((layout->labeltype == LABEL_TOP_ENVIRONMENT
880                      || layout->labeltype == LABEL_BIBLIO
881                      || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)
882                     && isFirstInSequence(pit, pars)
883                     && !par.getLabelstring().empty())
884                 {
885                         labeladdon = int(
886                                   labelfont_metrics.maxHeight()
887                                         * layout->spacing.getValue()
888                                         * text_->spacing(buffer, par)
889                                 + (layout->topsep + layout->labelbottomsep) * dh);
890                 }
891
892                 // Add the layout spaces, for example before and after
893                 // a section, or between the items of a itemize or enumerate
894                 // environment.
895
896                 pit_type prev = depthHook(pit, pars, par.getDepth());
897                 Paragraph const & prevpar = pars[prev];
898                 if (prev != pit
899                     && prevpar.layout() == layout
900                     && prevpar.getDepth() == par.getDepth()
901                     && prevpar.getLabelWidthString()
902                                         == par.getLabelWidthString()) {
903                         layoutasc = layout->itemsep * dh;
904                 } else if (pit != 0 || first != 0) {
905                         if (layout->topsep > 0)
906                                 layoutasc = layout->topsep * dh;
907                 }
908
909                 prev = outerHook(pit, pars);
910                 if (prev != pit_type(pars.size())) {
911                         maxasc += int(pars[prev].layout()->parsep * dh);
912                 } else if (pit != 0) {
913                         Paragraph const & prevpar = pars[pit - 1];
914                         if (prevpar.getDepth() != 0 ||
915                                         prevpar.layout() == layout) {
916                                 maxasc += int(layout->parsep * dh);
917                         }
918                 }
919         }
920
921         // is it a bottom line?
922         if (end >= par.size()) {
923                 // add the layout spaces, for example before and after
924                 // a section, or between the items of a itemize or enumerate
925                 // environment
926                 pit_type nextpit = pit + 1;
927                 if (nextpit != pit_type(pars.size())) {
928                         pit_type cpit = pit;
929                         double usual = 0;
930                         double unusual = 0;
931
932                         if (pars[cpit].getDepth() > pars[nextpit].getDepth()) {
933                                 usual = pars[cpit].layout()->bottomsep * dh;
934                                 cpit = depthHook(cpit, pars, pars[nextpit].getDepth());
935                                 if (pars[cpit].layout() != pars[nextpit].layout()
936                                         || pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
937                                 {
938                                         unusual = pars[cpit].layout()->bottomsep * dh;
939                                 }
940                                 layoutdesc = max(unusual, usual);
941                         } else if (pars[cpit].getDepth() == pars[nextpit].getDepth()) {
942                                 if (pars[cpit].layout() != pars[nextpit].layout()
943                                         || pars[nextpit].getLabelWidthString() != pars[cpit].getLabelWidthString())
944                                         layoutdesc = int(pars[cpit].layout()->bottomsep * dh);
945                         }
946                 }
947         }
948
949         // incalculate the layout spaces
950         maxasc  += int(layoutasc  * 2 / (2 + pars[pit].getDepth()));
951         maxdesc += int(layoutdesc * 2 / (2 + pars[pit].getDepth()));
952
953         // FIXME: the correct way is to do the following is to move the
954         // following code in another method specially tailored for the
955         // main Text. The following test is thus bogus.
956         // Top and bottom margin of the document (only at top-level)
957         if (main_text_) {
958                 if (pit == 0 && first == 0)
959                         maxasc += 20;
960                 if (pit + 1 == pit_type(pars.size()) &&
961                     end == par.size() &&
962                                 !(end > 0 && par.isNewline(end - 1)))
963                         maxdesc += 20;
964         }
965
966         return boost::make_tuple(maxasc + labeladdon, maxdesc);
967 }
968
969
970 // x is an absolute screen coord
971 // returns the column near the specified x-coordinate of the row
972 // x is set to the real beginning of this column
973 pos_type TextMetrics::getColumnNearX(pit_type const pit,
974                 Row const & row, int & x, bool & boundary) const
975 {
976         Buffer const & buffer = bv_->buffer();
977
978         /// For the main Text, it is possible that this pit is not
979         /// yet in the CoordCache when moving cursor up.
980         /// x Paragraph coordinate is always 0 for main text anyway.
981         int const xo = main_text_? 0 : bv_->coordCache().get(text_, pit).x_;
982         x -= xo;
983         Paragraph const & par = text_->getPar(pit);
984         ParagraphMetrics const & pm = par_metrics_[pit];
985         Bidi bidi;
986         bidi.computeTables(par, buffer, row);
987
988         pos_type vc = row.pos();
989         pos_type end = row.endpos();
990         pos_type c = 0;
991         LayoutPtr const & layout = par.layout();
992
993         bool left_side = false;
994
995         pos_type body_pos = par.beginOfBody();
996
997         double tmpx = row.x;
998         double last_tmpx = tmpx;
999
1000         if (body_pos > 0 &&
1001             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1002                 body_pos = 0;
1003
1004         // check for empty row
1005         if (vc == end) {
1006                 x = int(tmpx) + xo;
1007                 return 0;
1008         }
1009
1010         while (vc < end && tmpx <= x) {
1011                 c = bidi.vis2log(vc);
1012                 last_tmpx = tmpx;
1013                 if (body_pos > 0 && c == body_pos - 1) {
1014                         FontMetrics const & fm = theFontMetrics(
1015                                 text_->getLabelFont(buffer, par));
1016                         tmpx += row.label_hfill + fm.width(layout->labelsep);
1017                         if (par.isLineSeparator(body_pos - 1))
1018                                 tmpx -= singleWidth(pit, body_pos - 1);
1019                 }
1020
1021                 if (pm.hfillExpansion(row, c)) {
1022                         tmpx += singleWidth(pit, c);
1023                         if (c >= body_pos)
1024                                 tmpx += row.hfill;
1025                         else
1026                                 tmpx += row.label_hfill;
1027                 } else if (par.isSeparator(c)) {
1028                         tmpx += singleWidth(pit, c);
1029                         if (c >= body_pos)
1030                                 tmpx += row.separator;
1031                 } else {
1032                         tmpx += singleWidth(pit, c);
1033                 }
1034                 ++vc;
1035         }
1036
1037         if ((tmpx + last_tmpx) / 2 > x) {
1038                 tmpx = last_tmpx;
1039                 left_side = true;
1040         }
1041
1042         BOOST_ASSERT(vc <= end);  // This shouldn't happen.
1043
1044         boundary = false;
1045         // This (rtl_support test) is not needed, but gives
1046         // some speedup if rtl_support == false
1047         bool const lastrow = lyxrc.rtl_support && row.endpos() == par.size();
1048
1049         // If lastrow is false, we don't need to compute
1050         // the value of rtl.
1051         bool const rtl = lastrow ? text_->isRTL(buffer, par) : false;
1052         if (lastrow &&
1053             ((rtl  &&  left_side && vc == row.pos() && x < tmpx - 5) ||
1054              (!rtl && !left_side && vc == end  && x > tmpx + 5)))
1055                 c = end;
1056         else if (vc == row.pos()) {
1057                 c = bidi.vis2log(vc);
1058                 if (bidi.level(c) % 2 == 1)
1059                         ++c;
1060         } else {
1061                 c = bidi.vis2log(vc - 1);
1062                 bool const rtl = (bidi.level(c) % 2 == 1);
1063                 if (left_side == rtl) {
1064                         ++c;
1065                         boundary = isRTLBoundary(par, c);
1066                 }
1067         }
1068
1069 // I believe this code is not needed anymore (Jug 20050717)
1070 #if 0
1071         // The following code is necessary because the cursor position past
1072         // the last char in a row is logically equivalent to that before
1073         // the first char in the next row. That's why insets causing row
1074         // divisions -- Newline and display-style insets -- must be treated
1075         // specially, so cursor up/down doesn't get stuck in an air gap -- MV
1076         // Newline inset, air gap below:
1077         if (row.pos() < end && c >= end && par.isNewline(end - 1)) {
1078                 if (bidi.level(end -1) % 2 == 0)
1079                         tmpx -= singleWidth(pit, end - 1);
1080                 else
1081                         tmpx += singleWidth(pit, end - 1);
1082                 c = end - 1;
1083         }
1084
1085         // Air gap above display inset:
1086         if (row.pos() < end && c >= end && end < par.size()
1087             && par.isInset(end) && par.getInset(end)->display()) {
1088                 c = end - 1;
1089         }
1090         // Air gap below display inset:
1091         if (row.pos() < end && c >= end && par.isInset(end - 1)
1092             && par.getInset(end - 1)->display()) {
1093                 c = end - 1;
1094         }
1095 #endif
1096
1097         x = int(tmpx) + xo;
1098         pos_type const col = c - row.pos();
1099
1100         if (!c || end == par.size())
1101                 return col;
1102
1103         if (c==end && !par.isLineSeparator(c-1) && !par.isNewline(c-1)) {
1104                 boundary = true;
1105                 return col;
1106         }
1107
1108         return min(col, end - 1 - row.pos());
1109 }
1110
1111
1112 pos_type TextMetrics::x2pos(pit_type pit, int row, int x) const
1113 {
1114         ParagraphMetrics const & pm = par_metrics_[pit];
1115         BOOST_ASSERT(!pm.rows().empty());
1116         BOOST_ASSERT(row < int(pm.rows().size()));
1117         bool bound = false;
1118         Row const & r = pm.rows()[row];
1119         return r.pos() + getColumnNearX(pit, r, x, bound);
1120 }
1121
1122
1123 // y is screen coordinate
1124 pit_type TextMetrics::getPitNearY(int y)
1125 {
1126         BOOST_ASSERT(!text_->paragraphs().empty());
1127         BOOST_ASSERT(bv_->coordCache().getParPos().find(text_) != bv_->coordCache().getParPos().end());
1128         CoordCache::InnerParPosCache const & cc = bv_->coordCache().getParPos().find(text_)->second;
1129         LYXERR(Debug::DEBUG)
1130                 << BOOST_CURRENT_FUNCTION
1131                 << ": y: " << y << " cache size: " << cc.size()
1132                 << endl;
1133
1134         // look for highest numbered paragraph with y coordinate less than given y
1135         pit_type pit = 0;
1136         int yy = -1;
1137         CoordCache::InnerParPosCache::const_iterator it = cc.begin();
1138         CoordCache::InnerParPosCache::const_iterator et = cc.end();
1139         CoordCache::InnerParPosCache::const_iterator last = et; last--;
1140
1141         ParagraphMetrics const & pm = par_metrics_[it->first];
1142
1143         // If we are off-screen (before the visible part)
1144         if (y < 0
1145                 // and even before the first paragraph in the cache.
1146                 && y < it->second.y_ - int(pm.ascent())) {
1147                 //  and we are not at the first paragraph in the inset.
1148                 if (it->first == 0)
1149                         return 0;
1150                 // then this is the paragraph we are looking for.
1151                 pit = it->first - 1;
1152                 // rebreak it and update the CoordCache.
1153                 redoParagraph(pit);
1154                 bv_->coordCache().parPos()[text_][pit] =
1155                         Point(0, it->second.y_ - pm.descent());
1156                 return pit;
1157         }
1158
1159         ParagraphMetrics const & pm_last = par_metrics_[last->first];
1160
1161         // If we are off-screen (after the visible part)
1162         if (y > bv_->workHeight()
1163                 // and even after the first paragraph in the cache.
1164                 && y >= last->second.y_ + int(pm_last.descent())) {
1165                 pit = last->first + 1;
1166                 //  and we are not at the last paragraph in the inset.
1167                 if (pit == int(text_->paragraphs().size()))
1168                         return last->first;
1169                 // then this is the paragraph we are looking for.
1170                 // rebreak it and update the CoordCache.
1171                 redoParagraph(pit);
1172                 bv_->coordCache().parPos()[text_][pit] =
1173                         Point(0, last->second.y_ + pm_last.ascent());
1174                 return pit;
1175         }
1176
1177         for (; it != et; ++it) {
1178                 LYXERR(Debug::DEBUG)
1179                         << BOOST_CURRENT_FUNCTION
1180                         << "  examining: pit: " << it->first
1181                         << " y: " << it->second.y_
1182                         << endl;
1183
1184                 ParagraphMetrics const & pm = par_metrics_[it->first];
1185
1186                 if (it->first >= pit && int(it->second.y_) - int(pm.ascent()) <= y) {
1187                         pit = it->first;
1188                         yy = it->second.y_;
1189                 }
1190         }
1191
1192         LYXERR(Debug::DEBUG)
1193                 << BOOST_CURRENT_FUNCTION
1194                 << ": found best y: " << yy << " for pit: " << pit
1195                 << endl;
1196
1197         return pit;
1198 }
1199
1200
1201 Row const & TextMetrics::getRowNearY(int y, pit_type pit) const
1202 {
1203         ParagraphMetrics const & pm = par_metrics_[pit];
1204
1205         int yy = bv_->coordCache().get(text_, pit).y_ - pm.ascent();
1206         BOOST_ASSERT(!pm.rows().empty());
1207         RowList::const_iterator rit = pm.rows().begin();
1208         RowList::const_iterator const rlast = boost::prior(pm.rows().end());
1209         for (; rit != rlast; yy += rit->height(), ++rit)
1210                 if (yy + rit->height() > y)
1211                         break;
1212         return *rit;
1213 }
1214
1215
1216 // x,y are absolute screen coordinates
1217 // sets cursor recursively descending into nested editable insets
1218 Inset * TextMetrics::editXY(Cursor & cur, int x, int y)
1219 {
1220         if (lyxerr.debugging(Debug::WORKAREA)) {
1221                 lyxerr << "TextMetrics::editXY(cur, " << x << ", " << y << ")" << std::endl;
1222                 cur.bv().coordCache().dump();
1223         }
1224         pit_type pit = getPitNearY(y);
1225         BOOST_ASSERT(pit != -1);
1226
1227         Row const & row = getRowNearY(y, pit);
1228         bool bound = false;
1229
1230         int xx = x; // is modified by getColumnNearX
1231         pos_type const pos = row.pos()
1232                 + getColumnNearX(pit, row, xx, bound);
1233         cur.pit() = pit;
1234         cur.pos() = pos;
1235         cur.boundary(bound);
1236         cur.setTargetX(x);
1237
1238         // try to descend into nested insets
1239         Inset * inset = checkInsetHit(x, y);
1240         //lyxerr << "inset " << inset << " hit at x: " << x << " y: " << y << endl;
1241         if (!inset) {
1242                 // Either we deconst editXY or better we move current_font
1243                 // and real_current_font to Cursor
1244                 // FIXME: what is needed now that current_font and real_current_font
1245                 // are transferred?
1246                 cur.setCurrentFont();
1247                 return 0;
1248         }
1249
1250         ParagraphList const & pars = text_->paragraphs();
1251         Inset const * insetBefore = pos? pars[pit].getInset(pos - 1): 0;
1252         //Inset * insetBehind = pars[pit].getInset(pos);
1253
1254         // This should be just before or just behind the
1255         // cursor position set above.
1256         BOOST_ASSERT((pos != 0 && inset == insetBefore)
1257                 || inset == pars[pit].getInset(pos));
1258
1259         // Make sure the cursor points to the position before
1260         // this inset.
1261         if (inset == insetBefore) {
1262                 --cur.pos();
1263                 cur.boundary(false);
1264         }
1265
1266         // Try to descend recursively inside the inset.
1267         inset = inset->editXY(cur, x, y);
1268
1269         if (cur.top().text() == text_)
1270                 cur.setCurrentFont();
1271         return inset;
1272 }
1273
1274
1275 void TextMetrics::setCursorFromCoordinates(Cursor & cur, int const x, int const y)
1276 {
1277         BOOST_ASSERT(text_ == cur.text());
1278         pit_type pit = getPitNearY(y);
1279
1280         ParagraphMetrics const & pm = par_metrics_[pit];
1281
1282         int yy = bv_->coordCache().get(text_, pit).y_ - pm.ascent();
1283         LYXERR(Debug::DEBUG)
1284                 << BOOST_CURRENT_FUNCTION
1285                 << ": x: " << x
1286                 << " y: " << y
1287                 << " pit: " << pit
1288                 << " yy: " << yy << endl;
1289
1290         int r = 0;
1291         BOOST_ASSERT(pm.rows().size());
1292         for (; r < int(pm.rows().size()) - 1; ++r) {
1293                 Row const & row = pm.rows()[r];
1294                 if (int(yy + row.height()) > y)
1295                         break;
1296                 yy += row.height();
1297         }
1298
1299         Row const & row = pm.rows()[r];
1300
1301         LYXERR(Debug::DEBUG)
1302                 << BOOST_CURRENT_FUNCTION
1303                 << ": row " << r
1304                 << " from pos: " << row.pos()
1305                 << endl;
1306
1307         bool bound = false;
1308         int xx = x;
1309         pos_type const pos = row.pos() + getColumnNearX(pit, row, xx, bound);
1310
1311         LYXERR(Debug::DEBUG)
1312                 << BOOST_CURRENT_FUNCTION
1313                 << ": setting cursor pit: " << pit
1314                 << " pos: " << pos
1315                 << endl;
1316
1317         text_->setCursor(cur, pit, pos, true, bound);
1318         // remember new position.
1319         cur.setTargetX();
1320 }
1321
1322
1323 //takes screen x,y coordinates
1324 Inset * TextMetrics::checkInsetHit(int x, int y)
1325 {
1326         pit_type pit = getPitNearY(y);
1327         BOOST_ASSERT(pit != -1);
1328
1329         Paragraph const & par = text_->paragraphs()[pit];
1330
1331         LYXERR(Debug::DEBUG)
1332                 << BOOST_CURRENT_FUNCTION
1333                 << ": x: " << x
1334                 << " y: " << y
1335                 << "  pit: " << pit
1336                 << endl;
1337         InsetList::const_iterator iit = par.insetlist.begin();
1338         InsetList::const_iterator iend = par.insetlist.end();
1339         for (; iit != iend; ++iit) {
1340                 Inset * inset = iit->inset;
1341 #if 1
1342                 LYXERR(Debug::DEBUG)
1343                         << BOOST_CURRENT_FUNCTION
1344                         << ": examining inset " << inset << endl;
1345
1346                 if (bv_->coordCache().getInsets().has(inset))
1347                         LYXERR(Debug::DEBUG)
1348                                 << BOOST_CURRENT_FUNCTION
1349                                 << ": xo: " << inset->xo(*bv_) << "..."
1350                                 << inset->xo(*bv_) + inset->width()
1351                                 << " yo: " << inset->yo(*bv_) - inset->ascent()
1352                                 << "..."
1353                                 << inset->yo(*bv_) + inset->descent()
1354                                 << endl;
1355                 else
1356                         LYXERR(Debug::DEBUG)
1357                                 << BOOST_CURRENT_FUNCTION
1358                                 << ": inset has no cached position" << endl;
1359 #endif
1360                 if (inset->covers(*bv_, x, y)) {
1361                         LYXERR(Debug::DEBUG)
1362                                 << BOOST_CURRENT_FUNCTION
1363                                 << ": Hit inset: " << inset << endl;
1364                         return inset;
1365                 }
1366         }
1367         LYXERR(Debug::DEBUG)
1368                 << BOOST_CURRENT_FUNCTION
1369                 << ": No inset hit. " << endl;
1370         return 0;
1371 }
1372
1373
1374 int TextMetrics::cursorX(CursorSlice const & sl,
1375                 bool boundary) const
1376 {
1377         BOOST_ASSERT(sl.text() == text_);
1378         pit_type const pit = sl.pit();
1379         Paragraph const & par = text_->paragraphs()[pit];
1380         ParagraphMetrics const & pm = par_metrics_[pit];
1381         if (pm.rows().empty())
1382                 return 0;
1383
1384         pos_type ppos = sl.pos();
1385         // Correct position in front of big insets
1386         bool const boundary_correction = ppos != 0 && boundary;
1387         if (boundary_correction)
1388                 --ppos;
1389
1390         Row const & row = pm.getRow(sl.pos(), boundary);
1391
1392         pos_type cursor_vpos = 0;
1393
1394         Buffer const & buffer = bv_->buffer();
1395         double x = row.x;
1396         Bidi bidi;
1397         bidi.computeTables(par, buffer, row);
1398
1399         pos_type const row_pos  = row.pos();
1400         pos_type const end      = row.endpos();
1401         // Spaces at logical line breaks in bidi text must be skipped during 
1402         // cursor positioning. However, they may appear visually in the middle
1403         // of a row; they must be skipped, wherever they are...
1404         // * logically "abc_[HEBREW_\nHEBREW]"
1405         // * visually "abc_[_WERBEH\nWERBEH]"
1406         pos_type skipped_sep_vpos = -1;
1407
1408         if (end <= row_pos)
1409                 cursor_vpos = row_pos;
1410         else if (ppos >= end)
1411                 cursor_vpos = text_->isRTL(buffer, par) ? row_pos : end;
1412         else if (ppos > row_pos && ppos >= end)
1413                 // Place cursor after char at (logical) position pos - 1
1414                 cursor_vpos = (bidi.level(ppos - 1) % 2 == 0)
1415                         ? bidi.log2vis(ppos - 1) + 1 : bidi.log2vis(ppos - 1);
1416         else
1417                 // Place cursor before char at (logical) position ppos
1418                 cursor_vpos = (bidi.level(ppos) % 2 == 0)
1419                         ? bidi.log2vis(ppos) : bidi.log2vis(ppos) + 1;
1420
1421         pos_type body_pos = par.beginOfBody();
1422         if (body_pos > 0 &&
1423             (body_pos > end || !par.isLineSeparator(body_pos - 1)))
1424                 body_pos = 0;
1425
1426         // Use font span to speed things up, see below
1427         FontSpan font_span;
1428         Font font;
1429
1430         // If the last logical character is a separator, skip it, unless
1431         // it's in the last row of a paragraph; see skipped_sep_vpos declaration
1432         if (end > 0 && end < par.size() && par.isSeparator(end - 1))
1433                 skipped_sep_vpos = bidi.log2vis(end - 1);
1434         
1435         for (pos_type vpos = row_pos; vpos < cursor_vpos; ++vpos) {
1436                 // Skip the separator which is at the logical end of the row
1437                 if (vpos == skipped_sep_vpos)
1438                         continue;
1439                 pos_type pos = bidi.vis2log(vpos);
1440                 if (body_pos > 0 && pos == body_pos - 1) {
1441                         FontMetrics const & labelfm = theFontMetrics(
1442                                 text_->getLabelFont(buffer, par));
1443                         x += row.label_hfill + labelfm.width(par.layout()->labelsep);
1444                         if (par.isLineSeparator(body_pos - 1))
1445                                 x -= singleWidth(pit, body_pos - 1);
1446                 }
1447
1448                 // Use font span to speed things up, see above
1449                 if (pos < font_span.first || pos > font_span.last) {
1450                         font_span = par.fontSpan(pos);
1451                         font = getDisplayFont(par, pos);
1452                 }
1453
1454                 x += pm.singleWidth(pos, font);
1455
1456                 if (pm.hfillExpansion(row, pos))
1457                         x += (pos >= body_pos) ? row.hfill : row.label_hfill;
1458                 else if (par.isSeparator(pos) && pos >= body_pos)
1459                         x += row.separator;
1460         }
1461
1462         // see correction above
1463         if (boundary_correction) {
1464                 if (isRTL(sl, boundary))
1465                         x -= singleWidth(pit, ppos);
1466                 else
1467                         x += singleWidth(pit, ppos);
1468         }
1469
1470         return int(x);
1471 }
1472
1473
1474 int TextMetrics::cursorY(CursorSlice const & sl, bool boundary) const
1475 {
1476         //lyxerr << "TextMetrics::cursorY: boundary: " << boundary << std::endl;
1477         ParagraphMetrics const & pm = par_metrics_[sl.pit()];
1478         if (pm.rows().empty())
1479                 return 0;
1480
1481         int h = 0;
1482         h -= par_metrics_[0].rows()[0].ascent();
1483         for (pit_type pit = 0; pit < sl.pit(); ++pit) {
1484                 h += par_metrics_[pit].height();
1485         }
1486         int pos = sl.pos();
1487         if (pos && boundary)
1488                 --pos;
1489         size_t const rend = pm.pos2row(pos);
1490         for (size_t rit = 0; rit != rend; ++rit)
1491                 h += pm.rows()[rit].height();
1492         h += pm.rows()[rend].ascent();
1493         return h;
1494 }
1495
1496
1497 void TextMetrics::cursorPrevious(Cursor & cur)
1498 {
1499         pos_type cpos = cur.pos();
1500         pit_type cpar = cur.pit();
1501
1502         int x = cur.x_target();
1503         setCursorFromCoordinates(cur, x, 0);
1504         cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
1505
1506         if (cpar == cur.pit() && cpos == cur.pos())
1507                 // we have a row which is taller than the workarea. The
1508                 // simplest solution is to move to the previous row instead.
1509                 cur.dispatch(FuncRequest(cur.selection()? LFUN_UP_SELECT: LFUN_UP));
1510
1511         finishUndo();
1512         cur.updateFlags(Update::Force | Update::FitCursor);
1513 }
1514
1515
1516 void TextMetrics::cursorNext(Cursor & cur)
1517 {
1518         pos_type cpos = cur.pos();
1519         pit_type cpar = cur.pit();
1520
1521         int x = cur.x_target();
1522         setCursorFromCoordinates(cur, x, cur.bv().workHeight() - 1);
1523         cur.dispatch(FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
1524
1525         if (cpar == cur.pit() && cpos == cur.pos())
1526                 // we have a row which is taller than the workarea. The
1527                 // simplest solution is to move to the next row instead.
1528                 cur.dispatch(
1529                         FuncRequest(cur.selection()? LFUN_DOWN_SELECT: LFUN_DOWN));
1530
1531         finishUndo();
1532         cur.updateFlags(Update::Force | Update::FitCursor);
1533 }
1534
1535
1536 // the cursor set functions have a special mechanism. When they
1537 // realize you left an empty paragraph, they will delete it.
1538
1539 bool TextMetrics::cursorHome(Cursor & cur)
1540 {
1541         BOOST_ASSERT(text_ == cur.text());
1542         ParagraphMetrics const & pm = par_metrics_[cur.pit()];
1543         Row const & row = pm.getRow(cur.pos(),cur.boundary());
1544         return text_->setCursor(cur, cur.pit(), row.pos());
1545 }
1546
1547
1548 bool TextMetrics::cursorEnd(Cursor & cur)
1549 {
1550         BOOST_ASSERT(text_ == cur.text());
1551         // if not on the last row of the par, put the cursor before
1552         // the final space exept if I have a spanning inset or one string
1553         // is so long that we force a break.
1554         pos_type end = cur.textRow().endpos();
1555         if (end == 0)
1556                 // empty text, end-1 is no valid position
1557                 return false;
1558         bool boundary = false;
1559         if (end != cur.lastpos()) {
1560                 if (!cur.paragraph().isLineSeparator(end-1)
1561                     && !cur.paragraph().isNewline(end-1))
1562                         boundary = true;
1563                 else
1564                         --end;
1565         }
1566         return text_->setCursor(cur, cur.pit(), end, true, boundary);
1567 }
1568
1569
1570 void TextMetrics::deleteLineForward(Cursor & cur)
1571 {
1572         BOOST_ASSERT(text_ == cur.text());
1573         if (cur.lastpos() == 0) {
1574                 // Paragraph is empty, so we just go to the right
1575                 text_->cursorRight(cur);
1576         } else {
1577                 cur.resetAnchor();
1578                 cur.selection() = true; // to avoid deletion
1579                 cursorEnd(cur);
1580                 cur.setSelection();
1581                 // What is this test for ??? (JMarc)
1582                 if (!cur.selection())
1583                         text_->deleteWordForward(cur);
1584                 else
1585                         cap::cutSelection(cur, true, false);
1586                 checkBufferStructure(cur.buffer(), cur);
1587         }
1588 }
1589
1590
1591 bool TextMetrics::isLastRow(pit_type pit, Row const & row) const
1592 {
1593         ParagraphList const & pars = text_->paragraphs();
1594         return row.endpos() >= pars[pit].size()
1595                 && pit + 1 == pit_type(pars.size());
1596 }
1597
1598
1599 bool TextMetrics::isFirstRow(pit_type pit, Row const & row) const
1600 {
1601         return row.pos() == 0 && pit == 0;
1602 }
1603
1604
1605 int TextMetrics::leftMargin(int max_width, pit_type pit) const
1606 {
1607         BOOST_ASSERT(pit >= 0);
1608         BOOST_ASSERT(pit < int(text_->paragraphs().size()));
1609         return leftMargin(max_width, pit, text_->paragraphs()[pit].size());
1610 }
1611
1612
1613 int TextMetrics::leftMargin(int max_width,
1614                 pit_type const pit, pos_type const pos) const
1615 {
1616         ParagraphList const & pars = text_->paragraphs();
1617
1618         BOOST_ASSERT(pit >= 0);
1619         BOOST_ASSERT(pit < int(pars.size()));
1620         Paragraph const & par = pars[pit];
1621         BOOST_ASSERT(pos >= 0);
1622         BOOST_ASSERT(pos <= par.size());
1623         Buffer const & buffer = bv_->buffer();
1624         //lyxerr << "TextMetrics::leftMargin: pit: " << pit << " pos: " << pos << endl;
1625         TextClass const & tclass = buffer.params().getTextClass();
1626         LayoutPtr const & layout = par.layout();
1627
1628         docstring parindent = layout->parindent;
1629
1630         int l_margin = 0;
1631
1632         if (text_->isMainText(buffer))
1633                 l_margin += changebarMargin();
1634
1635         l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
1636                 tclass.leftmargin());
1637
1638         if (par.getDepth() != 0) {
1639                 // find the next level paragraph
1640                 pit_type newpar = outerHook(pit, pars);
1641                 if (newpar != pit_type(pars.size())) {
1642                         if (pars[newpar].layout()->isEnvironment()) {
1643                                 l_margin = leftMargin(max_width, newpar);
1644                         }
1645                         if (par.layout() == tclass.defaultLayout()) {
1646                                 if (pars[newpar].params().noindent())
1647                                         parindent.erase();
1648                                 else
1649                                         parindent = pars[newpar].layout()->parindent;
1650                         }
1651                 }
1652         }
1653
1654         // This happens after sections in standard classes. The 1.3.x
1655         // code compared depths too, but it does not seem necessary
1656         // (JMarc)
1657         if (par.layout() == tclass.defaultLayout()
1658             && pit > 0 && pars[pit - 1].layout()->nextnoindent)
1659                 parindent.erase();
1660
1661         Font const labelfont = text_->getLabelFont(buffer, par);
1662         FontMetrics const & labelfont_metrics = theFontMetrics(labelfont);
1663
1664         switch (layout->margintype) {
1665         case MARGIN_DYNAMIC:
1666                 if (!layout->leftmargin.empty()) {
1667                         l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
1668                                 layout->leftmargin);
1669                 }
1670                 if (!par.getLabelstring().empty()) {
1671                         l_margin += labelfont_metrics.signedWidth(layout->labelindent);
1672                         l_margin += labelfont_metrics.width(par.getLabelstring());
1673                         l_margin += labelfont_metrics.width(layout->labelsep);
1674                 }
1675                 break;
1676
1677         case MARGIN_MANUAL: {
1678                 l_margin += labelfont_metrics.signedWidth(layout->labelindent);
1679                 // The width of an empty par, even with manual label, should be 0
1680                 if (!par.empty() && pos >= par.beginOfBody()) {
1681                         if (!par.getLabelWidthString().empty()) {
1682                                 docstring labstr = par.getLabelWidthString();
1683                                 l_margin += labelfont_metrics.width(labstr);
1684                                 l_margin += labelfont_metrics.width(layout->labelsep);
1685                         }
1686                 }
1687                 break;
1688         }
1689
1690         case MARGIN_STATIC: {
1691                 l_margin += theFontMetrics(buffer.params().getFont()).
1692                         signedWidth(layout->leftmargin) * 4     / (par.getDepth() + 4);
1693                 break;
1694         }
1695
1696         case MARGIN_FIRST_DYNAMIC:
1697                 if (layout->labeltype == LABEL_MANUAL) {
1698                         if (pos >= par.beginOfBody()) {
1699                                 l_margin += labelfont_metrics.signedWidth(layout->leftmargin);
1700                         } else {
1701                                 l_margin += labelfont_metrics.signedWidth(layout->labelindent);
1702                         }
1703                 } else if (pos != 0
1704                            // Special case to fix problems with
1705                            // theorems (JMarc)
1706                            || (layout->labeltype == LABEL_STATIC
1707                                && layout->latextype == LATEX_ENVIRONMENT
1708                                && !isFirstInSequence(pit, pars))) {
1709                         l_margin += labelfont_metrics.signedWidth(layout->leftmargin);
1710                 } else if (layout->labeltype != LABEL_TOP_ENVIRONMENT
1711                            && layout->labeltype != LABEL_BIBLIO
1712                            && layout->labeltype !=
1713                            LABEL_CENTERED_TOP_ENVIRONMENT) {
1714                         l_margin += labelfont_metrics.signedWidth(layout->labelindent);
1715                         l_margin += labelfont_metrics.width(layout->labelsep);
1716                         l_margin += labelfont_metrics.width(par.getLabelstring());
1717                 }
1718                 break;
1719
1720         case MARGIN_RIGHT_ADDRESS_BOX: {
1721 #if 0
1722                 // ok, a terrible hack. The left margin depends on the widest
1723                 // row in this paragraph.
1724                 RowList::iterator rit = par.rows().begin();
1725                 RowList::iterator end = par.rows().end();
1726                 // FIXME: This is wrong.
1727                 int minfill = max_width;
1728                 for ( ; rit != end; ++rit)
1729                         if (rit->fill() < minfill)
1730                                 minfill = rit->fill();
1731                 l_margin += theFontMetrics(params.getFont()).signedWidth(layout->leftmargin);
1732                 l_margin += minfill;
1733 #endif
1734                 // also wrong, but much shorter.
1735                 l_margin += max_width / 2;
1736                 break;
1737         }
1738         }
1739
1740         if (!par.params().leftIndent().zero())
1741                 l_margin += par.params().leftIndent().inPixels(max_width);
1742
1743         LyXAlignment align;
1744
1745         if (par.params().align() == LYX_ALIGN_LAYOUT)
1746                 align = layout->align;
1747         else
1748                 align = par.params().align();
1749
1750         // set the correct parindent
1751         if (pos == 0
1752             && (layout->labeltype == LABEL_NO_LABEL
1753                || layout->labeltype == LABEL_TOP_ENVIRONMENT
1754                || layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT
1755                || (layout->labeltype == LABEL_STATIC
1756                    && layout->latextype == LATEX_ENVIRONMENT
1757                    && !isFirstInSequence(pit, pars)))
1758             && align == LYX_ALIGN_BLOCK
1759             && !par.params().noindent()
1760             // in some insets, paragraphs are never indented
1761             && !(par.inInset() && par.inInset()->neverIndent(buffer))
1762             // display style insets are always centered, omit indentation
1763             && !(!par.empty()
1764                     && par.isInset(pos)
1765                     && par.getInset(pos)->display())
1766             && (par.layout() != tclass.defaultLayout()
1767                 || buffer.params().paragraph_separation ==
1768                    BufferParams::PARSEP_INDENT))
1769         {
1770                 l_margin += theFontMetrics(buffer.params().getFont()).signedWidth(
1771                         parindent);
1772         }
1773
1774         return l_margin;
1775 }
1776
1777
1778 int TextMetrics::singleWidth(pit_type pit, pos_type pos) const
1779 {
1780         ParagraphMetrics const & pm = par_metrics_[pit];
1781
1782         return pm.singleWidth(pos, getDisplayFont(text_->getPar(pit), pos));
1783 }
1784
1785
1786 // only used for inset right now. should also be used for main text
1787 void TextMetrics::draw(PainterInfo & pi, int x, int y) const
1788 {
1789         if (par_metrics_.empty())
1790                 return;
1791
1792         ParMetricsCache::const_iterator it = par_metrics_.begin();
1793         ParMetricsCache::const_iterator const end = par_metrics_.end();
1794         y -= it->second.ascent();
1795         for (; it != end; ++it) {
1796                 ParagraphMetrics const & pmi = it->second;
1797                 y += pmi.ascent();
1798                 drawParagraph(pi, it->first, x, y);
1799                 y += pmi.descent();
1800         }
1801 }
1802
1803
1804 void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const
1805 {
1806 //      lyxerr << "  paintPar: pit: " << pit << " at y: " << y << endl;
1807         int const ww = bv_->workHeight();
1808
1809         bv_->coordCache().parPos()[text_][pit] = Point(x, y);
1810
1811         ParagraphMetrics const & pm = par_metrics_[pit];
1812         if (pm.rows().empty())
1813                 return;
1814
1815         RowList::const_iterator const rb = pm.rows().begin();
1816         RowList::const_iterator const re = pm.rows().end();
1817
1818         Bidi bidi;
1819
1820         y -= rb->ascent();
1821         for (RowList::const_iterator rit = rb; rit != re; ++rit) {
1822                 y += rit->ascent();
1823
1824                 bool const inside = (y + rit->descent() >= 0
1825                         && y - rit->ascent() < ww);
1826                 // it is not needed to draw on screen if we are not inside.
1827                 pi.pain.setDrawingEnabled(inside);
1828                 RowPainter rp(pi, *text_, pit, *rit, bidi, x, y);
1829
1830                 // Row signature; has row changed since last paint?
1831                 bool row_has_changed = rit->changed();
1832                 
1833                 if (!pi.full_repaint && !row_has_changed) {
1834                         // Paint the only the insets if the text itself is
1835                         // unchanged.
1836                         rp.paintOnlyInsets();
1837                         y += rit->descent();
1838                         continue;
1839                 }
1840
1841                 // Paint the row if a full repaint has been requested or it has
1842                 // changed.
1843                 // Clear background of this row
1844                 // (if paragraph background was not cleared)
1845                 if (!pi.full_repaint && row_has_changed) {
1846                         pi.pain.fillRectangle(x, y - rit->ascent(),
1847                                 width(), rit->height(),
1848                                 Color_color(Color::color(pi.background_color)));
1849                 }
1850
1851                 // Instrumentation for testing row cache (see also
1852                 // 12 lines lower):
1853                 if (lyxerr.debugging(Debug::PAINTING)) {
1854                         if (text_->isMainText(bv_->buffer()))
1855                                 LYXERR(Debug::PAINTING) << "\n{" <<
1856                                 pi.full_repaint << row_has_changed << "}";
1857                         else
1858                                 LYXERR(Debug::PAINTING) << "[" <<
1859                                 pi.full_repaint << row_has_changed << "]";
1860                 }
1861
1862                 // Backup full_repaint status and force full repaint
1863                 // for inner insets as the Row has been cleared out.
1864                 bool tmp = pi.full_repaint;
1865                 pi.full_repaint = true;
1866                 rp.paintAppendix();
1867                 rp.paintDepthBar();
1868                 rp.paintChangeBar();
1869                 if (rit == rb)
1870                         rp.paintFirst();
1871                 rp.paintText();
1872                 if (rit + 1 == re)
1873                         rp.paintLast();
1874                 y += rit->descent();
1875                 // Restore full_repaint status.
1876                 pi.full_repaint = tmp;
1877         }
1878         // Re-enable screen drawing for future use of the painter.
1879         pi.pain.setDrawingEnabled(true);
1880
1881         //LYXERR(Debug::PAINTING) << "." << endl;
1882 }
1883
1884
1885 // only used for inset right now. should also be used for main text
1886 void TextMetrics::drawSelection(PainterInfo & pi, int x, int) const
1887 {
1888         Cursor & cur = bv_->cursor();
1889         if (!cur.selection())
1890                 return;
1891         if (!ptr_cmp(cur.text(), text_))
1892                 return;
1893
1894         LYXERR(Debug::DEBUG)
1895                 << BOOST_CURRENT_FUNCTION
1896                 << "draw selection at " << x
1897                 << endl;
1898
1899         DocIterator beg = cur.selectionBegin();
1900         DocIterator end = cur.selectionEnd();
1901
1902         // the selection doesn't touch the visible screen?
1903         if (bv_funcs::status(bv_, beg) == bv_funcs::CUR_BELOW
1904             || bv_funcs::status(bv_, end) == bv_funcs::CUR_ABOVE)
1905                 return;
1906
1907         if (beg.pit() < par_metrics_.begin()->first) {
1908                 beg.pit() = par_metrics_.begin()->first;
1909                 beg.pos() = 0;
1910         }
1911         if (end.pit() > par_metrics_.rbegin()->first) {
1912                 end.pit() = par_metrics_.rbegin()->first;
1913                 end.pos() = end.lastpos();
1914         }
1915
1916         ParagraphMetrics const & pm1 = par_metrics_[beg.pit()];
1917         ParagraphMetrics const & pm2 = par_metrics_[end.pit()];
1918         Row const & row1 = pm1.getRow(beg.pos(), beg.boundary());
1919         Row const & row2 = pm2.getRow(end.pos(), end.boundary());
1920
1921         // clip above
1922         int middleTop;
1923         bool const clipAbove = 
1924                 (bv_funcs::status(bv_, beg) == bv_funcs::CUR_ABOVE);
1925         if (clipAbove)
1926                 middleTop = 0;
1927         else
1928                 middleTop = bv_funcs::getPos(*bv_, beg, beg.boundary()).y_ + row1.descent();
1929         
1930         // clip below
1931         int middleBottom;
1932         bool const clipBelow = 
1933                 (bv_funcs::status(bv_, end) == bv_funcs::CUR_BELOW);
1934         if (clipBelow)
1935                 middleBottom = bv_->workHeight();
1936         else
1937                 middleBottom = bv_funcs::getPos(*bv_, end, end.boundary()).y_ - row2.ascent();
1938
1939         // start and end in the same line?
1940         if (!(clipAbove || clipBelow) && &row1 == &row2)
1941                 // then only draw this row's selection
1942                 drawRowSelection(pi, x, row1, beg, end, false, false);
1943         else {
1944                 if (!clipAbove) {
1945                         // get row end
1946                         DocIterator begRowEnd = beg;
1947                         begRowEnd.pos() = row1.endpos();
1948                         begRowEnd.boundary(true);
1949                         
1950                         // draw upper rectangle
1951                         drawRowSelection(pi, x, row1, beg, begRowEnd, false, true);
1952                 }
1953                         
1954                 if (middleTop < middleBottom) {
1955                         // draw middle rectangle
1956                         pi.pain.fillRectangle(x, middleTop, width(), middleBottom - middleTop,
1957                                 Color::selection);
1958                 }
1959
1960                 if (!clipBelow) {
1961                         // get row begin
1962                         DocIterator endRowBeg = end;
1963                         endRowBeg.pos() = row2.pos();
1964                         endRowBeg.boundary(false);
1965                         
1966                         // draw low rectangle
1967                         drawRowSelection(pi, x, row2, endRowBeg, end, true, false);
1968                 }
1969         }
1970 }
1971
1972
1973 void TextMetrics::drawRowSelection(PainterInfo & pi, int x, Row const & row,
1974                 DocIterator const & beg, DocIterator const & end,
1975                 bool drawOnBegMargin, bool drawOnEndMargin) const
1976 {
1977         Buffer & buffer = bv_->buffer();
1978         DocIterator cur = beg;
1979         int x1 = cursorX(beg.top(), beg.boundary());
1980         int x2 = cursorX(end.top(), end.boundary());
1981         int y1 = bv_funcs::getPos(*bv_, cur, cur.boundary()).y_ - row.ascent();
1982         int y2 = y1 + row.height();
1983         
1984         // draw the margins
1985         if (drawOnBegMargin) {
1986                 if (text_->isRTL(buffer, beg.paragraph()))
1987                         pi.pain.fillRectangle(x + x1, y1, width() - x1, y2 - y1, Color::selection);
1988                 else
1989                         pi.pain.fillRectangle(x, y1, x1, y2 - y1, Color::selection);
1990         }
1991         
1992         if (drawOnEndMargin) {
1993                 if (text_->isRTL(buffer, beg.paragraph()))
1994                         pi.pain.fillRectangle(x, y1, x2, y2 - y1, Color::selection);
1995                 else
1996                         pi.pain.fillRectangle(x + x2, y1, width() - x2, y2 - y1, Color::selection);
1997         }
1998         
1999         // if we are on a boundary from the beginning, it's probably
2000         // a RTL boundary and we jump to the other side directly as this
2001         // segement is 0-size and confuses the logic below
2002         if (cur.boundary())
2003                 cur.boundary(false);
2004         
2005         // go through row and draw from RTL boundary to RTL boundary
2006         while (cur < end) {
2007                 bool drawNow = false;
2008                 
2009                 // simplified cursorRight code below which does not
2010                 // descend into insets and which does not go into the
2011                 // next line. Compare the logic with the original cursorRight
2012                 
2013                 // if left of boundary -> just jump to right side
2014                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
2015                 if (cur.boundary()) {
2016                         cur.boundary(false);
2017                 }       else if (isRTLBoundary(cur.paragraph(), cur.pos() + 1)) {
2018                         // in front of RTL boundary -> Stay on this side of the boundary because:
2019                         //   ab|cDDEEFFghi -> abc|DDEEFFghi
2020                         ++cur.pos();
2021                         cur.boundary(true);
2022                         drawNow = true;
2023                 } else {
2024                         // move right
2025                         ++cur.pos();
2026                         
2027                         // line end?
2028                         if (cur.pos() == row.endpos())
2029                                 cur.boundary(true);
2030                 }
2031                         
2032                 if (x1 == -1) {
2033                         // the previous segment was just drawn, now the next starts
2034                         x1 = cursorX(cur.top(), cur.boundary());
2035                 }
2036                 
2037                 if (!(cur < end) || drawNow) {
2038                         x2 = cursorX(cur.top(), cur.boundary());
2039                         pi.pain.fillRectangle(x + min(x1,x2), y1, abs(x2 - x1), y2 - y1,
2040                                 Color::selection);
2041                         
2042                         // reset x1, so it is set again next round (which will be on the 
2043                         // right side of a boundary or at the selection end)
2044                         x1 = -1;
2045                 }
2046         }
2047 }
2048
2049 //int TextMetrics::pos2x(pit_type pit, pos_type pos) const
2050 //{
2051 //      ParagraphMetrics const & pm = par_metrics_[pit];
2052 //      Row const & r = pm.rows()[row];
2053 //      int x = 0;
2054 //      pos -= r.pos();
2055 //}
2056
2057
2058 int defaultRowHeight()
2059 {
2060         return int(theFontMetrics(Font(Font::ALL_SANE)).maxHeight() *  1.2);
2061 }
2062
2063 } // namespace lyx