]> git.lyx.org Git - lyx.git/blob - src/paragraph_pimpl.C
1a8cbfef35ce704358d6cb1a6e7b335ede563f08
[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(
487                                                 os, basefont, basefont, bparams);
488                                         open_font = false;
489                                 }
490                                 basefont = owner_->getLayoutFont(bparams, outerfont);
491                                 running_font = basefont;
492
493                                 if (font.family() == LyXFont::TYPEWRITER_FAMILY)
494                                         os << '~';
495
496                                 if (runparams.moving_arg)
497                                         os << "\\protect ";
498
499                                 os << "\\\\\n";
500                         }
501                         texrow.newline();
502                         texrow.start(owner_->id(), i + 1);
503                         column = 0;
504                         break;
505                 }
506
507                 // output change tracking marks only if desired,
508                 // if dvipost is installed,
509                 // and with dvi/ps (other formats don't work)
510                 LaTeXFeatures features(buf, bparams, runparams);
511                 bool const output = bparams.outputChanges
512                         && runparams.flavor == OutputParams::LATEX
513                         && features.isAvailable("dvipost");
514
515                 if (inset->canTrackChanges()) {
516                         column += Changes::latexMarkChange(os, running_change,
517                                 Change::UNCHANGED, output);
518                         running_change = Change::UNCHANGED;
519                 }
520
521                 bool close = false;
522                 odocstream::pos_type const len = os.tellp();
523
524                 if ((inset->lyxCode() == InsetBase::GRAPHICS_CODE
525                      || inset->lyxCode() == InsetBase::MATH_CODE
526                      || inset->lyxCode() == InsetBase::URL_CODE)
527                     && running_font.isRightToLeft()) {
528                         os << "\\L{";
529                         close = true;
530                 }
531
532 #ifdef WITH_WARNINGS
533 #warning Bug: we can have an empty font change here!
534 // if there has just been a font change, we are going to close it
535 // right now, which means stupid latex code like \textsf{}. AFAIK,
536 // this does not harm dvi output. A minor bug, thus (JMarc)
537 #endif
538                 // some insets cannot be inside a font change command
539                 if (open_font && inset->noFontChange()) {
540                         column += running_font.latexWriteEndChanges(
541                                         os, basefont, basefont, bparams);
542                         open_font = false;
543                         basefont = owner_->getLayoutFont(bparams, outerfont);
544                         running_font = basefont;
545                 }
546
547                 int tmp = inset->latex(buf, os, runparams);
548
549                 if (close)
550                         os << '}';
551
552                 if (tmp) {
553                         for (int j = 0; j < tmp; ++j) {
554                                 texrow.newline();
555                         }
556                         texrow.start(owner_->id(), i + 1);
557                         column = 0;
558                 } else {
559                         column += os.tellp() - len;
560                 }
561         }
562         break;
563
564         default:
565                 // And now for the special cases within each mode
566
567                 switch (c) {
568                 case '\\':
569                         os << "\\textbackslash{}";
570                         column += 15;
571                         break;
572
573                 // The following characters could be written literally in latin1, but they
574                 // would be wrongly converted on systems where char is signed, so we give
575                 // the code points.
576                 // This also makes us independant from the encoding of this source file.
577                 case 0xb1:    // ± PLUS-MINUS SIGN
578                 case 0xb2:    // ² SUPERSCRIPT TWO
579                 case 0xb3:    // ³ SUPERSCRIPT THREE
580                 case 0xd7:    // × MULTIPLICATION SIGN
581                 case 0xf7:    // ÷ DIVISION SIGN
582                 case 0xb9:    // ¹ SUPERSCRIPT ONE
583                 case 0xac:    // ¬ NOT SIGN
584                 case 0xb5:    // µ MICRO SIGN
585                         if (isEncoding(bparams, font, "latin1")
586                             || isEncoding(bparams, font, "latin9")) {
587                                 os << "\\ensuremath{";
588                                 os.put(c);
589                                 os << '}';
590                                 column += 13;
591                         } else {
592                                 os.put(c);
593                         }
594                         break;
595
596                 case '|': case '<': case '>':
597                         // In T1 encoding, these characters exist
598                         if (lyxrc.fontenc == "T1") {
599                                 os.put(c);
600                                 //... but we should avoid ligatures
601                                 if ((c == '>' || c == '<')
602                                     && i <= size() - 2
603                                     && getChar(i + 1) == c) {
604                                         //os << "\\textcompwordmark{}";
605                                         // Jean-Marc, have a look at
606                                         // this. I think this works
607                                         // equally well:
608                                         os << "\\,{}";
609                                         // Lgb
610                                         column += 19;
611                                 }
612                                 break;
613                         }
614                         // Typewriter font also has them
615                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
616                                 os.put(c);
617                                 break;
618                         }
619                         // Otherwise, we use what LaTeX
620                         // provides us.
621                         switch (c) {
622                         case '<':
623                                 os << "\\textless{}";
624                                 column += 10;
625                                 break;
626                         case '>':
627                                 os << "\\textgreater{}";
628                                 column += 13;
629                                 break;
630                         case '|':
631                                 os << "\\textbar{}";
632                                 column += 9;
633                                 break;
634                         }
635                         break;
636
637                 case '-': // "--" in Typewriter mode -> "-{}-"
638                         if (i <= size() - 2
639                             && getChar(i + 1) == '-'
640                             && font.family() == LyXFont::TYPEWRITER_FAMILY) {
641                                 os << "-{}";
642                                 column += 2;
643                         } else {
644                                 os << '-';
645                         }
646                         break;
647
648                 case '\"':
649                         os << "\\char`\\\"{}";
650                         column += 9;
651                         break;
652
653                 case 0xa3:    // £ POUND SIGN
654                         if (bparams.inputenc == "default") {
655                                 os << "\\pounds{}";
656                                 column += 8;
657                         } else {
658                                 os.put(c);
659                         }
660                         break;
661
662                 case 0x20ac:    // EURO SIGN
663                         if (isEncoding(bparams, font, "latin9")
664                             || isEncoding(bparams, font, "cp1251")
665                             || isEncoding(bparams, font, "utf8")
666                             || isEncoding(bparams, font, "latin10")
667                             || isEncoding(bparams, font, "cp858")) {
668                                 os.put(c);
669                         } else {
670                                 os << "\\texteuro{}";
671                                 column += 10;
672                         }
673                         break;
674
675                 // These characters are covered by latin1, but not
676                 // by latin9 (a.o.). We have to support them because
677                 // we switched the default of latin1-languages to latin9
678                 case 0xa4:    // CURRENCY SYMBOL
679                 case 0xa6:    // BROKEN BAR
680                 case 0xa8:    // DIAERESIS
681                 case 0xb4:    // ACUTE ACCENT
682                 case 0xb8:    // CEDILLA
683                 case 0xbd:    // 1/2 FRACTION
684                 case 0xbc:    // 1/4 FRACTION
685                 case 0xbe:    // 3/4 FRACTION
686                         if (isEncoding(bparams, font, "latin1")
687                             || isEncoding(bparams, font, "latin5")
688                             || isEncoding(bparams, font, "utf8")) {
689                                 os.put(c);
690                                 break;
691                         } else {
692                                 switch (c) {
693                                 case 0xa4:
694                                         os << "\\textcurrency{}";
695                                         column += 15;
696                                         break;
697                                 case 0xa6:
698                                         os << "\\textbrokenbar{}";
699                                         column += 16;
700                                         break;
701                                 case 0xa8:
702                                         os << "\\textasciidieresis{}";
703                                         column += 20;
704                                         break;
705                                 case 0xb4:
706                                         os << "\\textasciiacute{}";
707                                         column += 17;
708                                         break;
709                                 case 0xb8: // from latin1.def:
710                                         os << "\\c\\ ";
711                                         column += 3;
712                                         break;
713                                 case 0xbd:
714                                         os << "\\textonehalf{}";
715                                         column += 14;
716                                         break;
717                                 case 0xbc:
718                                         os << "\\textonequarter{}";
719                                         column += 17;
720                                         break;
721                                 case 0xbe:
722                                         os << "\\textthreequarters{}";
723                                         column += 20;
724                                         break;
725                                 }
726                                 break;
727                         }
728
729                 case '$': case '&':
730                 case '%': case '#': case '{':
731                 case '}': case '_':
732                         os << '\\';
733                         os.put(c);
734                         column += 1;
735                         break;
736
737                 case '~':
738                         os << "\\textasciitilde{}";
739                         column += 16;
740                         break;
741
742                 case '^':
743                         os << "\\textasciicircum{}";
744                         column += 17;
745                         break;
746
747                 case '*': case '[':
748                         // avoid being mistaken for optional arguments
749                         os << '{';
750                         os.put(c);
751                         os << '}';
752                         column += 2;
753                         break;
754
755                 case ' ':
756                         // Blanks are printed before font switching.
757                         // Sure? I am not! (try nice-latex)
758                         // I am sure it's correct. LyX might be smarter
759                         // in the future, but for now, nothing wrong is
760                         // written. (Asger)
761                         break;
762
763                 default:
764
765                         // I assume this is hack treating typewriter as verbatim
766                         if (font.family() == LyXFont::TYPEWRITER_FAMILY) {
767                                 if (c != '\0') {
768                                         os.put(c);
769                                 }
770                                 break;
771                         }
772
773                         // LyX, LaTeX etc.
774
775                         // FIXME: if we have "LaTeX" with a font
776                         // change in the middle (before the 'T', then
777                         // the "TeX" part is still special cased.
778                         // Really we should only operate this on
779                         // "words" for some definition of word
780
781                         size_t pnr = 0;
782
783                         for (; pnr < phrases_nr; ++pnr) {
784                                 if (isTextAt(special_phrases[pnr].phrase, i)) {
785                                         os << special_phrases[pnr].macro;
786                                         i += special_phrases[pnr].phrase.length() - 1;
787                                         column += special_phrases[pnr].macro.length() - 1;
788                                         break;
789                                 }
790                         }
791
792                         if (pnr == phrases_nr && c != '\0') {
793                                 os.put(c);
794                         }
795                         break;
796                 }
797         }
798 }
799
800
801 void Paragraph::Pimpl::validate(LaTeXFeatures & features,
802                                 LyXLayout const & layout) const
803 {
804         BufferParams const & bparams = features.bufferParams();
805
806         // check the params.
807         if (!params.spacing().isDefault())
808                 features.require("setspace");
809
810         // then the layouts
811         features.useLayout(layout.name());
812
813         // then the fonts
814         Language const * doc_language = bparams.language;
815
816         FontList::const_iterator fcit = fontlist.begin();
817         FontList::const_iterator fend = fontlist.end();
818         for (; fcit != fend; ++fcit) {
819                 if (fcit->font().noun() == LyXFont::ON) {
820                         lyxerr[Debug::LATEX] << "font.noun: "
821                                              << fcit->font().noun()
822                                              << endl;
823                         features.require("noun");
824                         lyxerr[Debug::LATEX] << "Noun enabled. Font: "
825                                              << to_utf8(fcit->font().stateText(0))
826                                              << endl;
827                 }
828                 switch (fcit->font().color()) {
829                 case LColor::none:
830                 case LColor::inherit:
831                 case LColor::ignore:
832                         // probably we should put here all interface colors used for
833                         // font displaying! For now I just add this ones I know of (Jug)
834                 case LColor::latex:
835                 case LColor::note:
836                         break;
837                 default:
838                         features.require("color");
839                         lyxerr[Debug::LATEX] << "Color enabled. Font: "
840                                              << to_utf8(fcit->font().stateText(0))
841                                              << endl;
842                 }
843
844                 Language const * language = fcit->font().language();
845                 if (language->babel() != doc_language->babel() &&
846                     language != ignore_language &&
847                     language != latex_language)
848                 {
849                         features.useLanguage(language);
850                         lyxerr[Debug::LATEX] << "Found language "
851                                              << language->babel() << endl;
852                 }
853         }
854
855         if (!params.leftIndent().zero())
856                 features.require("ParagraphLeftIndent");
857
858         // then the insets
859         InsetList::const_iterator icit = owner_->insetlist.begin();
860         InsetList::const_iterator iend = owner_->insetlist.end();
861         for (; icit != iend; ++icit) {
862                 if (icit->inset) {
863                         icit->inset->validate(features);
864                         if (layout.needprotect &&
865                             icit->inset->lyxCode() == InsetBase::FOOT_CODE)
866                                 features.require("NeedLyXFootnoteCode");
867                 }
868         }
869
870         // then the contents
871         for (pos_type i = 0; i < size() ; ++i) {
872                 for (size_t pnr = 0; pnr < phrases_nr; ++pnr) {
873                         if (!special_phrases[pnr].builtin
874                             && isTextAt(special_phrases[pnr].phrase, i)) {
875                                 features.require(special_phrases[pnr].phrase);
876                                 break;
877                         }
878                 }
879                 // these glyphs require the textcomp package
880                 if (getChar(i) == 0x20ac || getChar(i) == 0xa4
881                     || getChar(i) == 0xa6 || getChar(i) == 0xa8
882                     || getChar(i) == 0xb4 || getChar(i) == 0xbd
883                     || getChar(i) == 0xbc || getChar(i) == 0xbe)
884                         features.require("textcomp");
885         }
886 }
887
888
889 } // namespace lyx