]> git.lyx.org Git - lyx.git/blob - src/Paragraph.cpp
Fix bookmarks-goto inside insets.
[lyx.git] / src / Paragraph.cpp
1 /**
2  * \file Paragraph.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 Richard Kimberly Heck (XHTML output)
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Dekel Tsur
14  * \author Jürgen Vigna
15  *
16  * Full author contact details are available in file CREDITS.
17  */
18
19 #include <config.h>
20
21 #include "Paragraph.h"
22
23 #include "Buffer.h"
24 #include "BufferParams.h"
25 #include "BufferEncodings.h"
26 #include "Changes.h"
27 #include "Counters.h"
28 #include "InsetList.h"
29 #include "Language.h"
30 #include "LaTeXFeatures.h"
31 #include "Layout.h"
32 #include "Font.h"
33 #include "FontList.h"
34 #include "LyXRC.h"
35 #include "OutputParams.h"
36 #include "output_latex.h"
37 #include "output_xhtml.h"
38 #include "output_docbook.h"
39 #include "ParagraphParameters.h"
40 #include "Session.h"
41 #include "SpellChecker.h"
42 #include "texstream.h"
43 #include "TexRow.h"
44 #include "Text.h"
45 #include "TextClass.h"
46 #include "WordLangTuple.h"
47 #include "WordList.h"
48
49 #include "frontends/alert.h"
50
51 #include "insets/InsetArgument.h"
52 #include "insets/InsetBibitem.h"
53 #include "insets/InsetLabel.h"
54 #include "insets/InsetSpecialChar.h"
55 #include "insets/InsetText.h"
56
57 #include "mathed/InsetMathHull.h"
58
59 #include "support/debug.h"
60 #include "support/docstring_list.h"
61 #include "support/ExceptionMessage.h"
62 #include "support/gettext.h"
63 #include "support/lassert.h"
64 #include "support/lstrings.h"
65 #include "support/textutils.h"
66
67 #include <atomic>
68 #include <sstream>
69 #include <vector>
70
71 using namespace std;
72 using namespace lyx::support;
73
74 // OSX clang, gcc < 4.8.0, and msvc < 2015 do not support C++11 thread_local
75 #if defined(__APPLE__) || (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ < 8)
76 #define THREAD_LOCAL_STATIC static __thread
77 #elif defined(_MSC_VER) && (_MSC_VER < 1900)
78 #define THREAD_LOCAL_STATIC static __declspec(thread)
79 #else
80 #define THREAD_LOCAL_STATIC thread_local static
81 #endif
82
83 namespace lyx {
84
85 /////////////////////////////////////////////////////////////////////
86 //
87 // SpellResultRange
88 //
89 /////////////////////////////////////////////////////////////////////
90
91 class SpellResultRange {
92 public:
93         SpellResultRange(FontSpan range, SpellChecker::Result result)
94         : range_(range), result_(result)
95         {}
96         ///
97         FontSpan const & range() const { return range_; }
98         ///
99         void range(FontSpan const & r) { range_ = r; }
100         ///
101         SpellChecker::Result result() const { return result_; }
102         ///
103         void result(SpellChecker::Result r) { result_ = r; }
104         ///
105         bool contains(pos_type pos) const { return range_.contains(pos); }
106         ///
107         bool covered(FontSpan const & r) const
108         {
109                 // 1. first of new range inside current range or
110                 // 2. last of new range inside current range or
111                 // 3. first of current range inside new range or
112                 // 4. last of current range inside new range
113                 //FIXME: is this the same as !range_.intersect(r).empty() ?
114                 return range_.contains(r.first) || range_.contains(r.last) ||
115                         r.contains(range_.first) || r.contains(range_.last);
116         }
117         ///
118         void shift(pos_type pos, int offset)
119         {
120                 if (range_.first > pos) {
121                         range_.first += offset;
122                         range_.last += offset;
123                 } else if (range_.last >= pos) {
124                         range_.last += offset;
125                 }
126         }
127 private:
128         FontSpan range_ ;
129         SpellChecker::Result result_ ;
130 };
131
132
133 /////////////////////////////////////////////////////////////////////
134 //
135 // SpellCheckerState
136 //
137 /////////////////////////////////////////////////////////////////////
138
139 class SpellCheckerState {
140 public:
141         SpellCheckerState()
142         {
143                 needs_refresh_ = true;
144                 current_change_number_ = 0;
145         }
146
147         void setRange(FontSpan const & fp, SpellChecker::Result state)
148         {
149                 Ranges result;
150                 RangesIterator et = ranges_.end();
151                 RangesIterator it = ranges_.begin();
152                 for (; it != et; ++it) {
153                         if (!it->covered(fp))
154                                 result.push_back(SpellResultRange(it->range(), it->result()));
155                         else if (state == SpellChecker::WORD_OK) {
156                                 // trim or split the current misspelled range
157                                 // store misspelled ranges only
158                                 FontSpan range = it->range();
159                                 if (fp.first > range.first) {
160                                         // misspelled area in front of WORD_OK
161                                         range.last = fp.first - 1;
162                                         result.push_back(SpellResultRange(range, it->result()));
163                                         range = it->range();
164                                 }
165                                 if (fp.last < range.last) {
166                                         // misspelled area after WORD_OK range
167                                         range.first = fp.last + 1;
168                                         result.push_back(SpellResultRange(range, it->result()));
169                                 }
170                         }
171                 }
172                 ranges_ = result;
173                 if (state != SpellChecker::WORD_OK)
174                         ranges_.push_back(SpellResultRange(fp, state));
175         }
176
177         void increasePosAfterPos(pos_type pos)
178         {
179                 correctRangesAfterPos(pos, 1);
180                 needsRefresh(pos);
181         }
182
183         void decreasePosAfterPos(pos_type pos)
184         {
185                 correctRangesAfterPos(pos, -1);
186                 needsRefresh(pos);
187         }
188
189         void refreshLast(pos_type pos)
190         {
191                 if (pos < refresh_.last)
192                         refresh_.last = pos;
193         }
194
195         SpellChecker::Result getState(pos_type pos) const
196         {
197                 SpellChecker::Result result = SpellChecker::WORD_OK;
198                 RangesIterator et = ranges_.end();
199                 RangesIterator it = ranges_.begin();
200                 for (; it != et; ++it) {
201                         if(it->contains(pos)) {
202                                 return it->result();
203                         }
204                 }
205                 return result;
206         }
207
208         FontSpan const & getRange(pos_type pos) const
209         {
210                 /// empty span to indicate mismatch
211                 static FontSpan empty_;
212                 RangesIterator et = ranges_.end();
213                 RangesIterator it = ranges_.begin();
214                 for (; it != et; ++it) {
215                         if(it->contains(pos)) {
216                                 return it->range();
217                         }
218                 }
219                 return empty_;
220         }
221
222         bool needsRefresh() const
223         {
224                 return needs_refresh_;
225         }
226
227         SpellChecker::ChangeNumber currentChangeNumber() const
228         {
229                 return current_change_number_;
230         }
231
232         void refreshRange(pos_type & first, pos_type & last) const
233         {
234                 first = refresh_.first;
235                 last = refresh_.last;
236         }
237
238         void needsRefresh(pos_type pos)
239         {
240                 if (needs_refresh_ && pos != -1) {
241                         if (pos < refresh_.first)
242                                 refresh_.first = pos;
243                         if (pos > refresh_.last)
244                                 refresh_.last = pos;
245                 } else if (pos != -1) {
246                         // init request check for neighbour positions too
247                         refresh_.first = pos > 0 ? pos - 1 : 0;
248                         // no need for special end of paragraph check
249                         refresh_.last = pos + 1;
250                 }
251                 needs_refresh_ = pos != -1;
252         }
253
254         void needsCompleteRefresh(SpellChecker::ChangeNumber change_number)
255         {
256                 needs_refresh_ = true;
257                 refresh_.first = 0;
258                 refresh_.last = -1;
259                 current_change_number_ = change_number;
260         }
261 private:
262         typedef vector<SpellResultRange> Ranges;
263         typedef Ranges::const_iterator RangesIterator;
264         Ranges ranges_;
265         /// the area of the paragraph with pending spell check
266         FontSpan refresh_;
267         /// spell state cache version number
268         SpellChecker::ChangeNumber current_change_number_;
269         bool needs_refresh_;
270
271
272         void correctRangesAfterPos(pos_type pos, int offset)
273         {
274                 RangesIterator et = ranges_.end();
275                 Ranges::iterator it = ranges_.begin();
276                 for (; it != et; ++it) {
277                         it->shift(pos, offset);
278                 }
279         }
280
281 };
282
283 /////////////////////////////////////////////////////////////////////
284 //
285 // Paragraph::Private
286 //
287 /////////////////////////////////////////////////////////////////////
288
289 class Paragraph::Private
290 {
291         // Enforce our own "copy" constructor
292         Private(Private const &) = delete;
293         Private & operator=(Private const &) = delete;
294         // Unique ID generator
295         static int make_id();
296 public:
297         ///
298         Private(Paragraph * owner, Layout const & layout);
299         /// "Copy constructor"
300         Private(Private const &, Paragraph * owner);
301         /// Copy constructor from \p beg  to \p end
302         Private(Private const &, Paragraph * owner, pos_type beg, pos_type end);
303
304         ///
305         void insertChar(pos_type pos, char_type c, Change const & change);
306
307         /// Output the surrogate pair formed by \p c and \p next to \p os.
308         /// \return the number of characters written.
309         int latexSurrogatePair(BufferParams const &, otexstream & os,
310                                char_type c, char_type next,
311                                OutputParams const &) const;
312
313         /// Output a space in appropriate formatting (or a surrogate pair
314         /// if the next character is a combining character).
315         /// \return whether a surrogate pair was output.
316         bool simpleTeXBlanks(BufferParams const &,
317                              OutputParams const &,
318                              otexstream &,
319                              pos_type i,
320                              unsigned int & column,
321                              Font const & font,
322                              Layout const & style) const;
323
324         /// This could go to ParagraphParameters if we want to.
325         int startTeXParParams(BufferParams const &, otexstream &,
326                               OutputParams const &) const;
327
328         /// This could go to ParagraphParameters if we want to.
329         bool endTeXParParams(BufferParams const &, otexstream &,
330                              OutputParams const &) const;
331
332         ///
333         void latexInset(BufferParams const &,
334                                    otexstream &,
335                                    OutputParams &,
336                                    Font & running_font,
337                                    Font & basefont,
338                                    Font const & outerfont,
339                                    bool & open_font,
340                                    Change & running_change,
341                                    Layout const & style,
342                                    pos_type & i,
343                                    unsigned int & column,
344                                    bool const fontswitch_inset,
345                                    bool const closeLanguage,
346                                    bool const lang_switched_at_inset) const;
347
348         ///
349         void latexSpecialChar(
350                                    otexstream & os,
351                                    BufferParams const & bparams,
352                                    OutputParams const & runparams,
353                                    Font const & running_font,
354                                    string & alien_script,
355                                    Layout const & style,
356                                    pos_type & i,
357                                    pos_type end_pos,
358                                    unsigned int & column) const;
359
360         ///
361         bool latexSpecialT1(
362                 char_type const c,
363                 otexstream & os,
364                 pos_type i,
365                 unsigned int & column) const;
366         ///
367         bool latexSpecialTU(
368                 char_type const c,
369                 otexstream & os,
370                 pos_type i,
371                 unsigned int & column) const;
372         ///
373         bool latexSpecialT3(
374                 char_type const c,
375                 otexstream & os,
376                 pos_type i,
377                 unsigned int & column) const;
378
379         ///
380         void validate(LaTeXFeatures & features) const;
381
382         /// Checks if the paragraph contains only text and no inset or font change.
383         bool onlyText(Buffer const & buf, Font const & outerfont,
384                       pos_type initial) const;
385
386         /// a vector of speller skip positions
387         typedef vector<FontSpan> SkipPositions;
388         typedef SkipPositions::const_iterator SkipPositionsIterator;
389
390         void appendSkipPosition(SkipPositions & skips, pos_type const pos) const;
391
392         Language * getSpellLanguage(pos_type const from) const;
393
394         Language * locateSpellRange(pos_type & from, pos_type & to,
395                                     SkipPositions & skips) const;
396
397         bool hasSpellerChange() const
398         {
399                 SpellChecker::ChangeNumber speller_change_number = 0;
400                 if (theSpellChecker())
401                         speller_change_number = theSpellChecker()->changeNumber();
402                 return speller_change_number > speller_state_.currentChangeNumber();
403         }
404
405         bool ignoreWord(docstring const & word) const;
406
407         void setMisspelled(pos_type from, pos_type to, SpellChecker::Result state)
408         {
409                 pos_type textsize = owner_->size();
410                 // check for sane arguments
411                 if (to <= from || from >= textsize)
412                         return;
413                 FontSpan fp = FontSpan(from, to - 1);
414                 speller_state_.setRange(fp, state);
415         }
416
417         void requestSpellCheck(pos_type pos)
418         {
419                 if (pos == -1)
420                         speller_state_.needsCompleteRefresh(speller_state_.currentChangeNumber());
421                 else
422                         speller_state_.needsRefresh(pos);
423         }
424
425         void readySpellCheck()
426         {
427                 speller_state_.needsRefresh(-1);
428         }
429
430         bool needsSpellCheck() const
431         {
432                 return speller_state_.needsRefresh();
433         }
434
435         void rangeOfSpellCheck(pos_type & first, pos_type & last) const
436         {
437                 speller_state_.refreshRange(first, last);
438                 if (last == -1) {
439                         last = owner_->size();
440                         return;
441                 }
442                 pos_type endpos = last;
443                 owner_->locateWord(first, endpos, WHOLE_WORD, true);
444                 if (endpos < last) {
445                         endpos = last;
446                         owner_->locateWord(last, endpos, WHOLE_WORD, true);
447                 }
448                 last = endpos;
449         }
450
451         int countSkips(SkipPositionsIterator & it, SkipPositionsIterator const et,
452                             int & start) const
453         {
454                 int numskips = 0;
455                 while (it != et && it->first < start) {
456                         long skip = it->last - it->first + 1;
457                         start += skip;
458                         numskips += skip;
459                         ++it;
460                 }
461                 return numskips;
462         }
463
464         void markMisspelledWords(Language const * lang,
465                                  pos_type const & first, pos_type const & last,
466                                  SpellChecker::Result result,
467                                  docstring const & word,
468                                  SkipPositions const & skips);
469
470         InsetCode ownerCode() const
471         {
472                 return inset_owner_ ? inset_owner_->lyxCode() : NO_CODE;
473         }
474
475         /// Which Paragraph owns us?
476         Paragraph * owner_;
477
478         /// In which Inset?
479         Inset const * inset_owner_;
480
481         ///
482         FontList fontlist_;
483
484         ///
485         ParagraphParameters params_;
486
487         /// for recording and looking up changes
488         Changes changes_;
489
490         ///
491         InsetList insetlist_;
492
493         /// end of label
494         pos_type begin_of_body_;
495
496         typedef docstring TextContainer;
497         ///
498         TextContainer text_;
499
500         typedef set<docstring> Words;
501         typedef map<string, Words> LangWordsMap;
502         ///
503         LangWordsMap words_;
504         ///
505         Layout const * layout_;
506         ///
507         SpellCheckerState speller_state_;
508         ///
509         int id_;
510 };
511
512
513 Paragraph::Private::Private(Paragraph * owner, Layout const & layout)
514         : owner_(owner), inset_owner_(nullptr), begin_of_body_(0), layout_(&layout), id_(-1)
515 {
516         text_.reserve(100);
517 }
518
519
520 //static
521 int Paragraph::Private::make_id()
522 {
523         // The id is unique per session across buffers because it is used in
524         // LFUN_PARAGRAPH_GOTO to switch to a different buffer, for instance in the
525         // outliner.
526         // (thread-safe)
527         static int next_id(0);
528         return next_id++;
529 }
530
531
532 Paragraph::Private::Private(Private const & p, Paragraph * owner)
533         : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_),
534           params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_),
535           begin_of_body_(p.begin_of_body_), text_(p.text_), words_(p.words_),
536           layout_(p.layout_), id_(make_id())
537 {
538         requestSpellCheck(p.text_.size());
539 }
540
541
542 Paragraph::Private::Private(Private const & p, Paragraph * owner,
543         pos_type beg, pos_type end)
544         : owner_(owner), inset_owner_(p.inset_owner_),
545           params_(p.params_), changes_(p.changes_),
546           insetlist_(p.insetlist_, beg, end),
547           begin_of_body_(p.begin_of_body_), words_(p.words_),
548           layout_(p.layout_), id_(make_id())
549 {
550         if (beg >= pos_type(p.text_.size()))
551                 return;
552         text_ = p.text_.substr(beg, end - beg);
553
554         FontList::const_iterator fcit = fontlist_.begin();
555         FontList::const_iterator fend = fontlist_.end();
556         for (; fcit != fend; ++fcit) {
557                 if (fcit->pos() < beg)
558                         continue;
559                 if (fcit->pos() >= end) {
560                         // Add last entry in the fontlist_.
561                         fontlist_.set(text_.size() - 1, fcit->font());
562                         break;
563                 }
564                 // Add a new entry in the fontlist_.
565                 fontlist_.set(fcit->pos() - beg, fcit->font());
566         }
567         requestSpellCheck(p.text_.size());
568 }
569
570
571 /////////////////////////////////////////////////////////////////////
572 //
573 // Paragraph
574 //
575 /////////////////////////////////////////////////////////////////////
576
577 namespace {
578
579 /** This helper class should be instantiated at the start of methods
580  * that can create or merge changes. If as a result the value of
581  * Paragraph::isChanged is modified, it makes sure that updateBuffer()
582  * will be run.
583  */
584 struct ChangesMonitor {
585         ///
586         ChangesMonitor(Paragraph & par)
587                 : par_(par), was_changed_(par.isChanged()) {}
588         ///
589         ~ChangesMonitor()
590         {
591                 /* We may need to run updateBuffer to check whether the buffer
592                  * contains changes (and toggle the changes toolbar). We do it
593                  * when:
594                  * 1. the `changedness' of the paragraph has changed,
595                  * 2. and we are not in the situation where the buffer has changes
596                  * and new changes are added to the paragraph.
597                  */
598                 try {
599                         if (par_.isChanged() != was_changed_
600                                 && par_.inInset().isBufferValid()
601                                 && !(par_.inInset().buffer().areChangesPresent() && par_.isChanged()))
602                                 par_.inInset().buffer().forceUpdate();
603                 } catch(support::ExceptionMessage const &) {}
604         }
605
606 private:
607         ///
608         Paragraph const & par_;
609         ///
610         bool was_changed_;
611 };
612
613 }
614
615 void Paragraph::addChangesToToc(DocIterator const & cdit, Buffer const & buf,
616                                 bool output_active, TocBackend & backend) const
617 {
618         d->changes_.addToToc(cdit, buf, output_active, backend);
619 }
620
621
622 bool Paragraph::isDeleted(pos_type start, pos_type end) const
623 {
624         LASSERT(start >= 0 && start <= size(), return false);
625         LASSERT(end > start && end <= size() + 1, return false);
626
627         return d->changes_.isDeleted(start, end);
628 }
629
630
631 bool Paragraph::isChanged(pos_type start, pos_type end) const
632 {
633         LASSERT(start >= 0 && start <= size(), return false);
634         LASSERT(end > start && end <= size() + 1, return false);
635
636         return d->changes_.isChanged(start, end);
637 }
638
639 // FIXME: Ideally the diverse isChanged() methods should account for that!
640 bool Paragraph::hasChangedInsets(pos_type start, pos_type end) const
641 {
642         LASSERT(start >= 0 && start <= size(), return false);
643         LASSERT(end > start && end <= size() + 1, return false);
644
645         for (auto const & icit : d->insetlist_) {
646                 if (icit.pos < start)
647                         continue;
648                 if (icit.pos >= end)
649                         break;
650                 if (icit.inset && icit.inset->isChanged())
651                         return true;
652         }
653         return false;
654 }
655
656 bool Paragraph::isChanged() const
657 {
658         return d->changes_.isChanged();
659 }
660
661
662 bool Paragraph::isMergedOnEndOfParDeletion(bool trackChanges) const
663 {
664         // keep the logic here in sync with the logic of eraseChars()
665         if (!trackChanges)
666                 return true;
667
668         Change const & change = d->changes_.lookup(size());
669         return change.inserted() && change.currentAuthor();
670 }
671
672 Change Paragraph::parEndChange() const
673 {
674         return d->changes_.lookup(size());
675 }
676
677
678 void Paragraph::setChange(Change const & change)
679 {
680         // Make sure that Buffer::hasChangesPresent is updated
681         ChangesMonitor cm(*this);
682
683         // beware of the imaginary end-of-par character!
684         d->changes_.set(change, 0, size() + 1);
685
686         /*
687          * Propagate the change recursively - but not in case of DELETED!
688          *
689          * Imagine that your co-author makes changes in an existing inset. He
690          * sends your document to you and you come to the conclusion that the
691          * inset should go completely. If you erase it, LyX must not delete all
692          * text within the inset. Otherwise, the change tracked insertions of
693          * your co-author get lost and there is no way to restore them later.
694          *
695          * Conclusion: An inset's content should remain untouched if you delete it
696          */
697
698         if (!change.deleted()) {
699                 for (pos_type pos = 0; pos < size(); ++pos) {
700                         if (Inset * inset = getInset(pos))
701                                 inset->setChange(change);
702                 }
703         }
704 }
705
706
707 void Paragraph::setChange(pos_type pos, Change const & change)
708 {
709         // Make sure that Buffer::hasChangesPresent is updated
710         ChangesMonitor cm(*this);
711
712         LASSERT(pos >= 0 && pos <= size(), return);
713         d->changes_.set(change, pos);
714
715         // see comment in setChange(Change const &) above
716         if (!change.deleted() && pos < size())
717                 if (Inset * inset = getInset(pos))
718                         inset->setChange(change);
719 }
720
721
722 Change const & Paragraph::lookupChange(pos_type pos) const
723 {
724         LBUFERR(pos >= 0 && pos <= size());
725         return d->changes_.lookup(pos);
726 }
727
728
729 void Paragraph::acceptChanges(pos_type start, pos_type end)
730 {
731         // Make sure that Buffer::hasChangesPresent is updated
732         ChangesMonitor cm(*this);
733
734         LASSERT(start >= 0 && start <= size(), return);
735         LASSERT(end > start && end <= size() + 1, return);
736
737         for (pos_type pos = start; pos < end; ++pos) {
738                 switch (lookupChange(pos).type) {
739                         case Change::UNCHANGED:
740                                 // accept changes in nested inset
741                                 if (Inset * inset = getInset(pos))
742                                         inset->acceptChanges();
743                                 break;
744
745                         case Change::INSERTED:
746                                 d->changes_.set(Change(Change::UNCHANGED), pos);
747                                 // also accept changes in nested inset
748                                 if (Inset * inset = getInset(pos))
749                                         inset->acceptChanges();
750                                 break;
751
752                         case Change::DELETED:
753                                 // Suppress access to non-existent
754                                 // "end-of-paragraph char"
755                                 if (pos < size()) {
756                                         eraseChar(pos, false);
757                                         --end;
758                                         --pos;
759                                 }
760                                 break;
761                 }
762
763         }
764 }
765
766
767 void Paragraph::rejectChanges(pos_type start, pos_type end)
768 {
769         LASSERT(start >= 0 && start <= size(), return);
770         LASSERT(end > start && end <= size() + 1, return);
771
772         // Make sure that Buffer::hasChangesPresent is updated
773         ChangesMonitor cm(*this);
774
775         for (pos_type pos = start; pos < end; ++pos) {
776                 switch (lookupChange(pos).type) {
777                         case Change::UNCHANGED:
778                                 // reject changes in nested inset
779                                 if (Inset * inset = getInset(pos))
780                                                 inset->rejectChanges();
781                                 break;
782
783                         case Change::INSERTED:
784                                 // Suppress access to non-existent
785                                 // "end-of-paragraph char"
786                                 if (pos < size()) {
787                                         eraseChar(pos, false);
788                                         --end;
789                                         --pos;
790                                 }
791                                 break;
792
793                         case Change::DELETED:
794                                 d->changes_.set(Change(Change::UNCHANGED), pos);
795
796                                 // Do NOT reject changes within a deleted inset!
797                                 // There may be insertions of a co-author inside of it!
798
799                                 break;
800                 }
801         }
802 }
803
804
805 void Paragraph::Private::insertChar(pos_type pos, char_type c,
806                 Change const & change)
807 {
808         LASSERT(pos >= 0 && pos <= int(text_.size()), return);
809
810         // Make sure that Buffer::hasChangesPresent is updated
811         ChangesMonitor cm(*owner_);
812
813         // track change
814         changes_.insert(change, pos);
815
816         // This is actually very common when parsing buffers (and
817         // maybe inserting ascii text)
818         if (pos == pos_type(text_.size())) {
819                 // when appending characters, no need to update tables
820                 text_.push_back(c);
821                 // but we want spell checking
822                 requestSpellCheck(pos);
823                 return;
824         }
825
826         text_.insert(text_.begin() + pos, c);
827
828         // Update the font table.
829         fontlist_.increasePosAfterPos(pos);
830
831         // Update the insets
832         insetlist_.increasePosAfterPos(pos);
833
834         // Update list of misspelled positions
835         speller_state_.increasePosAfterPos(pos);
836
837         // Update bookmarks
838         theSession().bookmarks().adjustPosAfterPos(inset_owner_->buffer().fileName(),
839                                                    id_, pos, 1);
840 }
841
842
843 bool Paragraph::insertInset(pos_type pos, Inset * inset,
844                                    Font const & font, Change const & change)
845 {
846         LASSERT(inset, return false);
847         LASSERT(pos >= 0 && pos <= size(), return false);
848
849         // Make sure that Buffer::hasChangesPresent is updated
850         ChangesMonitor cm(*this);
851
852         // Paragraph::insertInset() can be used in cut/copy/paste operation where
853         // d->inset_owner_ is not set yet.
854         if (d->inset_owner_ && !d->inset_owner_->insetAllowed(inset->lyxCode()))
855                 return false;
856
857         d->insertChar(pos, META_INSET, change);
858         LASSERT(d->text_[pos] == META_INSET, return false);
859
860         // Add a new entry in the insetlist_.
861         d->insetlist_.insert(inset, pos);
862
863         // Some insets require run of spell checker
864         requestSpellCheck(pos);
865         setFont(pos, font);
866         return true;
867 }
868
869
870 bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
871 {
872         LASSERT(pos >= 0 && pos <= size(), return false);
873
874         // Make sure that Buffer::hasChangesPresent is updated
875         ChangesMonitor cm(*this);
876
877         // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
878
879         if (trackChanges) {
880                 Change change = d->changes_.lookup(pos);
881
882                 // set the character to DELETED if
883                 //  a) it was previously unchanged or
884                 //  b) it was inserted by a co-author
885
886                 if (!change.changed() ||
887                       (change.inserted() && !change.currentAuthor())) {
888                         setChange(pos, Change(Change::DELETED));
889                         // request run of spell checker
890                         requestSpellCheck(pos);
891                         return false;
892                 }
893
894                 if (change.deleted())
895                         return false;
896         }
897
898         // Don't physically access the imaginary end-of-paragraph character.
899         // eraseChar() can only mark it as DELETED. A physical deletion of
900         // end-of-par must be handled externally.
901         if (pos == size()) {
902                 return false;
903         }
904
905         // track change
906         d->changes_.erase(pos);
907
908         // if it is an inset, delete the inset entry
909         if (d->text_[pos] == META_INSET)
910                 d->insetlist_.erase(pos);
911
912         d->text_.erase(d->text_.begin() + pos);
913
914         // Update the fontlist_
915         d->fontlist_.erase(pos);
916
917         // Update the insetlist_
918         d->insetlist_.decreasePosAfterPos(pos);
919
920         // Update list of misspelled positions
921         d->speller_state_.decreasePosAfterPos(pos);
922         d->speller_state_.refreshLast(size());
923
924         // Update bookmarks
925         theSession().bookmarks().adjustPosAfterPos(d->inset_owner_->buffer().fileName(),
926                                                    d->id_, pos, -1);
927
928         return true;
929 }
930
931
932 int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges)
933 {
934         LASSERT(start >= 0 && start <= size(), return 0);
935         LASSERT(end >= start && end <= size() + 1, return 0);
936
937         pos_type i = start;
938         for (pos_type count = end - start; count; --count) {
939                 if (!eraseChar(i, trackChanges))
940                         ++i;
941         }
942         return end - i;
943 }
944
945
946 // Handle combining characters
947 int Paragraph::Private::latexSurrogatePair(BufferParams const & bparams,
948                 otexstream & os, char_type c, char_type next,
949                 OutputParams const & runparams) const
950 {
951         // Writing next here may circumvent a possible font change between
952         // c and next. Since next is only output if it forms a surrogate pair
953         // with c we can ignore this:
954         // A font change inside a surrogate pair does not make sense and is
955         // hopefully impossible to input.
956         // FIXME: change tracking
957         // Is this correct WRT change tracking?
958         Encoding const & encoding = *(runparams.encoding);
959         docstring latex1 = encoding.latexChar(next).first;
960         if (runparams.inIPA) {
961                 string const tipashortcut = Encodings::TIPAShortcut(next);
962                 if (!tipashortcut.empty()) {
963                         latex1 = from_ascii(tipashortcut);
964                 }
965         }
966         docstring latex2 = encoding.latexChar(c).first;
967
968         if (bparams.useNonTeXFonts || docstring(1, next) == latex1) {
969                 // Encoding supports the combination:
970                 // output as is (combining char after base char).
971                 os << latex2 << latex1;
972                 return latex1.length() + latex2.length();
973         }
974
975         os << latex1 << "{" << latex2 << "}";
976         return latex1.length() + latex2.length() + 2;
977 }
978
979
980 bool Paragraph::Private::simpleTeXBlanks(BufferParams const & bparams,
981                                        OutputParams const & runparams,
982                                        otexstream & os,
983                                        pos_type i,
984                                        unsigned int & column,
985                                        Font const & font,
986                                        Layout const & style) const
987 {
988         if (style.pass_thru || runparams.pass_thru)
989                 return false;
990
991         if (i + 1 < int(text_.size())) {
992                 char_type next = text_[i + 1];
993                 if (Encodings::isCombiningChar(next)) {
994                         // This space has an accent, so we must always output it.
995                         column += latexSurrogatePair(bparams, os, ' ', next, runparams) - 1;
996                         return true;
997                 }
998         }
999
1000         if (runparams.linelen > 0
1001             && column > runparams.linelen
1002             && i
1003             && text_[i - 1] != ' '
1004             && (i + 1 < int(text_.size()))
1005             // same in FreeSpacing mode
1006             && !owner_->isFreeSpacing()
1007             // In typewriter mode, we want to avoid
1008             // ! . ? : at the end of a line
1009             && !(font.fontInfo().family() == TYPEWRITER_FAMILY
1010                  && (text_[i - 1] == '.'
1011                      || text_[i - 1] == '?'
1012                      || text_[i - 1] == ':'
1013                      || text_[i - 1] == '!'))) {
1014                 os << '\n';
1015                 os.texrow().start(owner_->id(), i + 1);
1016                 column = 0;
1017         } else if (style.free_spacing) {
1018                 os << '~';
1019         } else {
1020                 os << ' ';
1021         }
1022         return false;
1023 }
1024
1025
1026 void Paragraph::Private::latexInset(BufferParams const & bparams,
1027                                     otexstream & os,
1028                                     OutputParams & runparams,
1029                                     Font & running_font,
1030                                     Font & basefont,
1031                                     Font const & outerfont,
1032                                     bool & open_font,
1033                                     Change & running_change,
1034                                     Layout const & style,
1035                                     pos_type & i,
1036                                     unsigned int & column,
1037                                     bool const fontswitch_inset,
1038                                     bool const closeLanguage,
1039                                     bool const lang_switched_at_inset) const
1040 {
1041         Inset * inset = owner_->getInset(i);
1042         LBUFERR(inset);
1043
1044         if (style.pass_thru) {
1045                 odocstringstream ods;
1046                 inset->plaintext(ods, runparams);
1047                 os << ods.str();
1048                 return;
1049         }
1050
1051         // FIXME: move this to InsetNewline::latex
1052         if (inset->lyxCode() == NEWLINE_CODE || inset->lyxCode() == SEPARATOR_CODE) {
1053                 // newlines are handled differently here than
1054                 // the default in simpleTeXSpecialChars().
1055                 if (!style.newline_allowed) {
1056                         os << '\n';
1057                 } else {
1058                         if (open_font) {
1059                                 bool needPar = false;
1060                                 column += running_font.latexWriteEndChanges(
1061                                         os, bparams, runparams,
1062                                         basefont, basefont, needPar);
1063                                 open_font = false;
1064                         }
1065
1066                         if (running_font.fontInfo().family() == TYPEWRITER_FAMILY)
1067                                 os << '~';
1068
1069                         basefont = owner_->getLayoutFont(bparams, outerfont);
1070                         running_font = basefont;
1071
1072                         if (runparams.moving_arg)
1073                                 os << "\\protect ";
1074
1075                 }
1076                 os.texrow().start(owner_->id(), i + 1);
1077                 column = 0;
1078         }
1079
1080         if (owner_->isDeleted(i)) {
1081                 if( ++runparams.inDeletedInset == 1)
1082                         runparams.changeOfDeletedInset = owner_->lookupChange(i);
1083         }
1084
1085         if (inset->canTrackChanges()) {
1086                 column += Changes::latexMarkChange(os, bparams, running_change,
1087                         Change(Change::UNCHANGED), runparams);
1088                 running_change = Change(Change::UNCHANGED);
1089         }
1090
1091         bool close = false;
1092         odocstream::pos_type const len = os.os().tellp();
1093
1094         if (inset->forceLTR(runparams)
1095             && running_font.isRightToLeft()
1096             // ERT is an exception, it should be output with no
1097             // decorations at all
1098             && inset->lyxCode() != ERT_CODE) {
1099                 if (runparams.use_polyglossia) {
1100                         os << "\\LRE{";
1101                 } else if (running_font.language()->lang() == "farsi"
1102                            || running_font.language()->lang() == "arabic_arabi")
1103                         os << "\\textLR{" << termcmd;
1104                 else
1105                         os << "\\L{";
1106                 close = true;
1107         }
1108
1109         if (open_font && fontswitch_inset) {
1110                 bool lang_closed = false;
1111                 // Close language if needed
1112                 if (closeLanguage && !lang_switched_at_inset) {
1113                         // We need prev_font here as language changes directly at inset
1114                         // will only be started inside the inset.
1115                         Font const prev_font = (i > 0) ?
1116                                                 owner_->getFont(bparams, i - 1, outerfont)
1117                                               : running_font;
1118                         Font tmpfont(basefont);
1119                         tmpfont.setLanguage(prev_font.language());
1120                         bool needPar = false;
1121                         unsigned int count = tmpfont.latexWriteEndChanges(os, bparams, runparams,
1122                                                                           basefont, basefont,
1123                                                                           needPar, closeLanguage);
1124                         column += count;
1125                         lang_closed = count > 0;
1126                 }
1127                 // Update the running_font, making sure, however,
1128                 // to leave the language as it was.
1129                 // FIXME: probably a better way to keep track of the old
1130                 // language, than copying the entire font?
1131                 Font const copy_font(running_font);
1132                 basefont = owner_->getLayoutFont(bparams, outerfont);
1133                 running_font = basefont;
1134                 if (!closeLanguage)
1135                         running_font.setLanguage(copy_font.language());
1136                 OutputParams rp = runparams;
1137                 rp.encoding = basefont.language()->encoding();
1138                 // For these, we use switches, so they should be taken as
1139                 // base inside the inset.
1140                 basefont.fontInfo().setSize(copy_font.fontInfo().size());
1141                 basefont.fontInfo().setFamily(copy_font.fontInfo().family());
1142                 basefont.fontInfo().setSeries(copy_font.fontInfo().series());
1143                 // Now re-do font changes in a way needed here
1144                 // (using switches with multi-par insets)
1145                 InsetText const * textinset = inset->asInsetText();
1146                 bool const cprotect = textinset
1147                         ? textinset->hasCProtectContent(runparams.moving_arg)
1148                           && !textinset->text().isMainText()
1149                         : false;
1150                 unsigned int count2 = basefont.latexWriteStartChanges(os, bparams,
1151                                                       rp, running_font,
1152                                                       basefont, true,
1153                                                       cprotect);
1154                 open_font = true;
1155                 column += count2;
1156                 if (count2 == 0 && (lang_closed || lang_switched_at_inset))
1157                         // All fonts closed
1158                         open_font = false;
1159                 if (closeLanguage)
1160                         runparams.local_font = &basefont;
1161         }
1162
1163         if (fontswitch_inset && !closeLanguage) {
1164                 // The directionality has been switched at inset.
1165                 // Force markup inside.
1166                 runparams.local_font = &basefont;
1167         }
1168
1169         size_t const previous_row_count = os.texrow().rows();
1170
1171         try {
1172                 runparams.lastid = id_;
1173                 runparams.lastpos = i;
1174                 inset->latex(os, runparams);
1175         } catch (EncodingException & e) {
1176                 // add location information and throw again.
1177                 e.par_id = id_;
1178                 e.pos = i;
1179                 throw;
1180         }
1181
1182         if (close)
1183                 os << '}';
1184
1185         if (os.texrow().rows() > previous_row_count) {
1186                 os.texrow().start(owner_->id(), i + 1);
1187                 column = 0;
1188         } else {
1189                 column += (unsigned int)(os.os().tellp() - len);
1190         }
1191
1192         if (owner_->isDeleted(i))
1193                 --runparams.inDeletedInset;
1194 }
1195
1196
1197 void Paragraph::Private::latexSpecialChar(otexstream & os,
1198                                           BufferParams const & bparams,
1199                                           OutputParams const & runparams,
1200                                           Font const & running_font,
1201                                           string & alien_script,
1202                                           Layout const & style,
1203                                           pos_type & i,
1204                                           pos_type end_pos,
1205                                           unsigned int & column) const
1206 {
1207         char_type const c = owner_->getUChar(bparams, runparams, i);
1208
1209         if (style.pass_thru || runparams.pass_thru || (runparams.for_searchAdv != OutputParams::NoSearch)
1210             || contains(style.pass_thru_chars, c)
1211             || contains(runparams.pass_thru_chars, c)) {
1212                 if (runparams.for_searchAdv != OutputParams::NoSearch) {
1213                         if (c == '\\')
1214                                 os << "\\\\";
1215                         else if (c == '{')
1216                                 os << "\\braceleft ";
1217                         else if (c == '}')
1218                                 os << "\\braceright ";
1219                         else if (c != '\0')
1220                                 os.put(c);
1221                 }
1222                 else if (c != '\0') {
1223                         Encoding const * const enc = runparams.encoding;
1224                         if (enc && !enc->encodable(c))
1225                                 throw EncodingException(c);
1226                         os.put(c);
1227                 }
1228                 return;
1229         }
1230
1231         // TIPA uses its own T3 encoding
1232         if (runparams.inIPA && latexSpecialT3(c, os, i, column))
1233                 return;
1234         // If T1 font encoding is used, use the special
1235         // characters it provides.
1236         // NOTE: Some languages reset the font encoding internally to a
1237         //       non-standard font encoding. If we are using such a language,
1238         //       we do not output special T1 chars.
1239         if (!runparams.inIPA && !running_font.language()->internalFontEncoding()
1240             && !runparams.isFullUnicode() && bparams.main_font_encoding() == "T1"
1241             && latexSpecialT1(c, os, i, column))
1242                 return;
1243         // NOTE: "fontspec" (non-TeX fonts) sets the font encoding to "TU" (untill 2017 "EU1" or "EU2")
1244         else if (!runparams.inIPA && !running_font.language()->internalFontEncoding()
1245                  && runparams.isFullUnicode() && latexSpecialTU(c, os, i, column))
1246                      return;
1247
1248         // Otherwise, we use what LaTeX provides us.
1249         switch (c) {
1250         case '\\':
1251                 os << "\\textbackslash" << termcmd;
1252                 column += 15;
1253                 break;
1254         case '<':
1255                 os << "\\textless" << termcmd;
1256                 column += 10;
1257                 break;
1258         case '>':
1259                 os << "\\textgreater" << termcmd;
1260                 column += 13;
1261                 break;
1262         case '|':
1263                 os << "\\textbar" << termcmd;
1264                 column += 9;
1265                 break;
1266         case '-':
1267                 os << '-';
1268                 if (i + 1 < static_cast<pos_type>(text_.size()) &&
1269                     (end_pos == -1 || i + 1 < end_pos) &&
1270                     text_[i+1] == '-') {
1271                         // Prevent "--" becoming an en dash and "---" an em dash.
1272                         // (Within \ttfamily, "---" is merged to en dash + hyphen.)
1273                         os << "{}";
1274                         column += 2;
1275                 }
1276                 break;
1277         case '\"':
1278                 os << "\\textquotedbl" << termcmd;
1279                 column += 14;
1280                 break;
1281
1282         case '$': case '&':
1283         case '%': case '#': case '{':
1284         case '}': case '_':
1285                 os << '\\';
1286                 os.put(c);
1287                 column += 1;
1288                 break;
1289
1290         case '~':
1291                 os << "\\textasciitilde" << termcmd;
1292                 column += 16;
1293                 break;
1294
1295         case '^':
1296                 os << "\\textasciicircum" << termcmd;
1297                 column += 17;
1298                 break;
1299
1300         case '*':
1301         case '[':
1302         case ']':
1303                 // avoid being mistaken for optional arguments
1304                 os << '{';
1305                 os.put(c);
1306                 os << '}';
1307                 column += 2;
1308                 break;
1309
1310         case ' ':
1311                 // Blanks are printed before font switching.
1312                 // Sure? I am not! (try nice-latex)
1313                 // I am sure it's correct. LyX might be smarter
1314                 // in the future, but for now, nothing wrong is
1315                 // written. (Asger)
1316                 break;
1317
1318         case 0x2013:
1319         case 0x2014:
1320                 // XeTeX's dash behaviour is determined via a global setting
1321                 if (bparams.use_dash_ligatures
1322                     && owner_->getFontSettings(bparams, i).fontInfo().family() != TYPEWRITER_FAMILY
1323                     && !runparams.inIPA
1324                         // TODO #10961: && not in inset Flex Code
1325                         // TODO #10961: && not in layout LyXCode
1326                     && (!bparams.useNonTeXFonts || runparams.flavor != Flavor::XeTeX)) {
1327                         if (c == 0x2013) {
1328                                 // en-dash
1329                                 os << "--";
1330                                 column +=2;
1331                         } else {
1332                                 // em-dash
1333                                 os << "---";
1334                                 column +=3;
1335                         }
1336                         break;
1337                 }
1338                 // fall through
1339         default:
1340                 if (c == '\0')
1341                         return;
1342
1343                 Encoding const & encoding = *(runparams.encoding);
1344                 char_type next = '\0';
1345                 if (i + 1 < int(text_.size())) {
1346                         next = text_[i + 1];
1347                         if (Encodings::isCombiningChar(next)) {
1348                                 column += latexSurrogatePair(bparams, os, c, next, runparams) - 1;
1349                                 ++i;
1350                                 break;
1351                         }
1352                 }
1353                 pair<docstring, bool> latex = encoding.latexChar(c);
1354                 docstring nextlatex;
1355                 bool nexttipas = false;
1356                 string nexttipashortcut;
1357                 if (next != '\0' && next != META_INSET && !encoding.encodable(next)) {
1358                         nextlatex = encoding.latexChar(next).first;
1359                         if (runparams.inIPA) {
1360                                 nexttipashortcut = Encodings::TIPAShortcut(next);
1361                                 nexttipas = !nexttipashortcut.empty();
1362                         }
1363                 }
1364                 bool tipas = false;
1365                 if (runparams.inIPA) {
1366                         string const tipashortcut = Encodings::TIPAShortcut(c);
1367                         if (!tipashortcut.empty()) {
1368                                 latex.first = from_ascii(tipashortcut);
1369                                 latex.second = false;
1370                                 tipas = true;
1371                         }
1372                 }
1373                 // eventually close "script wrapper" command (see `Paragraph::latex`)
1374                 if (!alien_script.empty()
1375                         && alien_script != Encodings::isKnownScriptChar(next)) {
1376                         column += latex.first.length();
1377                         alien_script.clear();
1378                         os << latex.first << "}";
1379                         break;
1380                 }
1381                 if (latex.second
1382                          && ((!prefixIs(nextlatex, '\\')
1383                                && !prefixIs(nextlatex, '{')
1384                                && !prefixIs(nextlatex, '}'))
1385                              || (nexttipas
1386                                  && !prefixIs(from_ascii(nexttipashortcut), '\\')))
1387                          && !tipas) {
1388                         // Prevent eating of a following space or command corruption by
1389                         // following characters
1390                         if (next == ' ' || next == '\0') {
1391                                 column += latex.first.length() + 1;
1392                                 os << latex.first << "{}";
1393                         } else {
1394                                 column += latex.first.length();
1395                                 os << latex.first << " ";
1396                         }
1397                 } else {
1398                         column += latex.first.length() - 1;
1399                         os << latex.first;
1400                 }
1401                 break;
1402         }
1403 }
1404
1405
1406 bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os,
1407         pos_type i, unsigned int & column) const
1408 {
1409         switch (c) {
1410         case '>':
1411         case '<':
1412                 os.put(c);
1413                 // In T1 encoding, these characters exist
1414                 // but we should avoid ligatures
1415                 if (i + 1 >= int(text_.size()) || text_[i + 1] != c)
1416                         return true;
1417                 os << "\\textcompwordmark" << termcmd;
1418                 column += 19;
1419                 return true;
1420         case '|':
1421                 os.put(c);
1422                 return true;
1423         case '\"':
1424                 // soul.sty breaks with \char`\"
1425                 os << "\\textquotedbl" << termcmd;
1426                 column += 14;
1427                 return true;
1428         default:
1429                 return false;
1430         }
1431 }
1432
1433
1434 bool Paragraph::Private::latexSpecialTU(char_type const c, otexstream & os,
1435         pos_type i, unsigned int & column) const
1436 {
1437         // TU encoding is currently on par with T1.
1438         return latexSpecialT1(c, os, i, column);
1439 }
1440
1441
1442 bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os,
1443         pos_type /*i*/, unsigned int & column) const
1444 {
1445         switch (c) {
1446         case '*':
1447         case '[':
1448         case ']':
1449         case '\"':
1450                 os.put(c);
1451                 return true;
1452         case '|':
1453                 os << "\\textvertline" << termcmd;
1454                 column += 14;
1455                 return true;
1456         default:
1457                 return false;
1458         }
1459 }
1460
1461
1462 void Paragraph::Private::validate(LaTeXFeatures & features) const
1463 {
1464         if (layout_->inpreamble && inset_owner_) {
1465                 // FIXME: Using a string stream here circumvents the encoding
1466                 // switching machinery of odocstream. Therefore the
1467                 // output is wrong if this paragraph contains content
1468                 // that needs to switch encoding.
1469                 Buffer const & buf = inset_owner_->buffer();
1470                 otexstringstream os;
1471                 os << layout_->preamble();
1472                 size_t const length = os.length();
1473                 TeXOnePar(buf, *inset_owner_->getText(int(buf.getParFromID(owner_->id()).idx())),
1474                           buf.getParFromID(owner_->id()).pit(), os,
1475                           features.runparams(), string(), 0, -1, true);
1476                 if (os.length() > length)
1477                         features.addPreambleSnippet(os.release(), true);
1478         }
1479
1480         if (features.runparams().flavor == Flavor::Html
1481             && layout_->htmltitle()) {
1482                 features.setHTMLTitle(owner_->asString(AS_STR_INSETS | AS_STR_SKIPDELETE));
1483         }
1484
1485         // check the params.
1486         if (!params_.spacing().isDefault())
1487                 features.require("setspace");
1488
1489         // then the layouts
1490         features.useLayout(layout_->name());
1491
1492         // then the fonts
1493         fontlist_.validate(features);
1494
1495         // then the indentation
1496         if (!params_.leftIndent().zero())
1497                 features.require("ParagraphLeftIndent");
1498
1499         // then the insets
1500         InsetList::const_iterator icit = insetlist_.begin();
1501         InsetList::const_iterator iend = insetlist_.end();
1502         for (; icit != iend; ++icit) {
1503                 if (icit->inset) {
1504                         features.inDeletedInset(owner_->isDeleted(icit->pos));
1505                         if (icit->inset->lyxCode() == FOOT_CODE) {
1506                                 // FIXME: an item inset would make things much easier.
1507                                 if ((layout_->latextype == LATEX_LIST_ENVIRONMENT
1508                                      || (layout_->latextype == LATEX_ITEM_ENVIRONMENT
1509                                          && layout_->margintype == MARGIN_FIRST_DYNAMIC))
1510                                     && (icit->pos < begin_of_body_
1511                                         || (icit->pos == begin_of_body_
1512                                             && (icit->pos == 0 || text_[icit->pos - 1] != ' '))))
1513                                         features.saveNoteEnv("description");
1514                         }
1515                         icit->inset->validate(features);
1516                         features.inDeletedInset(false);
1517                         if (layout_->needprotect &&
1518                             icit->inset->lyxCode() == FOOT_CODE)
1519                                 features.require("NeedLyXFootnoteCode");
1520                 }
1521         }
1522
1523         // then the contents
1524         BufferParams const bp = features.runparams().is_child
1525                 ? features.buffer().masterParams() : features.buffer().params();
1526         for (pos_type i = 0; i < int(text_.size()) ; ++i) {
1527                 char_type c = text_[i];
1528                 CharInfo const & ci = Encodings::unicodeCharInfo(c);
1529                 if (c == 0x0022) {
1530                         if (features.runparams().isFullUnicode() && bp.useNonTeXFonts)
1531                                 features.require("textquotedblp");
1532                         else if (bp.main_font_encoding() != "T1"
1533                                  || ((&owner_->getFontSettings(bp, i))->language()->internalFontEncoding()))
1534                                 features.require("textquotedbl");
1535                 } else if (ci.textfeature() && contains(ci.textpreamble(), '=')) {
1536                         // features that depend on the font or input encoding
1537                         string feats = ci.textpreamble();
1538                         string fontenc = (&owner_->getFontSettings(bp, i))->language()->fontenc(bp);
1539                         if (fontenc.empty())
1540                                 fontenc = features.runparams().main_fontenc;
1541                         while (!feats.empty()) {
1542                                 string feat;
1543                                 feats = split(feats, feat, ',');
1544                                 if (contains(feat, "!=")) {
1545                                         // a feature that is required except for the spcified
1546                                         // font or input encodings
1547                                         string realfeature;
1548                                         string const contexts = ltrim(split(feat, realfeature, '!'), "=");
1549                                         // multiple encodings are separated by semicolon
1550                                         vector<string> context = getVectorFromString(contexts, ";");
1551                                         // require feature if the context matches neither current font
1552                                         // nor input encoding
1553                                         if (std::find(context.begin(), context.end(), fontenc) == context.end()
1554                                             && std::find(context.begin(), context.end(),
1555                                                          features.runparams().encoding->name()) == context.end())
1556                                                 features.require(realfeature);
1557                                 } else if (contains(feat, '=')) {
1558                                         // a feature that is required only for the spcified
1559                                         // font or input encodings
1560                                         string realfeature;
1561                                         string const contexts = split(feat, realfeature, '=');
1562                                         // multiple encodings are separated by semicolon
1563                                         vector<string> context = getVectorFromString(contexts, ";");
1564                                         // require feature if the context matches either current font
1565                                         // or input encoding
1566                                         if (std::find(context.begin(), context.end(), fontenc) != context.end()
1567                                             || std::find(context.begin(), context.end(),
1568                                                          features.runparams().encoding->name()) != context.end())
1569                                                 features.require(realfeature);
1570                                 }
1571                         }
1572                 } else if (!bp.use_dash_ligatures
1573                            && (c == 0x2013 || c == 0x2014)
1574                            && bp.useNonTeXFonts
1575                            && features.runparams().flavor == Flavor::XeTeX)
1576                         // XeTeX's dash behaviour is determined via a global setting
1577                         features.require("xetexdashbreakstate");
1578                 BufferEncodings::validate(c, features);
1579         }
1580 }
1581
1582 /////////////////////////////////////////////////////////////////////
1583 //
1584 // Paragraph
1585 //
1586 /////////////////////////////////////////////////////////////////////
1587
1588 namespace {
1589         Layout const emptyParagraphLayout;
1590 }
1591
1592 Paragraph::Paragraph()
1593         : d(new Paragraph::Private(this, emptyParagraphLayout))
1594 {
1595         itemdepth = 0;
1596         d->params_.clear();
1597 }
1598
1599
1600 Paragraph::Paragraph(Paragraph const & par)
1601         : itemdepth(par.itemdepth),
1602         d(new Paragraph::Private(*par.d, this))
1603 {
1604         registerWords();
1605 }
1606
1607
1608 Paragraph::Paragraph(Paragraph const & par, pos_type beg, pos_type end)
1609         : itemdepth(par.itemdepth),
1610         d(new Paragraph::Private(*par.d, this, beg, end))
1611 {
1612         registerWords();
1613 }
1614
1615
1616 Paragraph & Paragraph::operator=(Paragraph const & par)
1617 {
1618         // needed as we will destroy the private part before copying it
1619         if (&par != this) {
1620                 itemdepth = par.itemdepth;
1621
1622                 deregisterWords();
1623                 delete d;
1624                 d = new Private(*par.d, this);
1625                 registerWords();
1626         }
1627         return *this;
1628 }
1629
1630
1631 Paragraph::~Paragraph()
1632 {
1633         deregisterWords();
1634         delete d;
1635 }
1636
1637
1638 namespace {
1639
1640 // this shall be called just before every "os << ..." action.
1641 void flushString(ostream & os, docstring & s)
1642 {
1643         os << to_utf8(s);
1644         s.erase();
1645 }
1646
1647 } // namespace
1648
1649
1650 void Paragraph::write(ostream & os, BufferParams const & bparams,
1651         depth_type & depth) const
1652 {
1653         // The beginning or end of a deeper (i.e. nested) area?
1654         if (depth != d->params_.depth()) {
1655                 if (d->params_.depth() > depth) {
1656                         while (d->params_.depth() > depth) {
1657                                 os << "\n\\begin_deeper";
1658                                 ++depth;
1659                         }
1660                 } else {
1661                         while (d->params_.depth() < depth) {
1662                                 os << "\n\\end_deeper";
1663                                 --depth;
1664                         }
1665                 }
1666         }
1667
1668         // First write the layout
1669         os << "\n\\begin_layout " << to_utf8(d->layout_->name()) << '\n';
1670
1671         d->params_.write(os);
1672
1673         Font font1(inherit_font, bparams.language);
1674
1675         Change running_change = Change(Change::UNCHANGED);
1676
1677         // this string is used as a buffer to avoid repetitive calls
1678         // to to_utf8(), which turn out to be expensive (JMarc)
1679         docstring write_buffer;
1680
1681         int column = 0;
1682         for (pos_type i = 0; i <= size(); ++i) {
1683
1684                 Change const & change = lookupChange(i);
1685                 if (change != running_change)
1686                         flushString(os, write_buffer);
1687                 Changes::lyxMarkChange(os, bparams, column, running_change, change);
1688                 running_change = change;
1689
1690                 if (i == size())
1691                         break;
1692
1693                 // Write font changes
1694                 Font font2 = getFontSettings(bparams, i);
1695                 if (font2 != font1) {
1696                         flushString(os, write_buffer);
1697                         font2.lyxWriteChanges(font1, os);
1698                         column = 0;
1699                         font1 = font2;
1700                 }
1701
1702                 char_type const c = d->text_[i];
1703                 switch (c) {
1704                 case META_INSET:
1705                         if (Inset const * inset = getInset(i)) {
1706                                 flushString(os, write_buffer);
1707                                 if (inset->directWrite()) {
1708                                         // international char, let it write
1709                                         // code directly so it's shorter in
1710                                         // the file
1711                                         inset->write(os);
1712                                 } else {
1713                                         if (i)
1714                                                 os << '\n';
1715                                         os << "\\begin_inset ";
1716                                         inset->write(os);
1717                                         os << "\n\\end_inset\n\n";
1718                                         column = 0;
1719                                 }
1720                                 // FIXME This can be removed again once the mystery
1721                                 // crash has been resolved.
1722                                 os << flush;
1723                         }
1724                         break;
1725                 case '\\':
1726                         flushString(os, write_buffer);
1727                         os << "\n\\backslash\n";
1728                         column = 0;
1729                         break;
1730                 case '.':
1731                         flushString(os, write_buffer);
1732                         if (i + 1 < size() && d->text_[i + 1] == ' ') {
1733                                 os << ".\n";
1734                                 column = 0;
1735                         } else
1736                                 os << '.';
1737                         break;
1738                 default:
1739                         if ((column > 70 && c == ' ')
1740                             || column > 79) {
1741                                 flushString(os, write_buffer);
1742                                 os << '\n';
1743                                 column = 0;
1744                         }
1745                         // this check is to amend a bug. LyX sometimes
1746                         // inserts '\0' this could cause problems.
1747                         if (c != '\0')
1748                                 write_buffer.push_back(c);
1749                         else
1750                                 LYXERR0("NUL char in structure.");
1751                         ++column;
1752                         break;
1753                 }
1754         }
1755
1756         flushString(os, write_buffer);
1757         os << "\n\\end_layout\n";
1758         // FIXME This can be removed again once the mystery
1759         // crash has been resolved.
1760         os << flush;
1761 }
1762
1763
1764 void Paragraph::validate(LaTeXFeatures & features) const
1765 {
1766         d->validate(features);
1767         bool fragile = features.runparams().moving_arg;
1768         fragile |= layout().needprotect;
1769         if (inInset().getLayout().isNeedProtect())
1770                 fragile = true;
1771         if (needsCProtection(fragile))
1772                 features.require("cprotect");
1773 }
1774
1775
1776 void Paragraph::insert(pos_type pos, docstring const & str,
1777                        Font const & font, Change const & change)
1778 {
1779         for (size_t i = 0, n = str.size(); i != n ; ++i)
1780                 insertChar(pos + i, str[i], font, change);
1781 }
1782
1783
1784 void Paragraph::appendChar(char_type c, Font const & font,
1785                 Change const & change)
1786 {
1787         // Make sure that Buffer::hasChangesPresent is updated
1788         ChangesMonitor cm(*this);
1789
1790         // track change
1791         d->changes_.insert(change, d->text_.size());
1792         // when appending characters, no need to update tables
1793         d->text_.push_back(c);
1794         setFont(d->text_.size() - 1, font);
1795         d->requestSpellCheck(d->text_.size() - 1);
1796 }
1797
1798
1799 void Paragraph::appendString(docstring const & s, Font const & font,
1800                 Change const & change)
1801 {
1802         // Make sure that Buffer::hasChangesPresent is updated
1803         ChangesMonitor cm(*this);
1804
1805         pos_type end = s.size();
1806         size_t oldsize = d->text_.size();
1807         size_t newsize = oldsize + end;
1808         size_t capacity = d->text_.capacity();
1809         if (newsize >= capacity)
1810                 d->text_.reserve(max(capacity + 100, newsize));
1811
1812         // when appending characters, no need to update tables
1813         d->text_.append(s);
1814
1815         // FIXME: Optimize this!
1816         for (size_t i = oldsize; i != newsize; ++i) {
1817                 // track change
1818                 d->changes_.insert(change, i);
1819                 d->requestSpellCheck(i);
1820         }
1821         d->fontlist_.set(oldsize, font);
1822         d->fontlist_.set(newsize - 1, font);
1823 }
1824
1825
1826 void Paragraph::insertChar(pos_type pos, char_type c,
1827                            bool trackChanges)
1828 {
1829         d->insertChar(pos, c, Change(trackChanges ?
1830                            Change::INSERTED : Change::UNCHANGED));
1831 }
1832
1833
1834 void Paragraph::insertChar(pos_type pos, char_type c,
1835                            Font const & font, bool trackChanges)
1836 {
1837         d->insertChar(pos, c, Change(trackChanges ?
1838                            Change::INSERTED : Change::UNCHANGED));
1839         setFont(pos, font);
1840 }
1841
1842
1843 void Paragraph::insertChar(pos_type pos, char_type c,
1844                            Font const & font, Change const & change)
1845 {
1846         d->insertChar(pos, c, change);
1847         setFont(pos, font);
1848 }
1849
1850
1851 void Paragraph::resetFonts(Font const & font)
1852 {
1853         d->fontlist_.clear();
1854         d->fontlist_.set(0, font);
1855         d->fontlist_.set(d->text_.size() - 1, font);
1856 }
1857
1858 // Gets uninstantiated font setting at position.
1859 Font const & Paragraph::getFontSettings(BufferParams const & bparams,
1860                                          pos_type pos) const
1861 {
1862         if (pos > size()) {
1863                 LYXERR0("pos: " << pos << " size: " << size());
1864                 LBUFERR(false);
1865         }
1866
1867         FontList::const_iterator cit = d->fontlist_.fontIterator(pos);
1868         if (cit != d->fontlist_.end())
1869                 return cit->font();
1870
1871         if (pos == size() && !empty())
1872                 return getFontSettings(bparams, pos - 1);
1873
1874         // Optimisation: avoid a full font instantiation if there is no
1875         // language change from previous call.
1876         static Font previous_font;
1877         static Language const * previous_lang = nullptr;
1878         Language const * lang = getParLanguage(bparams);
1879         if (lang != previous_lang) {
1880                 previous_lang = lang;
1881                 previous_font = Font(inherit_font, lang);
1882         }
1883         return previous_font;
1884 }
1885
1886
1887 FontSpan Paragraph::fontSpan(pos_type pos) const
1888 {
1889         LBUFERR(pos <= size());
1890
1891         if (pos == size())
1892                 return FontSpan(pos, pos);
1893
1894         pos_type start = 0;
1895         FontList::const_iterator cit = d->fontlist_.begin();
1896         FontList::const_iterator end = d->fontlist_.end();
1897         for (; cit != end; ++cit) {
1898                 if (cit->pos() >= pos) {
1899                         if (pos >= beginOfBody())
1900                                 return FontSpan(max(start, beginOfBody()),
1901                                                 cit->pos());
1902                         else
1903                                 return FontSpan(start,
1904                                                 min(beginOfBody() - 1,
1905                                                          cit->pos()));
1906                 }
1907                 start = cit->pos() + 1;
1908         }
1909
1910         // This should not happen, but if so, we take no chances.
1911         LYXERR0("Paragraph::fontSpan: position not found in fontinfo table!");
1912         LASSERT(false, return FontSpan(pos, pos));
1913 }
1914
1915
1916 // Gets uninstantiated font setting at position 0
1917 Font const & Paragraph::getFirstFontSettings(BufferParams const & bparams) const
1918 {
1919         if (!empty() && !d->fontlist_.empty())
1920                 return d->fontlist_.begin()->font();
1921
1922         // Optimisation: avoid a full font instantiation if there is no
1923         // language change from previous call.
1924         static Font previous_font;
1925         static Language const * previous_lang = nullptr;
1926         if (bparams.language != previous_lang) {
1927                 previous_lang = bparams.language;
1928                 previous_font = Font(inherit_font, bparams.language);
1929         }
1930
1931         return previous_font;
1932 }
1933
1934
1935 // Gets the fully instantiated font at a given position in a paragraph
1936 // This is basically the same function as Text::GetFont() in text2.cpp.
1937 // The difference is that this one is used for generating the LaTeX file,
1938 // and thus cosmetic "improvements" are disallowed: This has to deliver
1939 // the true picture of the buffer. (Asger)
1940 Font const Paragraph::getFont(BufferParams const & bparams, pos_type pos,
1941                                  Font const & outerfont) const
1942 {
1943         LBUFERR(pos >= 0);
1944
1945         Font font = getFontSettings(bparams, pos);
1946
1947         pos_type const body_pos = beginOfBody();
1948         FontInfo & fi = font.fontInfo();
1949         if (pos < body_pos)
1950                 fi.realize(d->layout_->labelfont);
1951         else
1952                 fi.realize(d->layout_->font);
1953
1954         fi.realize(outerfont.fontInfo());
1955         fi.realize(bparams.getFont().fontInfo());
1956
1957         return font;
1958 }
1959
1960
1961 Font const Paragraph::getLabelFont
1962         (BufferParams const & bparams, Font const & outerfont) const
1963 {
1964         FontInfo tmpfont = d->layout_->labelfont;
1965         tmpfont.realize(outerfont.fontInfo());
1966         tmpfont.realize(bparams.getFont().fontInfo());
1967         return Font(tmpfont, getParLanguage(bparams));
1968 }
1969
1970
1971 Font const Paragraph::getLayoutFont
1972         (BufferParams const & bparams, Font const & outerfont) const
1973 {
1974         FontInfo tmpfont = d->layout_->font;
1975         tmpfont.realize(outerfont.fontInfo());
1976         tmpfont.realize(bparams.getFont().fontInfo());
1977         return Font(tmpfont, getParLanguage(bparams));
1978 }
1979
1980
1981 char_type Paragraph::getUChar(BufferParams const & bparams,
1982                               OutputParams const & rp,
1983                               pos_type pos) const
1984 {
1985         char_type c = d->text_[pos];
1986
1987         // Return unchanged character in LTR languages
1988         // or if we use poylglossia/bidi (XeTeX).
1989         if (rp.useBidiPackage()
1990             || !getFontSettings(bparams, pos).isRightToLeft())
1991                 return c;
1992
1993         // Without polyglossia/bidi, we need to account for some special cases.
1994         // FIXME This needs to be audited!
1995         // Check if:
1996         // * The input is as expected for all delimiters
1997         //   => checked for Hebrew!
1998         // * The output matches the display in the LyX workarea
1999         //   => checked for Hebrew!
2000         // * The special cases below are really necessary
2001         //   => checked for Hebrew!
2002         // * In arabic_arabi, brackets are transformed to Arabic
2003         //   Ornate Parentheses. Is this is really wanted?
2004
2005         string const & lang = getFontSettings(bparams, pos).language()->lang();
2006         char_type uc = c;
2007
2008         // 1. In the following languages, parentheses need to be reversed.
2009         //    Also with polyglodia/luabidi
2010         bool const reverseparens = (lang == "hebrew" || rp.use_polyglossia);
2011
2012         // 2. In the following languages, brackets don't need to be reversed.
2013         bool const reversebrackets = lang != "arabic_arabtex"
2014                         && lang != "arabic_arabi"
2015                         && lang != "farsi";
2016
2017         // Now swap delimiters if needed.
2018         switch (c) {
2019         case '(':
2020                 if (reverseparens)
2021                         uc = ')';
2022                 break;
2023         case ')':
2024                 if (reverseparens)
2025                         uc = '(';
2026                 break;
2027         case '[':
2028                 if (reversebrackets)
2029                         uc = ']';
2030                 break;
2031         case ']':
2032                 if (reversebrackets)
2033                         uc = '[';
2034                 break;
2035         case '{':
2036                 uc = '}';
2037                 break;
2038         case '}':
2039                 uc = '{';
2040                 break;
2041         case '<':
2042                 uc = '>';
2043                 break;
2044         case '>':
2045                 uc = '<';
2046                 break;
2047         }
2048
2049         return uc;
2050 }
2051
2052
2053 void Paragraph::setFont(pos_type pos, Font const & font)
2054 {
2055         LASSERT(pos <= size(), return);
2056
2057         // First, reduce font against layout/label font
2058         // Update: The setCharFont() routine in text2.cpp already
2059         // reduces font, so we don't need to do that here. (Asger)
2060
2061         d->fontlist_.set(pos, font);
2062 }
2063
2064
2065 void Paragraph::makeSameLayout(Paragraph const & par)
2066 {
2067         d->layout_ = par.d->layout_;
2068         d->params_ = par.d->params_;
2069 }
2070
2071
2072 bool Paragraph::stripLeadingSpaces(bool trackChanges)
2073 {
2074         if (isFreeSpacing())
2075                 return false;
2076
2077         int pos = 0;
2078         int count = 0;
2079
2080         while (pos < size() && (isNewline(pos) || isLineSeparator(pos))) {
2081                 if (eraseChar(pos, trackChanges))
2082                         ++count;
2083                 else
2084                         ++pos;
2085         }
2086
2087         return count > 0 || pos > 0;
2088 }
2089
2090
2091 bool Paragraph::hasSameLayout(Paragraph const & par) const
2092 {
2093         return par.d->layout_ == d->layout_
2094                 && d->params_.sameLayout(par.d->params_);
2095 }
2096
2097
2098 depth_type Paragraph::getDepth() const
2099 {
2100         return d->params_.depth();
2101 }
2102
2103
2104 depth_type Paragraph::getMaxDepthAfter() const
2105 {
2106         if (d->layout_->isEnvironment())
2107                 return d->params_.depth() + 1;
2108         else
2109                 return d->params_.depth();
2110 }
2111
2112
2113 LyXAlignment Paragraph::getAlign(BufferParams const & bparams) const
2114 {
2115         if (d->params_.align() == LYX_ALIGN_LAYOUT)
2116                 return getDefaultAlign(bparams);
2117         else
2118                 return d->params_.align();
2119 }
2120
2121
2122 LyXAlignment Paragraph::getDefaultAlign(BufferParams const & bparams) const
2123 {
2124         LyXAlignment res = layout().align;
2125         if (isRTL(bparams)) {
2126                 // Swap sides
2127                 if (res == LYX_ALIGN_LEFT)
2128                         res = LYX_ALIGN_RIGHT;
2129                 else if  (res == LYX_ALIGN_RIGHT)
2130                         res = LYX_ALIGN_LEFT;
2131         }
2132         return res;
2133 }
2134
2135
2136 docstring const & Paragraph::labelString() const
2137 {
2138         return d->params_.labelString();
2139 }
2140
2141
2142 // the next two functions are for the manual labels
2143 docstring const Paragraph::getLabelWidthString() const
2144 {
2145         if (d->layout_->margintype == MARGIN_MANUAL
2146             || d->layout_->latextype == LATEX_BIB_ENVIRONMENT)
2147                 return d->params_.labelWidthString();
2148         else
2149                 return _("Senseless with this layout!");
2150 }
2151
2152
2153 void Paragraph::setLabelWidthString(docstring const & s)
2154 {
2155         d->params_.labelWidthString(s);
2156 }
2157
2158
2159 docstring Paragraph::expandLabel(Layout const & layout,
2160                 BufferParams const & bparams) const
2161 {
2162         return expandParagraphLabel(layout, bparams, true);
2163 }
2164
2165
2166 docstring Paragraph::expandParagraphLabel(Layout const & layout,
2167                 BufferParams const & bparams, bool process_appendix) const
2168 {
2169         DocumentClass const & tclass = bparams.documentClass();
2170         string const & lang = getParLanguage(bparams)->code();
2171         bool const in_appendix = process_appendix && d->params_.appendix();
2172         docstring fmt = translateIfPossible(layout.labelstring(in_appendix), lang);
2173
2174         if (fmt.empty() && !layout.counter.empty())
2175                 return tclass.counters().theCounter(layout.counter, lang);
2176
2177         // handle 'inherited level parts' in 'fmt',
2178         // i.e. the stuff between '@' in   '@Section@.\arabic{subsection}'
2179         size_t const i = fmt.find('@', 0);
2180         if (i != docstring::npos) {
2181                 size_t const j = fmt.find('@', i + 1);
2182                 if (j != docstring::npos) {
2183                         docstring parent(fmt, i + 1, j - i - 1);
2184                         docstring label = from_ascii("??");
2185                         if (tclass.hasLayout(parent))
2186                                 label = expandParagraphLabel(tclass[parent], bparams,
2187                                                       process_appendix);
2188                         fmt = docstring(fmt, 0, i) + label
2189                                 + docstring(fmt, j + 1, docstring::npos);
2190                 }
2191         }
2192
2193         return tclass.counters().counterLabel(fmt, lang);
2194 }
2195
2196
2197 void Paragraph::applyLayout(Layout const & new_layout)
2198 {
2199         d->layout_ = &new_layout;
2200         LyXAlignment const oldAlign = d->params_.align();
2201
2202         if (!(oldAlign & d->layout_->alignpossible)) {
2203                 frontend::Alert::warning(_("Alignment not permitted"),
2204                         _("The new layout does not permit the alignment previously used.\nSetting to default."));
2205                 d->params_.align(LYX_ALIGN_LAYOUT);
2206         }
2207 }
2208
2209
2210 pos_type Paragraph::beginOfBody() const
2211 {
2212         return d->begin_of_body_;
2213 }
2214
2215
2216 void Paragraph::setBeginOfBody()
2217 {
2218         if (d->layout_->labeltype != LABEL_MANUAL) {
2219                 d->begin_of_body_ = 0;
2220                 return;
2221         }
2222
2223         // Unroll the first two cycles of the loop
2224         // and remember the previous character to
2225         // remove unnecessary getChar() calls
2226         pos_type i = 0;
2227         pos_type end = size();
2228         bool prev_char_deleted = false;
2229         if (i < end && (!(isNewline(i) || isEnvSeparator(i)) || isDeleted(i))) {
2230                 ++i;
2231                 if (i < end) {
2232                         char_type previous_char = d->text_[i];
2233                         if (!(isNewline(i) || isEnvSeparator(i))) {
2234                                 ++i;
2235                                 while (i < end && (previous_char != ' ' || prev_char_deleted)) {
2236                                         char_type temp = d->text_[i];
2237                                         prev_char_deleted = isDeleted(i);
2238                                         if (!isDeleted(i) && (isNewline(i) || isEnvSeparator(i)))
2239                                                 break;
2240                                         ++i;
2241                                         previous_char = temp;
2242                                 }
2243                         }
2244                 }
2245         }
2246
2247         d->begin_of_body_ = i;
2248 }
2249
2250
2251 bool Paragraph::allowParagraphCustomization() const
2252 {
2253         return inInset().allowParagraphCustomization();
2254 }
2255
2256
2257 bool Paragraph::usePlainLayout() const
2258 {
2259         return inInset().usePlainLayout();
2260 }
2261
2262
2263 bool Paragraph::isPassThru() const
2264 {
2265         return inInset().isPassThru() || d->layout_->pass_thru;
2266 }
2267
2268 namespace {
2269
2270 // paragraphs inside floats need different alignment tags to avoid
2271 // unwanted space
2272
2273 bool noTrivlistCentering(InsetCode code)
2274 {
2275         return code == FLOAT_CODE
2276                || code == WRAP_CODE
2277                || code == CELL_CODE;
2278 }
2279
2280
2281 string correction(string const & orig)
2282 {
2283         if (orig == "flushleft")
2284                 return "raggedright";
2285         if (orig == "flushright")
2286                 return "raggedleft";
2287         if (orig == "center")
2288                 return "centering";
2289         return orig;
2290 }
2291
2292
2293 bool corrected_env(otexstream & os, string const & suffix, string const & env,
2294         InsetCode code, bool const lastpar, int & col)
2295 {
2296         string macro = suffix + "{";
2297         if (noTrivlistCentering(code)) {
2298                 if (lastpar) {
2299                         // the last paragraph in non-trivlist-aligned
2300                         // context is special (to avoid unwanted whitespace)
2301                         if (suffix == "\\begin") {
2302                                 macro = "\\" + correction(env) + "{}";
2303                                 os << from_ascii(macro);
2304                                 col += macro.size();
2305                                 return true;
2306                         }
2307                         return false;
2308                 }
2309                 macro += correction(env);
2310         } else
2311                 macro += env;
2312         macro += "}";
2313         if (suffix == "\\par\\end") {
2314                 os << breakln;
2315                 col = 0;
2316         }
2317         os << from_ascii(macro);
2318         col += macro.size();
2319         if (suffix == "\\begin") {
2320                 os << breakln;
2321                 col = 0;
2322         }
2323         return true;
2324 }
2325
2326 } // namespace
2327
2328
2329 int Paragraph::Private::startTeXParParams(BufferParams const & bparams,
2330                         otexstream & os, OutputParams const & runparams) const
2331 {
2332         int column = 0;
2333
2334         bool canindent =
2335                 (bparams.paragraph_separation == BufferParams::ParagraphIndentSeparation) ?
2336                         (layout_->toggle_indent != ITOGGLE_NEVER) :
2337                         (layout_->toggle_indent == ITOGGLE_ALWAYS);
2338
2339         if (canindent && params_.noindent() && !layout_->pass_thru) {
2340                 os << "\\noindent ";
2341                 column += 10;
2342         }
2343
2344         LyXAlignment const curAlign = params_.align();
2345
2346         if (curAlign == layout_->align)
2347                 return column;
2348
2349         switch (curAlign) {
2350         case LYX_ALIGN_NONE:
2351         case LYX_ALIGN_BLOCK:
2352         case LYX_ALIGN_LAYOUT:
2353         case LYX_ALIGN_SPECIAL:
2354         case LYX_ALIGN_DECIMAL:
2355                 break;
2356         case LYX_ALIGN_LEFT:
2357         case LYX_ALIGN_RIGHT:
2358         case LYX_ALIGN_CENTER:
2359                 if (runparams.moving_arg) {
2360                         os << "\\protect";
2361                         column += 8;
2362                 }
2363                 break;
2364         }
2365
2366         string const begin_tag = "\\begin";
2367         InsetCode code = ownerCode();
2368         bool const lastpar = runparams.isLastPar;
2369         // RTL in classic (PDF)LaTeX (without the Bidi package)
2370         // Luabibdi (used by LuaTeX) behaves like classic
2371         bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft()
2372                 && !runparams.useBidiPackage();
2373
2374         switch (curAlign) {
2375         case LYX_ALIGN_NONE:
2376         case LYX_ALIGN_BLOCK:
2377         case LYX_ALIGN_LAYOUT:
2378         case LYX_ALIGN_SPECIAL:
2379         case LYX_ALIGN_DECIMAL:
2380                 break;
2381         case LYX_ALIGN_LEFT: {
2382                 if (rtl_classic)
2383                         // Classic (PDF)LaTeX switches the left/right logic in RTL mode
2384                         corrected_env(os, begin_tag, "flushright", code, lastpar, column);
2385                 else
2386                         corrected_env(os, begin_tag, "flushleft", code, lastpar, column);
2387                 break;
2388         } case LYX_ALIGN_RIGHT: {
2389                 if (rtl_classic)
2390                         // Classic (PDF)LaTeX switches the left/right logic in RTL mode
2391                         corrected_env(os, begin_tag, "flushleft", code, lastpar, column);
2392                 else
2393                         corrected_env(os, begin_tag, "flushright", code, lastpar, column);
2394                 break;
2395         } case LYX_ALIGN_CENTER: {
2396                 corrected_env(os, begin_tag, "center", code, lastpar, column);
2397                 break;
2398         }
2399         }
2400
2401         return column;
2402 }
2403
2404
2405 bool Paragraph::Private::endTeXParParams(BufferParams const & bparams,
2406                         otexstream & os, OutputParams const & runparams) const
2407 {
2408         LyXAlignment const curAlign = params_.align();
2409
2410         if (curAlign == layout_->align)
2411                 return false;
2412
2413         switch (curAlign) {
2414         case LYX_ALIGN_NONE:
2415         case LYX_ALIGN_BLOCK:
2416         case LYX_ALIGN_LAYOUT:
2417         case LYX_ALIGN_SPECIAL:
2418         case LYX_ALIGN_DECIMAL:
2419                 break;
2420         case LYX_ALIGN_LEFT:
2421         case LYX_ALIGN_RIGHT:
2422         case LYX_ALIGN_CENTER:
2423                 if (runparams.moving_arg)
2424                         os << "\\protect";
2425                 break;
2426         }
2427
2428         bool output = false;
2429         int col = 0;
2430         string const end_tag = "\\par\\end";
2431         InsetCode code = ownerCode();
2432         bool const lastpar = runparams.isLastPar;
2433         // RTL in classic (PDF)LaTeX (without the Bidi package)
2434         // Luabibdi (used by LuaTeX) behaves like classic
2435         bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft()
2436                 && !runparams.useBidiPackage();
2437
2438         switch (curAlign) {
2439         case LYX_ALIGN_NONE:
2440         case LYX_ALIGN_BLOCK:
2441         case LYX_ALIGN_LAYOUT:
2442         case LYX_ALIGN_SPECIAL:
2443         case LYX_ALIGN_DECIMAL:
2444                 break;
2445         case LYX_ALIGN_LEFT: {
2446                 if (rtl_classic)
2447                         // Classic (PDF)LaTeX switches the left/right logic in RTL mode
2448                         output = corrected_env(os, end_tag, "flushright", code, lastpar, col);
2449                 else
2450                         output = corrected_env(os, end_tag, "flushleft", code, lastpar, col);
2451                 break;
2452         } case LYX_ALIGN_RIGHT: {
2453                 if (rtl_classic)
2454                         // Classic (PDF)LaTeX switches the left/right logic in RTL mode
2455                         output = corrected_env(os, end_tag, "flushleft", code, lastpar, col);
2456                 else
2457                         output = corrected_env(os, end_tag, "flushright", code, lastpar, col);
2458                 break;
2459         } case LYX_ALIGN_CENTER: {
2460                 corrected_env(os, end_tag, "center", code, lastpar, col);
2461                 break;
2462         }
2463         }
2464
2465         return output || lastpar;
2466 }
2467
2468
2469 // This one spits out the text of the paragraph
2470 void Paragraph::latex(BufferParams const & bparams,
2471         Font const & outerfont,
2472         otexstream & os,
2473         OutputParams const & runparams,
2474         int start_pos, int end_pos, bool force) const
2475 {
2476         LYXERR(Debug::LATEX, "Paragraph::latex...     " << this);
2477
2478         // FIXME This check should not be needed. Perhaps issue an
2479         // error if it triggers.
2480         Layout const & style = inInset().forcePlainLayout() ?
2481                 bparams.documentClass().plainLayout() : *d->layout_;
2482
2483         if (!force && style.inpreamble)
2484                 return;
2485
2486         bool const allowcust = allowParagraphCustomization();
2487
2488         // Current base font for all inherited font changes, without any
2489         // change caused by an individual character, except for the language:
2490         // It is set to the language of the first character.
2491         // As long as we are in the label, this font is the base font of the
2492         // label. Before the first body character it is set to the base font
2493         // of the body.
2494         Font basefont;
2495
2496         // If there is an open font-encoding changing command (script wrapper),
2497         // alien_script is set to its name
2498         string alien_script;
2499         string script;
2500
2501         // Maybe we have to create a optional argument.
2502         pos_type body_pos = beginOfBody();
2503         unsigned int column = 0;
2504
2505         // If we are inside an non inheritFont() inset, the real outerfont is local_font
2506         Font const real_outerfont = (!inInset().inheritFont()
2507                                      && runparams.local_font != nullptr)
2508                         ? Font(runparams.local_font->fontInfo()) : outerfont;
2509
2510         if (body_pos > 0) {
2511                 // the optional argument is kept in curly brackets in
2512                 // case it contains a ']'
2513                 // This is not strictly needed, but if this is changed it
2514                 // would be a file format change, and tex2lyx would need
2515                 // to be adjusted, since it unconditionally removes the
2516                 // braces when it parses \item.
2517                 os << "[{";
2518                 column += 2;
2519                 basefont = getLabelFont(bparams, real_outerfont);
2520         } else {
2521                 basefont = getLayoutFont(bparams, real_outerfont);
2522         }
2523
2524         // Which font is currently active?
2525         Font running_font(basefont);
2526         // Do we have an open font change?
2527         bool open_font = false;
2528
2529         Change runningChange =
2530             runparams.inDeletedInset && !inInset().canTrackChanges()
2531             ? runparams.changeOfDeletedInset : Change(Change::UNCHANGED);
2532
2533         Encoding const * const prev_encoding = runparams.encoding;
2534
2535         os.texrow().start(id(), 0);
2536
2537         // if the paragraph is empty, the loop will not be entered at all
2538         if (empty()) {
2539                 // For InTitle commands, we have already opened a group
2540                 // in output_latex::TeXOnePar.
2541                 if (style.isCommand() && !style.intitle) {
2542                         os << '{';
2543                         ++column;
2544                 }
2545                 if (!style.leftdelim().empty()) {
2546                         os << style.leftdelim();
2547                         column += style.leftdelim().size();
2548                 }
2549                 if (allowcust)
2550                         column += d->startTeXParParams(bparams, os, runparams);
2551         }
2552
2553         // Whether a \par can be issued for insets typeset inline with text.
2554         // Yes if greater than 0. This has to be static.
2555         THREAD_LOCAL_STATIC int parInline = 0;
2556
2557         for (pos_type i = 0; i < size(); ++i) {
2558                 // First char in paragraph or after label?
2559                 if (i == body_pos) {
2560                         if (body_pos > 0) {
2561                                 if (open_font) {
2562                                         bool needPar = false;
2563                                         column += running_font.latexWriteEndChanges(
2564                                                 os, bparams, runparams,
2565                                                 basefont, basefont, needPar);
2566                                         open_font = false;
2567                                 }
2568                                 basefont = getLayoutFont(bparams, real_outerfont);
2569                                 running_font = basefont;
2570
2571                                 column += Changes::latexMarkChange(os, bparams,
2572                                                 runningChange, Change(Change::UNCHANGED),
2573                                                 runparams);
2574                                 runningChange = Change(Change::UNCHANGED);
2575
2576                                 os << (isEnvSeparator(i) ? "}]~" : "}] ");
2577                                 column +=3;
2578                         }
2579                         // For InTitle commands, we have already opened a group
2580                         // in output_latex::TeXOnePar.
2581                         if (style.isCommand() && !style.intitle) {
2582                                 os << '{';
2583                                 ++column;
2584                         }
2585
2586                         if (!style.leftdelim().empty()) {
2587                                 os << style.leftdelim();
2588                                 column += style.leftdelim().size();
2589                         }
2590
2591                         if (allowcust)
2592                                 column += d->startTeXParParams(bparams, os,
2593                                                             runparams);
2594                 }
2595
2596                 runparams.wasDisplayMath = runparams.inDisplayMath;
2597                 runparams.inDisplayMath = false;
2598                 bool deleted_display_math = false;
2599                 Change const & change = runparams.inDeletedInset
2600                         ? runparams.changeOfDeletedInset : lookupChange(i);
2601
2602                 char_type const c = d->text_[i];
2603
2604                 // Check whether a display math inset follows
2605                 bool output_changes;
2606                 if (runparams.for_searchAdv == OutputParams::NoSearch)
2607                         output_changes = bparams.output_changes;
2608                 else
2609                         output_changes = (runparams.for_searchAdv == OutputParams::SearchWithDeleted);
2610                 if (c == META_INSET
2611                     && i >= start_pos && (end_pos == -1 || i < end_pos)) {
2612                         if (isDeleted(i))
2613                                 runparams.ctObject = getInset(i)->getCtObject(runparams);
2614         
2615                         InsetMath const * im = getInset(i)->asInsetMath();
2616                         if (im && im->asHullInset()
2617                             && im->asHullInset()->outerDisplay()) {
2618                                 runparams.inDisplayMath = true;
2619                                 // runparams.inDeletedInset will be set by
2620                                 // latexInset later, but we need this info
2621                                 // before it is called. On the other hand, we
2622                                 // cannot set it here because it is a counter.
2623                                 deleted_display_math = isDeleted(i);
2624                         }
2625                         if (output_changes && deleted_display_math
2626                             && runningChange == change
2627                             && change.type == Change::DELETED
2628                             && !os.afterParbreak()) {
2629                                 // A display math in the same paragraph follows.
2630                                 // We have to close and then reopen \lyxdeleted,
2631                                 // otherwise the math will be shifted up.
2632                                 OutputParams rp = runparams;
2633                                 if (open_font) {
2634                                         bool needPar = false;
2635                                         column += running_font.latexWriteEndChanges(
2636                                                 os, bparams, rp, basefont,
2637                                                 basefont, needPar);
2638                                         open_font = false;
2639                                 }
2640                                 basefont = (body_pos > i) ? getLabelFont(bparams, real_outerfont)
2641                                                           : getLayoutFont(bparams, real_outerfont);
2642                                 running_font = basefont;
2643                                 column += Changes::latexMarkChange(os, bparams,
2644                                         Change(Change::INSERTED), change, rp);
2645                         }
2646                 }
2647
2648                 if (output_changes && runningChange != change) {
2649                         if (!alien_script.empty()) {
2650                                 column += 1;
2651                                 os << "}";
2652                                 alien_script.clear();
2653                         }
2654                         if (open_font) {
2655                                 bool needPar = false;
2656                                 column += running_font.latexWriteEndChanges(
2657                                                 os, bparams, runparams,
2658                                                 basefont, basefont, needPar);
2659                                 open_font = false;
2660                         }
2661                         basefont = (body_pos > i) ? getLabelFont(bparams, real_outerfont)
2662                                                   : getLayoutFont(bparams, real_outerfont);
2663                         running_font = basefont;
2664                         column += Changes::latexMarkChange(os, bparams, runningChange,
2665                                                            change, runparams);
2666                         runningChange = change;
2667                 }
2668
2669                 // do not output text which is marked deleted
2670                 // if change tracking output is disabled
2671                 if (!output_changes && change.deleted()) {
2672                         continue;
2673                 }
2674
2675                 ++column;
2676
2677                 // Fully instantiated font
2678                 Font current_font = getFont(bparams, i, outerfont);
2679                 // Previous font
2680                 Font const prev_font = (i > 0) ?
2681                                         getFont(bparams, i - 1, outerfont)
2682                                       : current_font;
2683
2684                 Font const last_font = running_font;
2685                 bool const in_ct_deletion = (output_changes
2686                                              && runningChange == change
2687                                              && change.type == Change::DELETED
2688                                              && !os.afterParbreak());
2689                 // Insets where font switches are used (rather than font commands)
2690                 bool const fontswitch_inset =
2691                                 c == META_INSET
2692                                 && getInset(i)
2693                                 && getInset(i)->allowMultiPar()
2694                                 && getInset(i)->lyxCode() != ERT_CODE
2695                                 && getInset(i)->producesOutput();
2696
2697                 bool closeLanguage = false;
2698                 bool lang_switched_at_inset = false;
2699                 if (fontswitch_inset) {
2700                         // Some insets cannot be inside a font change command.
2701                         // However, even such insets *can* be placed in \L or \R
2702                         // or their equivalents (for RTL language switches),
2703                         // so we don't close the language in those cases
2704                         // (= differing isRightToLeft()).
2705                         // ArabTeX, though, doesn't seem to handle this special behavior.
2706                         closeLanguage = basefont.isRightToLeft() == current_font.isRightToLeft()
2707                                         || basefont.language()->lang() == "arabic_arabtex"
2708                                         || current_font.language()->lang() == "arabic_arabtex";
2709                         // We need to check prev_font as language changes directly at inset
2710                         // will only be started inside the inset.
2711                         lang_switched_at_inset = prev_font.language() != current_font.language();
2712                 }
2713
2714                 // Do we need to close the previous font?
2715                 bool langClosed = false;
2716                 if (open_font &&
2717                     ((current_font != running_font
2718                       || current_font.language() != running_font.language())
2719                      || (fontswitch_inset
2720                          && (current_font == prev_font))))
2721                 {
2722                         // ensure there is no open script-wrapper
2723                         if (!alien_script.empty()) {
2724                                 column += 1;
2725                                 os << "}";
2726                                 alien_script.clear();
2727                         }
2728                         if (in_ct_deletion) {
2729                                 // We have to close and then reopen \lyxdeleted,
2730                                 // as strikeout needs to be on lowest level.
2731                                 os << '}';
2732                                 column += 1;
2733                         }
2734                         if (closeLanguage)
2735                                 // Force language closing
2736                                 current_font.setLanguage(basefont.language());
2737                         Font const nextfont = (i == body_pos-1) ? basefont : current_font;
2738                         bool needPar = false;
2739                         column += running_font.latexWriteEndChanges(
2740                                     os, bparams, runparams, basefont,
2741                                     nextfont, needPar);
2742                         if (in_ct_deletion) {
2743                                 // We have to close and then reopen \lyxdeleted,
2744                                 // as strikeout needs to be on lowest level.
2745                                 OutputParams rp = runparams;
2746                                 column += Changes::latexMarkChange(os, bparams,
2747                                         Change(Change::UNCHANGED), Change(Change::DELETED), rp);
2748                         }
2749                         open_font = false;
2750                         // Has the language been closed in the latexWriteEndChanges() call above?
2751                         langClosed = running_font.language() != basefont.language()
2752                                         && running_font.language() != nextfont.language()
2753                                         && (running_font.language()->encoding()->package() != Encoding::CJK);
2754                         running_font = basefont;
2755                 }
2756
2757                 // if necessary, close language environment before opening CJK
2758                 string const running_lang = running_font.language()->babel();
2759                 string const lang_end_command = lyxrc.language_command_end;
2760                 if (!lang_end_command.empty() && !bparams.useNonTeXFonts
2761                         && !running_lang.empty()
2762                         && running_lang == openLanguageName()
2763                         && current_font.language()->encoding()->package() == Encoding::CJK) {
2764                         string end_tag = subst(lang_end_command, "$$lang", running_lang);
2765                         os << from_ascii(end_tag);
2766                         column += end_tag.length();
2767                         popLanguageName();
2768                 }
2769
2770                 // Switch file encoding if necessary (and allowed)
2771                 if ((!fontswitch_inset || closeLanguage)
2772                     && !runparams.pass_thru && !style.pass_thru &&
2773                     runparams.encoding->package() != Encoding::none &&
2774                     current_font.language()->encoding()->package() != Encoding::none) {
2775                         pair<bool, int> const enc_switch =
2776                                 switchEncoding(os.os(), bparams, runparams,
2777                                         *(current_font.language()->encoding()));
2778                         if (enc_switch.first) {
2779                                 column += enc_switch.second;
2780                                 runparams.encoding = current_font.language()->encoding();
2781                         }
2782                 }
2783
2784                 // A display math inset inside an ulem command will be output
2785                 // as a box of width \linewidth, so we have to either disable
2786                 // indentation if the inset starts a paragraph, or start a new
2787                 // line to accommodate such box. This has to be done before
2788                 // writing any font changing commands.
2789                 if (runparams.inDisplayMath && !deleted_display_math
2790                     && runparams.inulemcmd) {
2791                         if (os.afterParbreak())
2792                                 os << "\\noindent";
2793                         else
2794                                 os << "\\\\\n";
2795                 }
2796
2797                 // Do we need to change font?
2798                 if ((current_font != running_font ||
2799                      current_font.language() != running_font.language())
2800                     && i != body_pos - 1)
2801                 {
2802                         if (!fontswitch_inset) {
2803                                 if (in_ct_deletion) {
2804                                         // We have to close and then reopen \lyxdeleted,
2805                                         // as strikeout needs to be on lowest level.
2806                                         OutputParams rp = runparams;
2807                                         bool needPar = false;
2808                                         column += running_font.latexWriteEndChanges(
2809                                                 os, bparams, rp, basefont,
2810                                                 basefont, needPar);
2811                                         os << '}';
2812                                         column += 1;
2813                                 }
2814                                 otexstringstream ots;
2815                                 InsetText const * textinset = inInset().asInsetText();
2816                                 bool const cprotect = textinset
2817                                         ? textinset->hasCProtectContent(runparams.moving_arg)
2818                                           && !textinset->text().isMainText()
2819                                         : false;
2820                                 column += current_font.latexWriteStartChanges(ots, bparams,
2821                                                                               runparams, basefont, last_font, false,
2822                                                                               cprotect);
2823                                 // Check again for display math in ulem commands as a
2824                                 // font change may also occur just before a math inset.
2825                                 if (runparams.inDisplayMath && !deleted_display_math
2826                                     && runparams.inulemcmd) {
2827                                         if (os.afterParbreak())
2828                                                 os << "\\noindent";
2829                                         else
2830                                                 os << "\\\\\n";
2831                                 }
2832                                 running_font = current_font;
2833                                 open_font = true;
2834                                 docstring fontchange = ots.str();
2835                                 os << fontchange;
2836                                 // check whether the fontchange ends with a \\textcolor
2837                                 // modifier and the text starts with a space. If so we
2838                                 // need to add } in order to prevent \\textcolor from gobbling
2839                                 // the space (bug 4473).
2840                                 docstring const last_modifier = rsplit(fontchange, '\\');
2841                                 if (prefixIs(last_modifier, from_ascii("textcolor")) && c == ' ')
2842                                         os << from_ascii("{}");
2843                                 else if (ots.terminateCommand())
2844                                         os << termcmd;
2845                                 if (in_ct_deletion) {
2846                                         // We have to close and then reopen \lyxdeleted,
2847                                         // as strikeout needs to be on lowest level.
2848                                         OutputParams rp = runparams;
2849                                         column += Changes::latexMarkChange(os, bparams,
2850                                                 Change(Change::UNCHANGED), change, rp);
2851                                 }
2852                         } else {
2853                                 running_font = current_font;
2854                                 open_font = !langClosed;
2855                         }
2856                 }
2857
2858                 // FIXME: think about end_pos implementation...
2859                 if (c == ' ' && i >= start_pos && (end_pos == -1 || i < end_pos)) {
2860                         // FIXME: integrate this case in latexSpecialChar
2861                         // Do not print the separation of the optional argument
2862                         // if style.pass_thru is false. This works because
2863                         // latexSpecialChar ignores spaces if
2864                         // style.pass_thru is false.
2865                         if (i != body_pos - 1) {
2866                                 if (d->simpleTeXBlanks(bparams, runparams, os,
2867                                                 i, column, current_font, style)) {
2868                                         // A surrogate pair was output. We
2869                                         // must not call latexSpecialChar
2870                                         // in this iteration, since it would output
2871                                         // the combining character again.
2872                                         ++i;
2873                                         continue;
2874                                 }
2875                         }
2876                 }
2877
2878                 OutputParams rp = runparams;
2879                 rp.free_spacing = style.free_spacing;
2880                 rp.local_font = &current_font;
2881                 rp.intitle = style.intitle;
2882
2883                 // Two major modes:  LaTeX or plain
2884                 // Handle here those cases common to both modes
2885                 // and then split to handle the two modes separately.
2886                 if (c == META_INSET) {
2887                         if (i >= start_pos && (end_pos == -1 || i < end_pos)) {
2888                                 // Greyedout notes and, in general, all insets
2889                                 // with InsetLayout::isDisplay() == false,
2890                                 // are typeset inline with the text. So, we
2891                                 // can add a \par to the last paragraph of
2892                                 // such insets only if nothing else follows.
2893                                 bool incremented = false;
2894                                 Inset const * inset = getInset(i);
2895                                 InsetText const * textinset = inset
2896                                                         ? inset->asInsetText()
2897                                                         : nullptr;
2898                                 if (i + 1 == size() && textinset
2899                                     && !inset->getLayout().isDisplay()) {
2900                                         ParagraphList const & pars =
2901                                                 textinset->text().paragraphs();
2902                                         pit_type const pit = pars.size() - 1;
2903                                         Font const lastfont =
2904                                                 pit < 0 || pars[pit].empty()
2905                                                 ? pars[pit].getLayoutFont(
2906                                                                 bparams,
2907                                                                 real_outerfont)
2908                                                 : pars[pit].getFont(bparams,
2909                                                         pars[pit].size() - 1,
2910                                                         real_outerfont);
2911                                         if (lastfont.fontInfo().size() !=
2912                                             basefont.fontInfo().size()) {
2913                                                 ++parInline;
2914                                                 incremented = true;
2915                                         }
2916                                 }
2917                                 // We need to restore parts of this after insets with
2918                                 // allowMultiPar() true
2919                                 Font const save_basefont = basefont;
2920                                 d->latexInset(bparams, os, rp, running_font,
2921                                                 basefont, real_outerfont, open_font,
2922                                                 runningChange, style, i, column, fontswitch_inset,
2923                                                 closeLanguage, lang_switched_at_inset);
2924                                 if (fontswitch_inset) {
2925                                         if (open_font) {
2926                                                 bool needPar = false;
2927                                                 column += running_font.latexWriteEndChanges(
2928                                                         os, bparams, runparams,
2929                                                         basefont, basefont, needPar);
2930                                                 open_font = false;
2931                                         }
2932                                         basefont.fontInfo().setSize(save_basefont.fontInfo().size());
2933                                         basefont.fontInfo().setFamily(save_basefont.fontInfo().family());
2934                                         basefont.fontInfo().setSeries(save_basefont.fontInfo().series());
2935                                 }
2936                                 if (incremented)
2937                                         --parInline;
2938
2939                                 if (runparams.ctObject == CtObject::DisplayObject
2940                                     || runparams.ctObject == CtObject::UDisplayObject) {
2941                                         // Close \lyx*deleted and force its
2942                                         // reopening (if needed)
2943                                         os << '}';
2944                                         column++;
2945                                         runningChange = Change(Change::UNCHANGED);
2946                                         runparams.ctObject = CtObject::Normal;
2947                                 }
2948                         }
2949                 } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) {
2950                         if (!bparams.useNonTeXFonts)
2951                           script = Encodings::isKnownScriptChar(c);
2952                         if (script != alien_script) {
2953                                 if (!alien_script.empty()) {
2954                                         os << "}";
2955                                         alien_script.clear();
2956                                 }
2957                                 string fontenc = running_font.language()->fontenc(bparams);
2958                                 if (!script.empty()
2959                                         && !Encodings::fontencSupportsScript(fontenc, script)) {
2960                                         column += script.length() + 2;
2961                                         os << "\\" << script << "{";
2962                                         alien_script = script;
2963                                 }
2964                         }
2965                         try {
2966                                 d->latexSpecialChar(os, bparams, rp, running_font,
2967                                                                         alien_script, style, i, end_pos, column);
2968                         } catch (EncodingException & e) {
2969                                 if (runparams.dryrun) {
2970                                         os << "<" << _("LyX Warning: ")
2971                                            << _("uncodable character") << " '";
2972                                         os.put(c);
2973                                         os << "'>";
2974                                 } else {
2975                                         // add location information and throw again.
2976                                         e.par_id = id();
2977                                         e.pos = i;
2978                                         throw;
2979                                 }
2980                         }
2981                 }
2982
2983                 // Set the encoding to that returned from latexSpecialChar (see
2984                 // comment for encoding member in OutputParams.h)
2985                 runparams.encoding = rp.encoding;
2986
2987                 // Also carry on the info on a closed ulem command for insets
2988                 // such as Note that do not produce any output, so that no
2989                 // command is ever executed but its opening was recorded.
2990                 runparams.inulemcmd = rp.inulemcmd;
2991
2992                 // These need to be passed upstream as well
2993                 runparams.need_maketitle = rp.need_maketitle;
2994                 runparams.have_maketitle = rp.have_maketitle;
2995
2996                 // And finally, pass the post_macros upstream
2997                 runparams.post_macro = rp.post_macro;
2998         }
2999
3000         // Close wrapper for alien script
3001         if (!alien_script.empty()) {
3002                 os << "}";
3003                 alien_script.clear();
3004         }
3005
3006         Font const font = empty()
3007                 ? getLayoutFont(bparams, real_outerfont)
3008                 : getFont(bparams, size() - 1, real_outerfont);
3009
3010         InsetText const * textinset = inInset().asInsetText();
3011
3012         bool const maintext = textinset
3013                 ? textinset->text().isMainText()
3014                 : false;
3015
3016         size_t const numpars = textinset
3017                 ? textinset->text().paragraphs().size()
3018                 : 0;
3019
3020         bool needPar = false;
3021
3022         if (style.resfont.size() != font.fontInfo().size()
3023             && (!runparams.isLastPar || maintext
3024                 || (numpars > 1 && d->ownerCode() != CELL_CODE
3025                     && (inInset().getLayout().isDisplay()
3026                         || parInline)))
3027             && !style.isCommand()) {
3028                 needPar = true;
3029         }
3030
3031         // If we have an open font definition, we have to close it
3032         if (open_font) {
3033                 // Make sure that \\par is done with the font of the last
3034                 // character if this has another size as the default.
3035                 // This is necessary because LaTeX (and LyX on the screen)
3036                 // calculates the space between the baselines according
3037                 // to this font. (Matthias)
3038                 //
3039                 // We must not change the font for the last paragraph
3040                 // of non-multipar insets, tabular cells or commands,
3041                 // since this produces unwanted whitespace.
3042 #ifdef FIXED_LANGUAGE_END_DETECTION
3043                 if (next_) {
3044                         running_font.latexWriteEndChanges(os, bparams,
3045                                         runparams, basefont,
3046                                         next_->getFont(bparams, 0, outerfont),
3047                                                        needPar);
3048                 } else {
3049                         running_font.latexWriteEndChanges(os, bparams,
3050                                         runparams, basefont, basefont, needPar);
3051                 }
3052 #else
3053 //FIXME: For now we ALWAYS have to close the foreign font settings if they are
3054 //FIXME: there as we start another \selectlanguage with the next paragraph if
3055 //FIXME: we are in need of this. This should be fixed sometime (Jug)
3056                 running_font.latexWriteEndChanges(os, bparams, runparams,
3057                                 basefont, basefont, needPar);
3058 #endif
3059         }
3060         if (needPar) {
3061                 // The \par could not be inserted at the same nesting
3062                 // level of the font size change, so do it now.
3063                 os << "{\\" << font.latexSize() << "\\par}";
3064         }
3065
3066         if (!runparams.inDeletedInset || inInset().canTrackChanges())
3067                 column += Changes::latexMarkChange(os, bparams, runningChange,
3068                                         Change(Change::UNCHANGED), runparams);
3069
3070         // Needed if there is an optional argument but no contents.
3071         if (body_pos > 0 && body_pos == size()) {
3072                 os << "}]~";
3073         }
3074
3075         if (!style.rightdelim().empty()) {
3076                 os << style.rightdelim();
3077                 column += style.rightdelim().size();
3078         }
3079
3080         if (allowcust && d->endTeXParParams(bparams, os, runparams)
3081             && runparams.encoding != prev_encoding) {
3082                 runparams.encoding = prev_encoding;
3083                 os << setEncoding(prev_encoding->iconvName());
3084         }
3085
3086         LYXERR(Debug::LATEX, "Paragraph::latex... done " << this);
3087 }
3088
3089
3090 bool Paragraph::emptyTag() const
3091 {
3092         for (pos_type i = 0; i < size(); ++i) {
3093                 if (Inset const * inset = getInset(i)) {
3094                         InsetCode lyx_code = inset->lyxCode();
3095                         // FIXME testing like that is wrong. What is
3096                         // the intent?
3097                         if (lyx_code != TOC_CODE &&
3098                             lyx_code != INCLUDE_CODE &&
3099                             lyx_code != GRAPHICS_CODE &&
3100                             lyx_code != ERT_CODE &&
3101                             lyx_code != LISTINGS_CODE &&
3102                             lyx_code != FLOAT_CODE &&
3103                             lyx_code != TABULAR_CODE) {
3104                                 return false;
3105                         }
3106                 } else {
3107                         char_type c = d->text_[i];
3108                         if (c != ' ' && c != '\t')
3109                                 return false;
3110                 }
3111         }
3112         return true;
3113 }
3114
3115
3116 string Paragraph::getID(Buffer const &, OutputParams const &)
3117         const
3118 {
3119         for (pos_type i = 0; i < size(); ++i) {
3120                 if (Inset const * inset = getInset(i)) {
3121                         InsetCode lyx_code = inset->lyxCode();
3122                         if (lyx_code == LABEL_CODE) {
3123                                 InsetLabel const * const il = static_cast<InsetLabel const *>(inset);
3124                                 docstring const & id = il->getParam("name");
3125                                 return "id='" + to_utf8(xml::cleanID(id)) + "'";
3126                         }
3127                 }
3128         }
3129         return string();
3130 }
3131
3132
3133 pos_type Paragraph::firstWordDocBook(XMLStream & xs, OutputParams const & runparams) const
3134 {
3135         pos_type i;
3136         for (i = 0; i < size(); ++i) {
3137                 if (Inset const * inset = getInset(i)) {
3138                         inset->docbook(xs, runparams);
3139                 } else {
3140                         char_type c = d->text_[i];
3141                         if (c == ' ')
3142                                 break;
3143                         xs << c;
3144                 }
3145         }
3146         return i;
3147 }
3148
3149
3150 pos_type Paragraph::firstWordLyXHTML(XMLStream & xs, OutputParams const & runparams)
3151         const
3152 {
3153         pos_type i;
3154         for (i = 0; i < size(); ++i) {
3155                 if (Inset const * inset = getInset(i)) {
3156                         inset->xhtml(xs, runparams);
3157                 } else {
3158                         char_type c = d->text_[i];
3159                         if (c == ' ')
3160                                 break;
3161                         xs << c;
3162                 }
3163         }
3164         return i;
3165 }
3166
3167
3168 bool Paragraph::Private::onlyText(Buffer const & buf, Font const & outerfont, pos_type initial) const
3169 {
3170         Font font_old;
3171         pos_type size = text_.size();
3172         for (pos_type i = initial; i < size; ++i) {
3173                 Font font = owner_->getFont(buf.params(), i, outerfont);
3174                 if (text_[i] == META_INSET)
3175                         return false;
3176                 if (i != initial && font != font_old)
3177                         return false;
3178                 font_old = font;
3179         }
3180
3181         return true;
3182 }
3183
3184
3185 namespace {
3186
3187 void doFontSwitchDocBook(vector<xml::FontTag> & tagsToOpen,
3188                   vector<xml::EndFontTag> & tagsToClose,
3189                   bool & flag, FontState curstate, xml::FontTypes type)
3190 {
3191         if (curstate == FONT_ON) {
3192                 tagsToOpen.push_back(docbookStartFontTag(type));
3193                 flag = true;
3194         } else if (flag) {
3195                 tagsToClose.push_back(docbookEndFontTag(type));
3196                 flag = false;
3197         }
3198 }
3199
3200 class OptionalFontType {
3201 public:
3202         xml::FontTypes ft;
3203         bool has_value;
3204
3205         OptionalFontType(): ft(xml::FT_EMPH), has_value(false) {} // A possible value at random for ft.
3206         OptionalFontType(xml::FontTypes ft): ft(ft), has_value(true) {}
3207 };
3208
3209 OptionalFontType fontShapeToXml(FontShape fs)
3210 {
3211         switch (fs) {
3212         case ITALIC_SHAPE:
3213                 return {xml::FT_ITALIC};
3214         case SLANTED_SHAPE:
3215                 return {xml::FT_SLANTED};
3216         case SMALLCAPS_SHAPE:
3217                 return {xml::FT_SMALLCAPS};
3218         case UP_SHAPE:
3219         case INHERIT_SHAPE:
3220                 return {};
3221         default:
3222                 // the other tags are for internal use
3223                 LATTEST(false);
3224                 return {};
3225         }
3226 }
3227
3228 OptionalFontType fontFamilyToXml(FontFamily fm)
3229 {
3230         switch (fm) {
3231         case ROMAN_FAMILY:
3232                 return {xml::FT_ROMAN};
3233         case SANS_FAMILY:
3234                 return {xml::FT_SANS};
3235         case TYPEWRITER_FAMILY:
3236                 return {xml::FT_TYPE};
3237         case INHERIT_FAMILY:
3238                 return {};
3239         default:
3240                 // the other tags are for internal use
3241                 LATTEST(false);
3242                 return {};
3243         }
3244 }
3245
3246 OptionalFontType fontSizeToXml(FontSize fs)
3247 {
3248         switch (fs) {
3249         case TINY_SIZE:
3250                 return {xml::FT_SIZE_TINY};
3251         case SCRIPT_SIZE:
3252                 return {xml::FT_SIZE_SCRIPT};
3253         case FOOTNOTE_SIZE:
3254                 return {xml::FT_SIZE_FOOTNOTE};
3255         case SMALL_SIZE:
3256                 return {xml::FT_SIZE_SMALL};
3257         case LARGE_SIZE:
3258                 return {xml::FT_SIZE_LARGE};
3259         case LARGER_SIZE:
3260                 return {xml::FT_SIZE_LARGER};
3261         case LARGEST_SIZE:
3262                 return {xml::FT_SIZE_LARGEST};
3263         case HUGE_SIZE:
3264                 return {xml::FT_SIZE_HUGE};
3265         case HUGER_SIZE:
3266                 return {xml::FT_SIZE_HUGER};
3267         case INCREASE_SIZE:
3268                 return {xml::FT_SIZE_INCREASE};
3269         case DECREASE_SIZE:
3270                 return {xml::FT_SIZE_DECREASE};
3271         case INHERIT_SIZE:
3272         case NORMAL_SIZE:
3273                 return {};
3274         default:
3275                 // the other tags are for internal use
3276                 LATTEST(false);
3277                 return {};
3278         }
3279 }
3280
3281 struct DocBookFontState
3282 {
3283         FontShape  curr_fs   = INHERIT_SHAPE;
3284         FontFamily curr_fam  = INHERIT_FAMILY;
3285         FontSize   curr_size = INHERIT_SIZE;
3286
3287         // track whether we have opened these tags
3288         bool emph_flag = false;
3289         bool bold_flag = false;
3290         bool noun_flag = false;
3291         bool ubar_flag = false;
3292         bool dbar_flag = false;
3293         bool sout_flag = false;
3294         bool xout_flag = false;
3295         bool wave_flag = false;
3296         // shape tags
3297         bool shap_flag = false;
3298         // family tags
3299         bool faml_flag = false;
3300         // size tags
3301         bool size_flag = false;
3302 };
3303
3304 std::tuple<vector<xml::FontTag>, vector<xml::EndFontTag>> computeDocBookFontSwitch(FontInfo const & font_old,
3305                                                                                            Font const & font,
3306                                                                                            std::string const & default_family,
3307                                                                                            DocBookFontState & fs)
3308 {
3309         vector<xml::FontTag> tagsToOpen;
3310         vector<xml::EndFontTag> tagsToClose;
3311
3312         // emphasis
3313         FontState curstate = font.fontInfo().emph();
3314         if (font_old.emph() != curstate)
3315                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.emph_flag, curstate, xml::FT_EMPH);
3316
3317         // noun
3318         curstate = font.fontInfo().noun();
3319         if (font_old.noun() != curstate)
3320                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.noun_flag, curstate, xml::FT_NOUN);
3321
3322         // underbar
3323         curstate = font.fontInfo().underbar();
3324         if (font_old.underbar() != curstate)
3325                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.ubar_flag, curstate, xml::FT_UBAR);
3326
3327         // strikeout
3328         curstate = font.fontInfo().strikeout();
3329         if (font_old.strikeout() != curstate)
3330                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.sout_flag, curstate, xml::FT_SOUT);
3331
3332         // xout
3333         curstate = font.fontInfo().xout();
3334         if (font_old.xout() != curstate)
3335                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.xout_flag, curstate, xml::FT_XOUT);
3336
3337         // double underbar
3338         curstate = font.fontInfo().uuline();
3339         if (font_old.uuline() != curstate)
3340                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.dbar_flag, curstate, xml::FT_DBAR);
3341
3342         // wavy line
3343         curstate = font.fontInfo().uwave();
3344         if (font_old.uwave() != curstate)
3345                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.wave_flag, curstate, xml::FT_WAVE);
3346
3347         // bold
3348         // a little hackish, but allows us to reuse what we have.
3349         curstate = (font.fontInfo().series() == BOLD_SERIES ? FONT_ON : FONT_OFF);
3350         if (font_old.series() != font.fontInfo().series())
3351                 doFontSwitchDocBook(tagsToOpen, tagsToClose, fs.bold_flag, curstate, xml::FT_BOLD);
3352
3353         // Font shape
3354         fs.curr_fs = font.fontInfo().shape();
3355         FontShape old_fs = font_old.shape();
3356         if (old_fs != fs.curr_fs) {
3357                 if (fs.shap_flag) {
3358                         OptionalFontType tag = fontShapeToXml(old_fs);
3359                         if (tag.has_value)
3360                                 tagsToClose.push_back(docbookEndFontTag(tag.ft));
3361                         fs.shap_flag = false;
3362                 }
3363
3364                 OptionalFontType tag = fontShapeToXml(fs.curr_fs);
3365                 if (tag.has_value)
3366                         tagsToOpen.push_back(docbookStartFontTag(tag.ft));
3367         }
3368
3369         // Font family
3370         fs.curr_fam = font.fontInfo().family();
3371         FontFamily old_fam = font_old.family();
3372         if (old_fam != fs.curr_fam) {
3373                 if (fs.faml_flag) {
3374                         OptionalFontType tag = fontFamilyToXml(old_fam);
3375                         if (tag.has_value)
3376                                 tagsToClose.push_back(docbookEndFontTag(tag.ft));
3377                         fs.faml_flag = false;
3378                 }
3379                 switch (fs.curr_fam) {
3380                         case ROMAN_FAMILY:
3381                                 // we will treat a "default" font family as roman, since we have
3382                                 // no other idea what to do.
3383                                 if (default_family != "rmdefault" && default_family != "default") {
3384                                         tagsToOpen.push_back(docbookStartFontTag(xml::FT_ROMAN));
3385                                         fs.faml_flag = true;
3386                                 }
3387                                 break;
3388                         case SANS_FAMILY:
3389                                 if (default_family != "sfdefault") {
3390                                         tagsToOpen.push_back(docbookStartFontTag(xml::FT_SANS));
3391                                         fs.faml_flag = true;
3392                                 }
3393                                 break;
3394                         case TYPEWRITER_FAMILY:
3395                                 if (default_family != "ttdefault") {
3396                                         tagsToOpen.push_back(docbookStartFontTag(xml::FT_TYPE));
3397                                         fs.faml_flag = true;
3398                                 }
3399                                 break;
3400                         case INHERIT_FAMILY:
3401                                 break;
3402                         default:
3403                                 // the other tags are for internal use
3404                                 LATTEST(false);
3405                                 break;
3406                 }
3407         }
3408
3409         // Font size
3410         fs.curr_size = font.fontInfo().size();
3411         FontSize old_size = font_old.size();
3412         if (old_size != fs.curr_size) {
3413                 if (fs.size_flag) {
3414                         OptionalFontType tag = fontSizeToXml(old_size);
3415                         if (tag.has_value)
3416                                 tagsToClose.push_back(docbookEndFontTag(tag.ft));
3417                         fs.size_flag = false;
3418                 }
3419
3420                 OptionalFontType tag = fontSizeToXml(fs.curr_size);
3421                 if (tag.has_value) {
3422                         tagsToOpen.push_back(docbookStartFontTag(tag.ft));
3423                         fs.size_flag = true;
3424                 }
3425         }
3426
3427         return std::tuple<vector<xml::FontTag>, vector<xml::EndFontTag>>(tagsToOpen, tagsToClose);
3428 }
3429
3430 } // anonymous namespace
3431
3432
3433 std::tuple<std::vector<docstring>, std::vector<docstring>, std::vector<docstring>>
3434     Paragraph::simpleDocBookOnePar(Buffer const & buf,
3435                                                       OutputParams const & runparams,
3436                                                       Font const & outerfont,
3437                                                       pos_type initial,
3438                                                       bool is_last_par,
3439                                                       bool ignore_fonts) const
3440 {
3441         std::vector<docstring> prependedParagraphs;
3442         std::vector<docstring> generatedParagraphs;
3443         std::vector<docstring> appendedParagraphs;
3444         odocstringstream os;
3445
3446         // If there is an argument that must be output before the main tag, do it before handling the rest of the paragraph.
3447         // Also tag all arguments that shouldn't go in the main content right now, so that they are never generated at the
3448         // wrong place.
3449         OutputParams rp = runparams;
3450     for (pos_type i = initial; i < size(); ++i) {
3451         if (getInset(i) && getInset(i)->lyxCode() == ARG_CODE) {
3452             const InsetArgument * arg = getInset(i)->asInsetArgument();
3453             if (arg->docbookargumentbeforemaintag()) {
3454                 auto xs_local = XMLStream(os);
3455                 arg->docbook(xs_local, rp);
3456
3457                 prependedParagraphs.push_back(os.str());
3458                 os.str(from_ascii(""));
3459
3460                 rp.docbook_prepended_arguments.insert(arg);
3461             } else if (arg->docbookargumentaftermaintag()) {
3462                 rp.docbook_appended_arguments.insert(arg);
3463             }
3464         }
3465     }
3466
3467     // State variables for the main loop.
3468     auto xs = new XMLStream(os); // XMLStream has no copy constructor: to create a new object, the only solution
3469     // is to hold a pointer to the XMLStream (xs = XMLStream(os) is not allowed once the first object is built).
3470     std::vector<char_type> delayedChars; // When a font tag ends with a space, output it after the closing font tag.
3471     // This requires to store delayed characters at some point.
3472
3473     DocBookFontState fs; // Track whether we have opened font tags
3474     DocBookFontState old_fs = fs;
3475
3476     Layout const & style = *d->layout_;
3477     FontInfo font_old = style.labeltype == LABEL_MANUAL ? style.labelfont : style.font;
3478     string const default_family = buf.masterBuffer()->params().fonts_default_family;
3479
3480     vector<xml::FontTag> tagsToOpen;
3481     vector<xml::EndFontTag> tagsToClose;
3482
3483         // Parsing main loop.
3484         for (pos_type i = initial; i < size(); ++i) {
3485             bool ignore_fonts_i = ignore_fonts
3486                               || style.docbooknofontinside()
3487                               || (getInset(i) && getInset(i)->getLayout().docbooknofontinside());
3488
3489                 // Don't show deleted material in the output.
3490                 if (isDeleted(i))
3491                         continue;
3492
3493                 // If this is an InsetNewline, generate a new paragraph. Also reset the fonts, so that tags are closed in
3494                 // this paragraph.
3495                 if (getInset(i) && getInset(i)->lyxCode() == NEWLINE_CODE) {
3496                         if (!ignore_fonts_i)
3497                                 xs->closeFontTags();
3498
3499                         // Output one paragraph (i.e. one string entry in generatedParagraphs).
3500                         generatedParagraphs.push_back(os.str());
3501
3502                         // Create a new XMLStream for the new paragraph, completely independent from the previous one. This implies
3503                         // that the string stream must be reset.
3504                         os.str(from_ascii(""));
3505                         delete xs;
3506                         xs = new XMLStream(os);
3507
3508                         // Restore the fonts for the new paragraph, so that the right tags are opened for the new entry.
3509                         if (!ignore_fonts_i) {
3510                                 font_old = outerfont.fontInfo();
3511                                 fs = old_fs;
3512                         }
3513                 }
3514
3515                 // Determine which tags should be opened or closed regarding fonts.
3516                 Font const font = getFont(buf.masterBuffer()->params(), i, outerfont);
3517         tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs);
3518
3519                 if (!ignore_fonts_i) {
3520             vector<xml::EndFontTag>::const_iterator cit = tagsToClose.begin();
3521             vector<xml::EndFontTag>::const_iterator cen = tagsToClose.end();
3522             for (; cit != cen; ++cit)
3523                 *xs << *cit;
3524         }
3525
3526         // Deal with the delayed characters *after* closing font tags.
3527         if (!delayedChars.empty()) {
3528             for (char_type c: delayedChars)
3529                 *xs << c;
3530             delayedChars.clear();
3531         }
3532
3533         if (!ignore_fonts_i) {
3534                         vector<xml::FontTag>::const_iterator sit = tagsToOpen.begin();
3535                         vector<xml::FontTag>::const_iterator sen = tagsToOpen.end();
3536                         for (; sit != sen; ++sit)
3537                                 *xs << *sit;
3538
3539                         tagsToClose.clear();
3540                         tagsToOpen.clear();
3541                 }
3542
3543         // Finally, write the next character or inset.
3544                 if (Inset const * inset = getInset(i)) {
3545                     bool inset_is_argument_elsewhere = getInset(i)->asInsetArgument() &&
3546                             rp.docbook_appended_arguments.find(inset->asInsetArgument()) != rp.docbook_appended_arguments.end() &&
3547                             rp.docbook_prepended_arguments.find(inset->asInsetArgument()) != rp.docbook_prepended_arguments.end();
3548
3549                         if ((!rp.for_toc || inset->isInToc()) && !inset_is_argument_elsewhere) {
3550                             // Arguments may need to be output
3551                                 OutputParams np = rp;
3552                                 np.local_font = &font;
3553
3554                                 // TODO: special case will bite here.
3555                                 np.docbook_in_par = true;
3556                                 inset->docbook(*xs, np);
3557                         }
3558                 } else {
3559                         char_type c = getUChar(buf.masterBuffer()->params(), rp, i);
3560                         if (lyx::isSpace(c) && !ignore_fonts)
3561                                 delayedChars.push_back(c);
3562                         else
3563                                 *xs << c;
3564                 }
3565                 font_old = font.fontInfo();
3566         }
3567
3568         // FIXME, this code is just imported from XHTML
3569         // I'm worried about what happens if a branch, say, is itself
3570         // wrapped in some font stuff. I think that will not work.
3571         if (!ignore_fonts)
3572                 xs->closeFontTags();
3573
3574         // Deal with the delayed characters *after* closing font tags.
3575         if (!delayedChars.empty())
3576                 for (char_type c: delayedChars)
3577                         *xs << c;
3578
3579         // In listings, new lines (i.e. \n characters in the output) are very important. Avoid generating one for the
3580         // last line to get a clean output.
3581         if (rp.docbook_in_listing && !is_last_par)
3582                 *xs << xml::CR();
3583
3584         // Finalise the last (and most likely only) paragraph.
3585         generatedParagraphs.push_back(os.str());
3586     os.str(from_ascii(""));
3587     delete xs;
3588
3589     // If there is an argument that must be output after the main tag, do it after handling the rest of the paragraph.
3590     for (pos_type i = initial; i < size(); ++i) {
3591         if (getInset(i) && getInset(i)->lyxCode() == ARG_CODE) {
3592             const InsetArgument * arg = getInset(i)->asInsetArgument();
3593             if (arg->docbookargumentaftermaintag()) {
3594                 // Don't use rp, as this argument would not generate anything.
3595                 auto xs_local = XMLStream(os);
3596                 arg->docbook(xs_local, runparams);
3597
3598                 appendedParagraphs.push_back(os.str());
3599                 os.str(from_ascii(""));
3600             }
3601         }
3602     }
3603
3604         return std::make_tuple(prependedParagraphs, generatedParagraphs, appendedParagraphs);
3605 }
3606
3607
3608 namespace {
3609
3610 void doFontSwitchXHTML(vector<xml::FontTag> & tagsToOpen,
3611                   vector<xml::EndFontTag> & tagsToClose,
3612                   bool & flag, FontState curstate, xml::FontTypes type)
3613 {
3614         if (curstate == FONT_ON) {
3615                 tagsToOpen.push_back(xhtmlStartFontTag(type));
3616                 flag = true;
3617         } else if (flag) {
3618                 tagsToClose.push_back(xhtmlEndFontTag(type));
3619                 flag = false;
3620         }
3621 }
3622
3623 } // anonymous namespace
3624
3625
3626 docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf,
3627                                     XMLStream & xs,
3628                                     OutputParams const & runparams,
3629                                     Font const & outerfont,
3630                                     bool start_paragraph, bool close_paragraph,
3631                                     pos_type initial) const
3632 {
3633         docstring retval;
3634
3635         // track whether we have opened these tags
3636         bool emph_flag = false;
3637         bool bold_flag = false;
3638         bool noun_flag = false;
3639         bool ubar_flag = false;
3640         bool dbar_flag = false;
3641         bool sout_flag = false;
3642         bool xout_flag = false;
3643         bool wave_flag = false;
3644         // shape tags
3645         bool shap_flag = false;
3646         // family tags
3647         bool faml_flag = false;
3648         // size tags
3649         bool size_flag = false;
3650
3651         Layout const & style = *d->layout_;
3652
3653         if (start_paragraph)
3654                 xs.startDivision(allowEmpty());
3655
3656         FontInfo font_old =
3657                 style.labeltype == LABEL_MANUAL ? style.labelfont : style.font;
3658
3659         FontShape  curr_fs   = INHERIT_SHAPE;
3660         FontFamily curr_fam  = INHERIT_FAMILY;
3661         FontSize   curr_size = INHERIT_SIZE;
3662
3663         string const default_family =
3664                 buf.masterBuffer()->params().fonts_default_family;
3665
3666         vector<xml::FontTag> tagsToOpen;
3667         vector<xml::EndFontTag> tagsToClose;
3668
3669         // parsing main loop
3670         for (pos_type i = initial; i < size(); ++i) {
3671                 // let's not show deleted material in the output
3672                 if (isDeleted(i))
3673                         continue;
3674
3675                 Font const font = getFont(buf.masterBuffer()->params(), i, outerfont);
3676
3677                 // emphasis
3678                 FontState curstate = font.fontInfo().emph();
3679                 if (font_old.emph() != curstate)
3680                         doFontSwitchXHTML(tagsToOpen, tagsToClose, emph_flag, curstate, xml::FT_EMPH);
3681
3682                 // noun
3683                 curstate = font.fontInfo().noun();
3684                 if (font_old.noun() != curstate)
3685                         doFontSwitchXHTML(tagsToOpen, tagsToClose, noun_flag, curstate, xml::FT_NOUN);
3686
3687                 // underbar
3688                 curstate = font.fontInfo().underbar();
3689                 if (font_old.underbar() != curstate)
3690                         doFontSwitchXHTML(tagsToOpen, tagsToClose, ubar_flag, curstate, xml::FT_UBAR);
3691
3692                 // strikeout
3693                 curstate = font.fontInfo().strikeout();
3694                 if (font_old.strikeout() != curstate)
3695                         doFontSwitchXHTML(tagsToOpen, tagsToClose, sout_flag, curstate, xml::FT_SOUT);
3696
3697                 // xout
3698                 curstate = font.fontInfo().xout();
3699                 if (font_old.xout() != curstate)
3700                         doFontSwitchXHTML(tagsToOpen, tagsToClose, xout_flag, curstate, xml::FT_XOUT);
3701
3702                 // double underbar
3703                 curstate = font.fontInfo().uuline();
3704                 if (font_old.uuline() != curstate)
3705                         doFontSwitchXHTML(tagsToOpen, tagsToClose, dbar_flag, curstate, xml::FT_DBAR);
3706
3707                 // wavy line
3708                 curstate = font.fontInfo().uwave();
3709                 if (font_old.uwave() != curstate)
3710                         doFontSwitchXHTML(tagsToOpen, tagsToClose, wave_flag, curstate, xml::FT_WAVE);
3711
3712                 // bold
3713                 // a little hackish, but allows us to reuse what we have.
3714                 curstate = (font.fontInfo().series() == BOLD_SERIES ? FONT_ON : FONT_OFF);
3715                 if (font_old.series() != font.fontInfo().series())
3716                         doFontSwitchXHTML(tagsToOpen, tagsToClose, bold_flag, curstate, xml::FT_BOLD);
3717
3718                 // Font shape
3719                 curr_fs = font.fontInfo().shape();
3720                 FontShape old_fs = font_old.shape();
3721                 if (old_fs != curr_fs) {
3722                         if (shap_flag) {
3723                                 switch (old_fs) {
3724                                 case ITALIC_SHAPE:
3725                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_ITALIC));
3726                                         break;
3727                                 case SLANTED_SHAPE:
3728                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SLANTED));
3729                                         break;
3730                                 case SMALLCAPS_SHAPE:
3731                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SMALLCAPS));
3732                                         break;
3733                                 case UP_SHAPE:
3734                                 case INHERIT_SHAPE:
3735                                         break;
3736                                 default:
3737                                         // the other tags are for internal use
3738                                         LATTEST(false);
3739                                         break;
3740                                 }
3741                                 shap_flag = false;
3742                         }
3743                         switch (curr_fs) {
3744                         case ITALIC_SHAPE:
3745                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_ITALIC));
3746                                 shap_flag = true;
3747                                 break;
3748                         case SLANTED_SHAPE:
3749                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SLANTED));
3750                                 shap_flag = true;
3751                                 break;
3752                         case SMALLCAPS_SHAPE:
3753                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SMALLCAPS));
3754                                 shap_flag = true;
3755                                 break;
3756                         case UP_SHAPE:
3757                         case INHERIT_SHAPE:
3758                                 break;
3759                         default:
3760                                 // the other tags are for internal use
3761                                 LATTEST(false);
3762                                 break;
3763                         }
3764                 }
3765
3766                 // Font family
3767                 curr_fam = font.fontInfo().family();
3768                 FontFamily old_fam = font_old.family();
3769                 if (old_fam != curr_fam) {
3770                         if (faml_flag) {
3771                                 switch (old_fam) {
3772                                 case ROMAN_FAMILY:
3773                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_ROMAN));
3774                                         break;
3775                                 case SANS_FAMILY:
3776                                     tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SANS));
3777                                     break;
3778                                 case TYPEWRITER_FAMILY:
3779                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_TYPE));
3780                                         break;
3781                                 case INHERIT_FAMILY:
3782                                         break;
3783                                 default:
3784                                         // the other tags are for internal use
3785                                         LATTEST(false);
3786                                         break;
3787                                 }
3788                                 faml_flag = false;
3789                         }
3790                         switch (curr_fam) {
3791                         case ROMAN_FAMILY:
3792                                 // we will treat a "default" font family as roman, since we have
3793                                 // no other idea what to do.
3794                                 if (default_family != "rmdefault" && default_family != "default") {
3795                                         tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_ROMAN));
3796                                         faml_flag = true;
3797                                 }
3798                                 break;
3799                         case SANS_FAMILY:
3800                                 if (default_family != "sfdefault") {
3801                                         tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SANS));
3802                                         faml_flag = true;
3803                                 }
3804                                 break;
3805                         case TYPEWRITER_FAMILY:
3806                                 if (default_family != "ttdefault") {
3807                                         tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_TYPE));
3808                                         faml_flag = true;
3809                                 }
3810                                 break;
3811                         case INHERIT_FAMILY:
3812                                 break;
3813                         default:
3814                                 // the other tags are for internal use
3815                                 LATTEST(false);
3816                                 break;
3817                         }
3818                 }
3819
3820                 // Font size
3821                 curr_size = font.fontInfo().size();
3822                 FontSize old_size = font_old.size();
3823                 if (old_size != curr_size) {
3824                         if (size_flag) {
3825                                 switch (old_size) {
3826                                 case TINY_SIZE:
3827                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_TINY));
3828                                         break;
3829                                 case SCRIPT_SIZE:
3830                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_SCRIPT));
3831                                         break;
3832                                 case FOOTNOTE_SIZE:
3833                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_FOOTNOTE));
3834                                         break;
3835                                 case SMALL_SIZE:
3836                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_SMALL));
3837                                         break;
3838                                 case LARGE_SIZE:
3839                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_LARGE));
3840                                         break;
3841                                 case LARGER_SIZE:
3842                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_LARGER));
3843                                         break;
3844                                 case LARGEST_SIZE:
3845                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_LARGEST));
3846                                         break;
3847                                 case HUGE_SIZE:
3848                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_HUGE));
3849                                         break;
3850                                 case HUGER_SIZE:
3851                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_HUGER));
3852                                         break;
3853                                 case INCREASE_SIZE:
3854                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_INCREASE));
3855                                         break;
3856                                 case DECREASE_SIZE:
3857                                         tagsToClose.emplace_back(xhtmlEndFontTag(xml::FT_SIZE_DECREASE));
3858                                         break;
3859                                 case INHERIT_SIZE:
3860                                 case NORMAL_SIZE:
3861                                         break;
3862                                 default:
3863                                         // the other tags are for internal use
3864                                         LATTEST(false);
3865                                         break;
3866                                 }
3867                                 size_flag = false;
3868                         }
3869                         switch (curr_size) {
3870                         case TINY_SIZE:
3871                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_TINY));
3872                                 size_flag = true;
3873                                 break;
3874                         case SCRIPT_SIZE:
3875                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_SCRIPT));
3876                                 size_flag = true;
3877                                 break;
3878                         case FOOTNOTE_SIZE:
3879                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_FOOTNOTE));
3880                                 size_flag = true;
3881                                 break;
3882                         case SMALL_SIZE:
3883                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_SMALL));
3884                                 size_flag = true;
3885                                 break;
3886                         case LARGE_SIZE:
3887                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_LARGE));
3888                                 size_flag = true;
3889                                 break;
3890                         case LARGER_SIZE:
3891                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_LARGER));
3892                                 size_flag = true;
3893                                 break;
3894                         case LARGEST_SIZE:
3895                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_LARGEST));
3896                                 size_flag = true;
3897                                 break;
3898                         case HUGE_SIZE:
3899                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_HUGE));
3900                                 size_flag = true;
3901                                 break;
3902                         case HUGER_SIZE:
3903                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_HUGER));
3904                                 size_flag = true;
3905                                 break;
3906                         case INCREASE_SIZE:
3907                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_INCREASE));
3908                                 size_flag = true;
3909                                 break;
3910                         case DECREASE_SIZE:
3911                                 tagsToOpen.emplace_back(xhtmlStartFontTag(xml::FT_SIZE_DECREASE));
3912                                 size_flag = true;
3913                                 break;
3914                         case INHERIT_SIZE:
3915                         case NORMAL_SIZE:
3916                                 break;
3917                         default:
3918                                 // the other tags are for internal use
3919                                 LATTEST(false);
3920                                 break;
3921                         }
3922                 }
3923
3924                 // FIXME XHTML
3925                 // Other such tags? What about the other text ranges?
3926
3927                 vector<xml::EndFontTag>::const_iterator cit = tagsToClose.begin();
3928                 vector<xml::EndFontTag>::const_iterator cen = tagsToClose.end();
3929                 for (; cit != cen; ++cit)
3930                         xs << *cit;
3931
3932                 vector<xml::FontTag>::const_iterator sit = tagsToOpen.begin();
3933                 vector<xml::FontTag>::const_iterator sen = tagsToOpen.end();
3934                 for (; sit != sen; ++sit)
3935                         xs << *sit;
3936
3937                 tagsToClose.clear();
3938                 tagsToOpen.clear();
3939
3940                 Inset const * inset = getInset(i);
3941                 if (inset) {
3942                         if (!runparams.for_toc || inset->isInToc()) {
3943                                 OutputParams np = runparams;
3944                                 np.local_font = &font;
3945                                 // If the paragraph has size 1, then we are in the "special
3946                                 // case" where we do not output the containing paragraph info
3947                                 if (!inset->getLayout().htmlisblock() && size() != 1)
3948                                         np.html_in_par = true;
3949                                 retval += inset->xhtml(xs, np);
3950                         }
3951                 } else {
3952                         char_type c = getUChar(buf.masterBuffer()->params(),
3953                                                runparams, i);
3954                         if (c == ' ' && (style.free_spacing || runparams.free_spacing))
3955                                 xs << XMLStream::ESCAPE_NONE << "&nbsp;";
3956                         else
3957                                 xs << c;
3958                 }
3959                 font_old = font.fontInfo();
3960         }
3961
3962         // FIXME XHTML
3963         // I'm worried about what happens if a branch, say, is itself
3964         // wrapped in some font stuff. I think that will not work.
3965         xs.closeFontTags();
3966         if (close_paragraph)
3967                 xs.endDivision();
3968
3969         return retval;
3970 }
3971
3972
3973 bool Paragraph::isHfill(pos_type pos) const
3974 {
3975         Inset const * inset = getInset(pos);
3976         return inset && inset->isHfill();
3977 }
3978
3979
3980 bool Paragraph::isNewline(pos_type pos) const
3981 {
3982         // U+2028 LINE SEPARATOR
3983         // U+2029 PARAGRAPH SEPARATOR
3984         char_type const c = d->text_[pos];
3985         if (c == 0x2028 || c == 0x2029)
3986                 return true;
3987         Inset const * inset = getInset(pos);
3988         return inset && inset->lyxCode() == NEWLINE_CODE;
3989 }
3990
3991
3992 bool Paragraph::isEnvSeparator(pos_type pos) const
3993 {
3994         Inset const * inset = getInset(pos);
3995         return inset && inset->lyxCode() == SEPARATOR_CODE;
3996 }
3997
3998
3999 bool Paragraph::isLineSeparator(pos_type pos) const
4000 {
4001         char_type const c = d->text_[pos];
4002         if (isLineSeparatorChar(c))
4003                 return true;
4004         Inset const * inset = getInset(pos);
4005         return inset && inset->isLineSeparator();
4006 }
4007
4008
4009 bool Paragraph::isWordSeparator(pos_type pos, bool const ignore_deleted) const
4010 {
4011         if (pos == size())
4012                 return true;
4013         if (ignore_deleted && isDeleted(pos))
4014                 return false;
4015         if (Inset const * inset = getInset(pos))
4016                 return !inset->isLetter();
4017         // if we have a hard hyphen (no en- or emdash) or apostrophe
4018         // we pass this to the spell checker
4019         // FIXME: this method is subject to change, visit
4020         // https://bugzilla.mozilla.org/show_bug.cgi?id=355178
4021         // to get an impression how complex this is.
4022         if (isHardHyphenOrApostrophe(pos))
4023                 return false;
4024         char_type const c = d->text_[pos];
4025         // We want to pass the escape chars to the spellchecker
4026         docstring const escape_chars = from_utf8(lyxrc.spellchecker_esc_chars);
4027         return !isLetterChar(c) && !isDigitASCII(c) && !contains(escape_chars, c);
4028 }
4029
4030
4031 bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const
4032 {
4033         pos_type const psize = size();
4034         if (pos >= psize)
4035                 return false;
4036         char_type const c = d->text_[pos];
4037         if (c != '-' && c != '\'')
4038                 return false;
4039         pos_type nextpos = pos + 1;
4040         pos_type prevpos = pos > 0 ? pos - 1 : 0;
4041         if ((nextpos == psize || isSpace(nextpos))
4042                 && (pos == 0 || isSpace(prevpos)))
4043                 return false;
4044         return true;
4045 }
4046
4047
4048 bool Paragraph::needsCProtection(bool const fragile) const
4049 {
4050         // first check the layout of the paragraph, but only in insets
4051         InsetText const * textinset = inInset().asInsetText();
4052         bool const maintext = textinset
4053                 ? textinset->text().isMainText()
4054                 : false;
4055
4056         if (!maintext && layout().needcprotect) {
4057                 // Environments need cprotection regardless the content
4058                 if (layout().latextype == LATEX_ENVIRONMENT)
4059                         return true;
4060
4061                 // Commands need cprotection if they contain specific chars
4062                 int const nchars_escape = 9;
4063                 static char_type const chars_escape[nchars_escape] = {
4064                         '&', '_', '$', '%', '#', '^', '{', '}', '\\'};
4065
4066                 docstring const pars = asString();
4067                 for (int k = 0; k < nchars_escape; k++) {
4068                         if (contains(pars, chars_escape[k]))
4069                                 return true;
4070                 }
4071         }
4072
4073         // now check whether we have insets that need cprotection
4074         pos_type size = pos_type(d->text_.size());
4075         for (pos_type i = 0; i < size; ++i) {
4076                 if (!isInset(i))
4077                         continue;
4078                 Inset const * ins = getInset(i);
4079                 if (ins->needsCProtection(maintext, fragile))
4080                         return true;
4081                 // Now check math environments
4082                 InsetMath const * im = getInset(i)->asInsetMath();
4083                 if (!im || im->cell(0).empty())
4084                         continue;
4085                 switch(im->cell(0)[0]->lyxCode()) {
4086                 case MATH_AMSARRAY_CODE:
4087                 case MATH_SUBSTACK_CODE:
4088                 case MATH_ENV_CODE:
4089                 case MATH_XYMATRIX_CODE:
4090                         // these need cprotection
4091                         return true;
4092                 default:
4093                         break;
4094                 }
4095         }
4096
4097         return false;
4098 }
4099
4100
4101 FontSpan const & Paragraph::getSpellRange(pos_type pos) const
4102 {
4103         return d->speller_state_.getRange(pos);
4104 }
4105
4106
4107 bool Paragraph::isChar(pos_type pos) const
4108 {
4109         if (Inset const * inset = getInset(pos))
4110                 return inset->isChar();
4111         char_type const c = d->text_[pos];
4112         return !isLetterChar(c) && !isDigitASCII(c) && !lyx::isSpace(c);
4113 }
4114
4115
4116 bool Paragraph::isSpace(pos_type pos) const
4117 {
4118         if (Inset const * inset = getInset(pos))
4119                 return inset->isSpace();
4120         char_type const c = d->text_[pos];
4121         return lyx::isSpace(c);
4122 }
4123
4124
4125 Language const *
4126 Paragraph::getParLanguage(BufferParams const & bparams) const
4127 {
4128         if (!empty())
4129                 return getFirstFontSettings(bparams).language();
4130         // FIXME: we should check the prev par as well (Lgb)
4131         return bparams.language;
4132 }
4133
4134
4135 bool Paragraph::isRTL(BufferParams const & bparams) const
4136 {
4137         return getParLanguage(bparams)->rightToLeft()
4138                 && !inInset().getLayout().forceLTR();
4139 }
4140
4141
4142 void Paragraph::changeLanguage(BufferParams const & bparams,
4143                                Language const * from, Language const * to)
4144 {
4145         // change language including dummy font change at the end
4146         for (pos_type i = 0; i <= size(); ++i) {
4147                 Font font = getFontSettings(bparams, i);
4148                 if (font.language() == from) {
4149                         font.setLanguage(to);
4150                         setFont(i, font);
4151                         d->requestSpellCheck(i);
4152                 }
4153         }
4154 }
4155
4156
4157 bool Paragraph::isMultiLingual(BufferParams const & bparams) const
4158 {
4159         Language const * doc_language = bparams.language;
4160         for (auto const & f : d->fontlist_)
4161                 if (f.font().language() != ignore_language &&
4162                     f.font().language() != latex_language &&
4163                     f.font().language() != doc_language)
4164                         return true;
4165         return false;
4166 }
4167
4168
4169 void Paragraph::getLanguages(std::set<Language const *> & langs) const
4170 {
4171         for (auto const & f : d->fontlist_) {
4172                 Language const * lang = f.font().language();
4173                 if (lang != ignore_language &&
4174                     lang != latex_language)
4175                         langs.insert(lang);
4176         }
4177 }
4178
4179
4180 docstring Paragraph::asString(int options) const
4181 {
4182         return asString(0, size(), options);
4183 }
4184
4185
4186 docstring Paragraph::asString(pos_type beg, pos_type end, int options, const OutputParams *runparams) const
4187 {
4188         odocstringstream os;
4189
4190         if (beg == 0
4191             && options & AS_STR_LABEL
4192             && !d->params_.labelString().empty())
4193                 os << d->params_.labelString() << ' ';
4194
4195         for (pos_type i = beg; i < end; ++i) {
4196                 if ((options & AS_STR_SKIPDELETE) && isDeleted(i))
4197                         continue;
4198                 char_type const c = d->text_[i];
4199                 if (isPrintable(c) || c == '\t'
4200                     || (c == '\n' && (options & AS_STR_NEWLINES)))
4201                         os.put(c);
4202                 else if (c == META_INSET && (options & AS_STR_INSETS)) {
4203                         if (c == META_INSET && (options & AS_STR_PLAINTEXT)) {
4204                                 LASSERT(runparams != nullptr, return docstring());
4205                                 getInset(i)->plaintext(os, *runparams);
4206                         } else if (c == META_INSET && (options & AS_STR_MATHED)
4207                                    && getInset(i)->lyxCode() == REF_CODE) {
4208                                 Buffer const & buf = getInset(i)->buffer();
4209                                 OutputParams rp(&buf.params().encoding());
4210                                 Font const font(inherit_font, buf.params().language);
4211                                 rp.local_font = &font;
4212                                 otexstream ots(os);
4213                                 getInset(i)->latex(ots, rp);
4214                         } else {
4215                                 getInset(i)->toString(os);
4216                         }
4217                 }
4218         }
4219
4220         return os.str();
4221 }
4222
4223
4224 void Paragraph::forOutliner(docstring & os, size_t const maxlen,
4225                             bool const shorten, bool const label) const
4226 {
4227         size_t tmplen = shorten ? maxlen + 1 : maxlen;
4228         if (label && !labelString().empty())
4229                 os += labelString() + ' ';
4230         if (!layout().isTocCaption())
4231                 return;
4232         for (pos_type i = 0; i < size() && os.length() < tmplen; ++i) {
4233                 if (isDeleted(i))
4234                         continue;
4235                 char_type const c = d->text_[i];
4236                 if (isPrintable(c))
4237                         os += c;
4238                 else if (c == META_INSET)
4239                         getInset(i)->forOutliner(os, tmplen, false);
4240         }
4241         if (shorten)
4242                 Text::shortenForOutliner(os, maxlen);
4243 }
4244
4245
4246 void Paragraph::setInsetOwner(Inset const * inset)
4247 {
4248         d->inset_owner_ = inset;
4249 }
4250
4251
4252 int Paragraph::id() const
4253 {
4254         return d->id_;
4255 }
4256
4257
4258 void Paragraph::setId(int id)
4259 {
4260         d->id_ = id;
4261 }
4262
4263
4264 Layout const & Paragraph::layout() const
4265 {
4266         return *d->layout_;
4267 }
4268
4269
4270 void Paragraph::setLayout(Layout const & layout)
4271 {
4272         d->layout_ = &layout;
4273 }
4274
4275
4276 void Paragraph::setDefaultLayout(DocumentClass const & tc)
4277 {
4278         setLayout(tc.defaultLayout());
4279 }
4280
4281
4282 void Paragraph::setPlainLayout(DocumentClass const & tc)
4283 {
4284         setLayout(tc.plainLayout());
4285 }
4286
4287
4288 void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tc)
4289 {
4290         if (usePlainLayout())
4291                 setPlainLayout(tc);
4292         else
4293                 setDefaultLayout(tc);
4294 }
4295
4296
4297 Inset const & Paragraph::inInset() const
4298 {
4299         LBUFERR(d->inset_owner_);
4300         return *d->inset_owner_;
4301 }
4302
4303
4304 ParagraphParameters & Paragraph::params()
4305 {
4306         return d->params_;
4307 }
4308
4309
4310 ParagraphParameters const & Paragraph::params() const
4311 {
4312         return d->params_;
4313 }
4314
4315
4316 bool Paragraph::isFreeSpacing() const
4317 {
4318         if (d->layout_->free_spacing)
4319                 return true;
4320         return d->inset_owner_ && d->inset_owner_->isFreeSpacing();
4321 }
4322
4323
4324 bool Paragraph::allowEmpty() const
4325 {
4326         if (d->layout_->keepempty)
4327                 return true;
4328         return d->inset_owner_ && d->inset_owner_->allowEmpty();
4329 }
4330
4331
4332 bool Paragraph::brokenBiblio() const
4333 {
4334         // There is a problem if there is no bibitem at position 0 in
4335         // paragraphs that need one, if there is another bibitem in the
4336         // paragraph or if this paragraph is not supposed to have
4337         // a bibitem inset at all.
4338         return ((d->layout_->labeltype == LABEL_BIBLIO
4339                 && (d->insetlist_.find(BIBITEM_CODE) != 0
4340                     || d->insetlist_.find(BIBITEM_CODE, 1) > 0))
4341                 || (d->layout_->labeltype != LABEL_BIBLIO
4342                     && d->insetlist_.find(BIBITEM_CODE) != -1));
4343 }
4344
4345
4346 int Paragraph::fixBiblio(Buffer const & buffer)
4347 {
4348         // FIXME: when there was already an inset at 0, the return value is 1,
4349         // which does not tell whether another inset has been removed; the
4350         // cursor cannot be correctly updated.
4351
4352         bool const track_changes = buffer.params().track_changes;
4353         int bibitem_pos = d->insetlist_.find(BIBITEM_CODE);
4354
4355         // The case where paragraph is not BIBLIO
4356         if (d->layout_->labeltype != LABEL_BIBLIO) {
4357                 if (bibitem_pos == -1)
4358                         // No InsetBibitem => OK
4359                         return 0;
4360                 // There is an InsetBibitem: remove it!
4361                 d->insetlist_.release(bibitem_pos);
4362                 eraseChar(bibitem_pos, track_changes);
4363                 return (bibitem_pos == 0) ? -1 : -bibitem_pos;
4364         }
4365
4366         bool const hasbibitem0 = bibitem_pos == 0;
4367         if (hasbibitem0) {
4368                 bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1);
4369                 // There was an InsetBibitem at pos 0,
4370                 // and no other one => OK
4371                 if (bibitem_pos == -1)
4372                         return 0;
4373                 // there is a bibitem at the 0 position, but since
4374                 // there is a second one, we copy the second on the
4375                 // first. We're assuming there are at most two of
4376                 // these, which there should be.
4377                 // FIXME: why does it make sense to do that rather
4378                 // than keep the first? (JMarc)
4379                 Inset * inset = releaseInset(bibitem_pos);
4380                 d->insetlist_.begin()->inset = inset;
4381                 // This needs to be done to update the counter (#8499)
4382                 buffer.updateBuffer();
4383                 return -bibitem_pos;
4384         }
4385
4386         // We need to create an inset at the beginning
4387         Inset * inset = nullptr;
4388         if (bibitem_pos > 0) {
4389                 // there was one somewhere in the paragraph, let's move it
4390                 inset = d->insetlist_.release(bibitem_pos);
4391                 eraseChar(bibitem_pos, track_changes);
4392         } else
4393                 // make a fresh one
4394                 inset = new InsetBibitem(const_cast<Buffer *>(&buffer),
4395                                          InsetCommandParams(BIBITEM_CODE));
4396
4397         Font font(inherit_font, buffer.params().language);
4398         insertInset(0, inset, font, Change(track_changes ? Change::INSERTED
4399                                                    : Change::UNCHANGED));
4400
4401         // This is needed to get the counters right
4402         buffer.updateBuffer();
4403         return 1;
4404 }
4405
4406
4407 void Paragraph::checkAuthors(AuthorList const & authorList)
4408 {
4409         d->changes_.checkAuthors(authorList);
4410 }
4411
4412
4413 bool Paragraph::isChanged(pos_type pos) const
4414 {
4415         return lookupChange(pos).changed();
4416 }
4417
4418
4419 bool Paragraph::isInserted(pos_type pos) const
4420 {
4421         return lookupChange(pos).inserted();
4422 }
4423
4424
4425 bool Paragraph::isDeleted(pos_type pos) const
4426 {
4427         return lookupChange(pos).deleted();
4428 }
4429
4430
4431 InsetList const & Paragraph::insetList() const
4432 {
4433         return d->insetlist_;
4434 }
4435
4436
4437 void Paragraph::setInsetBuffers(Buffer & b)
4438 {
4439         d->insetlist_.setBuffer(b);
4440 }
4441
4442
4443 void Paragraph::resetBuffer()
4444 {
4445         d->insetlist_.resetBuffer();
4446 }
4447
4448
4449 Inset * Paragraph::releaseInset(pos_type pos)
4450 {
4451         Inset * inset = d->insetlist_.release(pos);
4452         /// does not honour change tracking!
4453         eraseChar(pos, false);
4454         return inset;
4455 }
4456
4457
4458 Inset * Paragraph::getInset(pos_type pos)
4459 {
4460         return (pos < pos_type(d->text_.size()) && d->text_[pos] == META_INSET)
4461                  ? d->insetlist_.get(pos) : nullptr;
4462 }
4463
4464
4465 Inset const * Paragraph::getInset(pos_type pos) const
4466 {
4467         return (pos < pos_type(d->text_.size()) && d->text_[pos] == META_INSET)
4468                  ? d->insetlist_.get(pos) : nullptr;
4469 }
4470
4471
4472 void Paragraph::changeCase(BufferParams const & bparams, pos_type pos,
4473                 pos_type & right, TextCase action)
4474 {
4475         // process sequences of modified characters; in change
4476         // tracking mode, this approach results in much better
4477         // usability than changing case on a char-by-char basis
4478         // We also need to track the current font, since font
4479         // changes within sequences can occur.
4480         vector<pair<char_type, Font> > changes;
4481
4482         bool const trackChanges = bparams.track_changes;
4483
4484         bool capitalize = true;
4485
4486         for (; pos < right; ++pos) {
4487                 char_type oldChar = d->text_[pos];
4488                 char_type newChar = oldChar;
4489
4490                 // ignore insets and don't play with deleted text!
4491                 if (oldChar != META_INSET && !isDeleted(pos)) {
4492                         switch (action) {
4493                                 case text_lowercase:
4494                                         newChar = lowercase(oldChar);
4495                                         break;
4496                                 case text_capitalization:
4497                                         if (capitalize) {
4498                                                 newChar = uppercase(oldChar);
4499                                                 capitalize = false;
4500                                         }
4501                                         break;
4502                                 case text_uppercase:
4503                                         newChar = uppercase(oldChar);
4504                                         break;
4505                         }
4506                 }
4507
4508                 if (isWordSeparator(pos) || isDeleted(pos)) {
4509                         // permit capitalization again
4510                         capitalize = true;
4511                 }
4512
4513                 if (oldChar != newChar) {
4514                         changes.push_back(make_pair(newChar, getFontSettings(bparams, pos)));
4515                         if (pos != right - 1)
4516                                 continue;
4517                         // step behind the changing area
4518                         pos++;
4519                 }
4520
4521                 int erasePos = pos - changes.size();
4522                 for (auto const & change : changes) {
4523                         insertChar(pos, change.first, change.second, trackChanges);
4524                         if (!eraseChar(erasePos, trackChanges)) {
4525                                 ++erasePos;
4526                                 ++pos; // advance
4527                                 ++right; // expand selection
4528                         }
4529                 }
4530                 changes.clear();
4531         }
4532 }
4533
4534
4535 int Paragraph::find(docstring const & str, bool cs, bool mw,
4536                 pos_type start_pos, bool del) const
4537 {
4538         pos_type pos = start_pos;
4539         int const strsize = str.length();
4540         int i = 0;
4541         pos_type const parsize = d->text_.size();
4542         for (i = 0; i < strsize && pos < parsize; ++i, ++pos) {
4543                 // ignore deleted matter
4544                 if (!del && isDeleted(pos)) {
4545                         if (pos == parsize - 1)
4546                                 break;
4547                         pos++;
4548                         --i;
4549                         continue;
4550                 }
4551                 // Ignore "invisible" letters such as ligature breaks
4552                 // and hyphenation chars while searching
4553                 bool nonmatch = false;
4554                 while (pos < parsize && isInset(pos)) {
4555                         Inset const * inset = getInset(pos);
4556                         if (!inset->isLetter() && !inset->isChar())
4557                                 break;
4558                         odocstringstream os;
4559                         inset->toString(os);
4560                         docstring const insetstring = os.str();
4561                         if (!insetstring.empty()) {
4562                                 int const insetstringsize = insetstring.length();
4563                                 for (int j = 0; j < insetstringsize && pos < parsize; ++i, ++j) {
4564                                         if ((cs && str[i] != insetstring[j])
4565                                             || (!cs && uppercase(str[i]) != uppercase(insetstring[j]))) {
4566                                                 nonmatch = true;
4567                                                 break;
4568                                         }
4569                                 }
4570                         }
4571                         pos++;
4572                 }
4573                 if (nonmatch || i == strsize)
4574                         break;
4575                 if (cs && str[i] != d->text_[pos])
4576                         break;
4577                 if (!cs && uppercase(str[i]) != uppercase(d->text_[pos]))
4578                         break;
4579         }
4580
4581         if (i != strsize)
4582                 return 0;
4583
4584         // if necessary, check whether string matches word
4585         if (mw) {
4586                 if (start_pos > 0 && !isWordSeparator(start_pos - 1))
4587                         return 0;
4588                 if (pos < parsize
4589                         && !isWordSeparator(pos))
4590                         return 0;
4591         }
4592
4593         return pos - start_pos;
4594 }
4595
4596
4597 char_type Paragraph::getChar(pos_type pos) const
4598 {
4599         return d->text_[pos];
4600 }
4601
4602
4603 pos_type Paragraph::size() const
4604 {
4605         return d->text_.size();
4606 }
4607
4608
4609 bool Paragraph::empty() const
4610 {
4611         return d->text_.empty();
4612 }
4613
4614
4615 bool Paragraph::isInset(pos_type pos) const
4616 {
4617         return d->text_[pos] == META_INSET;
4618 }
4619
4620
4621 bool Paragraph::isSeparator(pos_type pos) const
4622 {
4623         //FIXME: Are we sure this can be the only separator?
4624         return d->text_[pos] == ' ';
4625 }
4626
4627
4628 void Paragraph::deregisterWords()
4629 {
4630         Private::LangWordsMap::const_iterator itl = d->words_.begin();
4631         Private::LangWordsMap::const_iterator ite = d->words_.end();
4632         for (; itl != ite; ++itl) {
4633                 WordList & wl = theWordList(itl->first);
4634                 Private::Words::const_iterator it = (itl->second).begin();
4635                 Private::Words::const_iterator et = (itl->second).end();
4636                 for (; it != et; ++it)
4637                         wl.remove(*it);
4638         }
4639         d->words_.clear();
4640 }
4641
4642
4643 void Paragraph::locateWord(pos_type & from, pos_type & to,
4644         word_location const loc, bool const ignore_deleted) const
4645 {
4646         switch (loc) {
4647         case WHOLE_WORD_STRICT:
4648                 if (from == 0 || from == size()
4649                     || isWordSeparator(from, ignore_deleted)
4650                     || isWordSeparator(from - 1, ignore_deleted)) {
4651                         to = from;
4652                         return;
4653                 }
4654                 // fall through
4655
4656         case WHOLE_WORD:
4657                 // If we are already at the beginning of a word, do nothing
4658                 if (!from || isWordSeparator(from - 1, ignore_deleted))
4659                         break;
4660                 // fall through
4661
4662         case PREVIOUS_WORD:
4663                 // always move the cursor to the beginning of previous word
4664                 while (from && !isWordSeparator(from - 1, ignore_deleted))
4665                         --from;
4666                 break;
4667         case NEXT_WORD:
4668                 LYXERR0("Paragraph::locateWord: NEXT_WORD not implemented yet");
4669                 break;
4670         case PARTIAL_WORD:
4671                 // no need to move the 'from' cursor
4672                 break;
4673         }
4674         to = from;
4675         while (to < size() && !isWordSeparator(to, ignore_deleted))
4676                 ++to;
4677 }
4678
4679
4680 void Paragraph::collectWords()
4681 {
4682         for (pos_type pos = 0; pos < size(); ++pos) {
4683                 if (isWordSeparator(pos))
4684                         continue;
4685                 pos_type from = pos;
4686                 locateWord(from, pos, WHOLE_WORD);
4687                 // Work around MSVC warning: The statement
4688                 // if (pos < from + lyxrc.completion_minlength)
4689                 // triggers a signed vs. unsigned warning.
4690                 // I don't know why this happens, it could be a MSVC bug, or
4691                 // related to LLP64 (windows) vs. LP64 (unix) programming
4692                 // model, or the C++ standard might be ambigous in the section
4693                 // defining the "usual arithmetic conversions". However, using
4694                 // a temporary variable is safe and works on all compilers.
4695                 pos_type const endpos = from + lyxrc.completion_minlength;
4696                 if (pos < endpos)
4697                         continue;
4698                 FontList::const_iterator cit = d->fontlist_.fontIterator(from);
4699                 if (cit == d->fontlist_.end())
4700                         return;
4701                 Language const * lang = cit->font().language();
4702                 docstring const word = asString(from, pos, AS_STR_NONE);
4703                 d->words_[lang->lang()].insert(word);
4704         }
4705 }
4706
4707
4708 void Paragraph::registerWords()
4709 {
4710         Private::LangWordsMap::const_iterator itl = d->words_.begin();
4711         Private::LangWordsMap::const_iterator ite = d->words_.end();
4712         for (; itl != ite; ++itl) {
4713                 WordList & wl = theWordList(itl->first);
4714                 Private::Words::const_iterator it = (itl->second).begin();
4715                 Private::Words::const_iterator et = (itl->second).end();
4716                 for (; it != et; ++it)
4717                         wl.insert(*it);
4718         }
4719 }
4720
4721
4722 void Paragraph::updateWords()
4723 {
4724         deregisterWords();
4725         collectWords();
4726         registerWords();
4727 }
4728
4729
4730 void Paragraph::Private::appendSkipPosition(SkipPositions & skips, pos_type const pos) const
4731 {
4732         SkipPositionsIterator begin = skips.begin();
4733         SkipPositions::iterator end = skips.end();
4734         if (pos > 0 && begin < end) {
4735                 --end;
4736                 if (end->last == pos - 1) {
4737                         end->last = pos;
4738                         return;
4739                 }
4740         }
4741         skips.insert(end, FontSpan(pos, pos));
4742 }
4743
4744
4745 Language * Paragraph::Private::locateSpellRange(
4746         pos_type & from, pos_type & to,
4747         SkipPositions & skips) const
4748 {
4749         // skip leading white space
4750         while (from < to && owner_->isWordSeparator(from))
4751                 ++from;
4752         // don't check empty range
4753         if (from >= to)
4754                 return nullptr;
4755         // get current language
4756         Language * lang = getSpellLanguage(from);
4757         pos_type last = from;
4758         bool samelang = true;
4759         bool sameinset = true;
4760         while (last < to && samelang && sameinset) {
4761                 // hop to end of word
4762                 while (last < to && !owner_->isWordSeparator(last)) {
4763                         Inset const * inset = owner_->getInset(last);
4764                         if (inset && dynamic_cast<const InsetSpecialChar *>(inset)) {
4765                                 // check for "invisible" letters such as ligature breaks
4766                                 odocstringstream os;
4767                                 inset->toString(os);
4768                                 if (os.str().length() != 0) {
4769                                         // avoid spell check of visible special char insets
4770                                         // stop the loop in front of the special char inset
4771                                         sameinset = false;
4772                                         break;
4773                                 }
4774                         } else if (inset) {
4775                                 appendSkipPosition(skips, last);
4776                         } else if (owner_->isDeleted(last)) {
4777                                 appendSkipPosition(skips, last);
4778                         }
4779                         ++last;
4780                 }
4781                 // hop to next word while checking for insets
4782                 while (sameinset && last < to && owner_->isWordSeparator(last)) {
4783                         if (Inset const * inset = owner_->getInset(last))
4784                                 sameinset = inset->isChar() && inset->isLetter();
4785                         if (sameinset && owner_->isDeleted(last)) {
4786                                 appendSkipPosition(skips, last);
4787                         }
4788                         if (sameinset)
4789                                 last++;
4790                 }
4791                 if (sameinset && last < to) {
4792                         // now check for language change
4793                         samelang = lang == getSpellLanguage(last);
4794                 }
4795         }
4796         // if language change detected backstep is needed
4797         if (!samelang)
4798                 --last;
4799         to = last;
4800         return lang;
4801 }
4802
4803
4804 Language * Paragraph::Private::getSpellLanguage(pos_type const from) const
4805 {
4806         Language * lang =
4807                 const_cast<Language *>(owner_->getFontSettings(
4808                         inset_owner_->buffer().params(), from).language());
4809         if (lang == inset_owner_->buffer().params().language
4810                 && !lyxrc.spellchecker_alt_lang.empty()) {
4811                 string lang_code;
4812                 string const lang_variety =
4813                         split(lyxrc.spellchecker_alt_lang, lang_code, '-');
4814                 lang->setCode(lang_code);
4815                 lang->setVariety(lang_variety);
4816         }
4817         return lang;
4818 }
4819
4820
4821 void Paragraph::requestSpellCheck(pos_type pos)
4822 {
4823         d->requestSpellCheck(pos);
4824 }
4825
4826
4827 bool Paragraph::needsSpellCheck() const
4828 {
4829         SpellChecker::ChangeNumber speller_change_number = 0;
4830         if (theSpellChecker())
4831                 speller_change_number = theSpellChecker()->changeNumber();
4832         if (speller_change_number > d->speller_state_.currentChangeNumber()) {
4833                 d->speller_state_.needsCompleteRefresh(speller_change_number);
4834         }
4835         return d->needsSpellCheck();
4836 }
4837
4838
4839 bool Paragraph::Private::ignoreWord(docstring const & word) const
4840 {
4841         // Ignore words with digits
4842         // FIXME: make this customizable
4843         // (note that some checkers ignore words with digits by default)
4844         docstring::const_iterator cit = word.begin();
4845         docstring::const_iterator const end = word.end();
4846         for (; cit != end; ++cit) {
4847                 if (isNumber((*cit)))
4848                         return true;
4849         }
4850         return false;
4851 }
4852
4853
4854 SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to,
4855         WordLangTuple & wl, docstring_list & suggestions,
4856         bool do_suggestion, bool check_learned) const
4857 {
4858         SpellChecker::Result result = SpellChecker::WORD_OK;
4859         SpellChecker * speller = theSpellChecker();
4860         if (!speller)
4861                 return result;
4862
4863         if (!d->layout_->spellcheck || !inInset().allowSpellCheck())
4864                 return result;
4865
4866         locateWord(from, to, WHOLE_WORD, true);
4867         if (from == to || from >= size())
4868                 return result;
4869
4870         docstring word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE);
4871         Language * lang = d->getSpellLanguage(from);
4872
4873         BufferParams const & bparams = d->inset_owner_->buffer().params();
4874
4875         if (getFontSettings(bparams, from).fontInfo().nospellcheck() == FONT_ON)
4876                 return result;
4877
4878         wl = WordLangTuple(word, lang);
4879
4880         if (word.empty())
4881                 return result;
4882
4883         if (needsSpellCheck() || check_learned) {
4884                 pos_type end = to;
4885                 if (!d->ignoreWord(word)) {
4886                         bool const trailing_dot = to < size() && d->text_[to] == '.';
4887                         result = speller->check(wl, bparams.spellignore());
4888                         if (SpellChecker::misspelled(result) && trailing_dot) {
4889                                 wl = WordLangTuple(word.append(from_ascii(".")), lang);
4890                                 result = speller->check(wl, bparams.spellignore());
4891                                 if (!SpellChecker::misspelled(result)) {
4892                                         LYXERR(Debug::GUI, "misspelled word is correct with dot: \"" <<
4893                                            word << "\" [" <<
4894                                            from << ".." << to << "]");
4895                                 } else {
4896                                         // spell check with dot appended failed too
4897                                         // restore original word/lang value
4898                                         word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE);
4899                                         wl = WordLangTuple(word, lang);
4900                                 }
4901                         }
4902                 }
4903                 if (!SpellChecker::misspelled(result)) {
4904                         // area up to the begin of the next word is not misspelled
4905                         while (end < size() && isWordSeparator(end))
4906                                 ++end;
4907                 }
4908                 d->setMisspelled(from, end, result);
4909         } else {
4910                 result = d->speller_state_.getState(from);
4911         }
4912
4913         if (do_suggestion)
4914                 suggestions.clear();
4915
4916         if (SpellChecker::misspelled(result)) {
4917                 LYXERR(Debug::GUI, "misspelled word: \"" <<
4918                            word << "\" [" <<
4919                            from << ".." << to << "]");
4920                 if (do_suggestion)
4921                         speller->suggest(wl, suggestions);
4922         }
4923         return result;
4924 }
4925
4926
4927 void Paragraph::anonymize()
4928 {
4929         // This is a very crude anonymization for now
4930         for (char_type & c : d->text_)
4931                 if (isLetterChar(c) || isNumber(c))
4932                         c = 'a';
4933 }
4934
4935
4936 void Paragraph::Private::markMisspelledWords(
4937         Language const * lang,
4938         pos_type const & first, pos_type const & last,
4939         SpellChecker::Result result,
4940         docstring const & word,
4941         SkipPositions const & skips)
4942 {
4943         if (!SpellChecker::misspelled(result)) {
4944                 setMisspelled(first, last, SpellChecker::WORD_OK);
4945                 return;
4946         }
4947         int snext = first;
4948         SpellChecker * speller = theSpellChecker();
4949         // locate and enumerate the error positions
4950         int nerrors = speller->numMisspelledWords();
4951         int numskipped = 0;
4952         SkipPositionsIterator it = skips.begin();
4953         SkipPositionsIterator et = skips.end();
4954         for (int index = 0; index < nerrors; ++index) {
4955                 int wstart;
4956                 int wlen = 0;
4957                 speller->misspelledWord(index, wstart, wlen);
4958                 /// should not happen if speller supports range checks
4959                 if (!wlen)
4960                         continue;
4961                 WordLangTuple const candidate(word.substr(wstart, wlen), lang);
4962                 wstart += first + numskipped;
4963                 if (snext < wstart) {
4964                         /// mark the range of correct spelling
4965                         numskipped += countSkips(it, et, wstart);
4966                         setMisspelled(snext,
4967                                 wstart - 1, SpellChecker::WORD_OK);
4968                 }
4969                 snext = wstart + wlen;
4970                 // Check whether the candidate is in the document's local dict
4971                 SpellChecker::Result actresult = result;
4972                 if (inset_owner_->buffer().params().spellignored(candidate))
4973                         actresult = SpellChecker::DOCUMENT_LEARNED_WORD;
4974                 numskipped += countSkips(it, et, snext);
4975                 /// mark the range of misspelling
4976                 setMisspelled(wstart, snext, actresult);
4977                 if (actresult == SpellChecker::DOCUMENT_LEARNED_WORD)
4978                         LYXERR(Debug::GUI, "local dictionary word: \"" <<
4979                                    candidate.word() << "\" [" <<
4980                                    wstart << ".." << (snext-1) << "]");
4981                 else
4982                         LYXERR(Debug::GUI, "misspelled word: \"" <<
4983                                    candidate.word() << "\" [" <<
4984                                    wstart << ".." << (snext-1) << "]");
4985                 ++snext;
4986         }
4987         if (snext <= last) {
4988                 /// mark the range of correct spelling at end
4989                 setMisspelled(snext, last, SpellChecker::WORD_OK);
4990         }
4991 }
4992
4993
4994 void Paragraph::spellCheck() const
4995 {
4996         SpellChecker * speller = theSpellChecker();
4997         if (!speller || empty() ||!needsSpellCheck())
4998                 return;
4999         pos_type start;
5000         pos_type endpos;
5001         d->rangeOfSpellCheck(start, endpos);
5002         if (speller->canCheckParagraph()) {
5003                 // loop until we leave the range
5004                 for (pos_type first = start; first < endpos; ) {
5005                         pos_type last = endpos;
5006                         Private::SkipPositions skips;
5007                         Language * lang = d->locateSpellRange(first, last, skips);
5008                         if (first >= endpos)
5009                                 break;
5010                         // start the spell checker on the unit of meaning
5011                         docstring word = asString(first, last, AS_STR_INSETS + AS_STR_SKIPDELETE);
5012                         WordLangTuple wl = WordLangTuple(word, lang);
5013                         BufferParams const & bparams = d->inset_owner_->buffer().params();
5014                         SpellChecker::Result result = !word.empty() ?
5015                                 speller->check(wl, bparams.spellignore()) : SpellChecker::WORD_OK;
5016                         d->markMisspelledWords(lang, first, last, result, word, skips);
5017                         first = ++last;
5018                 }
5019         } else {
5020                 static docstring_list suggestions;
5021                 pos_type to = endpos;
5022                 while (start < endpos) {
5023                         WordLangTuple wl;
5024                         spellCheck(start, to, wl, suggestions, false);
5025                         start = to + 1;
5026                 }
5027         }
5028         d->readySpellCheck();
5029 }
5030
5031
5032 bool Paragraph::isMisspelled(pos_type pos, bool check_boundary) const
5033 {
5034         bool result = SpellChecker::misspelled(d->speller_state_.getState(pos));
5035         if (result || pos <= 0 || pos > size())
5036                 return result;
5037         if (check_boundary && (pos == size() || isWordSeparator(pos)))
5038                 result = SpellChecker::misspelled(d->speller_state_.getState(pos - 1));
5039         return result;
5040 }
5041
5042
5043 string Paragraph::magicLabel() const
5044 {
5045         stringstream ss;
5046         ss << "magicparlabel-" << id();
5047         return ss.str();
5048 }
5049
5050
5051 } // namespace lyx