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