]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
Fix bug 2474; partial fix for 1777. Added last_reference_ member to QRef class and...
[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 } // 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(Encoding const & encoding,
389                                        odocstream & os, TexRow & texrow,
390                                        pos_type & i,
391                                        unsigned int & column,
392                                        LyXFont const & font,
393                                        LyXLayout const & style)
394 {
395         if (style.pass_thru)
396                 return false;
397
398         if (i < size() - 1) {
399                 char_type next = getChar(i + 1);
400                 if (Encodings::isCombiningChar(next)) {
401                         // This space has an accent, so we must always output it.
402                         column += latexSurrogatePair(os, ' ', next, encoding) - 1;
403                         ++i;
404                         return true;
405                 }
406         }
407
408         if (lyxrc.plaintext_linelen > 0
409             && column > lyxrc.plaintext_linelen
410             && i
411             && getChar(i - 1) != ' '
412             && (i < size() - 1)
413             // same in FreeSpacing mode
414             && !owner_->isFreeSpacing()
415             // In typewriter mode, we want to avoid
416             // ! . ? : at the end of a line
417             && !(font.family() == LyXFont::TYPEWRITER_FAMILY
418                  && (getChar(i - 1) == '.'
419                      || getChar(i - 1) == '?'
420                      || getChar(i - 1) == ':'
421                      || getChar(i - 1) == '!'))) {
422                 os << '\n';
423                 texrow.newline();
424                 texrow.start(owner_->id(), i + 1);
425                 column = 0;
426         } else if (style.free_spacing) {
427                 os << '~';
428         } else {
429                 os << ' ';
430         }
431         return false;
432 }
433
434
435 bool Paragraph::Pimpl::isTextAt(string const & str, pos_type pos) const
436 {
437         pos_type const len = str.length();
438
439         // is the paragraph large enough?
440         if (pos + len > size())
441                 return false;
442
443         // does the wanted text start at point?
444         for (string::size_type i = 0; i < str.length(); ++i) {
445                 // Caution: direct comparison of characters works only
446                 // because str is pure ASCII.
447                 if (str[i] != owner_->text_[pos + i])
448                         return false;
449         }
450
451         // is there a font change in middle of the word?
452         FontList::const_iterator cit = fontlist.begin();
453         FontList::const_iterator end = fontlist.end();
454         for (; cit != end; ++cit) {
455                 if (cit->pos() >= pos)
456                         break;
457         }
458         if (cit != end && pos + len - 1 > cit->pos())
459                 return false;
460
461         return true;
462 }
463
464
465 void Paragraph::Pimpl::simpleTeXSpecialChars(Buffer const & buf,
466                                              BufferParams const & bparams,
467                                              odocstream & os,
468                                              TexRow & texrow,
469                                              OutputParams const & runparams,
470                                              LyXFont & running_font,
471                                              LyXFont & basefont,
472                                              LyXFont const & outerfont,
473                                              bool & open_font,
474                                              Change::Type & running_change,
475                                              LyXLayout const & style,
476                                              pos_type & i,
477                                              unsigned int & column,
478                                              value_type const c)
479 {
480         if (style.pass_thru) {
481                 if (c != Paragraph::META_INSET) {
482                         if (c != '\0')
483                                 // FIXME UNICODE: This can fail if c cannot
484                                 // be encoded in the current encoding.
485                                 os.put(c);
486                 } else
487                         owner_->getInset(i)->plaintext(buf, os, runparams);
488                 return;
489         }
490
491         // Two major modes:  LaTeX or plain
492         // Handle here those cases common to both modes
493         // and then split to handle the two modes separately.
494         switch (c) {
495         case Paragraph::META_INSET: {
496                 InsetBase * inset = owner_->getInset(i);
497
498                 // FIXME: remove this check
499                 if (!inset)
500                         break;
501
502                 // FIXME: move this to InsetNewline::latex
503                 if (inset->lyxCode() == InsetBase::NEWLINE_CODE) {
504                         // newlines are handled differently here than
505                         // the default in simpleTeXSpecialChars().
506                         if (!style.newline_allowed) {
507                                 os << '\n';
508                         } else {
509                                 if (open_font) {
510                                         column += running_font.latexWriteEndChanges(
511                                                 os, basefont, basefont);
512                                         open_font = false;
513                                 }
514
515                                 if (running_font.family() == LyXFont::TYPEWRITER_FAMILY)
516                                         os << '~';
517
518                                 basefont = owner_->getLayoutFont(bparams, outerfont);
519                                 running_font = basefont;
520
521                                 if (runparams.moving_arg)
522                                         os << "\\protect ";
523
524                                 os << "\\\\\n";
525                         }
526                         texrow.newline();
527                         texrow.start(owner_->id(), i + 1);
528                         column = 0;
529                         break;
530                 }
531
532                 // output change tracking marks only if desired,
533                 // if dvipost is installed,
534                 // and with dvi/ps (other formats don't work)
535                 LaTeXFeatures features(buf, bparams, runparams);
536                 bool const output = bparams.outputChanges
537                         && runparams.flavor == OutputParams::LATEX
538                         && features.isAvailable("dvipost");
539
540                 if (inset->canTrackChanges()) {
541                         column += Changes::latexMarkChange(os, running_change,
542                                 Change::UNCHANGED, output);
543                         running_change = Change::UNCHANGED;
544                 }
545
546                 bool close = false;
547                 odocstream::pos_type const len = os.tellp();
548
549                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
550                      || inset->lyxCode() == InsetBase::MATH_CODE
551                      || inset->lyxCode() == InsetBase::URL_CODE)
552                     && running_font.isRightToLeft()) {
553                         os << "\\L{";
554                         close = true;
555                 }
556
557 #ifdef WITH_WARNINGS
558 #warning Bug: we can have an empty font change here!
559 // if there has just been a font change, we are going to close it
560 // right now, which means stupid latex code like \textsf{}. AFAIK,
561 // this does not harm dvi output. A minor bug, thus (JMarc)
562 #endif
563                 // some insets cannot be inside a font change command
564                 if (open_font && inset->noFontChange()) {
565                         column += running_font.latexWriteEndChanges(
566                                         os, basefont, basefont);
567                         open_font = false;
568                         basefont = owner_->getLayoutFont(bparams, outerfont);
569                         running_font = basefont;
570                 }
571
572                 int tmp = inset->latex(buf, os, runparams);
573
574                 if (close)
575                         os << '}';
576
577                 if (tmp) {
578                         for (int j = 0; j < tmp; ++j) {
579                                 texrow.newline();
580                         }
581                         texrow.start(owner_->id(), i + 1);
582                         column = 0;
583                 } else {
584                         column += os.tellp() - len;
585                 }
586         }
587         break;
588
589         default:
590                 // And now for the special cases within each mode
591
592                 switch (c) {
593                 case '\\':
594                         os << "\\textbackslash{}";
595                         column += 15;
596                         break;
597
598                 // The following characters could be written literally in latin1, but they
599                 // would be wrongly converted on systems where char is signed, so we give
600                 // the code points.
601                 // This also makes us independant from the encoding of this source file.
602                 case '|': case '<': case '>':
603                         // In T1 encoding, these characters exist
604                         if (lyxrc.fontenc == "T1") {
605                                 os.put(c);
606                                 //... but we should avoid ligatures
607                                 if ((c == '>' || c == '<')
608                                     && i <= size() - 2
609                                     && getChar(i + 1) == c) {
610                                         //os << "\\textcompwordmark{}";
611                                         //column += 19;
612                                         // Jean-Marc, have a look at
613                                         // this. I think this works
614                                         // equally well:
615                                         os << "\\,{}";
616                                         // Lgb
617                                         column += 3;
618                                 }
619                                 break;
620                         }
621                         // Typewriter font also has them
622                         if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
623                                 os.put(c);
624                                 break;
625                         }
626                         // Otherwise, we use what LaTeX
627                         // provides us.
628                         switch (c) {
629                         case '<':
630                                 os << "\\textless{}";
631                                 column += 10;
632                                 break;
633                         case '>':
634                                 os << "\\textgreater{}";
635                                 column += 13;
636                                 break;
637                         case '|':
638                                 os << "\\textbar{}";
639                                 column += 9;
640                                 break;
641                         }
642                         break;
643
644                 case '-': // "--" in Typewriter mode -> "-{}-"
645                         if (i <= size() - 2 &&
646                             getChar(i + 1) == '-' &&
647                             running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
648                                 os << "-{}";
649                                 column += 2;
650                         } else {
651                                 os << '-';
652                         }
653                         break;
654
655                 case '\"':
656                         os << "\\char`\\\"{}";
657                         column += 9;
658                         break;
659
660                 case '$': case '&':
661                 case '%': case '#': case '{':
662                 case '}': case '_':
663                         os << '\\';
664                         os.put(c);
665                         column += 1;
666                         break;
667
668                 case '~':
669                         os << "\\textasciitilde{}";
670                         column += 16;
671                         break;
672
673                 case '^':
674                         os << "\\textasciicircum{}";
675                         column += 17;
676                         break;
677
678                 case '*': case '[':
679                         // avoid being mistaken for optional arguments
680                         os << '{';
681                         os.put(c);
682                         os << '}';
683                         column += 2;
684                         break;
685
686                 case ' ':
687                         // Blanks are printed before font switching.
688                         // Sure? I am not! (try nice-latex)
689                         // I am sure it's correct. LyX might be smarter
690                         // in the future, but for now, nothing wrong is
691                         // written. (Asger)
692                         break;
693
694                 default:
695
696                         // I assume this is hack treating typewriter as verbatim
697                         // FIXME UNICODE: This can fail if c cannot be encoded
698                         // in the current encoding.
699                         if (running_font.family() == LyXFont::TYPEWRITER_FAMILY) {
700                                 if (c != '\0') {
701                                         os.put(c);
702                                 }
703                                 break;
704                         }
705
706                         // LyX, LaTeX etc.
707
708                         // FIXME: if we have "LaTeX" with a font
709                         // change in the middle (before the 'T', then
710                         // the "TeX" part is still special cased.
711                         // Really we should only operate this on
712                         // "words" for some definition of word
713
714                         size_t pnr = 0;
715
716                         for (; pnr < phrases_nr; ++pnr) {
717                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
718                                         os << special_phrases[pnr].macro;
719                                         i += special_phrases[pnr].phrase.length() - 1;
720                                         column += special_phrases[pnr].macro.length() - 1;
721                                         break;
722                                 }
723                         }
724
725                         if (pnr == phrases_nr && c != '\0') {
726                                 Encoding const & encoding = *(runparams.encoding);
727                                 if (i < size() - 1) {
728                                         char_type next = getChar(i + 1);
729                                         if (Encodings::isCombiningChar(next)) {
730                                                 column += latexSurrogatePair(os, c, next, encoding) - 1;
731                                                 ++i;
732                                                 break;
733                                         }
734                                 }
735                                 docstring const latex = encoding.latexChar(c);
736                                 if (latex.length() > 1 &&
737                                     latex[latex.length() - 1] != '}') {
738                                         // Prevent eating of a following
739                                         // space or command corruption by
740                                         // following characters
741                                         column += latex.length() + 1;
742                                         os << latex << "{}";
743                                 } else {
744                                         column += latex.length() - 1;
745                                         os << latex;
746                                 }
747                         }
748                         break;
749                 }
750         }
751 }
752
753
754 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
755                                 LyXLayout const & layout) const
756 {
757         BufferParams const & bparams = features.bufferParams();
758
759         // check the params.
760         if (!params.spacing().isDefault())
761                 features.require("setspace");
762
763         // then the layouts
764         features.useLayout(layout.name());
765
766         // then the fonts
767         Language const * doc_language = bparams.language;
768
769         FontList::const_iterator fcit = fontlist.begin();
770         FontList::const_iterator fend = fontlist.end();
771         for (; fcit != fend; ++fcit) {
772                 if (fcit->font().noun() == LyXFont::ON) {
773                         LYXERR(Debug::LATEX) << "font.noun: "
774                                              << fcit->font().noun()
775                                              << endl;
776                         features.require("noun");
777                         LYXERR(Debug::LATEX) << "Noun enabled. Font: "
778                                              << to_utf8(fcit->font().stateText(0))
779                                              << endl;
780                 }
781                 switch (fcit->font().color()) {
782                 case LColor::none:
783                 case LColor::inherit:
784                 case LColor::ignore:
785                         // probably we should put here all interface colors used for
786                         // font displaying! For now I just add this ones I know of (Jug)
787                 case LColor::latex:
788                 case LColor::note:
789                         break;
790                 default:
791                         features.require("color");
792                         LYXERR(Debug::LATEX) << "Color enabled. Font: "
793                                              << to_utf8(fcit->font().stateText(0))
794                                              << endl;
795                 }
796
797                 Language const * language = fcit->font().language();
798                 if (language->babel() != doc_language->babel() &&
799                     language != ignore_language &&
800                     language != latex_language)
801                 {
802                         features.useLanguage(language);
803                         LYXERR(Debug::LATEX) << "Found language "
804                                              << language->babel() << endl;
805                 }
806         }
807
808         if (!params.leftIndent().zero())
809                 features.require("ParagraphLeftIndent");
810
811         // then the insets
812         InsetList::const_iterator icit = owner_->insetlist.begin();
813         InsetList::const_iterator iend = owner_->insetlist.end();
814         for (; icit != iend; ++icit) {
815                 if (icit->inset) {
816                         icit->inset->validate(features);
817                         if (layout.needprotect &&
818                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
819                                 features.require("NeedLyXFootnoteCode");
820                 }
821         }
822
823         // then the contents
824         for (pos_type i = 0; i < size() ; ++i) {
825                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
826                         if (!special_phrases[pnr].builtin
827                             && isTextAt(special_phrases[pnr].phrase, i)) {
828                                 features.require(special_phrases[pnr].phrase);
829                                 break;
830                         }
831                 }
832                 Encodings::validate(getChar(i), features);
833         }
834 }
835
836
837 } // namespace lyx