]> git.lyx.org Git - features.git/blob - src/Row.cpp
Fix endless loop when breaking text
[features.git] / src / Row.cpp
1 /**
2  * \file Row.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author John Levon
8  * \author André Pönitz
9  * \author Jürgen Vigna
10  * \author Jean-Marc Lasgouttes
11  *
12  * Full author contact details are available in file CREDITS.
13  *
14  * Metrics for an on-screen text row.
15  */
16
17 #include <config.h>
18
19 #include "Row.h"
20
21 #include "DocIterator.h"
22 #include "Language.h"
23
24 #include "frontends/FontMetrics.h"
25
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/lstrings.h"
29 #include "support/lyxlib.h"
30
31 #include <algorithm>
32 #include <ostream>
33
34 using namespace std;
35
36 namespace lyx {
37
38 using frontend::FontMetrics;
39
40
41 // Maximum length that a space can be stretched when justifying text
42 static double const MAX_SPACE_STRETCH = 1.5; //em
43
44
45 int Row::Element::countSeparators() const
46 {
47         if (type != STRING)
48                 return 0;
49         return count(str.begin(), str.end(), ' ');
50 }
51
52
53 int Row::Element::countExpanders() const
54 {
55         if (type != STRING)
56                 return 0;
57         return theFontMetrics(font).countExpanders(str);
58 }
59
60
61 int Row::Element::expansionAmount() const
62 {
63         if (type != STRING)
64                 return 0;
65         return countExpanders() * theFontMetrics(font).em();
66 }
67
68
69 void Row::Element::setExtra(double extra_per_em)
70 {
71         if (type != STRING)
72                 return;
73         extra = extra_per_em * theFontMetrics(font).em();
74 }
75
76
77 double Row::Element::pos2x(pos_type const i) const
78 {
79         // This can happen with inline completion when clicking on the
80         // row after the completion.
81         if (i < pos || i > endpos)
82                 return 0;
83
84         double w = 0;
85         //handle first the two bounds of the element
86         if (i == endpos && type != VIRTUAL)
87                 w = isRTL() ? 0 : full_width();
88         else if (i == pos || type != STRING)
89                 w = isRTL() ? full_width() : 0;
90         else {
91                 FontMetrics const & fm = theFontMetrics(font);
92                 w = fm.pos2x(str, i - pos, isRTL(), extra);
93         }
94
95         return w;
96 }
97
98
99 pos_type Row::Element::x2pos(int &x) const
100 {
101         //lyxerr << "x2pos: x=" << x << " w=" << width() << " " << *this;
102         size_t i = 0;
103
104         switch (type) {
105         case STRING: {
106                 FontMetrics const & fm = theFontMetrics(font);
107                 i = fm.x2pos(str, x, isRTL(), extra);
108                 break;
109         }
110         case VIRTUAL:
111                 // those elements are actually empty (but they have a width)
112                 i = 0;
113                 x = isRTL() ? int(full_width()) : 0;
114                 break;
115         case INSET:
116         case SPACE:
117                 // those elements contain only one position. Round to
118                 // the closest side.
119                 if (x > (full_width() + 1) / 2) {
120                         x = int(full_width());
121                         i = !isRTL();
122                 } else {
123                         x = 0;
124                         i = isRTL();
125                 }
126         }
127         //lyxerr << "=> p=" << pos + i << " x=" << x << endl;
128         return pos + i;
129 }
130
131
132 bool Row::Element::splitAt(int const width, int next_width, bool force,
133                            Row::Elements & tail)
134 {
135         // Not a string or already OK.
136         if (type != STRING || (dim.wid > 0 && dim.wid < width))
137                 return false;
138
139         FontMetrics const & fm = theFontMetrics(font);
140
141         // A a string that is not breakable
142         if (!(row_flags & CanBreakInside)) {
143                 // has width been computed yet?
144                 if (dim.wid == 0)
145                         dim.wid = fm.width(str);
146                 return false;
147         }
148
149         bool const wrap_any = !font.language()->wordWrap();
150         FontMetrics::Breaks breaks = fm.breakString(str, width, next_width,
151                                                 isRTL(), wrap_any | force);
152
153         // if breaking did not really work, give up
154         if (!force && breaks.front().wid > width) {
155                 if (dim.wid == 0)
156                         dim.wid = fm.width(str);
157                 return false;
158         }
159
160         Element first_e(STRING, pos, font, change);
161         // should next element eventually replace *this?
162         bool first = true;
163         docstring::size_type i = 0;
164         for (FontMetrics::Break const & brk : breaks) {
165                 /* For some reason breakString can decide to break before the
166                  * first character (normally we use a 0-width nbsp to prevent
167                  * that). Skip leading empty elements, they are never wanted.
168                  */
169                 if (first && brk.len == 0 && breaks.size() > 1)
170                         continue;
171                 Element e(STRING, pos + i, font, change);
172                 e.str = str.substr(i, brk.len);
173                 e.endpos = e.pos + brk.len;
174                 e.dim.wid = brk.wid;
175                 e.row_flags = CanBreakInside | BreakAfter;
176                 if (first) {
177                         // this element eventually goes to *this
178                         e.row_flags |= row_flags & ~AfterFlags;
179                         first_e = e;
180                         first = false;
181                 } else
182                         tail.push_back(e);
183                 i += brk.len;
184         }
185
186         if (!tail.empty()) {
187                 // Avoid having a last empty element. This happens when
188                 // breaking at the trailing space of string
189                 if (tail.back().str.empty())
190                         tail.pop_back();
191                 else {
192                         // Copy the after flags of the original element to the last one.
193                         tail.back().row_flags &= ~BreakAfter;
194                         tail.back().row_flags |= row_flags & AfterFlags;
195                 }
196                 // first_e row should be broken after the original element
197                 first_e.row_flags |= BreakAfter;
198         } else {
199                 // Restore the after flags of the original element.
200                 first_e.row_flags &= ~BreakAfter;
201                 first_e.row_flags |= row_flags & AfterFlags;
202         }
203
204         // update ourselves
205         swap(first_e, *this);
206         return true;
207 }
208
209
210 void Row::Element::rtrim()
211 {
212         if (type != STRING)
213                 return;
214         /* This is intended for strings that have been created by splitAt.
215          * They may have trailing spaces, but they are not counted in the
216          * string length (QTextLayout feature, actually). We remove them,
217          * and decrease endpos, since spaces at row break are invisible.
218          */
219         str = support::rtrim(str);
220         endpos = pos + str.length();
221 }
222
223
224 bool Row::isMarginSelected(bool left, DocIterator const & beg,
225                 DocIterator const & end) const
226 {
227         pos_type const sel_pos = left ? sel_beg : sel_end;
228         pos_type const margin_pos = left ? pos_ : end_;
229
230         // Is there a selection and is the chosen margin selected ?
231         if (!selection() || sel_pos != margin_pos)
232                 return false;
233         else if (beg.pos() == end.pos())
234                 // This is a special case in which the space between after
235                 // pos i-1 and before pos i is selected, i.e. the margins
236                 // (see DocIterator::boundary_).
237                 return beg.boundary() && !end.boundary();
238         else if (end.pos() == margin_pos)
239                 // If the selection ends around the margin, it is only
240                 // drawn if the cursor is after the margin.
241                 return !end.boundary();
242         else if (beg.pos() == margin_pos)
243                 // If the selection begins around the margin, it is
244                 // only drawn if the cursor is before the margin.
245                 return beg.boundary();
246         else
247                 return true;
248 }
249
250
251 void Row::setSelectionAndMargins(DocIterator const & beg,
252                 DocIterator const & end) const
253 {
254         setSelection(beg.pos(), end.pos());
255
256         change(end_margin_sel, isMarginSelected(false, beg, end));
257         change(begin_margin_sel, isMarginSelected(true, beg, end));
258 }
259
260
261 void Row::clearSelectionAndMargins() const
262 {
263         change(sel_beg, -1);
264         change(sel_end, -1);
265         change(end_margin_sel, false);
266         change(begin_margin_sel, false);
267 }
268
269
270 void Row::setSelection(pos_type beg, pos_type end) const
271 {
272         if (pos_ >= beg && pos_ <= end)
273                 change(sel_beg, pos_);
274         else if (beg > pos_ && beg <= end_)
275                 change(sel_beg, beg);
276         else
277                 change(sel_beg, -1);
278
279         if (end_ >= beg && end_ <= end)
280                 change(sel_end,end_);
281         else if (end < end_ && end >= pos_)
282                 change(sel_end, end);
283         else
284                 change(sel_end, -1);
285 }
286
287
288 bool Row::selection() const
289 {
290         return sel_beg != -1 && sel_end != -1;
291 }
292
293
294 ostream & operator<<(ostream & os, Row::Element const & e)
295 {
296         if (e.isRTL())
297                 os << e.endpos << "<<" << e.pos << " ";
298         else
299                 os << e.pos << ">>" << e.endpos << " ";
300
301         switch (e.type) {
302         case Row::STRING:
303                 os << "STRING: `" << to_utf8(e.str) << "' ("
304                    << e.countExpanders() << " expanders.), ";
305                 break;
306         case Row::VIRTUAL:
307                 os << "VIRTUAL: `" << to_utf8(e.str) << "', ";
308                 break;
309         case Row::INSET:
310                 os << "INSET: " << to_utf8(e.inset->layoutName()) << ", ";
311                 break;
312         case Row::SPACE:
313                 os << "SPACE: ";
314         }
315         os << "width=" << e.full_width() << ", row_flags=" << e.row_flags;
316         return os;
317 }
318
319
320 ostream & operator<<(ostream & os, Row::Elements const & elts)
321 {
322         double x = 0;
323         for (Row::Element const & e : elts) {
324                 os << "x=" << x << " => " << e << endl;
325                 x += e.full_width();
326         }
327         return os;
328 }
329
330
331 ostream & operator<<(ostream & os, Row const & row)
332 {
333         os << " pos: " << row.pos_ << " end: " << row.end_
334            << " left_margin: " << row.left_margin
335            << " width: " << row.dim_.wid
336            << " right_margin: " << row.right_margin
337            << " ascent: " << row.dim_.asc
338            << " descent: " << row.dim_.des
339            << " separator: " << row.separator
340            << " label_hfill: " << row.label_hfill
341            << " row_boundary: " << row.right_boundary() << "\n";
342         // We cannot use the operator above, unfortunately
343         double x = row.left_margin;
344         for (Row::Element const & e : row.elements_) {
345                 os << "x=" << x << " => " << e << endl;
346                 x += e.full_width();
347         }
348         return os;
349 }
350
351
352 int Row::left_x() const
353 {
354         double x = left_margin;
355         const_iterator const end = elements_.end();
356         const_iterator cit = elements_.begin();
357         while (cit != end && cit->isVirtual()) {
358                 x += cit->full_width();
359                 ++cit;
360         }
361         return support::iround(x);
362 }
363
364
365 int Row::right_x() const
366 {
367         double x = dim_.wid;
368         const_iterator const begin = elements_.begin();
369         const_iterator cit = elements_.end();
370         while (cit != begin) {
371                 --cit;
372                 if (cit->isVirtual())
373                         x -= cit->full_width();
374                 else
375                         break;
376         }
377         return support::iround(x);
378 }
379
380
381 int Row::countSeparators() const
382 {
383         int n = 0;
384         const_iterator const end = elements_.end();
385         for (const_iterator cit = elements_.begin() ; cit != end ; ++cit)
386                 n += cit->countSeparators();
387         return n;
388 }
389
390
391 bool Row::setExtraWidth(int w)
392 {
393         if (w < 0)
394                 // this is not expected to happen (but it does)
395                 return false;
396         // amount of expansion: number of expanders time the em value for each
397         // string element
398         int exp_amount = 0;
399         for (Element const & e : elements_)
400                 exp_amount += e.expansionAmount();
401         if (!exp_amount)
402                 return false;
403         // extra length per expander per em
404         double extra_per_em = double(w) / exp_amount;
405         if (extra_per_em > MAX_SPACE_STRETCH)
406                 // do not stretch more than MAX_SPACE_STRETCH em per expander
407                 return false;
408         // add extra length to each element proportionally to its em.
409         for (Element & e : elements_)
410                 if (e.type == STRING)
411                         e.setExtra(extra_per_em);
412         // update row dimension
413         dim_.wid += w;
414         return true;
415 }
416
417
418 bool Row::sameString(Font const & f, Change const & ch) const
419 {
420         if (elements_.empty())
421                 return false;
422         Element const & elt = elements_.back();
423         return elt.type == STRING && !elt.final
424                    && elt.font == f && elt.change == ch;
425 }
426
427
428 void Row::finalizeLast()
429 {
430         if (elements_.empty())
431                 return;
432         Element & elt = elements_.back();
433         if (elt.final)
434                 return;
435         elt.final = true;
436         if (elt.change.changed())
437                 changebar_ = true;
438 }
439
440
441 void Row::add(pos_type const pos, Inset const * ins, Dimension const & dim,
442               Font const & f, Change const & ch)
443 {
444         finalizeLast();
445         Element e(INSET, pos, f, ch);
446         e.inset = ins;
447         e.dim = dim;
448         e.row_flags = ins->rowFlags();
449         elements_.push_back(e);
450         dim_.wid += dim.wid;
451         changebar_ |= ins->isChanged();
452 }
453
454
455 void Row::add(pos_type const pos, char_type const c,
456               Font const & f, Change const & ch, bool can_break)
457 {
458         if (!sameString(f, ch)) {
459                 finalizeLast();
460                 Element e(STRING, pos, f, ch);
461                 e.row_flags = can_break ? CanBreakInside : Inline;
462                 elements_.push_back(e);
463         }
464         back().str += c;
465         back().endpos = pos + 1;
466 }
467
468
469 void Row::addVirtual(pos_type const pos, docstring const & s,
470                      Font const & f, Change const & ch)
471 {
472         finalizeLast();
473         Element e(VIRTUAL, pos, f, ch);
474         e.str = s;
475         e.dim.wid = theFontMetrics(f).width(s);
476         dim_.wid += e.dim.wid;
477         e.endpos = pos;
478         // Copy after* flags from previous elements, forbid break before element
479         int const prev_row_flags = elements_.empty() ? Inline : elements_.back().row_flags;
480         int const can_inherit = AfterFlags & ~AlwaysBreakAfter;
481         e.row_flags = (prev_row_flags & can_inherit) | NoBreakBefore;
482         elements_.push_back(e);
483         finalizeLast();
484 }
485
486
487 void Row::addSpace(pos_type const pos, int const width,
488                    Font const & f, Change const & ch)
489 {
490         finalizeLast();
491         Element e(SPACE, pos, f, ch);
492         e.dim.wid = width;
493         elements_.push_back(e);
494         dim_.wid += e.dim.wid;
495 }
496
497
498 void Row::push_back(Row::Element const & e)
499 {
500         dim_.wid += e.dim.wid;
501         elements_.push_back(e);
502 }
503
504
505 void Row::pop_back()
506 {
507         dim_.wid -= elements_.back().dim.wid;
508         elements_.pop_back();
509 }
510
511
512 namespace {
513
514 // Move stuff after \c it from \c from and the end of \c to.
515 void moveElements(Row::Elements & from, Row::Elements::iterator const & it,
516                   Row::Elements & to)
517 {
518         to.insert(to.end(), it, from.end());
519         from.erase(it, from.end());
520         if (!from.empty())
521                 from.back().row_flags = (from.back().row_flags & ~AfterFlags) | BreakAfter;
522 }
523
524 }
525
526
527 Row::Elements Row::shortenIfNeeded(int const w, int const next_width)
528 {
529         // FIXME: performance: if the last element is a string, we would
530         // like to avoid computing its length.
531         finalizeLast();
532         if (empty() || width() <= w)
533                 return Elements();
534
535         Elements::iterator const beg = elements_.begin();
536         Elements::iterator const end = elements_.end();
537         int wid = left_margin;
538
539         // Search for the first element that goes beyond right margin
540         Elements::iterator cit = beg;
541         for ( ; cit != end ; ++cit) {
542                 if (wid + cit->dim.wid > w)
543                         break;
544                 wid += cit->dim.wid;
545         }
546
547         if (cit == end) {
548                 // This should not happen since the row is too long.
549                 LYXERR0("Something is wrong, cannot shorten row: " << *this);
550                 return Elements();
551         }
552
553         // Iterate backwards over breakable elements and try to break them
554         Elements::iterator cit_brk = cit;
555         int wid_brk = wid + cit_brk->dim.wid;
556         ++cit_brk;
557         Elements tail;
558         while (cit_brk != beg) {
559                 --cit_brk;
560                 // make a copy of the element to work on it.
561                 Element brk = *cit_brk;
562                 /* If the current element is an inset that allows breaking row
563                  * after itself, and if the row is already short enough after
564                  * this inset, then cut right after this element.
565                  */
566                 if (wid_brk <= w && brk.row_flags & CanBreakAfter) {
567                         end_ = brk.endpos;
568                         dim_.wid = wid_brk;
569                         moveElements(elements_, cit_brk + 1, tail);
570                         return tail;
571                 }
572                 // assume now that the current element is not there
573                 wid_brk -= brk.dim.wid;
574                 /* We have found a suitable separable element. This is the common case.
575                  * Try to break it cleanly at a length that is both
576                  * - less than the available space on the row
577                  * - shorter than the natural width of the element, in order to enforce
578                  *   break-up.
579                  */
580                 if (brk.splitAt(min(w - wid_brk, brk.dim.wid - 2), next_width, false, tail)) {
581                         /* if this element originally did not cause a row overflow
582                          * in itself, and the remainder of the row would still be
583                          * too large after breaking, then we will have issues in
584                          * next row. Thus breaking does not help.
585                          */
586                         if (wid_brk + cit_brk->dim.wid < w
587                             && dim_.wid - (wid_brk + brk.dim.wid) >= next_width) {
588                                 break;
589                         }
590                         end_ = brk.endpos;
591                         *cit_brk = brk;
592                         dim_.wid = wid_brk + brk.dim.wid;
593                         // If there are other elements, they should be removed.
594                         moveElements(elements_, cit_brk + 1, tail);
595                         return tail;
596                 }
597                 LATTEST(tail.empty());
598         }
599
600         if (cit != beg && cit->row_flags & NoBreakBefore) {
601                 // It is not possible to separate this element from the
602                 // previous one. (e.g. VIRTUAL)
603                 --cit;
604                 wid -= cit->dim.wid;
605         }
606
607         if (cit != beg) {
608                 // There is no usable separator, but several elements have
609                 // been added. We can cut right here.
610                 end_ = cit->pos;
611                 dim_.wid = wid;
612                 moveElements(elements_, cit, tail);
613                 return tail;
614         }
615
616         /* If we are here, it means that we have not found a separator to
617          * shorten the row. Let's try to break it again, but force
618          * splitting this time.
619          */
620         if (cit->splitAt(w - wid, next_width, true, tail)) {
621                 end_ = cit->endpos;
622                 dim_.wid = wid + cit->dim.wid;
623                 // If there are other elements, they should be removed.
624                 moveElements(elements_, cit + 1, tail);
625                 return tail;
626         }
627
628         return Elements();
629 }
630
631
632 void Row::reverseRTL()
633 {
634         pos_type i = 0;
635         pos_type const end = elements_.size();
636         while (i < end) {
637                 // gather a sequence of elements with the same direction
638                 bool const rtl = elements_[i].isRTL();
639                 pos_type j = i;
640                 while (j < end && elements_[j].isRTL() == rtl)
641                         ++j;
642                 // if the direction is not the same as the paragraph
643                 // direction, the sequence has to be reverted.
644                 if (rtl != rtl_)
645                         reverse(elements_.begin() + i, elements_.begin() + j);
646                 i = j;
647         }
648         // If the paragraph itself is RTL, reverse everything
649         if (rtl_)
650                 reverse(elements_.begin(), elements_.end());
651 }
652
653 Row::const_iterator const
654 Row::findElement(pos_type const pos, bool const boundary, double & x) const
655 {
656         /**
657          * When boundary is true, position i is in the row element (pos, endpos)
658          * if
659          *    pos < i <= endpos
660          * whereas, when boundary is false, the test is
661          *    pos <= i < endpos
662          * The correction below allows to handle both cases.
663         */
664         int const boundary_corr = (boundary && pos) ? -1 : 0;
665
666         x = left_margin;
667
668         /** Early return in trivial cases
669          * 1) the row is empty
670          * 2) the position is the left-most position of the row; there
671          * is a quirk here however: if the first element is virtual
672          * (end-of-par marker for example), then we have to look
673          * closer
674          */
675         if (empty()
676             || (pos == begin()->left_pos() && !boundary
677                         && !begin()->isVirtual()))
678                 return begin();
679
680         const_iterator cit = begin();
681         for ( ; cit != end() ; ++cit) {
682                 /** Look whether the cursor is inside the element's span. Note
683                  * that it is necessary to take the boundary into account, and
684                  * to accept virtual elements, in which case the position
685                  * will be before the virtual element.
686                  */
687                 if (cit->isVirtual() && pos + boundary_corr == cit->pos)
688                         break;
689                 else if (pos + boundary_corr >= cit->pos
690                          && pos + boundary_corr < cit->endpos) {
691                         x += cit->pos2x(pos);
692                         break;
693                 }
694                 x += cit->full_width();
695         }
696
697         if (cit == end())
698                 --cit;
699
700         return cit;
701 }
702
703
704 } // namespace lyx