]> git.lyx.org Git - features.git/blob - src/paragraph_pimpl.C
Prevent invalid latex for multilingual sections and inputenc == auto.
[features.git] / src / paragraph_pimpl.C
1 /**
2  * \file paragraph_pimpl.C
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author John Levon
9  * \author André Pönitz
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
14 #include <config.h>
15
16 #include "paragraph_pimpl.h"
17 #include "paragraph.h"
18
19 #include "bufferparams.h"
20 #include "debug.h"
21 #include "encoding.h"
22 #include "language.h"
23 #include "LaTeXFeatures.h"
24 #include "LColor.h"
25 #include "lyxlength.h"
26 #include "lyxrc.h"
27 #include "outputparams.h"
28 #include "texrow.h"
29
30 #include <boost/next_prior.hpp>
31
32
33 namespace lyx {
34
35 using std::endl;
36 using std::upper_bound;
37 using std::lower_bound;
38 using std::string;
39
40
41 // Initialization of the counter for the paragraph id's,
42 unsigned int Paragraph::Pimpl::paragraph_id = 0;
43
44 namespace {
45
46 struct special_phrase {
47         string phrase;
48         docstring macro;
49         bool builtin;
50 };
51
52 special_phrase const special_phrases[] = {
53         { "LyX", from_ascii("\\LyX{}"), false },
54         { "TeX", from_ascii("\\TeX{}"), true },
55         { "LaTeX2e", from_ascii("\\LaTeXe{}"), true },
56         { "LaTeX", from_ascii("\\LaTeX{}"), true },
57 };
58
59 size_t const phrases_nr = sizeof(special_phrases)/sizeof(special_phrase);
60
61 } // namespace anon
62
63
64 Paragraph::Pimpl::Pimpl(Paragraph * owner)
65         : owner_(owner)
66 {
67         inset_owner = 0;
68         id_ = paragraph_id++;
69 }
70
71
72 Paragraph::Pimpl::Pimpl(Pimpl const & p, Paragraph * owner)
73         : params(p.params), changes_(p.changes_), owner_(owner)
74 {
75         inset_owner = p.inset_owner;
76         fontlist = p.fontlist;
77         id_ = paragraph_id++;
78 }
79
80
81 bool Paragraph::Pimpl::isChanged(pos_type start, pos_type end) const
82 {
83         BOOST_ASSERT(start >= 0 && start <= size());
84         BOOST_ASSERT(end > start && end <= size() + 1);
85
86         return changes_.isChanged(start, end);
87 }
88
89
90 bool Paragraph::Pimpl::isMergedOnEndOfParDeletion(bool trackChanges) const {
91         // keep the logic here in sync with the logic of eraseChars()
92
93         if (!trackChanges) {
94                 return true;
95         }
96
97         Change change = changes_.lookup(size());
98
99         return change.type == Change::INSERTED && change.author == 0;
100 }
101
102
103 void Paragraph::Pimpl::setChange(Change const & change)
104 {
105         // beware of the imaginary end-of-par character!
106         changes_.set(change, 0, size() + 1);
107
108         /*
109          * Propagate the change recursively - but not in case of DELETED!
110          *
111          * Imagine that your co-author makes changes in an existing inset. He
112          * sends your document to you and you come to the conclusion that the
113          * inset should go completely. If you erase it, LyX must not delete all
114          * text within the inset. Otherwise, the change tracked insertions of
115          * your co-author get lost and there is no way to restore them later.
116          *
117          * Conclusion: An inset's content should remain untouched if you delete it
118          */
119
120         if (change.type != Change::DELETED) {
121                 for (pos_type pos = 0; pos < size(); ++pos) {
122                         if (owner_->isInset(pos)) {
123                                 owner_->getInset(pos)->setChange(change);
124                         }
125                 }
126         }
127 }
128
129
130 void Paragraph::Pimpl::setChange(pos_type pos, Change const & change)
131 {
132         BOOST_ASSERT(pos >= 0 && pos <= size());
133
134         changes_.set(change, pos);
135
136         // see comment in setChange(Change const &) above
137
138         if (change.type != Change::DELETED &&
139             pos < size() && owner_->isInset(pos)) {
140                 owner_->getInset(pos)->setChange(change);
141         }
142 }
143
144
145 Change const & Paragraph::Pimpl::lookupChange(pos_type pos) const
146 {
147         BOOST_ASSERT(pos >= 0 && pos <= size());
148
149         return changes_.lookup(pos);
150 }
151
152
153 void Paragraph::Pimpl::acceptChanges(BufferParams const & bparams, pos_type start, pos_type end)
154 {
155         BOOST_ASSERT(start >= 0 && start <= size());
156         BOOST_ASSERT(end > start && end <= size() + 1);
157
158         for (pos_type pos = start; pos < end; ++pos) {
159                 switch (lookupChange(pos).type) {
160                         case Change::UNCHANGED:
161                                 // accept changes in nested inset
162                                 if (pos < size() && owner_->isInset(pos)) {
163                                         owner_->getInset(pos)->acceptChanges(bparams);
164                                 }
165
166                                 break;
167
168                         case Change::INSERTED:
169                                 changes_.set(Change(Change::UNCHANGED), pos);
170                                 // also accept changes in nested inset
171                                 if (pos < size() && owner_->isInset(pos)) {
172                                         owner_->getInset(pos)->acceptChanges(bparams);
173                                 }
174                                 break;
175
176                         case Change::DELETED:
177                                 // Suppress access to non-existent
178                                 // "end-of-paragraph char"
179                                 if (pos < size()) {
180                                         eraseChar(pos, false);
181                                         --end;
182                                         --pos;
183                                 }
184                                 break;
185                 }
186
187         }
188 }
189
190
191 void Paragraph::Pimpl::rejectChanges(BufferParams const & bparams, pos_type start, pos_type end)
192 {
193         BOOST_ASSERT(start >= 0 && start <= size());
194         BOOST_ASSERT(end > start && end <= size() + 1);
195
196         for (pos_type pos = start; pos < end; ++pos) {
197                 switch (lookupChange(pos).type) {
198                         case Change::UNCHANGED:
199                                 // reject changes in nested inset
200                                 if (pos < size() && owner_->isInset(pos)) {
201                                         owner_->getInset(pos)->rejectChanges(bparams);
202                                 }
203                                 break;
204
205                         case Change::INSERTED:
206                                 // Suppress access to non-existent
207                                 // "end-of-paragraph char"
208                                 if (pos < size()) {
209                                         eraseChar(pos, false);
210                                         --end;
211                                         --pos;
212                                 }
213                                 break;
214
215                         case Change::DELETED:
216                                 changes_.set(Change(Change::UNCHANGED), pos);
217
218                                 // Do NOT reject changes within a deleted inset!
219                                 // There may be insertions of a co-author inside of it!
220
221                                 break;
222                 }
223         }
224 }
225
226
227 Paragraph::value_type Paragraph::Pimpl::getChar(pos_type pos) const
228 {
229         BOOST_ASSERT(pos >= 0 && pos <= size());
230
231         return owner_->getChar(pos);
232 }
233
234
235 void Paragraph::Pimpl::insertChar(pos_type pos, value_type c, Change const & change)
236 {
237         BOOST_ASSERT(pos >= 0 && pos <= size());
238
239         // track change
240         changes_.insert(change, pos);
241
242         // This is actually very common when parsing buffers (and
243         // maybe inserting ascii text)
244         if (pos == size()) {
245                 // when appending characters, no need to update tables
246                 owner_->text_.push_back(c);
247                 return;
248         }
249
250         owner_->text_.insert(owner_->text_.begin() + pos, c);
251
252         // Update the font table.
253         FontTable search_font(pos, LyXFont());
254         for (FontList::iterator it 
255               = lower_bound(fontlist.begin(), fontlist.end(), search_font, matchFT());
256              it != fontlist.end(); ++it)
257         {
258                 it->pos(it->pos() + 1);
259         }
260
261         // Update the insets
262         owner_->insetlist.increasePosAfterPos(pos);
263 }
264
265
266 void Paragraph::Pimpl::insertInset(pos_type pos, InsetBase * inset,
267                                    Change const & change)
268 {
269         BOOST_ASSERT(inset);
270         BOOST_ASSERT(pos >= 0 && pos <= size());
271
272         insertChar(pos, META_INSET, change);
273         BOOST_ASSERT(owner_->text_[pos] == META_INSET);
274
275         // Add a new entry in the insetlist.
276         owner_->insetlist.insert(inset, pos);
277 }
278
279
280 bool Paragraph::Pimpl::eraseChar(pos_type pos, bool trackChanges)
281 {
282         BOOST_ASSERT(pos >= 0 && pos <= size());
283
284         // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion()
285
286         if (trackChanges) {
287                 Change change = changes_.lookup(pos);
288
289                 // set the character to DELETED if 
290                 //  a) it was previously unchanged or
291                 //  b) it was inserted by a co-author
292
293                 if (change.type == Change::UNCHANGED ||
294                     (change.type == Change::INSERTED && change.author != 0)) {
295                         setChange(pos, Change(Change::DELETED));
296                         return false;
297                 }
298
299                 if (change.type == Change::DELETED)
300                         return false;
301         }
302
303         // Don't physically access the imaginary end-of-paragraph character.
304         // eraseChar() can only mark it as DELETED. A physical deletion of
305         // end-of-par must be handled externally.
306         if (pos == size()) {
307                 return false;
308         }
309
310         // track change
311         changes_.erase(pos);
312
313         // if it is an inset, delete the inset entry
314         if (owner_->text_[pos] == Paragraph::META_INSET) {
315                 owner_->insetlist.erase(pos);
316         }
317
318         owner_->text_.erase(owner_->text_.begin() + pos);
319
320         // Erase entries in the tables.
321         FontTable search_font(pos, LyXFont());
322
323         FontList::iterator it =
324                 lower_bound(fontlist.begin(),
325                             fontlist.end(),
326                             search_font, matchFT());
327         if (it != fontlist.end() && it->pos() == pos &&
328             (pos == 0 ||
329              (it != fontlist.begin()
330               && boost::prior(it)->pos() == pos - 1))) {
331                 // If it is a multi-character font
332                 // entry, we just make it smaller
333                 // (see update below), otherwise we
334                 // should delete it.
335                 unsigned int const i = it - fontlist.begin();
336                 fontlist.erase(fontlist.begin() + i);
337                 it = fontlist.begin() + i;
338                 if (i > 0 && i < fontlist.size() &&
339                     fontlist[i - 1].font() == fontlist[i].font()) {
340                         fontlist.erase(fontlist.begin() + i - 1);
341                         it = fontlist.begin() + i - 1;
342                 }
343         }
344
345         // Update all other entries
346         FontList::iterator fend = fontlist.end();
347         for (; it != fend; ++it)
348                 it->pos(it->pos() - 1);
349
350         // Update the insetlist
351         owner_->insetlist.decreasePosAfterPos(pos);
352
353         return true;
354 }
355
356
357 int Paragraph::Pimpl::eraseChars(pos_type start, pos_type end, bool trackChanges)
358 {
359         BOOST_ASSERT(start >= 0 && start <= size());
360         BOOST_ASSERT(end >= start && end <= size() + 1);
361
362         pos_type i = start;
363         for (pos_type count = end - start; count; --count) {
364                 if (!eraseChar(i, trackChanges))
365                         ++i;
366         }
367         return end - i;
368 }
369
370
371 int Paragraph::Pimpl::latexSurrogatePair(odocstream & os, value_type c,
372                 value_type next, Encoding const & encoding)
373 {
374         // Writing next here may circumvent a possible font change between
375         // c and next. Since next is only output if it forms a surrogate pair
376         // with c we can ignore this:
377         // A font change inside a surrogate pair does not make sense and is
378         // hopefully impossible to input.
379         // FIXME: change tracking
380         // Is this correct WRT change tracking?
381         docstring const latex1 = encoding.latexChar(next);
382         docstring const latex2 = encoding.latexChar(c);
383         os << latex1 << '{' << latex2 << '}';
384         return latex1.length() + latex2.length() + 2;
385 }
386
387
388 bool Paragraph::Pimpl::simpleTeXBlanks(BufferParams const & bparams,
389                                        Encoding const & encoding,
390                                        odocstream & os, TexRow & texrow,
391                                        pos_type & i,
392                                        unsigned int & column,
393                                        LyXFont const & font,
394                                        LyXLayout const & style)
395 {
396         if (style.pass_thru)
397                 return false;
398
399         if (i < size() - 1) {
400                 char_type next = getChar(i + 1);
401                 if (Encodings::isCombiningChar(next)) {
402                         // This space has an accent, so we must always output it.
403                         column += latexSurrogatePair(os, ' ', next, encoding) - 1;
404                         ++i;
405                         return true;
406                 }
407         }
408
409         if (lyxrc.plaintext_linelen > 0
410             && column > lyxrc.plaintext_linelen
411             && i
412             && getChar(i - 1) != ' '
413             && (i < size() - 1)
414             // same in FreeSpacing mode
415             && !owner_->isFreeSpacing()
416             // In typewriter mode, we want to avoid
417             // ! . ? : at the end of a line
418             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
419                  && (getChar(i - 1) == '.'
420                      || getChar(i - 1) == '?'
421                      || getChar(i - 1) == ':'
422                      || getChar(i - 1) == '!'))) {
423                 os << '\n';
424                 texrow.newline();
425                 texrow.start(owner_->id(), i + 1);
426                 column = 0;
427         } else if (style.free_spacing) {
428                 os << '~';
429         } else {
430                 os << ' ';
431         }
432         return false;
433 }
434
435
436 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
437 {
438         pos_type const len = str.length();
439
440         // is the paragraph large enough?
441         if (pos + len > size())
442                 return false;
443
444         // does the wanted text start at point?
445         for (string::size_type i = 0; i < str.length(); ++i) {
446                 // Caution: direct comparison of characters works only
447                 // because str is pure ASCII.
448                 if (str[i] != owner_->text_[pos + i])
449                         return false;
450         }
451
452         // is there a font change in middle of the word?
453         FontList::const_iterator cit = fontlist.begin();
454         FontList::const_iterator end = fontlist.end();
455         for (; cit != end; ++cit) {
456                 if (cit->pos() >= pos)
457                         break;
458         }
459         if (cit != end && pos + len - 1 > cit->pos())
460                 return false;
461
462         return true;
463 }
464
465
466 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
467                                              BufferParams const & bparams,
468                                              odocstream & os,
469                                              TexRow & texrow,
470                                              OutputParams const & runparams,
471                                              LyXFont & running_font,
472                                              LyXFont & basefont,
473                                              LyXFont const & outerfont,
474                                              bool & open_font,
475                                              Change::Type & running_change,
476                                              LyXLayout const & style,
477                                              pos_type & i,
478                                              unsigned int & column,
479                                              value_type const c)
480 {
481         if (style.pass_thru) {
482                 if (c != Paragraph::META_INSET) {
483                         if (c != '\0')
484                                 // FIXME UNICODE: This can fail if c cannot
485                                 // be encoded in the current encoding.
486                                 os.put(c);
487                 } else
488                         owner_->getInset(i)->plaintext(buf, os, runparams);
489                 return;
490         }
491
492         // Two major modes:  LaTeX or plain
493         // Handle here those cases common to both modes
494         // and then split to handle the two modes separately.
495         switch (c) {
496         case Paragraph::META_INSET: {
497                 InsetBase * inset = owner_->getInset(i);
498
499                 // FIXME: remove this check
500                 if (!inset)
501                         break;
502
503                 // FIXME: move this to InsetNewline::latex
504                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
505                         // newlines are handled differently here than
506                         // the default in simpleTeXSpecialChars().
507                         if (!style.newline_allowed) {
508                                 os << '\n';
509                         } else {
510                                 if (open_font) {
511                                         column += running_font.latexWriteEndChanges(
512                                                 os, basefont, basefont);
513                                         open_font = false;
514                                 }
515
516                                 if (running_font.family() == LyXFont::TYPEWRITER_FAMILY)
517                                         os << '~';
518
519                                 basefont = owner_->getLayoutFont(bparams, outerfont);
520                                 running_font = basefont;
521
522                                 if (runparams.moving_arg)
523                                         os << "\\protect ";
524
525                                 os << "\\\\\n";
526                         }
527                         texrow.newline();
528                         texrow.start(owner_->id(), i + 1);
529                         column = 0;
530                         break;
531                 }
532
533                 // output change tracking marks only if desired,
534                 // if dvipost is installed,
535                 // and with dvi/ps (other formats don't work)
536                 LaTeXFeatures features(buf, bparams, runparams);
537                 bool const output = bparams.outputChanges
538                         && runparams.flavor == OutputParams::LATEX
539                         && features.isAvailable("dvipost");
540
541                 if (inset->canTrackChanges()) {
542                         column += Changes::latexMarkChange(os, running_change,
543                                 Change::UNCHANGED, output);
544                         running_change = Change::UNCHANGED;
545                 }
546
547                 bool close = false;
548                 odocstream::pos_type const len = os.tellp();
549
550                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
551                      || inset->lyxCode() == InsetBase::MATH_CODE
552                      || inset->lyxCode() == InsetBase::URL_CODE)
553                     && running_font.isRightToLeft()) {
554                         os << "\\L{";
555                         close = true;
556                 }
557
558 #ifdef WITH_WARNINGS
559 #warning Bug: we can have an empty font change here!
560 // if there has just been a font change, we are going to close it
561 // right now, which means stupid latex code like \textsf{}. AFAIK,
562 // this does not harm dvi output. A minor bug, thus (JMarc)
563 #endif
564                 // some insets cannot be inside a font change command
565                 if (open_font && inset->noFontChange()) {
566                         column += running_font.latexWriteEndChanges(
567                                         os, basefont, basefont);
568                         open_font = false;
569                         basefont = owner_->getLayoutFont(bparams, outerfont);
570                         running_font = basefont;
571                 }
572
573                 int tmp = inset->latex(buf, os, runparams);
574
575                 if (close)
576                         os << '}';
577
578                 if (tmp) {
579                         for (int j = 0; j < tmp; ++j) {
580                                 texrow.newline();
581                         }
582                         texrow.start(owner_->id(), i + 1);
583                         column = 0;
584                 } else {
585                         column += os.tellp() - len;
586                 }
587         }
588         break;
589
590         default:
591                 // And now for the special cases within each mode
592
593                 switch (c) {
594                 case '\\':
595                         os << "\\textbackslash{}";
596                         column += 15;
597                         break;
598
599                 // The following characters could be written literally in latin1, but they
600                 // would be wrongly converted on systems where char is signed, so we give
601                 // the code points.
602                 // This also makes us independant from the encoding of this source file.
603                 case '|': case '<': case '>':
604                         // In T1 encoding, these characters exist
605                         if (lyxrc.fontenc == "T1") {
606                                 os.put(c);
607                                 //... but we should avoid ligatures
608                                 if ((c == '>' || c == '<')
609                                     && i <= size() - 2
610                                     && getChar(i + 1) == c) {
611                                         //os << "\\textcompwordmark{}";
612                                         //column += 19;
613                                         // Jean-Marc, have a look at
614                                         // this. I think this works
615                                         // equally well:
616                                         os << "\\,{}";
617                                         // Lgb
618                                         column += 3;
619                                 }
620                                 break;
621                         }
622                         // Typewriter font also has them
623                         if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
624                                 os.put(c);
625                                 break;
626                         }
627                         // Otherwise, we use what LaTeX
628                         // provides us.
629                         switch (c) {
630                         case '<':
631                                 os << "\\textless{}";
632                                 column += 10;
633                                 break;
634                         case '>':
635                                 os << "\\textgreater{}";
636                                 column += 13;
637                                 break;
638                         case '|':
639                                 os << "\\textbar{}";
640                                 column += 9;
641                                 break;
642                         }
643                         break;
644
645                 case '-': // "--" in Typewriter mode -> "-{}-"
646                         if (i <= size() - 2 &&
647                             getChar(i + 1) == '-' &&
648                             running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
649                                 os << "-{}";
650                                 column += 2;
651                         } else {
652                                 os << '-';
653                         }
654                         break;
655
656                 case '\"':
657                         os << "\\char`\\\"{}";
658                         column += 9;
659                         break;
660
661                 case '$': case '&':
662                 case '%': case '#': case '{':
663                 case '}': case '_':
664                         os << '\\';
665                         os.put(c);
666                         column += 1;
667                         break;
668
669                 case '~':
670                         os << "\\textasciitilde{}";
671                         column += 16;
672                         break;
673
674                 case '^':
675                         os << "\\textasciicircum{}";
676                         column += 17;
677                         break;
678
679                 case '*': case '[':
680                         // avoid being mistaken for optional arguments
681                         os << '{';
682                         os.put(c);
683                         os << '}';
684                         column += 2;
685                         break;
686
687                 case ' ':
688                         // Blanks are printed before font switching.
689                         // Sure? I am not! (try nice-latex)
690                         // I am sure it's correct. LyX might be smarter
691                         // in the future, but for now, nothing wrong is
692                         // written. (Asger)
693                         break;
694
695                 default:
696
697                         // I assume this is hack treating typewriter as verbatim
698                         // FIXME UNICODE: This can fail if c cannot be encoded
699                         // in the current encoding.
700                         if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
701                                 if (c != '\0') {
702                                         os.put(c);
703                                 }
704                                 break;
705                         }
706
707                         // LyX, LaTeX etc.
708
709                         // FIXME: if we have "LaTeX" with a font
710                         // change in the middle (before the 'T', then
711                         // the "TeX" part is still special cased.
712                         // Really we should only operate this on
713                         // "words" for some definition of word
714
715                         size_t pnr = 0;
716
717                         for (; pnr < phrases_nr; ++pnr) {
718                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
719                                         os << special_phrases[pnr].macro;
720                                         i += special_phrases[pnr].phrase.length() - 1;
721                                         column += special_phrases[pnr].macro.length() - 1;
722                                         break;
723                                 }
724                         }
725
726                         if (pnr == phrases_nr && c != '\0') {
727                                 Encoding const & encoding = *(runparams.encoding);
728                                 if (i < size() - 1) {
729                                         char_type next = getChar(i + 1);
730                                         if (Encodings::isCombiningChar(next)) {
731                                                 column += latexSurrogatePair(os, c, next, encoding) - 1;
732                                                 ++i;
733                                                 break;
734                                         }
735                                 }
736                                 docstring const latex = encoding.latexChar(c);
737                                 if (latex.length() > 1 &&
738                                     latex[latex.length() - 1] != '}') {
739                                         // Prevent eating of a following
740                                         // space or command corruption by
741                                         // following characters
742                                         column += latex.length() + 1;
743                                         os << latex << "{}";
744                                 } else {
745                                         column += latex.length() - 1;
746                                         os << latex;
747                                 }
748                         }
749                         break;
750                 }
751         }
752 }
753
754
755 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
756                                 LyXLayout const & layout) const
757 {
758         BufferParams const & bparams = features.bufferParams();
759
760         // check the params.
761         if (!params.spacing().isDefault())
762                 features.require("setspace");
763
764         // then the layouts
765         features.useLayout(layout.name());
766
767         // then the fonts
768         Language const * doc_language = bparams.language;
769
770         FontList::const_iterator fcit = fontlist.begin();
771         FontList::const_iterator fend = fontlist.end();
772         for (; fcit != fend; ++fcit) {
773                 if (fcit->font().noun() == LyXFont::ON) {
774                         LYXERR(Debug::LATEX) << "font.noun: "
775                                              << fcit->font().noun()
776                                              << endl;
777                         features.require("noun");
778                         LYXERR(Debug::LATEX) << "Noun enabled. Font: "
779                                              << to_utf8(fcit->font().stateText(0))
780                                              << endl;
781                 }
782                 switch (fcit->font().color()) {
783                 case LColor::none:
784                 case LColor::inherit:
785                 case LColor::ignore:
786                         // probably we should put here all interface colors used for
787                         // font displaying! For now I just add this ones I know of (Jug)
788                 case LColor::latex:
789                 case LColor::note:
790                         break;
791                 default:
792                         features.require("color");
793                         LYXERR(Debug::LATEX) << "Color enabled. Font: "
794                                              << to_utf8(fcit->font().stateText(0))
795                                              << endl;
796                 }
797
798                 Language const * language = fcit->font().language();
799                 if (language->babel() != doc_language->babel() &&
800                     language != ignore_language &&
801                     language != latex_language)
802                 {
803                         features.useLanguage(language);
804                         LYXERR(Debug::LATEX) << "Found language "
805                                              << language->babel() << endl;
806                 }
807         }
808
809         if (!params.leftIndent().zero())
810                 features.require("ParagraphLeftIndent");
811
812         // then the insets
813         InsetList::const_iterator icit = owner_->insetlist.begin();
814         InsetList::const_iterator iend = owner_->insetlist.end();
815         for (; icit != iend; ++icit) {
816                 if (icit->inset) {
817                         icit->inset->validate(features);
818                         if (layout.needprotect &&
819                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
820                                 features.require("NeedLyXFootnoteCode");
821                 }
822         }
823
824         // then the contents
825         for (pos_type i = 0; i < size() ; ++i) {
826                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
827                         if (!special_phrases[pnr].builtin
828                             && isTextAt(special_phrases[pnr].phrase, i)) {
829                                 features.require(special_phrases[pnr].phrase);
830                                 break;
831                         }
832                 }
833                 Encodings::validate(getChar(i), features);
834         }
835 }
836
837
838 } // namespace lyx