]> git.lyx.org Git - lyx.git/blob - src/rowpainter.cpp
* RowPainter:
[lyx.git] / src / rowpainter.cpp
1 /**
2  * \file rowpainter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author various
7  * \author John Levon
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "rowpainter.h"
15
16 #include "Bidi.h"
17 #include "Buffer.h"
18 #include "CoordCache.h"
19 #include "Cursor.h"
20 #include "debug.h"
21 #include "BufferParams.h"
22 #include "BufferView.h"
23 #include "Encoding.h"
24 #include "gettext.h"
25 #include "Language.h"
26 #include "Color.h"
27 #include "LyXRC.h"
28 #include "Row.h"
29 #include "MetricsInfo.h"
30 #include "Paragraph.h"
31 #include "ParagraphMetrics.h"
32 #include "paragraph_funcs.h"
33 #include "ParagraphParameters.h"
34 #include "TextMetrics.h"
35 #include "VSpace.h"
36
37 #include "frontends/FontMetrics.h"
38 #include "frontends/Painter.h"
39
40 #include "insets/InsetText.h"
41
42 #include "support/textutils.h"
43
44 #include <boost/crc.hpp>
45
46 using std::endl;
47 using std::max;
48 using std::string;
49
50
51 namespace lyx {
52
53 using frontend::Painter;
54 using frontend::FontMetrics;
55
56 RowPainter::RowPainter(PainterInfo & pi,
57         Text const & text, pit_type pit, Row const & row, Bidi & bidi, int x, int y)
58         : bv_(*pi.base.bv), pain_(pi.pain), text_(text),
59           text_metrics_(pi.base.bv->textMetrics(&text)),
60           pars_(text.paragraphs()),
61           row_(row), pit_(pit), par_(text.paragraphs()[pit]),
62           pm_(text_metrics_.parMetrics(pit)),
63           bidi_(bidi), erased_(pi.erased_),
64           xo_(x), yo_(y), width_(text_metrics_.width())
65 {
66         bidi_.computeTables(par_, bv_.buffer(), row_);
67         x_ = row_.x + xo_;
68
69         //lyxerr << "RowPainter: x: " << x_ << " xo: " << xo_ << " yo: " << yo_ << endl;
70         //row_.dump();
71
72         BOOST_ASSERT(pit >= 0);
73         BOOST_ASSERT(pit < int(text.paragraphs().size()));
74 }
75
76
77 Font const RowPainter::getLabelFont() const
78 {
79         return text_.getLabelFont(bv_.buffer(), par_);
80 }
81
82
83 int RowPainter::leftMargin() const
84 {
85         return text_.leftMargin(bv_.buffer(), text_metrics_.width(), pit_,
86                 row_.pos());
87 }
88
89
90 void RowPainter::paintHfill(pos_type const pos, pos_type const body_pos)
91 {
92         x_ += 1;
93
94         int const y0 = yo_;
95         int const y1 = y0 - defaultRowHeight() / 2;
96
97         pain_.line(int(x_), y1, int(x_), y0, Color::added_space);
98
99         if (par_.hfillExpansion(row_, pos)) {
100                 int const y2 = (y0 + y1) / 2;
101
102                 if (pos >= body_pos) {
103                         pain_.line(int(x_), y2, int(x_ + row_.hfill), y2,
104                                 Color::added_space,
105                                 Painter::line_onoffdash);
106                         x_ += row_.hfill;
107                 } else {
108                         pain_.line(int(x_), y2, int(x_ + row_.label_hfill), y2,
109                                 Color::added_space,
110                                 Painter::line_onoffdash);
111                         x_ += row_.label_hfill;
112                 }
113                 pain_.line(int(x_), y1, int(x_), y0, Color::added_space);
114         }
115         x_ += 2;
116 }
117
118
119 // If you want to debug inset metrics uncomment the following line:
120 //#define DEBUG_METRICS
121 // This draws green lines around each inset.
122
123
124 void RowPainter::paintInset(Inset const * inset, pos_type const pos)
125 {
126         Font font = text_.getFont(bv_.buffer(), par_, pos);
127
128         BOOST_ASSERT(inset);
129         PainterInfo pi(const_cast<BufferView *>(&bv_), pain_);
130         // FIXME: We should always use font, see documentation of
131         // noFontChange() in Inset.h.
132         pi.base.font = inset->noFontChange() ?
133                 bv_.buffer().params().getFont() :
134                 font;
135         pi.ltr_pos = (bidi_.level(pos) % 2 == 0);
136         pi.erased_ = erased_ || par_.isDeleted(pos);
137         // insets are painted completely. Recursive
138         inset->drawSelection(pi, int(x_), yo_);
139         inset->draw(pi, int(x_), yo_);
140
141         paintForeignMark(x_, font, inset->descent());
142
143         x_ += inset->width();
144
145 #ifdef DEBUG_METRICS
146         int const x1 = int(x_ - inset->width());
147         Dimension dim;
148         BOOST_ASSERT(max_witdh_ > 0);
149         int right_margin = text_metrics_.rightMargin(pm_);
150         int const w = max_witdh_ - leftMargin() - right_margin;
151         MetricsInfo mi(&bv_, font, w);
152         inset->metrics(mi, dim);
153         if (inset->width() > dim.wid)
154                 lyxerr << "Error: inset " << to_ascii(inset->getInsetName())
155                        << " draw width " << inset->width()
156                        << "> metrics width " << dim.wid << "." << std::endl;
157         if (inset->ascent() > dim.asc)
158                 lyxerr << "Error: inset " << to_ascii(inset->getInsetName())
159                        << " draw ascent " << inset->ascent()
160                        << "> metrics ascent " << dim.asc << "." << std::endl;
161         if (inset->descent() > dim.des)
162                 lyxerr << "Error: inset " << to_ascii(inset->getInsetName())
163                        << " draw ascent " << inset->descent()
164                        << "> metrics descent " << dim.des << "." << std::endl;
165         BOOST_ASSERT(inset->width() <= dim.wid);
166         BOOST_ASSERT(inset->ascent() <= dim.asc);
167         BOOST_ASSERT(inset->descent() <= dim.des);
168         int const x2 = x1 + dim.wid;
169         int const y1 = yo_ + dim.des;
170         int const y2 = yo_ - dim.asc;
171         pi.pain.line(x1, y1, x1, y2, Color::green);
172         pi.pain.line(x1, y1, x2, y1, Color::green);
173         pi.pain.line(x2, y1, x2, y2, Color::green);
174         pi.pain.line(x1, y2, x2, y2, Color::green);
175 #endif
176 }
177
178
179 void RowPainter::paintHebrewComposeChar(pos_type & vpos, Font const & font)
180 {
181         pos_type pos = bidi_.vis2log(vpos);
182
183         docstring str;
184
185         // first char
186         char_type c = par_.getChar(pos);
187         str += c;
188         ++vpos;
189
190         int const width = theFontMetrics(font).width(c);
191         int dx = 0;
192
193         for (pos_type i = pos - 1; i >= 0; --i) {
194                 c = par_.getChar(i);
195                 if (!Encodings::isComposeChar_hebrew(c)) {
196                         if (isPrintableNonspace(c)) {
197                                 int const width2 = pm_.singleWidth(i,
198                                         text_.getFont(bv_.buffer(), par_, i));
199                                 dx = (c == 0x05e8 || // resh
200                                       c == 0x05d3)   // dalet
201                                         ? width2 - width
202                                         : (width2 - width) / 2;
203                         }
204                         break;
205                 }
206         }
207
208         // Draw nikud
209         pain_.text(int(x_) + dx, yo_, str, font);
210 }
211
212
213 void RowPainter::paintArabicComposeChar(pos_type & vpos, Font const & font)
214 {
215         pos_type pos = bidi_.vis2log(vpos);
216         docstring str;
217
218         // first char
219         char_type c = par_.getChar(pos);
220         c = par_.transformChar(c, pos);
221         str += c;
222         ++vpos;
223
224         int const width = theFontMetrics(font).width(c);
225         int dx = 0;
226
227         for (pos_type i = pos - 1; i >= 0; --i) {
228                 c = par_.getChar(i);
229                 if (!Encodings::isComposeChar_arabic(c)) {
230                         if (isPrintableNonspace(c)) {
231                                 int const width2 = pm_.singleWidth(i,
232                                                 text_.getFont(bv_.buffer(), par_, i));
233                                 dx = (width2 - width) / 2;
234                         }
235                         break;
236                 }
237         }
238         // Draw nikud
239         pain_.text(int(x_) + dx, yo_, str, font);
240 }
241
242
243 void RowPainter::paintChars(pos_type & vpos, Font const & font,
244                             bool hebrew, bool arabic)
245 {
246         // This method takes up 70% of time when typing
247         pos_type pos = bidi_.vis2log(vpos);
248         pos_type const end = row_.endpos();
249         FontSpan const font_span = par_.fontSpan(pos);
250         Change::Type const prev_change = par_.lookupChange(pos).type;
251
252         // first character
253         std::vector<char_type> str;
254         str.reserve(100);
255         str.push_back(par_.getChar(pos));
256
257         if (arabic) {
258                 char_type c = str[0];
259                 if (c == '(')
260                         c = ')';
261                 else if (c == ')')
262                         c = '(';
263                 str[0] = par_.transformChar(c, pos);
264         }
265
266         // collect as much similar chars as we can
267         for (++vpos ; vpos < end ; ++vpos) {
268                 pos = bidi_.vis2log(vpos);
269                 if (pos < font_span.first || pos > font_span.last)
270                         break;
271
272                 if (prev_change != par_.lookupChange(pos).type)
273                         break;
274
275                 char_type c = par_.getChar(pos);
276
277                 if (!isPrintableNonspace(c))
278                         break;
279
280                 /* Because we do our own bidi, at this point the strings are
281                  * already in visual order. However, Qt also applies its own
282                  * bidi algorithm to strings that it paints to the screen.
283                  * Therefore, if we were to paint Hebrew/Arabic words as a
284                  * single string, the letters in the words would get reversed
285                  * again. In order to avoid that, we don't collect Hebrew/
286                  * Arabic characters, but rather paint them one at a time.
287                  * See also http://thread.gmane.org/gmane.editors.lyx.devel/79740
288                  */
289                 if (hebrew)
290                         break;
291
292                 /* FIXME: these checks are irrelevant, since 'arabic' and
293                  * 'hebrew' alone are already going to trigger a break.
294                  * However, this should not be removed completely, because
295                  * if an alternative solution is found which allows grouping
296                  * of arabic and hebrew characters, then these breaks may have
297                  * to be re-applied.
298
299                 if (arabic && Encodings::isComposeChar_arabic(c))
300                         break;
301
302                 if (hebrew && Encodings::isComposeChar_hebrew(c))
303                         break;
304                 */
305
306                 if (arabic) {
307                         if (c == '(')
308                                 c = ')';
309                         else if (c == ')')
310                                 c = '(';
311                         c = par_.transformChar(c, pos);
312                         /* see comment in hebrew, explaining why we break */
313                         break;
314                 }
315
316                 str.push_back(c);
317         }
318
319         docstring s(&str[0], str.size());
320
321         if (prev_change != Change::UNCHANGED) {
322                 Font copy(font);
323                 if (prev_change == Change::DELETED) {
324                         copy.setColor(Color::deletedtext);
325                 } else if (prev_change == Change::INSERTED) {
326                         copy.setColor(Color::addedtext);
327                 }
328                 x_ += pain_.text(int(x_), yo_, s, copy);
329         } else {
330                 x_ += pain_.text(int(x_), yo_, s, font);
331         }
332 }
333
334
335 void RowPainter::paintForeignMark(double orig_x, Font const & font, int desc)
336 {
337         if (!lyxrc.mark_foreign_language)
338                 return;
339         if (font.language() == latex_language)
340                 return;
341         if (font.language() == bv_.buffer().params().language)
342                 return;
343
344         int const y = yo_ + 1 + desc;
345         pain_.line(int(orig_x), y, int(x_), y, Color::language);
346 }
347
348
349 void RowPainter::paintFromPos(pos_type & vpos)
350 {
351         pos_type const pos = bidi_.vis2log(vpos);
352         Font orig_font = text_.getFont(bv_.buffer(), par_, pos);
353         double const orig_x = x_;
354
355         // usual characters, no insets
356         char_type const c = par_.getChar(pos);
357
358         // special case languages
359         std::string const & lang = orig_font.language()->lang();
360         bool const hebrew = lang == "hebrew";
361         bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" || 
362                                                 lang == "farsi";
363
364         // draw as many chars as we can
365         if ((!hebrew && !arabic)
366                 || (hebrew && !Encodings::isComposeChar_hebrew(c))
367                 || (arabic && !Encodings::isComposeChar_arabic(c))) {
368                 paintChars(vpos, orig_font, hebrew, arabic);
369         } else if (hebrew) {
370                 paintHebrewComposeChar(vpos, orig_font);
371         } else if (arabic) {
372                 paintArabicComposeChar(vpos, orig_font);
373         }
374
375         paintForeignMark(orig_x, orig_font);
376 }
377
378
379 void RowPainter::paintChangeBar()
380 {
381         pos_type const start = row_.pos();
382         pos_type end = row_.endpos();
383
384         if (par_.size() == end) {
385                 // this is the last row of the paragraph;
386                 // thus, we must also consider the imaginary end-of-par character
387                 end++;
388         }
389
390         if (start == end || !par_.isChanged(start, end))
391                 return;
392
393         int const height = text_.isLastRow(pit_, row_)
394                 ? row_.ascent()
395                 : row_.height();
396
397         pain_.fillRectangle(5, yo_ - row_.ascent(), 3, height, Color::changebar);
398 }
399
400
401 void RowPainter::paintAppendix()
402 {
403         // only draw the appendix frame once (for the main text)
404         if (!par_.params().appendix() || !text_.isMainText(bv_.buffer()))
405                 return;
406
407         int y = yo_ - row_.ascent();
408
409         if (par_.params().startOfAppendix())
410                 y += 2 * defaultRowHeight();
411
412         pain_.line(1, y, 1, yo_ + row_.height(), Color::appendix);
413         pain_.line(width_ - 2, y, width_ - 2, yo_ + row_.height(), Color::appendix);
414 }
415
416
417 void RowPainter::paintDepthBar()
418 {
419         depth_type const depth = par_.getDepth();
420
421         if (depth <= 0)
422                 return;
423
424         depth_type prev_depth = 0;
425         if (!text_.isFirstRow(pit_, row_)) {
426                 pit_type pit2 = pit_;
427                 if (row_.pos() == 0)
428                         --pit2;
429                 prev_depth = pars_[pit2].getDepth();
430         }
431
432         depth_type next_depth = 0;
433         if (!text_.isLastRow(pit_, row_)) {
434                 pit_type pit2 = pit_;
435                 if (row_.endpos() >= pars_[pit2].size())
436                         ++pit2;
437                 next_depth = pars_[pit2].getDepth();
438         }
439
440         for (depth_type i = 1; i <= depth; ++i) {
441                 int const w = nestMargin() / 5;
442                 int x = int(xo_) + w * i;
443                 // only consider the changebar space if we're drawing outermost text
444                 if (text_.isMainText(bv_.buffer()))
445                         x += changebarMargin();
446
447                 int const starty = yo_ - row_.ascent();
448                 int const h =  row_.height() - 1 - (i - next_depth - 1) * 3;
449
450                 pain_.line(x, starty, x, starty + h, Color::depthbar);
451
452                 if (i > prev_depth)
453                         pain_.fillRectangle(x, starty, w, 2, Color::depthbar);
454                 if (i > next_depth)
455                         pain_.fillRectangle(x, starty + h, w, 2, Color::depthbar);
456         }
457 }
458
459
460 int RowPainter::paintAppendixStart(int y)
461 {
462         Font pb_font;
463         pb_font.setColor(Color::appendix);
464         pb_font.decSize();
465
466         int w = 0;
467         int a = 0;
468         int d = 0;
469
470         docstring const label = _("Appendix");
471         theFontMetrics(pb_font).rectText(label, w, a, d);
472
473         int const text_start = int(xo_ + (width_ - w) / 2);
474         int const text_end = text_start + w;
475
476         pain_.rectText(text_start, y + d, label, pb_font, Color::none, Color::none);
477
478         pain_.line(int(xo_ + 1), y, text_start, y, Color::appendix);
479         pain_.line(text_end, y, int(xo_ + width_ - 2), y, Color::appendix);
480
481         return 3 * defaultRowHeight();
482 }
483
484
485 void RowPainter::paintFirst()
486 {
487         ParagraphParameters const & parparams = par_.params();
488
489         int y_top = 0;
490
491         // start of appendix?
492         if (parparams.startOfAppendix())
493                 y_top += paintAppendixStart(yo_ - row_.ascent() + 2 * defaultRowHeight());
494
495         Buffer const & buffer = bv_.buffer();
496
497         LayoutPtr const & layout = par_.layout();
498
499         if (buffer.params().paragraph_separation == BufferParams::PARSEP_SKIP) {
500                 if (pit_ != 0) {
501                         if (layout->latextype == LATEX_PARAGRAPH
502                                 && !par_.getDepth()) {
503                                 y_top += buffer.params().getDefSkip().inPixels(bv_);
504                         } else {
505                                 LayoutPtr const & playout = pars_[pit_ - 1].layout();
506                                 if (playout->latextype == LATEX_PARAGRAPH
507                                         && !pars_[pit_ - 1].getDepth()) {
508                                         // is it right to use defskip here, too? (AS)
509                                         y_top += buffer.params().getDefSkip().inPixels(bv_);
510                                 }
511                         }
512                 }
513         }
514
515         bool const is_rtl = text_.isRTL(buffer, par_);
516         bool const is_seq = isFirstInSequence(pit_, text_.paragraphs());
517         //lyxerr << "paintFirst: " << par_.id() << " is_seq: " << is_seq << std::endl;
518
519         // should we print a label?
520         if (layout->labeltype >= LABEL_STATIC
521             && (layout->labeltype != LABEL_STATIC
522                       || layout->latextype != LATEX_ENVIRONMENT
523                       || is_seq)) {
524
525                 Font const font = getLabelFont();
526                 FontMetrics const & fm = theFontMetrics(font);
527
528                 docstring const str = par_.getLabelstring();
529                 if (!str.empty()) {
530                         double x = x_;
531
532                         // this is special code for the chapter layout. This is
533                         // printed in an extra row and has a pagebreak at
534                         // the top.
535                         if (layout->counter == "chapter") {
536                                 double spacing_val = 1.0;
537                                 if (!parparams.spacing().isDefault()) {
538                                         spacing_val = parparams.spacing().getValue();
539                                 } else {
540                                         spacing_val = buffer.params().spacing().getValue();
541                                 }
542
543                                 int const labeladdon = int(fm.maxHeight() * layout->spacing.getValue() * spacing_val);
544
545                                 int const maxdesc = int(fm.maxDescent() * layout->spacing.getValue() * spacing_val)
546                                         + int(layout->parsep) * defaultRowHeight();
547
548                                 if (is_rtl) {
549                                         x = width_ - leftMargin() -
550                                                 fm.width(str);
551                                 }
552
553                                 pain_.text(int(x), yo_ - maxdesc - labeladdon, str, font);
554                         } else {
555                                 if (is_rtl) {
556                                         x = width_ - leftMargin()
557                                                 + fm.width(layout->labelsep);
558                                 } else {
559                                         x = x_ - fm.width(layout->labelsep)
560                                                 - fm.width(str);
561                                 }
562
563                                 pain_.text(int(x), yo_, str, font);
564                         }
565                 }
566
567         // the labels at the top of an environment.
568         // More or less for bibliography
569         } else if (is_seq &&
570                 (layout->labeltype == LABEL_TOP_ENVIRONMENT ||
571                 layout->labeltype == LABEL_BIBLIO ||
572                 layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT)) {
573                 Font font = getLabelFont();
574                 if (!par_.getLabelstring().empty()) {
575                         docstring const str = par_.getLabelstring();
576                         double spacing_val = 1.0;
577                         if (!parparams.spacing().isDefault())
578                                 spacing_val = parparams.spacing().getValue();
579                         else
580                                 spacing_val = buffer.params().spacing().getValue();
581
582                         FontMetrics const & fm = theFontMetrics(font);
583
584                         int const labeladdon = int(fm.maxHeight()
585                                 * layout->spacing.getValue() * spacing_val);
586
587                         int maxdesc =
588                                 int(fm.maxDescent() * layout->spacing.getValue() * spacing_val
589                                 + (layout->labelbottomsep * defaultRowHeight()));
590
591                         double x = x_;
592                         if (layout->labeltype == LABEL_CENTERED_TOP_ENVIRONMENT) {
593                                 if (is_rtl)
594                                         x = leftMargin();
595                                 x += (width_ - text_metrics_.rightMargin(pm_) - leftMargin()) / 2;
596                                 x -= fm.width(str) / 2;
597                         } else if (is_rtl) {
598                                 x = width_ - leftMargin() -     fm.width(str);
599                         }
600                         pain_.text(int(x), yo_ - maxdesc - labeladdon, str, font);
601                 }
602         }
603 }
604
605
606 void RowPainter::paintLast()
607 {
608         bool const is_rtl = text_.isRTL(bv_.buffer(), par_);
609         int const endlabel = getEndLabel(pit_, text_.paragraphs());
610
611         // paint imaginary end-of-paragraph character
612
613         if (par_.isInserted(par_.size()) || par_.isDeleted(par_.size())) {
614                 FontMetrics const & fm = theFontMetrics(bv_.buffer().params().getFont());
615                 int const length = fm.maxAscent() / 2;
616                 Color::color col = par_.isInserted(par_.size()) ? Color::addedtext : Color::deletedtext;
617
618                 pain_.line(int(x_) + 1, yo_ + 2, int(x_) + 1, yo_ + 2 - length, col,
619                            Painter::line_solid, Painter::line_thick);
620                 pain_.line(int(x_) + 1 - length, yo_ + 2, int(x_) + 1, yo_ + 2, col,
621                            Painter::line_solid, Painter::line_thick);
622         }
623
624         // draw an endlabel
625
626         switch (endlabel) {
627         case END_LABEL_BOX:
628         case END_LABEL_FILLED_BOX: {
629                 Font const font = getLabelFont();
630                 FontMetrics const & fm = theFontMetrics(font);
631                 int const size = int(0.75 * fm.maxAscent());
632                 int const y = yo_ - size;
633                 int x = is_rtl ? nestMargin() + changebarMargin() : width_ - size;
634
635                 if (width_ - int(row_.width()) <= size)
636                         x += (size - width_ + row_.width() + 1) * (is_rtl ? -1 : 1);
637
638                 if (endlabel == END_LABEL_BOX)
639                         pain_.rectangle(x, y, size, size, Color::eolmarker);
640                 else
641                         pain_.fillRectangle(x, y, size, size, Color::eolmarker);
642                 break;
643         }
644
645         case END_LABEL_STATIC: {
646                 Font font = getLabelFont();
647                 FontMetrics const & fm = theFontMetrics(font);
648                 docstring const & str = par_.layout()->endlabelstring();
649                 double const x = is_rtl ?
650                         x_ - fm.width(str)
651                         : - text_metrics_.rightMargin(pm_) - row_.width();
652                 pain_.text(int(x), yo_, str, font);
653                 break;
654         }
655
656         case END_LABEL_NO_LABEL:
657                 break;
658         }
659 }
660
661
662 void RowPainter::paintOnlyInsets()
663 {
664         pos_type const end = row_.endpos();
665         for (pos_type pos = row_.pos(); pos != end; ++pos) {
666                 if (!par_.isInset(pos))
667                         continue;
668
669                 // If outer row has changed, nested insets are repaint completely.
670                 Inset const * inset = par_.getInset(pos);
671
672                 if (x_ > bv_.workWidth())
673                         continue;
674
675                 x_ = bv_.coordCache().getInsets().x(inset);
676                 paintInset(inset, pos);
677         }
678 }
679
680
681 void RowPainter::paintText()
682 {
683         pos_type const end = row_.endpos();
684         // Spaces at logical line breaks in bidi text must be skipped during 
685         // painting. However, they may appear visually in the middle
686         // of a row; they must be skipped, wherever they are...
687         // * logically "abc_[HEBREW_\nHEBREW]"
688         // * visually "abc_[_WERBEH\nWERBEH]"
689         pos_type skipped_sep_vpos = -1;
690         pos_type body_pos = par_.beginOfBody();
691         if (body_pos > 0 &&
692                 (body_pos > end || !par_.isLineSeparator(body_pos - 1))) {
693                 body_pos = 0;
694         }
695
696         LayoutPtr const & layout = par_.layout();
697
698         bool running_strikeout = false;
699         bool is_struckout = false;
700         int last_strikeout_x = 0;
701
702         // Use font span to speed things up, see below
703         FontSpan font_span;
704         Font font;
705         Buffer const & buffer = bv_.buffer();
706
707         // If the last logical character is a separator, don't paint it, unless
708         // it's in the last row of a paragraph; see skipped_sep_vpos declaration
709         if (end > 0 && end < par_.size() && par_.isSeparator(end - 1))
710                 skipped_sep_vpos = bidi_.log2vis(end - 1);
711         
712         for (pos_type vpos = row_.pos(); vpos < end; ) {
713                 if (x_ > bv_.workWidth())
714                         break;
715
716                 // Skip the separator at the logical end of the row
717                 if (vpos == skipped_sep_vpos) {
718                         ++vpos;
719                         continue;
720                 }
721
722                 pos_type const pos = bidi_.vis2log(vpos);
723
724                 if (pos >= par_.size()) {
725                         ++vpos;
726                         continue;
727                 }
728
729                 // Use font span to speed things up, see above
730                 if (vpos < font_span.first || vpos > font_span.last) {
731                         font_span = par_.fontSpan(vpos);
732                         font = text_.getFont(buffer, par_, vpos);
733                 }
734
735                 const int width_pos = pm_.singleWidth(pos, font);
736
737                 if (x_ + width_pos < 0) {
738                         x_ += width_pos;
739                         ++vpos;
740                         continue;
741                 }
742
743                 is_struckout = par_.isDeleted(pos);
744
745                 if (is_struckout && !running_strikeout) {
746                         running_strikeout = true;
747                         last_strikeout_x = int(x_);
748                 }
749
750                 bool const highly_editable_inset = par_.isInset(pos)
751                         && isHighlyEditableInset(par_.getInset(pos));
752
753                 // If we reach the end of a struck out range, paint it.
754                 // We also don't paint across things like tables
755                 if (running_strikeout && (highly_editable_inset || !is_struckout)) {
756                         // Calculate 1/3 height of the buffer's default font
757                         FontMetrics const & fm
758                                 = theFontMetrics(bv_.buffer().params().getFont());
759                         int const middle = yo_ - fm.maxAscent() / 3;
760                         pain_.line(last_strikeout_x, middle, int(x_), middle,
761                                 Color::deletedtext, Painter::line_solid, Painter::line_thin);
762                         running_strikeout = false;
763                 }
764
765                 if (body_pos > 0 && pos == body_pos - 1) {
766                         int const lwidth = theFontMetrics(getLabelFont())
767                                 .width(layout->labelsep);
768
769                         x_ += row_.label_hfill + lwidth - width_pos;
770                 }
771
772                 if (par_.isHfill(pos)) {
773                         paintHfill(pos, body_pos);
774                         ++vpos;
775
776                 } else if (par_.isSeparator(pos)) {
777                         Font orig_font = text_.getFont(buffer, par_, pos);
778                         double const orig_x = x_;
779                         x_ += width_pos;
780                         if (pos >= body_pos)
781                                 x_ += row_.separator;
782                         paintForeignMark(orig_x, orig_font);
783                         ++vpos;
784
785                 } else if (par_.isInset(pos)) {
786                         // If outer row has changed, nested insets are repaint completely.
787                         Inset const * inset = par_.getInset(pos);
788                         bv_.coordCache().insets().add(inset, int(x_), yo_);
789                         paintInset(inset, pos);
790                         ++vpos;
791
792                 } else {
793                         // paint as many characters as possible.
794                         paintFromPos(vpos);
795                 }
796         }
797
798         // if we reach the end of a struck out range, paint it
799         if (running_strikeout) {
800                 // calculate 1/3 height of the buffer's default font
801                 FontMetrics const & fm
802                         = theFontMetrics(bv_.buffer().params().getFont());
803                 int const middle = yo_ - fm.maxAscent() / 3;
804                 pain_.line(last_strikeout_x, middle, int(x_), middle,
805                         Color::deletedtext, Painter::line_solid, Painter::line_thin);
806                 running_strikeout = false;
807         }
808 }
809
810 } // namespace lyx