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